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
|
@Parcelize
|
||||||
data class VkConversation(
|
data class VkConversation(
|
||||||
@PrimaryKey(autoGenerate = false)
|
@PrimaryKey(autoGenerate = false)
|
||||||
val id: Int,
|
var id: Int,
|
||||||
val ownerId: Int?,
|
var ownerId: Int?,
|
||||||
val title: String?,
|
var title: String?,
|
||||||
val photo200: String?,
|
var photo200: String?,
|
||||||
val type: String,
|
var type: String,
|
||||||
val callInProgress: Boolean,
|
var callInProgress: Boolean,
|
||||||
val isPhantom: Boolean,
|
var isPhantom: Boolean,
|
||||||
val lastConversationMessageId: Int,
|
var lastConversationMessageId: Int,
|
||||||
val inRead: Int,
|
var inRead: Int,
|
||||||
val outRead: Int,
|
var outRead: Int,
|
||||||
val isMarkedUnread: Boolean,
|
var isMarkedUnread: Boolean,
|
||||||
val lastMessageId: Int,
|
var lastMessageId: Int,
|
||||||
val unreadCount: Int?,
|
var unreadCount: Int?,
|
||||||
val membersCount: Int?,
|
var membersCount: Int?,
|
||||||
val isPinned: Boolean,
|
var isPinned: Boolean,
|
||||||
|
var canChangePin: Boolean,
|
||||||
|
|
||||||
@Embedded(prefix = "pinnedMessage_")
|
@Embedded(prefix = "pinnedMessage_")
|
||||||
var pinnedMessage: VkMessage? = null,
|
var pinnedMessage: VkMessage? = null,
|
||||||
|
|
||||||
@Embedded(prefix = "lastMessage_")
|
@Embedded(prefix = "lastMessage_")
|
||||||
var lastMessage: VkMessage? = null
|
var lastMessage: VkMessage? = null,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
fun isChat() = type == "chat"
|
fun isChat() = type == "chat"
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||||
import com.meloda.fast.base.adapter.SelectableItem
|
import com.meloda.fast.base.adapter.SelectableItem
|
||||||
|
import com.meloda.fast.util.TimeUtils
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@@ -58,6 +60,10 @@ data class VkMessage(
|
|||||||
return Action.parse(action)
|
return Action.parse(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canEdit() =
|
||||||
|
fromId == UserConfig.userId &&
|
||||||
|
(System.currentTimeMillis() / 1000 - date.toLong() < TimeUtils.ONE_DAY_IN_SECONDS)
|
||||||
|
|
||||||
fun copyMessage(
|
fun copyMessage(
|
||||||
id: Int = this.id,
|
id: Int = this.id,
|
||||||
text: String? = this.text,
|
text: String? = this.text,
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ data class BaseVkConversation(
|
|||||||
unreadCount = unread_count,
|
unreadCount = unread_count,
|
||||||
membersCount = chat_settings?.members_count,
|
membersCount = chat_settings?.members_count,
|
||||||
ownerId = chat_settings?.owner_id,
|
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 {
|
).apply {
|
||||||
this.lastMessage = lastMessage
|
this.lastMessage = lastMessage
|
||||||
this.pinnedMessage = chat_settings?.pinned_message?.asVkMessage()
|
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
|
@Parcelize
|
||||||
data class MessagesGetLongPollServerRequest(
|
data class MessagesGetLongPollServerRequest(
|
||||||
val needPts: Boolean,
|
val needPts: Boolean,
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.meloda.fast.api.network
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
object VKUrls {
|
object VkUrls {
|
||||||
|
|
||||||
const val OAUTH = "https://oauth.vk.com"
|
const val OAUTH = "https://oauth.vk.com"
|
||||||
const val API = "https://api.vk.com/method"
|
const val API = "https://api.vk.com/method"
|
||||||
@@ -22,6 +22,8 @@ object VKUrls {
|
|||||||
const val GetHistory = "$API/messages.getHistory"
|
const val GetHistory = "$API/messages.getHistory"
|
||||||
const val Send = "$API/messages.send"
|
const val Send = "$API/messages.send"
|
||||||
const val MarkAsImportant = "$API/messages.markAsImportant"
|
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 GetLongPollServer = "$API/messages.getLongPollServer"
|
||||||
const val GetLongPollHistory = "$API/messages.getLongPollHistory"
|
const val GetLongPollHistory = "$API/messages.getLongPollHistory"
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.meloda.fast.api.network.datasource
|
package com.meloda.fast.api.network.datasource
|
||||||
|
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
import com.meloda.fast.api.model.request.MessagesGetHistoryRequest
|
import com.meloda.fast.api.model.request.*
|
||||||
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.network.repo.MessagesRepo
|
import com.meloda.fast.api.network.repo.MessagesRepo
|
||||||
import com.meloda.fast.database.dao.MessagesDao
|
import com.meloda.fast.database.dao.MessagesDao
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -26,8 +23,14 @@ class MessagesDataSource @Inject constructor(
|
|||||||
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
||||||
repo.getLongPollServer(params.map)
|
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
|
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.model.response.ResponseAuthDirect
|
||||||
import com.meloda.fast.api.network.Answer
|
import com.meloda.fast.api.network.Answer
|
||||||
import com.meloda.fast.api.model.response.ResponseSendSms
|
import com.meloda.fast.api.model.response.ResponseSendSms
|
||||||
@@ -8,10 +8,10 @@ import retrofit2.http.*
|
|||||||
|
|
||||||
interface AuthRepo {
|
interface AuthRepo {
|
||||||
|
|
||||||
@GET(VKUrls.Auth.DirectAuth)
|
@GET(VkUrls.Auth.DirectAuth)
|
||||||
suspend fun auth(@QueryMap param: Map<String, String?>): Answer<ResponseAuthDirect>
|
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>
|
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.base.ApiResponse
|
||||||
import com.meloda.fast.api.network.Answer
|
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 com.meloda.fast.api.model.response.ConversationsGetResponse
|
||||||
import retrofit2.http.FieldMap
|
import retrofit2.http.FieldMap
|
||||||
import retrofit2.http.FormUrlEncoded
|
import retrofit2.http.FormUrlEncoded
|
||||||
@@ -11,7 +11,7 @@ import retrofit2.http.POST
|
|||||||
interface ConversationsRepo {
|
interface ConversationsRepo {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(VKUrls.Conversations.Get)
|
@POST(VkUrls.Conversations.Get)
|
||||||
suspend fun getAllChats(@FieldMap params: Map<String, String>): Answer<ApiResponse<ConversationsGetResponse>>
|
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.base.ApiResponse
|
||||||
import com.meloda.fast.api.model.base.BaseVkLongPoll
|
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.model.response.MessagesGetHistoryResponse
|
||||||
import com.meloda.fast.api.network.Answer
|
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.FieldMap
|
||||||
import retrofit2.http.FormUrlEncoded
|
import retrofit2.http.FormUrlEncoded
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
@@ -12,19 +13,27 @@ import retrofit2.http.POST
|
|||||||
interface MessagesRepo {
|
interface MessagesRepo {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(VKUrls.Messages.GetHistory)
|
@POST(VkUrls.Messages.GetHistory)
|
||||||
suspend fun getHistory(@FieldMap params: Map<String, String>): Answer<ApiResponse<MessagesGetHistoryResponse>>
|
suspend fun getHistory(@FieldMap params: Map<String, String>): Answer<ApiResponse<MessagesGetHistoryResponse>>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(VKUrls.Messages.Send)
|
@POST(VkUrls.Messages.Send)
|
||||||
suspend fun send(@FieldMap params: Map<String, String>): Answer<ApiResponse<Int>>
|
suspend fun send(@FieldMap params: Map<String, String>): Answer<ApiResponse<Int>>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(VKUrls.Messages.MarkAsImportant)
|
@POST(VkUrls.Messages.MarkAsImportant)
|
||||||
suspend fun markAsImportant(@FieldMap params: Map<String, String>): Answer<ApiResponse<List<Int>>>
|
suspend fun markAsImportant(@FieldMap params: Map<String, String>): Answer<ApiResponse<List<Int>>>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(VKUrls.Messages.GetLongPollServer)
|
@POST(VkUrls.Messages.GetLongPollServer)
|
||||||
suspend fun getLongPollServer(@FieldMap params: Map<String, String>): Answer<ApiResponse<BaseVkLongPoll>>
|
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.base.ApiResponse
|
||||||
import com.meloda.fast.api.model.base.BaseVkUser
|
import com.meloda.fast.api.model.base.BaseVkUser
|
||||||
import com.meloda.fast.api.network.Answer
|
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.FieldMap
|
||||||
import retrofit2.http.FormUrlEncoded
|
import retrofit2.http.FormUrlEncoded
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
@@ -11,7 +11,7 @@ import retrofit2.http.POST
|
|||||||
interface UsersRepo {
|
interface UsersRepo {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(VKUrls.Users.GetById)
|
@POST(VkUrls.Users.GetById)
|
||||||
suspend fun getById(
|
suspend fun getById(
|
||||||
@FieldMap params: Map<String, String>?
|
@FieldMap params: Map<String, String>?
|
||||||
): Answer<ApiResponse<List<BaseVkUser>>>
|
): Answer<ApiResponse<List<BaseVkUser>>>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.meloda.fast.activity.MainActivity
|
|||||||
import com.meloda.fast.api.UserConfig
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import com.meloda.fast.base.viewmodel.IllegalTokenEvent
|
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.collect
|
||||||
import kotlinx.coroutines.flow.onEach
|
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) {
|
if (event is IllegalTokenEvent) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
requireContext(), R.string.authorization_failed, Toast.LENGTH_LONG
|
requireContext(), R.string.authorization_failed, Toast.LENGTH_LONG
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ abstract class BaseViewModel : ViewModel() {
|
|||||||
|
|
||||||
var unknownErrorDefaultText: String = ""
|
var unknownErrorDefaultText: String = ""
|
||||||
|
|
||||||
protected val tasksEventChannel = Channel<VKEvent>()
|
protected val tasksEventChannel = Channel<VkEvent>()
|
||||||
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
||||||
|
|
||||||
protected fun <T> makeJob(
|
protected fun <T> makeJob(
|
||||||
@@ -25,22 +25,35 @@ abstract class BaseViewModel : ViewModel() {
|
|||||||
onEnd: (suspend () -> Unit)? = null,
|
onEnd: (suspend () -> Unit)? = null,
|
||||||
onError: (suspend (Throwable) -> Unit)? = null
|
onError: (suspend (Throwable) -> Unit)? = null
|
||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch {
|
||||||
onStart?.invoke()
|
onStart?.invoke() ?: onStart()
|
||||||
when (val response = job()) {
|
when (val response = job()) {
|
||||||
is Answer.Success -> onAnswer(response.data)
|
is Answer.Success -> onAnswer(response.data)
|
||||||
is Answer.Error -> {
|
is Answer.Error -> {
|
||||||
checkErrors(response.throwable)
|
checkErrors(response.throwable)
|
||||||
onError?.invoke(response.throwable) ?: sendEvent(
|
onError?.invoke(response.throwable) ?: onError(response.throwable)
|
||||||
ErrorEvent(
|
}
|
||||||
response.throwable.message
|
}
|
||||||
?: unknownErrorDefaultText
|
}.also {
|
||||||
)
|
it.invokeOnCompletion {
|
||||||
)
|
viewModelScope.launch {
|
||||||
|
onEnd?.invoke() ?: onStop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
|
||||||
|
|
||||||
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) {
|
private suspend fun checkErrors(throwable: Throwable) {
|
||||||
when (throwable) {
|
when (throwable) {
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ data class ShowDialogInfoEvent(
|
|||||||
val message: String,
|
val message: String,
|
||||||
val positiveBtn: String? = null,
|
val positiveBtn: String? = null,
|
||||||
val negativeBtn: 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()
|
object IllegalTokenEvent : VkEvent()
|
||||||
data class CaptchaEvent(val sid: String, val image: String) : VKEvent()
|
data class CaptchaEvent(val sid: String, val image: String) : VkEvent()
|
||||||
data class ValidationEvent(val sid: String) : VKEvent()
|
data class ValidationEvent(val sid: String) : VkEvent()
|
||||||
|
|
||||||
object StartProgressEvent : VKEvent()
|
object StartProgressEvent : VkEvent()
|
||||||
object StopProgressEvent : VKEvent()
|
object StopProgressEvent : VkEvent()
|
||||||
|
|
||||||
abstract class VKEvent
|
abstract class VkEvent
|
||||||
@@ -19,7 +19,7 @@ import com.meloda.fast.database.dao.UsersDao
|
|||||||
VkUser::class,
|
VkUser::class,
|
||||||
VkGroup::class
|
VkGroup::class
|
||||||
],
|
],
|
||||||
version = 25,
|
version = 26,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@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.BaseViewModelFragment
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
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.AppGlobal
|
||||||
import com.meloda.fast.common.AppSettings
|
import com.meloda.fast.common.AppSettings
|
||||||
import com.meloda.fast.common.dataStore
|
import com.meloda.fast.common.dataStore
|
||||||
@@ -185,7 +185,7 @@ class ConversationsFragment :
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: VKEvent) {
|
override fun onEvent(event: VkEvent) {
|
||||||
super.onEvent(event)
|
super.onEvent(event)
|
||||||
when (event) {
|
when (event) {
|
||||||
is ConversationsLoaded -> refreshConversations(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.ConversationsDataSource
|
||||||
import com.meloda.fast.api.network.datasource.UsersDataSource
|
import com.meloda.fast.api.network.datasource.UsersDataSource
|
||||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.VkEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
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 {
|
fun loadProfileUser() = viewModelScope.launch {
|
||||||
@@ -95,4 +88,4 @@ data class ConversationsLoaded(
|
|||||||
val conversations: List<VkConversation>,
|
val conversations: List<VkConversation>,
|
||||||
val profiles: HashMap<Int, VkUser>,
|
val profiles: HashMap<Int, VkUser>,
|
||||||
val groups: HashMap<Int, VkGroup>
|
val groups: HashMap<Int, VkGroup>
|
||||||
) : VKEvent()
|
) : VkEvent()
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
binding.loginInput.clearFocus()
|
binding.loginInput.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: VKEvent) {
|
override fun onEvent(event: VkEvent) {
|
||||||
super.onEvent(event)
|
super.onEvent(event)
|
||||||
|
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|||||||
@@ -51,26 +51,25 @@ class LoginViewModel @Inject constructor(
|
|||||||
sendEvent(SuccessAuth())
|
sendEvent(SuccessAuth())
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
if (it !is VKException) return@makeJob
|
if (it !is VKException) {
|
||||||
|
onError(it)
|
||||||
|
return@makeJob
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: 9/27/2021 use `delay` parameter
|
// TODO: 9/27/2021 use `delay` parameter
|
||||||
twoFaCode?.let { sendEvent(CodeSent) }
|
twoFaCode?.let { sendEvent(CodeSent) }
|
||||||
},
|
}
|
||||||
onStart = { sendEvent(StartProgressEvent) },
|
|
||||||
onEnd = { sendEvent(StopProgressEvent) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendSms(validationSid: String) = viewModelScope.launch {
|
fun sendSms(validationSid: String) = viewModelScope.launch {
|
||||||
makeJob({ dataSource.sendSms(validationSid) },
|
makeJob({ dataSource.sendSms(validationSid) },
|
||||||
onAnswer = { sendEvent(CodeSent) },
|
onAnswer = { sendEvent(CodeSent) }
|
||||||
onError = {},
|
)
|
||||||
onStart = {},
|
|
||||||
onEnd = {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 playColor = ContextCompat.getColor(context, R.color.a3_700)
|
||||||
private val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200)
|
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() {
|
fun inflate() {
|
||||||
if (message.attachments.isNullOrEmpty()) return
|
if (message.attachments.isNullOrEmpty()) return
|
||||||
attachments = message.attachments!!
|
attachments = message.attachments!!
|
||||||
@@ -114,6 +121,12 @@ class AttachmentInflater constructor(
|
|||||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (photoClickListener != null) {
|
||||||
|
newPhoto.setOnClickListener { photoClickListener?.invoke(size.url) }
|
||||||
|
} else {
|
||||||
|
newPhoto.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
val spacer = Space(context).also {
|
val spacer = Space(context).also {
|
||||||
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.graphics.drawable.ColorDrawable
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat
|
import androidx.appcompat.widget.LinearLayoutCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
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.VkGroup
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
import com.meloda.fast.api.model.VkUser
|
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.api.model.attachments.VkPhoto
|
||||||
import com.meloda.fast.base.adapter.BaseAdapter
|
import com.meloda.fast.base.adapter.BaseAdapter
|
||||||
import com.meloda.fast.base.adapter.BaseHolder
|
import com.meloda.fast.base.adapter.BaseHolder
|
||||||
@@ -36,6 +38,8 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
var onItemClickListener: ((position: Int, view: View) -> Unit)? = null
|
var onItemClickListener: ((position: Int, view: View) -> Unit)? = null
|
||||||
|
|
||||||
|
var attachmentClickListener: ((attachment: VkAttachment) -> Unit)? = null
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
when {
|
when {
|
||||||
isPositionHeader(position) -> return HEADER
|
isPositionHeader(position) -> return HEADER
|
||||||
@@ -123,6 +127,8 @@ class MessagesHistoryAdapter constructor(
|
|||||||
prevMessage = prevMessage,
|
prevMessage = prevMessage,
|
||||||
nextMessage = nextMessage,
|
nextMessage = nextMessage,
|
||||||
|
|
||||||
|
title = binding.title,
|
||||||
|
|
||||||
avatar = binding.avatar,
|
avatar = binding.avatar,
|
||||||
bubble = binding.bubble,
|
bubble = binding.bubble,
|
||||||
text = binding.text,
|
text = binding.text,
|
||||||
@@ -133,7 +139,9 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
profiles = profiles,
|
profiles = profiles,
|
||||||
groups = groups
|
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.BaseViewModelFragment
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
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.databinding.FragmentMessagesHistoryBinding
|
||||||
import com.meloda.fast.extensions.TextViewExtensions.clear
|
import com.meloda.fast.extensions.TextViewExtensions.clear
|
||||||
import com.meloda.fast.extensions.isNotVisible
|
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import com.meloda.fast.util.TimeUtils
|
import com.meloda.fast.util.TimeUtils
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -49,7 +48,7 @@ class MessagesHistoryFragment :
|
|||||||
private val action = MutableLiveData<Action>()
|
private val action = MutableLiveData<Action>()
|
||||||
|
|
||||||
private enum class Action {
|
private enum class Action {
|
||||||
RECORD, SEND
|
RECORD, SEND, EDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
private val user: VkUser? by lazy {
|
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 var timestampTimer: Timer? = null
|
||||||
|
|
||||||
|
private lateinit var attachmentController: AttachmentPanelController
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
attachmentController = AttachmentPanelController().init()
|
||||||
|
|
||||||
val title = when {
|
val title = when {
|
||||||
conversation.isChat() -> conversation.title
|
conversation.isChat() -> conversation.title
|
||||||
conversation.isUser() -> user?.toString()
|
conversation.isUser() -> user?.toString()
|
||||||
@@ -167,8 +167,11 @@ class MessagesHistoryFragment :
|
|||||||
it.toString().isNotBlank()
|
it.toString().isNotBlank()
|
||||||
|
|
||||||
val newValue =
|
val newValue =
|
||||||
if (canSend) Action.SEND
|
when {
|
||||||
else Action.RECORD
|
attachmentController.isEditing -> Action.EDIT
|
||||||
|
canSend -> Action.SEND
|
||||||
|
else -> Action.RECORD
|
||||||
|
}
|
||||||
|
|
||||||
if (action.value != newValue) action.value = newValue
|
if (action.value != newValue) action.value = newValue
|
||||||
}
|
}
|
||||||
@@ -193,30 +196,21 @@ class MessagesHistoryFragment :
|
|||||||
Action.SEND -> {
|
Action.SEND -> {
|
||||||
binding.action.setImageResource(R.drawable.ic_round_send_24)
|
binding.action.setImageResource(R.drawable.ic_round_send_24)
|
||||||
}
|
}
|
||||||
|
Action.EDIT -> {
|
||||||
|
binding.action.setImageResource(R.drawable.ic_round_done_24)
|
||||||
|
}
|
||||||
else -> return@observe
|
else -> return@observe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isAttachmentPanelVisible.observe(viewLifecycleOwner) {
|
attachmentController.isPanelVisible.observe(viewLifecycleOwner) {
|
||||||
val layoutParams = binding.refreshLayout.layoutParams as CoordinatorLayout.LayoutParams
|
val layoutParams = binding.refreshLayout.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
layoutParams.bottomMargin =
|
layoutParams.bottomMargin =
|
||||||
if (it) (binding.attachmentPanel.height / 1.5).roundToInt() else 0
|
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@{
|
binding.attachmentPanel.setOnClickListener c@{
|
||||||
val message = replyMessage.value ?: return@c
|
val message = attachmentController.message.value ?: return@c
|
||||||
|
|
||||||
val index = adapter.values.indexOf(message)
|
val index = adapter.values.indexOf(message)
|
||||||
if (index == -1) return@c
|
if (index == -1) return@c
|
||||||
@@ -225,9 +219,8 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.dismissReply.setOnClickListener {
|
binding.dismissReply.setOnClickListener {
|
||||||
if (replyMessage.value != null) replyMessage.value = null
|
if (attachmentController.message.value != null)
|
||||||
|
attachmentController.message.value = null
|
||||||
hideAttachmentPanel()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,28 +275,6 @@ class MessagesHistoryFragment :
|
|||||||
binding.pin.isVisible = conversation.isPinned
|
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() {
|
private fun performAction() {
|
||||||
if (action.value == Action.RECORD) {
|
if (action.value == Action.RECORD) {
|
||||||
return
|
return
|
||||||
@@ -321,7 +292,7 @@ class MessagesHistoryFragment :
|
|||||||
fromId = UserConfig.userId,
|
fromId = UserConfig.userId,
|
||||||
date = (date / 1000).toInt(),
|
date = (date / 1000).toInt(),
|
||||||
randomId = 0,
|
randomId = 0,
|
||||||
replyMessage = replyMessage.value
|
replyMessage = attachmentController.message.value
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter.add(message)
|
adapter.add(message)
|
||||||
@@ -329,10 +300,8 @@ class MessagesHistoryFragment :
|
|||||||
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
||||||
binding.message.clear()
|
binding.message.clear()
|
||||||
|
|
||||||
val replyMessage = replyMessage.value
|
val replyMessage = attachmentController.message.value
|
||||||
|
attachmentController.message.value = null
|
||||||
this.replyMessage.value = null
|
|
||||||
hideAttachmentPanel()
|
|
||||||
|
|
||||||
viewModel.sendMessage(
|
viewModel.sendMessage(
|
||||||
peerId = conversation.id,
|
peerId = conversation.id,
|
||||||
@@ -343,12 +312,13 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: VKEvent) {
|
override fun onEvent(event: VkEvent) {
|
||||||
super.onEvent(event)
|
super.onEvent(event)
|
||||||
|
|
||||||
when (event) {
|
when (event) {
|
||||||
is MessagesMarkAsImportant -> markMessagesAsImportant(event)
|
is MessagesMarkAsImportant -> markMessagesAsImportant(event)
|
||||||
is MessagesLoaded -> refreshMessages(event)
|
is MessagesLoaded -> refreshMessages(event)
|
||||||
|
is MessagesPin -> conversation.pinnedMessage = event.message
|
||||||
is StartProgressEvent -> onProgressStarted()
|
is StartProgressEvent -> onProgressStarted()
|
||||||
is StopProgressEvent -> onProgressStopped()
|
is StopProgressEvent -> onProgressStopped()
|
||||||
}
|
}
|
||||||
@@ -436,106 +406,67 @@ class MessagesHistoryFragment :
|
|||||||
val message = adapter.values[position]
|
val message = adapter.values[position]
|
||||||
if (message.action != null) return
|
if (message.action != null) return
|
||||||
|
|
||||||
// val popupMenu = PopupMenu(requireContext(), view)
|
val time = getString(
|
||||||
//
|
R.string.time_format,
|
||||||
// val reply = popupMenu.menu.add(
|
SimpleDateFormat(
|
||||||
// getString(R.string.message_context_action_reply)
|
"dd.MM.yyyy, HH:mm:ss",
|
||||||
// )
|
Locale.getDefault()
|
||||||
//
|
).format(message.date * 1000L)
|
||||||
// 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 reply = getString(R.string.message_context_action_reply)
|
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(
|
val important = getString(
|
||||||
if (message.important) R.string.message_context_action_unmark_as_important
|
if (message.important) R.string.message_context_action_unmark_as_important
|
||||||
else R.string.message_context_action_mark_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())
|
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setItems(params) { _, which ->
|
.setTitle(time)
|
||||||
|
.setItems(arrayParams) { _, which ->
|
||||||
when (params[which]) {
|
when (params[which]) {
|
||||||
important -> viewModel.markAsImportant(
|
important -> viewModel.markAsImportant(
|
||||||
messagesIds = listOf(message.id),
|
messagesIds = listOf(message.id),
|
||||||
important = !message.important
|
important = !message.important
|
||||||
)
|
)
|
||||||
reply -> {
|
reply -> {
|
||||||
val title = when {
|
if (attachmentController.message.value != message)
|
||||||
message.isGroup() && message.group.value != null -> message.group.value?.name
|
attachmentController.message.value = message
|
||||||
message.isUser() && message.user.value != null -> message.user.value?.fullName
|
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
|
pin -> viewModel.pinMessage(
|
||||||
|
peerId = conversation.id,
|
||||||
|
messageId = message.id,
|
||||||
|
pin = !isMessageAlreadyPinned
|
||||||
|
)
|
||||||
|
edit -> {
|
||||||
|
attachmentController.isEditing = true
|
||||||
|
|
||||||
if (replyMessage.value != message) replyMessage.value = message
|
if (attachmentController.message.value != message)
|
||||||
|
attachmentController.message.value = message
|
||||||
binding.replyMessageTitle.text = title
|
|
||||||
binding.replyMessageText.text = message.text ?: "[no_message]"
|
|
||||||
|
|
||||||
if (binding.attachmentPanel.isNotVisible) binding.avatar.performClick()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,4 +480,78 @@ class MessagesHistoryFragment :
|
|||||||
return true
|
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.VkGroup
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
import com.meloda.fast.api.model.VkUser
|
import com.meloda.fast.api.model.VkUser
|
||||||
import com.meloda.fast.api.model.request.MessagesGetHistoryRequest
|
import com.meloda.fast.api.model.request.*
|
||||||
import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
|
|
||||||
import com.meloda.fast.api.model.request.MessagesSendRequest
|
|
||||||
import com.meloda.fast.api.network.datasource.MessagesDataSource
|
import com.meloda.fast.api.network.datasource.MessagesDataSource
|
||||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.VkEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MessagesHistoryViewModel @Inject constructor(
|
class MessagesHistoryViewModel @Inject constructor(
|
||||||
private val dataSource: MessagesDataSource
|
private val messages: MessagesDataSource
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
fun loadHistory(
|
fun loadHistory(
|
||||||
peerId: Int
|
peerId: Int
|
||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch {
|
||||||
makeJob({
|
makeJob({
|
||||||
dataSource.getHistory(
|
messages.getHistory(
|
||||||
MessagesGetHistoryRequest(
|
MessagesGetHistoryRequest(
|
||||||
count = 30,
|
count = 30,
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
@@ -53,18 +49,18 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val messages = hashMapOf<Int, VkMessage>()
|
val hashMessages = hashMapOf<Int, VkMessage>()
|
||||||
response.items.forEach { baseMessage ->
|
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>()
|
val conversations = hashMapOf<Int, VkConversation>()
|
||||||
response.conversations?.let { baseConversations ->
|
response.conversations?.let { baseConversations ->
|
||||||
baseConversations.forEach { baseConversation ->
|
baseConversations.forEach { baseConversation ->
|
||||||
baseConversation.asVkConversation(
|
baseConversation.asVkConversation(
|
||||||
messages[baseConversation.last_message_id]
|
hashMessages[baseConversation.last_message_id]
|
||||||
).let { conversation -> conversations[conversation.id] = conversation }
|
).let { conversation -> conversations[conversation.id] = conversation }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,16 +71,10 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
profiles = profiles,
|
profiles = profiles,
|
||||||
groups = groups,
|
groups = groups,
|
||||||
conversations = conversations,
|
conversations = conversations,
|
||||||
messages = messages.values.toList()
|
messages = hashMessages.values.toList()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
onError = {
|
|
||||||
val throwable = it
|
|
||||||
throw it
|
|
||||||
},
|
|
||||||
onStart = { sendEvent(StartProgressEvent) },
|
|
||||||
onEnd = { sendEvent(StopProgressEvent) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMessage(
|
fun sendMessage(
|
||||||
@@ -96,7 +86,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch {
|
||||||
makeJob(
|
makeJob(
|
||||||
{
|
{
|
||||||
dataSource.send(
|
messages.send(
|
||||||
MessagesSendRequest(
|
MessagesSendRequest(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
randomId = randomId,
|
randomId = randomId,
|
||||||
@@ -108,10 +98,6 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
onAnswer = {
|
onAnswer = {
|
||||||
val response = it.response ?: return@makeJob
|
val response = it.response ?: return@makeJob
|
||||||
setId?.invoke(response)
|
setId?.invoke(response)
|
||||||
},
|
|
||||||
onError = {
|
|
||||||
val throwable = it
|
|
||||||
val i = 0
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +106,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
important: Boolean
|
important: Boolean
|
||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch {
|
||||||
makeJob({
|
makeJob({
|
||||||
dataSource.markAsImportant(
|
messages.markAsImportant(
|
||||||
MessagesMarkAsImportantRequest(
|
MessagesMarkAsImportantRequest(
|
||||||
messagesIds = messagesIds,
|
messagesIds = messagesIds,
|
||||||
important = important
|
important = important
|
||||||
@@ -135,13 +121,39 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
important = important
|
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(
|
data class MessagesLoaded(
|
||||||
@@ -150,9 +162,16 @@ data class MessagesLoaded(
|
|||||||
val messages: List<VkMessage>,
|
val messages: List<VkMessage>,
|
||||||
val profiles: HashMap<Int, VkUser>,
|
val profiles: HashMap<Int, VkUser>,
|
||||||
val groups: HashMap<Int, VkGroup>
|
val groups: HashMap<Int, VkGroup>
|
||||||
) : VKEvent()
|
) : VkEvent()
|
||||||
|
|
||||||
data class MessagesMarkAsImportant(
|
data class MessagesMarkAsImportant(
|
||||||
val messagesIds: List<Int>,
|
val messagesIds: List<Int>,
|
||||||
val important: Boolean
|
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 android.widget.TextView
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat
|
import androidx.appcompat.widget.LinearLayoutCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
@@ -65,14 +64,17 @@ class MessagesPreparator constructor(
|
|||||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
||||||
private val backgroundMiddleOut =
|
private val backgroundMiddleOut =
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
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 =
|
private val rootHighlightedColor =
|
||||||
ContextCompat.getColor(context, R.color.n2_100)
|
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() {
|
fun prepare() {
|
||||||
val messageUser: VkUser? = (if (message.isUser()) {
|
val messageUser: VkUser? = (if (message.isUser()) {
|
||||||
profiles[message.fromId]
|
profiles[message.fromId]
|
||||||
@@ -104,20 +106,24 @@ class MessagesPreparator constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (message.isPeerChat()) {
|
if (message.isPeerChat()) {
|
||||||
|
val prevSenderDiff = VkUtils.isPreviousMessageFromDifferentSender(prevMessage, message)
|
||||||
val fromDiffSender = VkUtils.isPreviousMessageFromDifferentSender(prevMessage, message)
|
val nextSenderDiff = VkUtils.isPreviousMessageFromDifferentSender(message, nextMessage)
|
||||||
val fiveMinAgo = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
val fiveMinAgo = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
||||||
|
|
||||||
val change = (prevMessage?.date ?: 0) - message.date
|
val change = (prevMessage?.date ?: 0) - message.date
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
"Fast::MessagesPreparator",
|
"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 {
|
} else {
|
||||||
title?.isVisible = false
|
title?.isVisible = false
|
||||||
avatar?.isVisible = false
|
avatar?.isVisible = false
|
||||||
@@ -131,7 +137,6 @@ class MessagesPreparator constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
title.text = titleString
|
title.text = titleString
|
||||||
title.measure(0, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,13 +169,16 @@ class MessagesPreparator constructor(
|
|||||||
attachmentContainer.removeAllViews()
|
attachmentContainer.removeAllViews()
|
||||||
} else {
|
} else {
|
||||||
attachmentContainer.isVisible = true
|
attachmentContainer.isVisible = true
|
||||||
|
|
||||||
AttachmentInflater(
|
AttachmentInflater(
|
||||||
context = context,
|
context = context,
|
||||||
container = attachmentContainer,
|
container = attachmentContainer,
|
||||||
message = message,
|
message = message,
|
||||||
groups = groups,
|
groups = groups,
|
||||||
profiles = profiles
|
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 {
|
object TimeUtils {
|
||||||
|
|
||||||
|
const val ONE_DAY_IN_SECONDS = 86400
|
||||||
|
|
||||||
fun removeTime(date: Date): Long {
|
fun removeTime(date: Date): Long {
|
||||||
return Calendar.getInstance().apply {
|
return Calendar.getInstance().apply {
|
||||||
time = date
|
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:minHeight="105dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_anchor="@+id/messagePanel"
|
app:layout_anchor="@+id/messagePanel"
|
||||||
app:layout_anchorGravity="center_vertical|top">
|
app:layout_anchorGravity="center_vertical|top"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/replyMessage"
|
android:id="@+id/replyMessage"
|
||||||
@@ -249,10 +251,9 @@
|
|||||||
android:id="@+id/dismissReply"
|
android:id="@+id/dismissReply"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/ic_image_button_circle_background"
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
android:backgroundTint="@color/n1_50"
|
|
||||||
android:src="@drawable/ic_round_close_20"
|
android:src="@drawable/ic_round_close_20"
|
||||||
android:tint="?colorSecondary3" />
|
android:tint="@color/n1_800" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,20 @@
|
|||||||
android:textColor="@color/n1_800"
|
android:textColor="@color/n1_800"
|
||||||
tools:text="This" />
|
tools:text="This" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/unread"
|
||||||
|
android:layout_width="13dp"
|
||||||
|
android:layout_height="13dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:src="@color/a3_200" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:id="@+id/attachmentSpacer"
|
android:id="@+id/attachmentSpacer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -84,18 +98,5 @@
|
|||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:id="@+id/unread"
|
|
||||||
android:layout_width="13dp"
|
|
||||||
android:layout_height="13dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginBottom="20dp"
|
|
||||||
android:src="@color/a3_200" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
@@ -35,16 +34,10 @@
|
|||||||
android:id="@+id/bubble"
|
android:id="@+id/bubble"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="end"
|
||||||
android:background="@drawable/ic_message_out_background"
|
android:background="@drawable/ic_message_out_background"
|
||||||
android:clipChildren="true"
|
|
||||||
android:clipToPadding="true"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -54,6 +47,8 @@
|
|||||||
android:textColor="@color/n1_900"
|
android:textColor="@color/n1_900"
|
||||||
tools:text="This is test" />
|
tools:text="This is test" />
|
||||||
|
|
||||||
|
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:id="@+id/attachmentSpacer"
|
android:id="@+id/attachmentSpacer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -65,14 +60,10 @@
|
|||||||
android:id="@+id/attachmentContainer"
|
android:id="@+id/attachmentContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/attachmentSpacer"
|
android:layout_gravity="end"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone" />
|
||||||
app:layout_anchor="@+id/text"
|
|
||||||
app:layout_anchorGravity="bottom" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -21,6 +21,17 @@
|
|||||||
android:id="@+id/messagesHistoryFragment"
|
android:id="@+id/messagesHistoryFragment"
|
||||||
android:name="com.meloda.fast.screens.messages.MessagesHistoryFragment"
|
android:name="com.meloda.fast.screens.messages.MessagesHistoryFragment"
|
||||||
android:label="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>
|
</navigation>
|
||||||
@@ -117,4 +117,8 @@
|
|||||||
<string name="message_context_action_reply">Reply</string>
|
<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_mark_as_important">Mark as important</string>
|
||||||
<string name="message_context_action_unmark_as_important">Unmark 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>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user