forked from melod1n/fast-messenger
code saving
This commit is contained in:
@@ -7,7 +7,6 @@ object VKConstants {
|
||||
const val USER_FIELDS =
|
||||
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex,online_info"
|
||||
|
||||
|
||||
const val API_VERSION = "5.132"
|
||||
const val VK_APP_ID = "2274003"
|
||||
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
||||
|
||||
@@ -27,7 +27,9 @@ object VkUtils {
|
||||
return throwable.error == VkErrors.NEED_CAPTCHA
|
||||
}
|
||||
|
||||
fun prepareMessageText(text: String): String {
|
||||
fun prepareMessageText(text: String?): String? {
|
||||
if (text == null) return null
|
||||
|
||||
return text
|
||||
.replace("\n", " ")
|
||||
.replace("&", "&")
|
||||
@@ -58,9 +60,7 @@ object VkUtils {
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.VIDEO -> {
|
||||
val video = baseAttachment.video ?: continue
|
||||
attachments += VkVideo(
|
||||
link = video.player
|
||||
)
|
||||
attachments += video.asVkVideo()
|
||||
}
|
||||
BaseVkAttachmentItem.AttachmentType.AUDIO -> {
|
||||
val audio = baseAttachment.audio ?: continue
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.meloda.fast.api.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -24,10 +24,14 @@ data class VkConversation(
|
||||
val lastMessageId: Int,
|
||||
val unreadCount: Int?,
|
||||
val membersCount: Int?,
|
||||
val isPinned: Boolean
|
||||
) : Parcelable {
|
||||
@Ignore
|
||||
val isPinned: Boolean,
|
||||
|
||||
@Embedded(prefix = "pinnedMessage_")
|
||||
var pinnedMessage: VkMessage? = null,
|
||||
|
||||
@Embedded(prefix = "lastMessage_")
|
||||
var lastMessage: VkMessage? = null
|
||||
) : Parcelable {
|
||||
|
||||
fun isChat() = type == "chat"
|
||||
fun isUser() = type == "user"
|
||||
|
||||
@@ -2,10 +2,8 @@ package com.meloda.fast.api.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Entity(tableName = "messages")
|
||||
@@ -25,16 +23,10 @@ data class VkMessage(
|
||||
val actionConversationMessageId: Int? = null,
|
||||
val actionMessage: String? = null,
|
||||
val geoType: String? = null,
|
||||
val important: Boolean = false
|
||||
) : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
@Ignore
|
||||
var forwards: List<VkMessage>? = null
|
||||
|
||||
@IgnoredOnParcel
|
||||
@Ignore
|
||||
val important: Boolean = false,
|
||||
var forwards: List<VkMessage>? = null,
|
||||
var attachments: List<VkAttachment>? = null
|
||||
) : Parcelable {
|
||||
|
||||
fun isPeerChat() = peerId > 2_000_000_000
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
abstract class VkAttachment : Parcelable
|
||||
@Parcelize
|
||||
open class VkAttachment : Parcelable
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkAudio(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkCall(
|
||||
val initiatorId: Int
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkFile(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkGift(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkGraffiti(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkGroupCall(
|
||||
val initiatorId: Int
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkLink(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkMiniApp(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import com.meloda.fast.api.model.base.attachments.Size
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -16,6 +17,9 @@ data class VkPhoto(
|
||||
val userId: Int?
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
|
||||
fun sizeOfType(type: Char): Size? {
|
||||
for (size in sizes) {
|
||||
if (size.type == type.toString())
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkPoll(
|
||||
val id: Int
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.meloda.fast.api.model.attachments
|
||||
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkSticker
|
||||
import com.meloda.fast.api.model.base.attachments.StickerSize
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -12,6 +13,9 @@ data class VkSticker(
|
||||
val backgroundImages: List<BaseVkSticker.Image>
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
|
||||
fun urlForSize(@StickerSize size: Int): String? {
|
||||
for (image in images) {
|
||||
if (image.width == size) return image.url
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkVideo
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkVideo(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
val id: Int,
|
||||
val images: List<BaseVkVideo.Image>,
|
||||
val firstFrames: List<BaseVkVideo.FirstFrame>
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
|
||||
fun imageForWidth(width: Int): BaseVkVideo.Image? {
|
||||
return images.find { it.width == width }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkVoiceMessage(
|
||||
val link: String
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkWall(
|
||||
val id: Int
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class VkWallReply(
|
||||
val id: Int
|
||||
) : VkAttachment()
|
||||
) : VkAttachment() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val className: String = this::class.java.name
|
||||
}
|
||||
@@ -54,7 +54,10 @@ data class BaseVkConversation(
|
||||
membersCount = chatSettings?.membersCount,
|
||||
ownerId = chatSettings?.ownerId,
|
||||
isPinned = sortId.majorId > 0
|
||||
).apply { this.lastMessage = lastMessage }
|
||||
).apply {
|
||||
this.lastMessage = lastMessage
|
||||
this.pinnedMessage = chatSettings?.pinnedMessage?.asVkMessage()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Peer(
|
||||
@@ -111,7 +114,9 @@ data class BaseVkConversation(
|
||||
val isDisappearing: Boolean,
|
||||
@SerializedName("is_service")
|
||||
val isService: Boolean,
|
||||
val theme: String
|
||||
val theme: String?,
|
||||
@SerializedName("pinned_message")
|
||||
val pinnedMessage: BaseVkMessage?
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.meloda.fast.api.model.base.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.meloda.fast.api.model.attachments.VkVideo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
//not all fields
|
||||
@Parcelize
|
||||
data class BaseVkVideo(
|
||||
val id: Int,
|
||||
@@ -53,6 +55,12 @@ data class BaseVkVideo(
|
||||
//ads
|
||||
) : BaseVkAttachment() {
|
||||
|
||||
fun asVkVideo() = VkVideo(
|
||||
id = id,
|
||||
images = image,
|
||||
firstFrames = firstFrame
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class Image(
|
||||
val height: Int,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.meloda.fast.api.network
|
||||
|
||||
import com.meloda.fast.api.VKException
|
||||
import com.meloda.fast.api.base.ApiResponse
|
||||
import okhttp3.Request
|
||||
import okio.IOException
|
||||
import okio.Timeout
|
||||
import org.json.JSONObject
|
||||
import retrofit2.*
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
@@ -86,6 +88,9 @@ internal class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Answer<T>>(proxy)
|
||||
}
|
||||
} else Answer.Error(IOException(response.errorBody()?.string() ?: ""))
|
||||
|
||||
if (result is Answer.Error) if (checkErrors(call, result)) return
|
||||
|
||||
|
||||
callback.onResponse(proxy, Response.success(result))
|
||||
}
|
||||
|
||||
@@ -95,6 +100,23 @@ internal class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Answer<T>>(proxy)
|
||||
Response.success(Answer.Error(throwable = error))
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkErrors(call: Call<T>, result: Answer.Error): Boolean {
|
||||
val json = JSONObject(result.throwable.message ?: "{}")
|
||||
|
||||
return if (json.has("error")) {
|
||||
val error = json.optString("error", "")
|
||||
val description = json.optString("error_description", "")
|
||||
|
||||
val exception = VKException(
|
||||
error = error,
|
||||
description = description,
|
||||
).also { it.json = json }
|
||||
|
||||
onFailure(call, exception)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
override fun timeout(): Timeout {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
@@ -25,4 +26,8 @@ class MessagesDataSource @Inject constructor(
|
||||
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
||||
repo.getLongPollServer(params.map)
|
||||
|
||||
suspend fun storeMessages(messages: List<VkMessage>) = dao.insert(messages)
|
||||
|
||||
suspend fun getCachedMessages(peerId: Int) = dao.getByPeerId(peerId)
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.meloda.fast.base.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.meloda.fast.api.VKException
|
||||
@@ -14,6 +13,8 @@ import kotlinx.coroutines.launch
|
||||
|
||||
abstract class BaseViewModel : ViewModel() {
|
||||
|
||||
var unknownErrorDefaultText: String = ""
|
||||
|
||||
protected val tasksEventChannel = Channel<VKEvent>()
|
||||
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
||||
|
||||
@@ -30,6 +31,12 @@ abstract class BaseViewModel : ViewModel() {
|
||||
is Answer.Error -> {
|
||||
checkErrors(response.throwable)
|
||||
onError?.invoke(response.throwable)
|
||||
?: sendEvent(
|
||||
ErrorEvent(
|
||||
response.throwable.message
|
||||
?: unknownErrorDefaultText
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
||||
@@ -37,31 +44,32 @@ abstract class BaseViewModel : ViewModel() {
|
||||
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
||||
|
||||
private suspend fun checkErrors(throwable: Throwable) {
|
||||
if (throwable is ApiError) {
|
||||
when (throwable.errorCode) {
|
||||
VkErrorCodes.USER_AUTHORIZATION_FAILED -> {
|
||||
sendEvent(IllegalTokenEvent)
|
||||
return
|
||||
when (throwable) {
|
||||
is ApiError -> {
|
||||
when (throwable.errorCode) {
|
||||
VkErrorCodes.USER_AUTHORIZATION_FAILED -> {
|
||||
sendEvent(IllegalTokenEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (throwable is VKException) {
|
||||
when (throwable.error) {
|
||||
VkErrors.NEED_CAPTCHA -> {
|
||||
throwable.captcha =
|
||||
(throwable.json?.optString("captcha_sid")
|
||||
?: "") to (throwable.json?.optString("captcha_img") ?: "")
|
||||
return
|
||||
is VKException -> {
|
||||
when (throwable.error) {
|
||||
VkErrors.NEED_CAPTCHA -> {
|
||||
val json = throwable.json ?: return
|
||||
sendEvent(
|
||||
CaptchaEvent(
|
||||
sid = json.optString("captcha_sid"),
|
||||
image = json.optString("captcha_img")
|
||||
)
|
||||
)
|
||||
}
|
||||
VkErrors.NEED_VALIDATION -> {
|
||||
val json = throwable.json ?: return
|
||||
sendEvent(ValidationEvent(sid = json.optString("validation_sid")))
|
||||
}
|
||||
}
|
||||
VkErrors.NEED_VALIDATION -> {
|
||||
throwable.validationSid = throwable.json?.optString("validation_sid")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sendEvent(ShowDialogInfoEvent(null, Log.getStackTraceString(throwable)))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,8 @@ data class ShowDialogInfoEvent(
|
||||
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 StartProgressEvent : VKEvent()
|
||||
object StopProgressEvent : VKEvent()
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.meloda.fast.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
@@ -18,9 +19,10 @@ import com.meloda.fast.database.dao.UsersDao
|
||||
VkUser::class,
|
||||
VkGroup::class
|
||||
],
|
||||
version = 18,
|
||||
exportSchema = false
|
||||
version = 24,
|
||||
exportSchema = false,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun conversationsDao(): ConversationsDao
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.meloda.fast.database
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.Gson
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||
import org.json.JSONObject
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class Converters {
|
||||
|
||||
@TypeConverter
|
||||
fun fromListVkMessageToString(messages: List<VkMessage>?): String? {
|
||||
if (messages == null) return null
|
||||
|
||||
val string =
|
||||
messages.map { fromVkMessageToString(it)!! }.stream()
|
||||
.collect(Collectors.joining("fastkruta228355"))
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToListVkMessage(string: String?): List<VkMessage>? {
|
||||
if (string == null) return null
|
||||
|
||||
if (string.contains("fastkruta228355")) {
|
||||
val messages =
|
||||
string.split("fastkruta228355").map { fromStringToVkMessage(it)!! }
|
||||
return messages
|
||||
}
|
||||
|
||||
|
||||
val message = fromStringToVkMessage(string)!!
|
||||
|
||||
return listOf(message)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromVkMessageToString(message: VkMessage?): String? {
|
||||
if (message == null) return null
|
||||
|
||||
return Gson().toJson(message)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToVkMessage(string: String?): VkMessage? {
|
||||
if (string == null) return null
|
||||
|
||||
return Gson().fromJson(string, VkMessage::class.java)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromListVkAttachmentToString(attachments: List<VkAttachment>?): String? {
|
||||
if (attachments == null) return null
|
||||
|
||||
val string =
|
||||
attachments.map { fromVkAttachmentToString(it)!! }.stream()
|
||||
.collect(Collectors.joining("fastkruta228355"))
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToListVkAttachment(string: String?): List<VkAttachment>? {
|
||||
if (string == null) return null
|
||||
|
||||
if (string.contains("fastkruta228355")) {
|
||||
val attachments =
|
||||
string.split("fastkruta228355").map { fromStringToVkAttachment(it)!! }
|
||||
return attachments
|
||||
}
|
||||
|
||||
|
||||
val attachment = fromStringToVkAttachment(string)!!
|
||||
|
||||
return listOf(attachment)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromVkAttachmentToString(attachment: VkAttachment?): String? {
|
||||
if (attachment == null) return null
|
||||
|
||||
return Gson().toJson(attachment)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToVkAttachment(string: String?): VkAttachment? {
|
||||
if (string == null) return null
|
||||
|
||||
val className = JSONObject(string).optString("className")
|
||||
|
||||
return Gson().fromJson(string, Class.forName(className)) as VkAttachment?
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
package com.meloda.fast.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
|
||||
@Dao
|
||||
interface MessagesDao {
|
||||
|
||||
@Query("SELECT * FROM messages")
|
||||
suspend fun getAll(): List<VkMessage>
|
||||
|
||||
@Query("SELECT * FROM messages WHERE id = :id")
|
||||
suspend fun getById(id: Int): VkMessage?
|
||||
|
||||
@Query("SELECT * FROM messages WHERE peerId = :peerId")
|
||||
suspend fun getByPeerId(peerId: Int): List<VkMessage>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(values: List<VkMessage>)
|
||||
|
||||
suspend fun insert(values: Array<out VkMessage>) = insert(values.toList())
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.meloda.fast.screens.conversations
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.viewbinding.library.fragment.viewBinding
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.viewModels
|
||||
@@ -82,6 +83,8 @@ class ConversationsFragment :
|
||||
return
|
||||
}
|
||||
|
||||
binding.toolbar.overflowIcon = ContextCompat.getDrawable(requireContext(), R.drawable.test)
|
||||
|
||||
viewModel.loadProfileUser()
|
||||
viewModel.loadConversations()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
@@ -21,9 +20,7 @@ import com.google.android.material.textfield.TextInputLayout
|
||||
import com.meloda.fast.BuildConfig
|
||||
import com.meloda.fast.R
|
||||
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.*
|
||||
import com.meloda.fast.databinding.DialogCaptchaBinding
|
||||
import com.meloda.fast.databinding.DialogValidationBinding
|
||||
import com.meloda.fast.databinding.FragmentLoginBinding
|
||||
@@ -60,10 +57,6 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
||||
prepareViews()
|
||||
|
||||
binding.loginInput.clearFocus()
|
||||
|
||||
setFragmentResultListener("validation") { _, bundle ->
|
||||
lifecycleScope.launch { viewModel.getValidatedData(bundle) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEvent(event: VKEvent) {
|
||||
@@ -71,15 +64,13 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
||||
|
||||
when (event) {
|
||||
is ShowError -> showErrorSnackbar(event.errorDescription)
|
||||
is CaptchaRequired -> showCaptchaDialog(event.captcha.first, event.captcha.second)
|
||||
|
||||
CodeSent -> showValidationDialog()
|
||||
|
||||
is ValidationRequired -> showValidationRequired()
|
||||
is CaptchaEvent -> showCaptchaDialog(event.sid, event.image)
|
||||
is ValidationEvent -> showValidationRequired(event.sid)
|
||||
is SuccessAuth -> goToMain(event.haveAuthorized)
|
||||
|
||||
StartProgressEvent -> onProgressStarted()
|
||||
StopProgressEvent -> onProgressStopped()
|
||||
is CodeSent -> showValidationDialog()
|
||||
is StartProgressEvent -> onProgressStarted()
|
||||
is StopProgressEvent -> onProgressStopped()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +277,9 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
||||
validationBinding.cancel.setOnClickListener { dialog.dismiss() }
|
||||
}
|
||||
|
||||
private fun showValidationRequired() {
|
||||
private fun showValidationRequired(validationSid: String) {
|
||||
Toast.makeText(requireContext(), R.string.validation_required, Toast.LENGTH_LONG).show()
|
||||
viewModel.sendSms(validationSid)
|
||||
}
|
||||
|
||||
private fun showErrorSnackbar(errorDescription: String) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.meloda.fast.screens.login
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.meloda.fast.api.UserConfig
|
||||
import com.meloda.fast.api.VKConstants
|
||||
import com.meloda.fast.api.VKException
|
||||
import com.meloda.fast.api.VkUtils
|
||||
import com.meloda.fast.api.model.request.RequestAuthDirect
|
||||
import com.meloda.fast.api.network.datasource.AuthDataSource
|
||||
import com.meloda.fast.base.viewmodel.*
|
||||
@@ -18,8 +16,6 @@ class LoginViewModel @Inject constructor(
|
||||
private val dataSource: AuthDataSource
|
||||
) : BaseViewModel() {
|
||||
|
||||
lateinit var unknownErrorDefaultText: String
|
||||
|
||||
fun login(
|
||||
login: String,
|
||||
password: String,
|
||||
@@ -52,24 +48,12 @@ class LoginViewModel @Inject constructor(
|
||||
UserConfig.userId = it.userId
|
||||
UserConfig.accessToken = it.accessToken
|
||||
|
||||
sendEvent(SuccessAuth(haveAuthorized = true))
|
||||
sendEvent(SuccessAuth())
|
||||
},
|
||||
onError = {
|
||||
if (it !is VKException) return@makeJob
|
||||
|
||||
twoFaCode?.let { sendEvent(CodeSent) }
|
||||
|
||||
if (VkUtils.isValidationRequired(it)) {
|
||||
it.validationSid?.let { sid ->
|
||||
sendEvent(ValidationRequired(validationSid = sid))
|
||||
|
||||
sendSms(sid)
|
||||
}
|
||||
} else if (VkUtils.isCaptchaRequired(it)) {
|
||||
it.captcha?.let { captcha ->
|
||||
sendEvent(CaptchaRequired(captcha.first to captcha.second))
|
||||
}
|
||||
}
|
||||
},
|
||||
onStart = { sendEvent(StartProgressEvent) },
|
||||
onEnd = { sendEvent(StopProgressEvent) }
|
||||
@@ -84,23 +68,10 @@ class LoginViewModel @Inject constructor(
|
||||
onEnd = {})
|
||||
}
|
||||
|
||||
suspend fun getValidatedData(bundle: Bundle) {
|
||||
val accessToken = bundle.getString("token") ?: ""
|
||||
val userId = bundle.getInt("userId")
|
||||
|
||||
UserConfig.accessToken = accessToken
|
||||
UserConfig.userId = userId
|
||||
|
||||
tasksEventChannel.send(SuccessAuth())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class ShowError(val errorDescription: String) : VKEvent()
|
||||
|
||||
data class ValidationRequired(val validationSid: String) : VKEvent()
|
||||
data class CaptchaRequired(val captcha: Pair<String, String>) : VKEvent()
|
||||
|
||||
object CodeSent : VKEvent()
|
||||
|
||||
data class SuccessAuth(val haveAuthorized: Boolean = true) : VKEvent()
|
||||
@@ -20,10 +20,13 @@ import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||
import com.meloda.fast.api.model.attachments.VkSticker
|
||||
import com.meloda.fast.api.model.attachments.VkVideo
|
||||
import com.meloda.fast.base.adapter.BaseAdapter
|
||||
import com.meloda.fast.base.adapter.BaseHolder
|
||||
import com.meloda.fast.databinding.*
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class MessagesHistoryAdapter constructor(
|
||||
@@ -50,6 +53,8 @@ class MessagesHistoryAdapter constructor(
|
||||
) return if (message.isOut) ATTACHMENT_PHOTOS_OUT
|
||||
else ATTACHMENT_PHOTOS_IN
|
||||
|
||||
if (attachments[0] is VkVideo) return if (message.isOut) ATTACHMENT_VIDEOS_OUT
|
||||
else ATTACHMENT_VIDEOS_IN
|
||||
|
||||
if (attachments[0] is VkSticker) return if (message.isOut) ATTACHMENT_STICKER_OUT
|
||||
else ATTACHMENT_STICKER_IN
|
||||
@@ -84,6 +89,9 @@ class MessagesHistoryAdapter constructor(
|
||||
ATTACHMENT_PHOTOS_OUT -> AttachmentPhotosOutgoing(
|
||||
ItemMessageAttachmentPhotosOutBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ATTACHMENT_VIDEOS_IN, ATTACHMENT_VIDEOS_OUT -> AttachmentVideosIncoming(
|
||||
ItemMessageAttachmentVideosInBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
OUTGOING -> OutgoingMessage(
|
||||
ItemMessageOutBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
@@ -215,6 +223,12 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
init {
|
||||
MessagesManager.setRootMaxWidth(binding.bubble)
|
||||
|
||||
binding.bubbleStroke.setOnClickListener { binding.bubble.performClick() }
|
||||
|
||||
binding.bubble.setOnClickListener {
|
||||
binding.time.isVisible = !binding.time.isVisible
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(position: Int) {
|
||||
@@ -238,6 +252,9 @@ class MessagesHistoryAdapter constructor(
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStroke
|
||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStroke
|
||||
else backgroundStroke
|
||||
|
||||
binding.time.text =
|
||||
SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -303,12 +320,52 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
val messageUser =
|
||||
if (message.isUser()) profiles[message.fromId]
|
||||
else null
|
||||
|
||||
val messageGroup =
|
||||
if (message.isGroup()) groups[message.fromId]
|
||||
else null
|
||||
|
||||
MessagesManager.loadMessageAvatar(
|
||||
message = message,
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup,
|
||||
imageView = binding.avatar
|
||||
)
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.avatar.isVisible = false
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.spacer.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
|
||||
binding.avatar.visibility =
|
||||
if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE
|
||||
else if (nextMessage.date - message.date >= 60) View.VISIBLE
|
||||
else View.INVISIBLE
|
||||
}
|
||||
|
||||
MessagesManager.loadPhotos(
|
||||
context = context,
|
||||
message = message,
|
||||
binding.photosContainer
|
||||
photosContainer = binding.photosContainer
|
||||
)
|
||||
|
||||
MessagesManager.setMessageText(
|
||||
message = message,
|
||||
textView = binding.text
|
||||
)
|
||||
|
||||
binding.bubble.isVisible = binding.text.text.toString().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,9 +381,66 @@ class MessagesHistoryAdapter constructor(
|
||||
message = message,
|
||||
photosContainer = binding.photosContainer
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inner class AttachmentVideosIncoming(
|
||||
private val binding: ItemMessageAttachmentVideosInBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
val prevMessage = getOrNull(position - 1)
|
||||
val nextMessage = getOrNull(position + 1)
|
||||
|
||||
val messageUser =
|
||||
if (message.isUser()) profiles[message.fromId]
|
||||
else null
|
||||
|
||||
val messageGroup =
|
||||
if (message.isGroup()) groups[message.fromId]
|
||||
else null
|
||||
|
||||
MessagesManager.loadMessageAvatar(
|
||||
message = message,
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup,
|
||||
imageView = binding.avatar
|
||||
)
|
||||
|
||||
if (!message.isPeerChat()) {
|
||||
binding.avatar.isVisible = false
|
||||
|
||||
binding.spacer.isVisible =
|
||||
!(prevMessage != null && prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60)
|
||||
} else {
|
||||
binding.spacer.isVisible =
|
||||
if (prevMessage == null || prevMessage.fromId != message.fromId) message.isPeerChat()
|
||||
else message.date - prevMessage.date >= 60
|
||||
|
||||
binding.avatar.visibility =
|
||||
if (nextMessage == null || nextMessage.fromId != message.fromId) if (message.isPeerChat()) View.VISIBLE else View.GONE
|
||||
else if (nextMessage.date - message.date >= 60) View.VISIBLE
|
||||
else View.INVISIBLE
|
||||
}
|
||||
|
||||
MessagesManager.loadVideos(
|
||||
context = context,
|
||||
message = message,
|
||||
videosContainer = binding.videosContainer
|
||||
)
|
||||
|
||||
MessagesManager.setMessageText(
|
||||
message = message,
|
||||
textView = binding.text
|
||||
)
|
||||
|
||||
binding.bubble.isVisible = binding.text.text.toString().isNotEmpty()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class AttachmentStickerOutgoing(
|
||||
private val binding: ItemMessageAttachmentStickerOutBinding
|
||||
) : Holder(binding.root) {
|
||||
@@ -444,8 +558,10 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
private const val ATTACHMENT_PHOTOS_IN = 101
|
||||
private const val ATTACHMENT_PHOTOS_OUT = 102
|
||||
private const val ATTACHMENT_STICKER_IN = 111
|
||||
private const val ATTACHMENT_STICKER_OUT = 112
|
||||
private const val ATTACHMENT_VIDEOS_IN = 111
|
||||
private const val ATTACHMENT_VIDEOS_OUT = 112
|
||||
private const val ATTACHMENT_STICKER_IN = 121
|
||||
private const val ATTACHMENT_STICKER_OUT = 122
|
||||
|
||||
private val COMPARATOR = object : DiffUtil.ItemCallback<VkMessage>() {
|
||||
override fun areItemsTheSame(
|
||||
|
||||
@@ -58,6 +58,8 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
baseMessage.asVkMessage().let { message -> messages[message.id] = message }
|
||||
}
|
||||
|
||||
dataSource.storeMessages(messages.values.toList())
|
||||
|
||||
val conversations = hashMapOf<Int, VkConversation>()
|
||||
response.conversations?.let { baseConversations ->
|
||||
baseConversations.forEach { baseConversation ->
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
package com.meloda.fast.screens.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.Space
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isNotEmpty
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.VkUtils
|
||||
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.VkPhoto
|
||||
import com.meloda.fast.api.model.attachments.VkVideo
|
||||
import com.meloda.fast.common.AppGlobal
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.widget.BoundedFrameLayout
|
||||
@@ -53,7 +62,7 @@ object MessagesManager {
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
it.shapeAppearanceModel =
|
||||
it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(4) }
|
||||
it.shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
||||
it.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
@@ -69,7 +78,77 @@ object MessagesManager {
|
||||
|
||||
photosContainer.addView(newPhoto)
|
||||
|
||||
newPhoto.load(size.url)
|
||||
newPhoto.load(size.url) { crossfade(100) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadVideos(
|
||||
context: Context,
|
||||
message: VkMessage,
|
||||
videosContainer: LinearLayoutCompat
|
||||
) {
|
||||
videosContainer.removeAllViews()
|
||||
val playColor = ContextCompat.getColor(context, R.color.a3_700)
|
||||
val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200)
|
||||
|
||||
message.attachments?.let { attachments ->
|
||||
val photos = attachments.map { it as VkVideo }
|
||||
|
||||
photos.forEach { video ->
|
||||
val size = video.images[1] ?: return
|
||||
|
||||
val layout = FrameLayout(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
val newPhoto = ShapeableImageView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
AndroidUtils.px(size.width).roundToInt(),
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
|
||||
shapeAppearanceModel =
|
||||
shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
val play = AppCompatImageView(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
AndroidUtils.px(50).roundToInt(),
|
||||
AndroidUtils.px(50).roundToInt()
|
||||
).also {
|
||||
it.gravity = Gravity.CENTER
|
||||
}
|
||||
|
||||
backgroundTintList = ColorStateList.valueOf(playBackgroundColor)
|
||||
imageTintList = ColorStateList.valueOf(playColor)
|
||||
|
||||
setBackgroundResource(R.drawable.ic_play_button_circle_background)
|
||||
setImageResource(R.drawable.ic_round_play_arrow_24)
|
||||
|
||||
setPadding(12)
|
||||
}
|
||||
|
||||
layout.addView(newPhoto)
|
||||
layout.addView(play)
|
||||
|
||||
val spacer = Space(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
AndroidUtils.px(5).roundToInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (videosContainer.isNotEmpty())
|
||||
videosContainer.addView(spacer)
|
||||
|
||||
videosContainer.addView(layout)
|
||||
|
||||
newPhoto.load(size.url) { crossfade(100) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +172,7 @@ object MessagesManager {
|
||||
message: VkMessage,
|
||||
textView: TextView
|
||||
) {
|
||||
textView.text = message.text ?: "[no_message]"
|
||||
textView.text = VkUtils.prepareMessageText(message.text)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user