Merge pull request #7 from melod1n/simple_conversations

Simple conversations & chat
This commit is contained in:
2021-09-13 01:19:00 +03:00
committed by GitHub
312 changed files with 4907 additions and 6876 deletions
+6 -5
View File
@@ -80,17 +80,18 @@ dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
implementation("com.google.android.material:material:1.4.0")
implementation("androidx.core:core-ktx:1.7.0-alpha01")
implementation("com.google.android.material:material:1.5.0-alpha03")
implementation("androidx.core:core-ktx:1.7.0-alpha02")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.fragment:fragment-ktx:1.3.6")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2-native-mt")
implementation("androidx.room:room-ktx:2.3.0")
implementation("androidx.room:room-runtime:2.3.0")
kapt("androidx.room:room-compiler:2.3.0")
@@ -112,7 +113,7 @@ dependencies {
kapt("com.google.dagger:hilt-android-compiler:2.38.1")
implementation("androidx.hilt:hilt-navigation-fragment:1.0.0")
implementation("com.github.yogacp:android-viewbinding:1.0.2")
implementation("com.github.yogacp:android-viewbinding:1.0.3")
implementation("io.coil-kt:coil:1.3.2")
-4
View File
@@ -27,10 +27,6 @@
</intent-filter>
</activity>
<service
android:name=".service.LongPollService"
android:enabled="true" />
<receiver
android:name=".receiver.MinuteReceiver"
android:enabled="true"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

@@ -1,5 +1,7 @@
package com.meloda.fast.api
import androidx.lifecycle.MutableLiveData
import com.meloda.fast.api.model.VkUser
import com.meloda.fast.common.AppGlobal
object UserConfig {
@@ -30,4 +32,6 @@ object UserConfig {
fun isLoggedIn() = userId > 0 && accessToken.isNotBlank()
val vkUser = MutableLiveData<VkUser?>(null)
}
@@ -12,6 +12,7 @@ object VKConstants {
const val VK_APP_ID = "2274003"
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
const val FAST_GROUP_ID = -119516304
object Auth {
const val SCOPE = "notify," +
@@ -1,378 +0,0 @@
package com.meloda.fast.api
import androidx.annotation.WorkerThread
import com.meloda.fast.api.model.*
import com.meloda.fast.api.network.VKErrors
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
// TODO: 8/31/2021 review
object VKUtil {
private const val TAG = "VKUtil"
fun isValidationRequired(throwable: Throwable): Boolean {
if (throwable !is VKException) return false
return throwable.error == VKErrors.NEED_VALIDATION
}
fun isCaptchaRequired(throwable: Throwable): Boolean {
if (throwable !is VKException) return false
return throwable.error == VKErrors.NEED_CAPTCHA
}
fun sortMessagesByDate(
values: ArrayList<VKMessage>,
firstOnTop: Boolean
): ArrayList<VKMessage> {
values.sortWith { m1, m2 ->
val d1 = m1.date
val d2 = m2.date
if (firstOnTop) {
d2 - d1
} else {
d1 - d2
}
}
return values
}
fun sortConversationsByDate(
values: ArrayList<VKConversation>,
firstOnTop: Boolean
): ArrayList<VKConversation> {
values.sortWith { c1, c2 ->
val d1 = c1.lastMessage.date
val d2 = c2.lastMessage.date
return@sortWith if (firstOnTop) {
d2 - d1
} else {
d1 - d2
}
}
return values
}
fun prepareMessageText(message: String): String {
if (message.isEmpty()) return message
var newText = message
val mentions = hashMapOf<String, String>()
var startFrom = 0
while (true) {
val leftBracketIndex = newText.indexOf('[', startFrom)
val verticalLineIndex = newText.indexOf('|', startFrom)
val rightBracketIndex = newText.indexOf(']', startFrom)
if (leftBracketIndex == -1 ||
verticalLineIndex == -1 ||
rightBracketIndex == -1
) {
break
}
val id = newText.substring(leftBracketIndex + 1, verticalLineIndex)
if (!id.matches(Regex("^id(\\d+)\$")) || rightBracketIndex - verticalLineIndex < 2) {
break
}
val text = newText.substring(verticalLineIndex + 1, rightBracketIndex)
val str = "[$id|$text]"
mentions[str] = text
startFrom = rightBracketIndex + 1
}
mentions.forEach {
newText = newText.replace(it.key, it.value)
}
return newText
}
// fun removeTime(date: Date): Long {
// return Calendar.getInstance().apply {
// time = date
// this[Calendar.HOUR_OF_DAY] = 0
// this[Calendar.MINUTE] = 0
// this[Calendar.SECOND] = 0
// this[Calendar.MILLISECOND] = 0
// }.timeInMillis
// }
//TODO: нормальное время
fun getLastSeenTime(date: Long): String {
return SimpleDateFormat("HH:mm", Locale.getDefault()).format(date)
}
fun getTitle(
conversation: VKConversation,
peerUser: VKUser?,
peerGroup: VKGroup?
): String {
return when {
conversation.isUser() -> peerUser?.let { return it.toString() } ?: ""
conversation.isGroup() -> peerGroup?.let { return it.name } ?: ""
conversation.isChat() -> conversation.title ?: ""
else -> ""
}
}
fun getMessageTitle(
message: VKMessage,
fromUser: VKUser?,
fromGroup: VKGroup?
): String {
return when {
message.isFromUser() -> {
fromUser?.let { return it.toString() } ?: ""
}
message.isFromGroup() -> {
fromGroup?.let { return it.name } ?: ""
}
else -> ""
}
}
fun getAvatar(
conversation: VKConversation,
peerUser: VKUser?,
peerGroup: VKGroup?
): String {
return when {
conversation.isUser() -> {
peerUser?.let { return it.photo200 } ?: ""
}
conversation.isGroup() -> {
peerGroup?.let { return it.photo200 } ?: ""
}
conversation.isChat() -> {
conversation.photo200
}
else -> ""
}
}
fun getUserAvatar(
message: VKMessage,
fromUser: VKUser?,
fromGroup: VKGroup?
): String {
return when {
message.isFromUser() -> {
fromUser?.let { return it.photo100 } ?: ""
}
message.isFromGroup() -> {
fromGroup?.let { return it.photo100 } ?: ""
}
else -> ""
}
}
fun getUserPhoto(user: VKUser): String {
if (user.photo200.isEmpty()) {
if (user.photo100.isEmpty()) {
if (user.photo50.isEmpty()) {
return ""
}
} else {
return user.photo100
}
} else {
return user.photo200
}
return ""
}
fun getGroupPhoto(group: VKGroup): String {
if (group.photo200.isEmpty()) {
if (group.photo100.isEmpty()) {
if (group.photo50.isEmpty()) {
return ""
}
} else {
return group.photo100
}
} else {
return group.photo200
}
return ""
}
fun parseConversations(array: JSONArray): ArrayList<VKConversation> {
val conversations = arrayListOf<VKConversation>()
for (i in 0 until array.length()) {
conversations.add(VKConversation(array.optJSONObject(i)))
}
return conversations
}
fun parseMessages(array: JSONArray): ArrayList<VKMessage> {
val messages = arrayListOf<VKMessage>()
for (i in 0 until array.length()) {
messages.add(VKMessage(array.optJSONObject(i)))
}
return messages
}
fun isMessageHasFlag(mask: Int, flagName: String): Boolean {
val o: Any? = VKMessage.flags[flagName]
return if (o != null) { //has flag
val flag = o as Int
flag and mask > 0
} else false
}
//TODO: rewrite parsing
//fromUser and fromGroup are null
@Deprecated("need to rewrite")
@WorkerThread
fun parseLongPollMessage(array: JSONArray): VKMessage {
val message = VKMessage()
val id = array.optInt(1)
val flags = array.optInt(2)
val peerId = array.optInt(3)
val date = array.optInt(4)
val text = array.optString(5)
message.id = id
message.peerId = peerId
message.date = date
message.text = text
// val fromId =
// if (isMessageHasFlag(flags, "outbox")) com.meloda.fast.api.UserConfig.userId
// else peerId
message.fromId = peerId
array.optJSONObject(6)?.let {
if (it.has("emoji")) message.hasEmoji = true
if (it.has("from")) {
message.fromId = it.optInt("from", -1)
}
if (it.has("source_act")) {
message.action = VKMessageAction().also { action ->
action.type =
VKMessageAction.Type.fromString(it.optString("source_act"))
when (action.type) {
VKMessageAction.Type.CHAT_CREATE -> {
action.text = it.optString("source_text")
}
VKMessageAction.Type.TITLE_UPDATE -> {
action.oldText = it.optString("source_old_text")
action.text = it.optString("source_text")
}
VKMessageAction.Type.PIN_MESSAGE -> {
action.memberId = it.optInt("source_mid")
action.conversationMessageId = it.optInt("source_chat_local_id")
it.optJSONObject("source_message")?.let { message ->
action.message = VKMessage(message)
}
}
VKMessageAction.Type.UNPIN_MESSAGE -> {
action.memberId = it.optInt("source_mid")
action.conversationMessageId = it.optInt("source_chat_local_id")
}
VKMessageAction.Type.INVITE_USER,
VKMessageAction.Type.KICK_USER,
VKMessageAction.Type.SCREENSHOT,
VKMessageAction.Type.INVITE_USER_BY_CALL -> {
action.memberId = it.optInt("source_mid")
}
}
}
}
}
array.optJSONObject(7)?.let {
/**
*
* fwd? reply? attachments_count? attachments?
*
*/
}
val randomId = array.optInt(8)
message.randomId = randomId
val conversationMessageId = array.optInt(9)
message.conversationMessageId = conversationMessageId
val editTime = array.optInt(10)
message.editTime = editTime
// val out = fromId == com.meloda.fast.api.UserConfig.userId
// message.isOut = out
//
// if (message.isFromUser()) {
// message.fromUser = MemoryCache.getUserById(fromId)
// } else {
// message.fromGroup = MemoryCache.getGroupById(abs(fromId))
// }
return message
}
fun parseJsonPhotos(jsonPhotos: JSONObject): List<String> {
val photos = arrayListOf<String>()
for (key in jsonPhotos.keys()) {
photos.add(jsonPhotos.getString(key))
}
return photos
}
fun putPhotosToJson(photo50: String, photo100: String, photo200: String): JSONObject {
val json = JSONObject()
json.put("photo_50", photo50)
json.put("photo_100", photo100)
json.put("photo_200", photo200)
return json
}
fun isGroupId(id: Int) = id < 0
fun isUserId(id: Int) = id in 1..1999999999
fun isChatId(id: Int) = id > 2_000_000_000
}
@@ -0,0 +1,429 @@
package com.meloda.fast.api
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import com.meloda.fast.R
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.*
import com.meloda.fast.api.model.base.BaseVkMessage
import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem
import com.meloda.fast.api.network.VKErrors
object VkUtils {
fun isValidationRequired(throwable: Throwable): Boolean {
if (throwable !is VKException) return false
return throwable.error == VKErrors.NEED_VALIDATION
}
fun isCaptchaRequired(throwable: Throwable): Boolean {
if (throwable !is VKException) return false
return throwable.error == VKErrors.NEED_CAPTCHA
}
fun prepareMessageText(text: String): String {
return text
.replace("\n", " ")
.replace("&amp", "&")
}
fun parseForwards(baseForwards: List<BaseVkMessage>?): List<VkMessage>? {
if (baseForwards.isNullOrEmpty()) return null
val forwards = mutableListOf<VkMessage>()
for (baseForward in baseForwards) {
forwards += baseForward.asVkMessage()
}
return forwards
}
fun parseAttachments(baseAttachments: List<BaseVkAttachmentItem>?): List<VkAttachment>? {
if (baseAttachments.isNullOrEmpty()) return null
val attachments = mutableListOf<VkAttachment>()
for (baseAttachment in baseAttachments) {
when (baseAttachment.getPreparedType()) {
BaseVkAttachmentItem.AttachmentType.PHOTO -> {
val photo = baseAttachment.photo ?: continue
attachments += VkPhoto(
link = photo.sizes[0].url
)
}
BaseVkAttachmentItem.AttachmentType.VIDEO -> {
val video = baseAttachment.video ?: continue
attachments += VkVideo(
link = video.player
)
}
BaseVkAttachmentItem.AttachmentType.AUDIO -> {
val audio = baseAttachment.audio ?: continue
attachments += VkAudio(
link = audio.url
)
}
BaseVkAttachmentItem.AttachmentType.FILE -> {
val file = baseAttachment.file ?: continue
attachments += VkFile(
link = file.url
)
}
BaseVkAttachmentItem.AttachmentType.LINK -> {
val link = baseAttachment.link ?: continue
attachments += VkLink(
link = link.url
)
}
BaseVkAttachmentItem.AttachmentType.MINI_APP -> {
val miniApp = baseAttachment.miniApp ?: continue
attachments += VkMiniApp(
link = miniApp.app.shareUrl
)
}
BaseVkAttachmentItem.AttachmentType.VOICE -> {
val voiceMessage = baseAttachment.voiceMessage ?: continue
attachments += VkVoiceMessage(
link = voiceMessage.linkMp3
)
}
BaseVkAttachmentItem.AttachmentType.STICKER -> {
val sticker = baseAttachment.sticker ?: continue
attachments += VkSticker(
link = sticker.images[0].url
)
}
BaseVkAttachmentItem.AttachmentType.GIFT -> {
val gift = baseAttachment.gift ?: continue
attachments += VkGift(
link = gift.thumb48
)
}
BaseVkAttachmentItem.AttachmentType.WALL -> {
val wall = baseAttachment.wall ?: continue
attachments += VkWall(
id = wall.id
)
}
BaseVkAttachmentItem.AttachmentType.GRAFFITI -> {
val graffiti = baseAttachment.graffiti ?: continue
attachments += VkGraffiti(
link = graffiti.url
)
}
BaseVkAttachmentItem.AttachmentType.POLL -> {
val poll = baseAttachment.poll ?: continue
attachments += VkPoll(
id = poll.id
)
}
BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> {
val wallReply = baseAttachment.wallReply ?: continue
attachments += VkWallReply(
id = wallReply.id
)
}
BaseVkAttachmentItem.AttachmentType.CALL -> {
val call = baseAttachment.call ?: continue
attachments += VkCall(
initiatorId = call.initiatorId
)
}
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> {
val groupCall = baseAttachment.groupCall ?: continue
attachments += VkGroupCall(
initiatorId = groupCall.initiatorId
)
}
else -> continue
}
}
return attachments
}
fun getActionConversationText(
message: VkMessage,
youPrefix: String,
profiles: HashMap<Int, VkUser>? = null,
groups: HashMap<Int, VkGroup>? = null,
messageUser: VkUser? = null,
messageGroup: VkGroup? = null
): String? {
return when (message.getPreparedAction()) {
VkMessage.Action.CHAT_CREATE -> {
val text = message.actionText ?: return null
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix created «$text»"
}
VkMessage.Action.CHAT_TITLE_UPDATE -> {
val text = message.actionText ?: return null
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix renamed chat to «$text»"
}
VkMessage.Action.CHAT_PHOTO_UPDATE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix updated the chat photo"
}
VkMessage.Action.CHAT_PHOTO_REMOVE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix deleted the chat photo"
}
VkMessage.Action.CHAT_KICK_USER -> {
val memberId = message.actionMemberId ?: return null
val isUser = memberId > 0
val isGroup = memberId < 0
val actionUser = profiles?.get(memberId)
val actionGroup = groups?.get(memberId)
// val actionUser = profiles?.find { it.id == memberId }
// val actionGroup = groups?.find { it.id == memberId }
if (isUser && actionUser == null) return null
if (isGroup && actionGroup == null) return null
if (memberId == message.fromId) {
val prefix = if (memberId == UserConfig.userId) youPrefix
else actionUser.toString()
"$prefix left the chat"
} else {
val prefix =
if (message.fromId == UserConfig.userId) youPrefix
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
val postfix =
if (memberId == UserConfig.userId) youPrefix.lowercase()
else actionUser.toString()
"$prefix kicked $postfix"
}
}
VkMessage.Action.CHAT_INVITE_USER -> {
val memberId = message.actionMemberId ?: 0
val isUser = memberId > 0
val isGroup = memberId < 0
val actionUser = profiles?.get(memberId)
val actionGroup = groups?.get(memberId)
// val actionUser = profiles?.find { it.id == memberId }
// val actionGroup = groups?.find { it.id == memberId }
if (isUser && actionUser == null) return null
if (isGroup && actionGroup == null) return null
if (memberId == message.fromId) {
val prefix = if (memberId == UserConfig.userId) youPrefix
else actionUser.toString()
"$prefix returned the chat"
} else {
val prefix = if (message.fromId == UserConfig.userId) youPrefix
else messageUser?.toString() ?: messageGroup?.toString() ?: "..."
val postfix =
if (memberId == UserConfig.userId) youPrefix.lowercase()
else actionUser.toString()
"$prefix invited $postfix"
}
}
VkMessage.Action.CHAT_INVITE_USER_BY_LINK -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix joined the chat via link"
}
VkMessage.Action.CHAT_INVITE_USER_BY_CALL_LINK -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix joined the call via link"
}
VkMessage.Action.CHAT_PIN_MESSAGE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
val actionMessage = message.actionMessage ?: return null
"$prefix pinned message «$actionMessage»"
}
VkMessage.Action.CHAT_UNPIN_MESSAGE -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix unpinned message"
}
VkMessage.Action.CHAT_SCREENSHOT -> {
val prefix = when {
message.fromId == UserConfig.userId -> youPrefix
message.isGroup() -> messageGroup?.name
message.isUser() -> messageUser?.toString()
else -> return null
} ?: return null
"$prefix took a screenshot"
}
null -> null
else -> "[${message.action}]"
}
}
fun getForwardsConversationText(context: Context, message: VkMessage): String? {
if (message.forwards.isNullOrEmpty()) return null
return message.forwards?.let { forwards ->
context.getString(
if (forwards.size == 1) R.string.forwarded_message
else R.string.forwarded_messages
)
}
}
fun getAttachmentConversationText(context: Context, message: VkMessage): String? {
message.geoType?.let {
return when (it) {
"point" -> context.getString(R.string.message_geo_point)
else -> context.getString(R.string.message_geo)
}
}
if (message.attachments.isNullOrEmpty()) return null
return message.attachments?.let { attachments ->
if (attachments.size == 1) {
getAttachmentTypeByClass(attachments[0])?.let { getAttachmentTextByType(it) }
} else {
if (isAttachmentsHaveOneType(attachments)) {
getAttachmentTypeByClass(attachments[0])?.let { getAttachmentTextByType(it) }
} else {
context.getString(R.string.message_attachments_many)
}
}
}
}
fun getAttachmentConversationIcon(context: Context, message: VkMessage): Drawable? {
message.geoType?.let {
return ContextCompat.getDrawable(context, R.drawable.ic_map_marker)
}
if (message.attachments.isNullOrEmpty()) return null
return message.attachments?.let { attachments ->
if (attachments.size == 1 || isAttachmentsHaveOneType(attachments)) {
getAttachmentTypeByClass(attachments[0])?.let {
getAttachmentIconByType(
context,
it
)
}
} else {
ContextCompat.getDrawable(context, R.drawable.ic_baseline_attach_file_24)
}
}
}
fun getAttachmentIconByType(
context: Context,
attachmentType: BaseVkAttachmentItem.AttachmentType
): Drawable? {
val resId = when (attachmentType) {
BaseVkAttachmentItem.AttachmentType.PHOTO -> R.drawable.ic_attachment_photo
BaseVkAttachmentItem.AttachmentType.VIDEO -> R.drawable.ic_attachment_video
BaseVkAttachmentItem.AttachmentType.AUDIO -> R.drawable.ic_attachment_audio
BaseVkAttachmentItem.AttachmentType.FILE -> R.drawable.ic_attachment_file
BaseVkAttachmentItem.AttachmentType.LINK -> R.drawable.ic_attachment_link
BaseVkAttachmentItem.AttachmentType.VOICE -> R.drawable.ic_attachment_voice
BaseVkAttachmentItem.AttachmentType.MINI_APP -> R.drawable.ic_attachment_mini_app
BaseVkAttachmentItem.AttachmentType.STICKER -> R.drawable.ic_attachment_sticker
BaseVkAttachmentItem.AttachmentType.GIFT -> R.drawable.ic_attachment_gift
BaseVkAttachmentItem.AttachmentType.WALL -> R.drawable.ic_attachment_wall
BaseVkAttachmentItem.AttachmentType.GRAFFITI -> R.drawable.ic_attachment_graffiti
BaseVkAttachmentItem.AttachmentType.POLL -> R.drawable.ic_attachment_poll
BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> R.drawable.ic_attachment_wall_reply
BaseVkAttachmentItem.AttachmentType.CALL -> R.drawable.ic_attachment_call
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> R.drawable.ic_attachment_group_call
}
return ContextCompat.getDrawable(context, resId)
}
fun isAttachmentsHaveOneType(attachments: List<VkAttachment>): Boolean {
if (attachments.isEmpty()) return true
if (attachments.size == 1) return true
val firstType = getAttachmentTypeByClass(attachments[0])
for (i in 1 until attachments.size) {
val type = getAttachmentTypeByClass(attachments[i])
if (type != firstType) return false
}
return true
}
fun getAttachmentTypeByClass(attachment: VkAttachment): BaseVkAttachmentItem.AttachmentType? {
return when (attachment) {
is VkPhoto -> BaseVkAttachmentItem.AttachmentType.PHOTO
is VkVideo -> BaseVkAttachmentItem.AttachmentType.VIDEO
is VkAudio -> BaseVkAttachmentItem.AttachmentType.AUDIO
is VkFile -> BaseVkAttachmentItem.AttachmentType.FILE
is VkLink -> BaseVkAttachmentItem.AttachmentType.LINK
is VkMiniApp -> BaseVkAttachmentItem.AttachmentType.MINI_APP
is VkVoiceMessage -> BaseVkAttachmentItem.AttachmentType.VOICE
is VkSticker -> BaseVkAttachmentItem.AttachmentType.STICKER
is VkGift -> BaseVkAttachmentItem.AttachmentType.GIFT
is VkWall -> BaseVkAttachmentItem.AttachmentType.WALL
is VkGraffiti -> BaseVkAttachmentItem.AttachmentType.GRAFFITI
is VkPoll -> BaseVkAttachmentItem.AttachmentType.POLL
is VkWallReply -> BaseVkAttachmentItem.AttachmentType.WALL_REPLY
is VkCall -> BaseVkAttachmentItem.AttachmentType.CALL
is VkGroupCall -> BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS
else -> null
}
}
fun getAttachmentTextByType(attachmentType: BaseVkAttachmentItem.AttachmentType): String? {
return when (attachmentType) {
else -> attachmentType.value
}
}
}
@@ -0,0 +1,11 @@
package com.meloda.fast.api.base
import com.google.gson.annotations.SerializedName
import java.io.IOException
data class ApiError(
@SerializedName("error_code")
val errorCode: Int,
@SerializedName("error_msg")
override var message: String
) : IOException()
@@ -0,0 +1,8 @@
package com.meloda.fast.api.base
data class ApiResponse<T>(
val error: ApiError? = null,
val response: T? = null
) {
val isSuccessful get() = error == null && response != null
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api.datasource
import com.meloda.fast.api.network.repo.AuthRepo
import com.meloda.fast.api.network.request.RequestAuthDirect
import javax.inject.Inject
class AuthDataSource @Inject constructor(
private val repo: AuthRepo
) {
suspend fun auth(params: RequestAuthDirect) = repo.auth(params.map)
suspend fun sendSms(validationSid: String) = repo.sendSms(validationSid)
}
@@ -0,0 +1,18 @@
package com.meloda.fast.api.datasource
import com.meloda.fast.api.model.VkConversation
import com.meloda.fast.api.network.repo.ConversationsRepo
import com.meloda.fast.api.network.request.ConversationsGetRequest
import com.meloda.fast.database.dao.ConversationsDao
import javax.inject.Inject
class ConversationsDataSource @Inject constructor(
private val repo: ConversationsRepo,
private val dao: ConversationsDao
) {
suspend fun getAllChats(params: ConversationsGetRequest) = repo.getAllChats(params.map)
suspend fun storeConversations(conversations: List<VkConversation>) = dao.insert(conversations)
}
@@ -0,0 +1,18 @@
package com.meloda.fast.api.datasource
import com.meloda.fast.api.network.repo.MessagesRepo
import com.meloda.fast.api.network.request.MessagesGetHistoryRequest
import com.meloda.fast.api.network.request.MessagesSendRequest
import com.meloda.fast.database.dao.MessagesDao
import javax.inject.Inject
class MessagesDataSource @Inject constructor(
private val repo: MessagesRepo,
private val dao: MessagesDao
) {
suspend fun getHistory(params: MessagesGetHistoryRequest) = repo.getHistory(params.map)
suspend fun send(params: MessagesSendRequest) = repo.send(params.map)
}
@@ -0,0 +1,18 @@
package com.meloda.fast.api.datasource
import com.meloda.fast.api.model.VkUser
import com.meloda.fast.api.network.repo.UsersRepo
import com.meloda.fast.api.network.request.UsersGetRequest
import com.meloda.fast.database.dao.UsersDao
import javax.inject.Inject
class UsersDataSource @Inject constructor(
private val repo: UsersRepo,
private val dao: UsersDao
) {
suspend fun getById(params: UsersGetRequest) = repo.getById(params.map)
suspend fun storeUsers(users: List<VkUser>) = dao.insert(users)
}
@@ -1,76 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONArray
import java.util.*
object VKAttachments {
fun parse(array: JSONArray): ArrayList<VKModel> {
val attachments = ArrayList<VKModel>(array.length())
for (i in 0 until array.length()) {
var attachment = array.optJSONObject(i) ?: continue
if (attachment.has("attachment")) {
attachment = attachment.optJSONObject("attachment") ?: continue
}
val type = Type.fromString(attachment.optString("type"))
val jsonObject = attachment.optJSONObject(type.value) ?: continue
when (type) {
Type.PHOTO -> attachments.add(VKPhoto(jsonObject))
Type.AUDIO -> attachments.add(VKAudio(jsonObject))
Type.VIDEO -> attachments.add(VKVideo(jsonObject))
Type.DOCUMENT -> attachments.add(VKDocument(jsonObject))
Type.STICKER -> attachments.add(VKSticker(jsonObject))
Type.LINK -> attachments.add(VKLink(jsonObject))
Type.GIFT -> attachments.add(VKGift(jsonObject))
Type.VOICE_MESSAGE -> attachments.add(VKAudioMessage(jsonObject))
Type.GRAFFITI -> attachments.add(VKGraffiti(jsonObject))
Type.POLL -> attachments.add(VKPoll(jsonObject))
Type.CALL -> attachments.add(VKCall(jsonObject))
Type.WALL_POST -> attachments.add(VKWall(jsonObject))
Type.WALL_REPLY -> attachments.add(VKComment(jsonObject))
Type.GEOLOCATION -> attachments.add(VKGeolocation(jsonObject))
else -> continue
}
}
return attachments
}
enum class Type(val value: String) {
NONE("none"),
PHOTO("photo"),
VIDEO("video"),
AUDIO("audio"),
AUDIO_PLAYLIST("audio_playlist"),
DOCUMENT("doc"),
LINK("link"),
STICKER("sticker"),
GIFT("gift"),
VOICE_MESSAGE("audio_message"),
GRAFFITI("graffiti"),
POLL("poll"),
GEOLOCATION("geo"),
WALL_POST("wall"),
WALL_REPLY("wall_reply"),
CALL("call"),
STORY("story"),
POINT("point"),
MARKET("market"),
ARTICLE("article"),
PODCAST("podcast"),
MONEY_REQUEST("money_request");
companion object {
fun fromString(value: String): Type {
for (v in values()) {
if (v.value == value) return v
}
return NONE
}
}
}
}
@@ -1,31 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKAudio() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.AUDIO
var id: Int = 0
var ownerId: Int = 0
var artist: String = ""
var title: String = ""
var duration: Int = 0
var url: String = ""
var date: Int = 0
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
ownerId = o.optInt("owner_id", -1)
artist = o.optString("artist")
title = o.optString("title")
duration = o.optInt("duration")
url = o.optString("url")
date = o.optInt("date")
}
}
@@ -1,31 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKAudioMessage() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.VOICE_MESSAGE
var duration: Int = 0
var waveform: ArrayList<Int> = arrayListOf()
var linkOgg: String = ""
var linkMp3: String = ""
constructor(o: JSONObject) : this() {
duration = o.optInt("duration")
linkOgg = o.optString("link_ogg")
linkMp3 = o.optString("link_mp3")
o.optJSONArray("waveform")?.let {
val waveform = ArrayList<Int>()
for (i in 0 until it.length()) {
waveform.add(it.optInt(i))
}
this.waveform = waveform
}
}
}
@@ -1,38 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKCall() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.CALL
var initiatorId: Int = 0
var receiverId: Int = 0
var state: State = State.NONE
var time: Int = 0
var duration: Int = 0
constructor(o: JSONObject) : this() {
initiatorId = o.optInt("initiator_id", -1)
receiverId = o.optInt("receiver_id", -1)
state = State.fromString(o.optString("state"))
time = o.optInt("time")
duration = o.optInt("duration")
}
enum class State(val value: String) {
NONE("none"),
REACHED("reached"),
CANCELLED_INITIATOR("canceled_by_initiator"),
CANCELLED_RECEIVER("canceled_by_receiver");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
}
@@ -1,15 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKComment() : VKModel() { //https://vk.com/dev/objects/comment
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.WALL_REPLY
constructor(o: JSONObject) : this() {}
}
@@ -1,156 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKConversation() : VKModel(), Cloneable {
override val attachmentType = VKAttachments.Type.NONE
companion object {
const val serialVersionUID: Long = 1L
var profiles = arrayListOf<VKUser>()
var groups = arrayListOf<VKGroup>()
var conversationsCount: Int = 0
var count: Int = 0
}
var isAllowed: Boolean = false
var notAllowedReason: Reason = Reason.NULL
var inReadMessageId: Int = 0
var outReadMessageId: Int = 0
var lastMessageId: Int = 0
var unreadCount: Int = 0
var id: Int = 0
var intType: Int = 0
var type: Type = Type.NULL
var localId: Int = 0
var notificationsEnabled: Boolean = false
var disabledUntil: Int = 0
var isDisabledForever: Boolean = false
var isNoSound: Boolean = false
var membersCount: Int = 0
var title: String? = null
var pinnedMessage: VKMessage? = null
var intState: Int = 0
var state: State = State.IN
var lastMessage: VKMessage = VKMessage()
var isGroupChannel: Boolean = false
var photo50: String = ""
var photo100: String = ""
var photo200: String = ""
var peerUser: VKUser? = null
var peerGroup: VKGroup? = null
constructor(o: JSONObject) : this() {
inReadMessageId = o.optInt("in_read")
outReadMessageId = o.optInt("out_read")
lastMessageId = o.optInt("last_message_id", -1)
unreadCount = o.optInt("unread_count", 0)
o.optJSONObject("peer")?.let {
id = it.optInt("id", -1)
type = Type.fromString(it.optString("type"))
localId = it.optInt("local_id")
}
o.optJSONObject("push_settings")?.let {
disabledUntil = it.optInt("disabled_until")
isDisabledForever = it.optBoolean("disabled_forever")
isNoSound = it.optBoolean("no_sound")
}
o.optJSONObject("can_write")?.let {
isAllowed = it.optBoolean("allowed")
notAllowedReason = Reason.fromInt(it.optInt("reason", -1))
}
o.optJSONObject("chat_settings")?.let {
membersCount = it.optInt("members_count")
title = it.optString("title")
if (title?.isBlank() == true) title = null
it.optJSONObject("pinned_message")?.let { pinned ->
pinnedMessage = VKMessage(pinned)
}
state = State.fromString(it.optString("state"))
it.optJSONObject("photo")?.let { photo ->
photo50 = photo.optString("photo_50")
photo100 = photo.optString("photo_100")
photo200 = photo.optString("photo_200")
}
isGroupChannel = it.optBoolean("is_group_channel")
}
}
fun isNotificationsDisabled() = (isDisabledForever || disabledUntil > 0 || isNoSound)
fun isChat() = type == Type.CHAT
fun isUser() = type == Type.USER
fun isGroup() = type == Type.GROUP
override fun toString() = title ?: ""
public override fun clone() = super.clone() as VKConversation
enum class Type(val value: String) {
NULL("null"),
USER("user"),
CHAT("chat"),
GROUP("group");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
enum class State(val value: String) {
IN("in"),
KICKED("kicked"),
LEFT("left");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
enum class Reason(val value: Int) {
NULL(-1),
U(0),
BLOCKED_DELETED(18),
BLACKLISTED(900),
BLOCKED_GROUP_MESSAGES(901),
PRIVACY_SETTINGS(902),
GROUP_DISABLED_MESSAGES(915),
GROUP_BLOCKED_MESSAGES(916),
NO_ACCESS_CHAT(917),
NO_ACCESS_EMAIL(918),
U1(925),
NO_ACCESS_COMMUNITY(203);
companion object {
fun fromInt(value: Int) = values().first { it.value == value }
}
}
}
@@ -1,101 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.io.Serializable
import java.util.*
class VKDocument() : VKModel() {
override val attachmentType = VKAttachments.Type.DOCUMENT
companion object {
const val serialVersionUID: Long = 1L
}
var id: Int = 0
var ownerId: Int = 0
var title: String = ""
var size: Int = 0
var ext: String = ""
var url: String = ""
var date: Int = 0
var type: Type = Type.UNKNOWN
var preview: Preview? = null
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
ownerId = o.optInt("owner_id", -1)
title = o.optString("title")
size = o.optInt("size")
ext = o.optString("ext")
url = o.optString("url")
date = o.optInt("date")
type = Type.fromInt(o.optInt("type"))
o.optJSONObject("preview")?.let {
preview = Preview(it)
}
}
class Preview(o: JSONObject) : Serializable {
companion object {
const val serialVersionUID: Long = 1L
}
var photo: Photo? = null
var graffiti: Graffiti? = null
inner class Photo(o: JSONObject) : Serializable {
var sizes: ArrayList<VKPhotoSize>? = null
init {
o.optJSONArray("sizes")?.let {
val sizes = ArrayList<VKPhotoSize>()
for (i in 0 until it.length()) {
sizes.add(VKPhotoSize(it.optJSONObject(i)))
}
this.sizes = sizes
}
}
}
class Graffiti(o: JSONObject) : Serializable {
companion object {
const val serialVersionUID: Long = 1L
}
var src: String = o.optString("src")
var width: Int = o.optInt("width")
var height: Int = o.optInt("height")
}
init {
o.optJSONObject("photo")?.let {
photo = Photo(it)
}
o.optJSONObject("graffiti")?.let {
graffiti = Graffiti(it)
}
}
}
enum class Type(val value: Int) {
NONE(0),
TEXT(1),
ARCHIVE(2),
GIF(3),
IMAGE(4),
AUDIO(5),
VIDEO(6),
BOOK(7),
UNKNOWN(8);
companion object {
fun fromInt(value: Int) = values().first { it.value == value }
}
}
}
@@ -1,15 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKGeolocation() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.GEOLOCATION
constructor(o: JSONObject) : this() {}
}
@@ -1,25 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKGift() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.GIFT
var id: Int = 0
var thumb256: String = ""
var thumb96: String = ""
var thumb48: String = ""
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
thumb256 = o.optString("thumb_256")
thumb96 = o.optString("thumb_96")
thumb48 = o.optString("thumb_48")
}
}
@@ -1,29 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKGraffiti() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.GRAFFITI
var id: Int = 0
var ownerId: Int = 0
var url: String = ""
var width: Int = 0
var height: Int = 0
var accessKey: String = ""
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
ownerId = o.optInt("owner_id", -1)
url = o.optString("url")
width = o.optInt("width")
height = o.optInt("height")
accessKey = o.optString("access_key")
}
}
@@ -1,56 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONArray
import org.json.JSONObject
open class VKGroup() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
companion object {
const val serialVersionUID: Long = 1L
fun parse(array: JSONArray): ArrayList<VKGroup> {
val groups = ArrayList<VKGroup>()
for (i in 0 until array.length()) {
groups.add(VKGroup(array.optJSONObject(i)))
}
return groups
}
}
var id: Int = 0
var name: String = ""
var screenName: String = ""
var isClosed: Boolean = false
var deactivated: String = ""
var type: Type = Type.NULL
var photo50: String = ""
var photo100: String = ""
var photo200: String = ""
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
name = o.optString("name")
screenName = o.optString("screen_name")
isClosed = o.optInt("is_closed") == 1
deactivated = o.optString("deactivated")
type = Type.fromString(o.optString("type"))
photo50 = o.optString("photo_50")
photo100 = o.optString("photo_100")
photo200 = o.optString("photo_200")
}
enum class Type(val value: String) {
NULL("null"),
GROUP("group"),
PAGE("page"),
EVENT("event");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
}
@@ -1,57 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.io.Serializable
class VKLink() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.LINK
var url: String = ""
var title: String = ""
var caption: String = ""
var description: String = ""
var previewPage: String = ""
var previewUrl: String = ""
var photo: VKPhoto? = null
var button: Button? = null
constructor(o: JSONObject): this() {
url = o.optString("url")
title = o.optString("title")
caption = o.optString("caption")
description = o.optString("description")
previewPage = o.optString("preview_page")
previewUrl = o.optString("preview_url")
o.optJSONObject("photo")?.let {
photo = VKPhoto(it)
}
o.optJSONObject("button")?.let {
button = Button(it)
}
}
class Button(o: JSONObject) : Serializable {
var title: String = o.optString("title")
var action: Action? = null
init {
o.optJSONObject("action")?.let {
action = Action(it)
}
}
class Action(o: JSONObject) : Serializable {
var type: String = o.optString("type")
var url: String = o.optString("url")
}
}
}
@@ -1,14 +0,0 @@
package com.meloda.fast.api.model
import java.util.*
class VKLongPollHistory : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
private val lpMessages: ArrayList<VKMessage>? = null
private val messages: ArrayList<VKMessage>? = null
private val profiles: ArrayList<VKUser>? = null
private val groups: ArrayList<VKGroup>? = null //TODO: использовать
}
@@ -1,19 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKLongPollServer() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
var key: String = ""
var server: String = ""
var ts: Long = 0
constructor(o: JSONObject) : this() {
key = o.optString("key")
server = o.optString("server").replace("\\", "")
ts = o.optLong("ts")
}
}
@@ -1,164 +0,0 @@
package com.meloda.fast.api.model
import android.util.ArrayMap
import com.meloda.fast.api.VKUtil
import org.json.JSONObject
open class VKMessage() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
companion object {
var profiles = arrayListOf<VKUser>()
var groups = arrayListOf<VKGroup>()
var conversations = arrayListOf<VKConversation>()
const val serialVersionUID: Long = 1L
var lastHistoryCount: Int = 0
const val UNREAD = 1 // Оно просто есть
const val OUTBOX = 1 shl 1 // Исходящее сообщение
const val REPLIED = 1 shl 2 // На сообщение был создан ответ
const val IMPORTANT = 1 shl 3 // Важное сообщение
const val FRIENDS = 1 shl 5 // Сообщение в чат друга
const val SPAM = 1 shl 6 // Сообщение помечено как спам
const val DELETED = 1 shl 7 // Удаление сообщения
const val AUDIO_LISTENED = 1 shl 12 // ГС прослушано
const val CHAT = 1 shl 13 // Сообщение отправлено в беседу
const val CANCEL_SPAM = 1 shl 15 // Отмена пометки спама
const val HIDDEN = 1 shl 16 // Приветственное сообщение сообщества
const val DELETE_FOR_ALL = 1 shl 17 // Сообщение удалено для всех
const val CHAT_IN = 1 shl 19 // Входящее сообщение в беседе
const val REPLY_MSG = 1 shl 21 // Ответ на сообщение
val flags = ArrayMap<String, Int>()
fun isOut(flags: Int): Boolean {
return OUTBOX and flags > 0
}
fun isDeleted(flags: Int): Boolean {
return DELETED and flags > 0
}
fun isUnread(flags: Int): Boolean {
return UNREAD and flags > 0
}
fun isSpam(flags: Int): Boolean {
return SPAM and flags > 0
}
fun isCanceledSpam(flags: Int): Boolean {
return CANCEL_SPAM and flags > 0
}
fun isImportant(flags: Int): Boolean {
return IMPORTANT and flags > 0
}
fun isDeletedForAll(flags: Int): Boolean {
return DELETE_FOR_ALL and flags > 0
}
init {
flags["unread"] = UNREAD
flags["outbox"] = OUTBOX
flags["replied"] = REPLIED
flags["important"] = IMPORTANT
flags["friends"] = FRIENDS
flags["spam"] = SPAM
flags["deleted"] = DELETED
flags["audio_listened"] = AUDIO_LISTENED
flags["chat"] = CHAT
flags["cancel_spam"] = CANCEL_SPAM
flags["hidden"] = HIDDEN
flags["delete_for_all"] = DELETE_FOR_ALL
flags["chat_in"] = CHAT_IN
flags["reply_msg"] = REPLY_MSG
}
}
var id: Int = 0
var date: Int = 0
var peerId: Int = 0
var fromId: Int = 0
var editTime: Int = 0
var isOut: Boolean = false
var text: String = ""
var randomId: Int = 0
var conversationMessageId: Int = 0
var hasEmoji: Boolean = false
var isImportant: Boolean = false
var isRead: Boolean = false
var attachments: ArrayList<VKModel> = arrayListOf()
var fwdMessages: ArrayList<VKMessage> = arrayListOf()
var replyMessage: VKMessage? = null
var action: VKMessageAction? = null
var fromUser: VKUser? = null
var fromGroup: VKGroup? = null
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
date = o.optInt("date")
peerId = o.optInt("peer_id", -1)
fromId = o.optInt("from_id", -1)
editTime = o.optInt("edit_time", -1)
isOut = o.optInt("out") == 1
text = VKUtil.prepareMessageText(o.optString("text"))
randomId = o.optInt("random_id", -1)
conversationMessageId = o.optInt("conversation_message_id", -1)
isImportant = o.optBoolean("important")
o.optJSONArray("attachments")?.let {
attachments = VKAttachments.parse(it)
}
o.optJSONArray("fwd_messages")?.let {
val fwdMessages = ArrayList<VKMessage>(it.length())
for (i in 0 until it.length()) {
fwdMessages.add(VKMessage(it.optJSONObject(i)))
}
this.fwdMessages = fwdMessages
}
o.optJSONObject("reply_message")?.let {
replyMessage = VKMessage(it)
}
o.optJSONObject("action")?.let {
action = VKMessageAction(it)
}
}
fun getForwardedMessages() = ArrayList<VKMessage>().apply {
for (model in fwdMessages) add(model)
}
fun isFromUser() = fromId > 0
fun isFromGroup() = fromId < 0
fun isOutbox() = isOut
fun isInbox() = !isOutbox()
override fun toString(): String {
return if (text.isNotEmpty()) {
text
} else {
super.toString()
}
}
}
@@ -1,47 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKMessageAction() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.NONE
var type: Type = Type.NONE
var memberId = 0
var message: VKMessage? = null
var conversationMessageId: Int = 0
var text: String = ""
var oldText: String = ""
//TODO: add photo
constructor(o: JSONObject) : this() {
type = Type.fromString(o.optString("type"))
memberId = o.optInt("member_id", -1)
text = o.optString("text")
}
enum class Type(val value: String) {
NONE("none"),
CHAT_CREATE("chat_create"),
PHOTO_UPDATE("chat_photo_update"),
PHOTO_REMOVE("chat_photo_remove"),
TITLE_UPDATE("chat_title_update"),
PIN_MESSAGE("chat_pin_message"),
UNPIN_MESSAGE("chat_unpin_message"),
INVITE_USER("chat_invite_user"),
INVITE_USER_BY_LINK("chat_invite_user_by_link"),
KICK_USER("chat_kick_user"),
SCREENSHOT("chat_screenshot"),
INVITE_USER_BY_CALL("chat_invite_user_by_call"),
INVITE_USER_BY_CALL_LINK("chat_invite_user_by_call_link");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
}
@@ -1,14 +0,0 @@
package com.meloda.fast.api.model
import com.meloda.fast.base.adapter.BaseItem
import java.io.Serializable
abstract class VKModel : BaseItem(), Serializable {
abstract val attachmentType: VKAttachments.Type
companion object {
const val serialVersionUID = 1L
}
}
@@ -1,40 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.util.*
class VKPhoto() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.PHOTO
var id: Int = 0
var albumId: Int = 0
var ownerId: Int = 0
var text: String = ""
var date: Int = 0
var width: Int = 0
var height: Int = 0
var sizes: ArrayList<VKPhotoSize>? = null
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
albumId = o.optInt("album_id", -1)
ownerId = o.optInt("owner_id", -1)
text = o.optString("text")
date = o.optInt("date")
width = o.optInt("width")
height = o.optInt("height")
o.optJSONArray("sizes")?.let {
val sizes = ArrayList<VKPhotoSize>()
for (i in 0 until it.length()) {
sizes.add(VKPhotoSize(it.optJSONObject(i)))
}
this.sizes = sizes
}
}
}
@@ -1,18 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKPhotoSize(o: JSONObject) : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.NONE
var type: String = o.optString("type")
var url: String = o.optString("url")
var height: Int = o.optInt("height")
var width: Int = o.optInt("width")
}
@@ -1,58 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKPoll() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.POLL
constructor(o: JSONObject): this() {}
// var id = o.optInt("id", -1)
// var ownerId = o.optInt("owner_id", -1)
// var created = o.optInt("created")
// var question: String = o.optString("question")
// var votes = o.optInt("votes")
// var answers = ArrayList<Answer>()
// var isAnonymous = o.optBoolean("anonymous")
// var isMultiple = o.optBoolean("multiple")
// var answerIds = ArrayList<Int>()
// var endDate = o.optInt("end_date")
// var isClosed = o.optBoolean("closed")
// var isBoard = o.optBoolean("is_board")
// var isCanEdit = o.optBoolean("can_edit")
// var isCanVote = false
// var isCanReport = false
// var isCanShare = false
// var authorId = 0
// var background = Color.WHITE
//TODO: private ArrayList friends
// init {
// o.optJSONArray("answers")?.let {
// val answers = ArrayList<Answer>()
// for (i in 0 until it.length()) {
// answers.add(Answer(it.optJSONObject(i)))
// }
// this.answers = answers
// }
// //setAnswerIds();
// // ...
// }
// class Answer(o: JSONObject) : Serializable {
// var id = o.optInt("id", -1)
// var text: String = o.optString("text")
// var votes = o.optInt("votes")
// var rate = o.optInt("rate")
// }
}
@@ -1,44 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.util.*
class VKSticker() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.STICKER
var productId: Int = 0
var stickerId: Int = 0
var images: ArrayList<Image>? = null
constructor(o: JSONObject) : this() {
productId = o.optInt("product_id", -1)
stickerId = o.optInt("sticker_id", -1)
o.optJSONArray("images")?.let {
val images = ArrayList<Image>()
for (i in 0 until it.length()) {
images.add(Image(it.optJSONObject(i)))
}
this.images = images
}
}
class Image(o: JSONObject) : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.NONE
var url: String = o.optString("url")
var width = o.optInt("width")
var height = o.optInt("height")
}
}
@@ -1,80 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONArray
import org.json.JSONObject
open class VKUser() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
companion object {
const val serialVersionUID: Long = 1L
var friendsCount: Int = 0
fun parse(array: JSONArray): ArrayList<VKUser> {
val users = ArrayList<VKUser>()
for (i in 0 until array.length()) {
users.add(VKUser(array.optJSONObject(i)))
}
return users
}
}
var sortId: Int = 0
var userId: Int = 0
var firstName: String = ""
var lastName: String = ""
var deactivated: String = ""
var isClosed: Boolean = false
var isCanAccessClosed: Boolean = true
var sex: Int = 0
var screenName: String = ""
var photo50: String = ""
var photo100: String = ""
var photo200: String = ""
var isOnline: Boolean = false
var isOnlineMobile: Boolean = false
var status: String = ""
var lastSeen: Int = 0
var lastSeenPlatform: Int = 0
var isVerified: Boolean = false
constructor(o: JSONObject) : this() {
sortId = 0
userId = o.optInt("id", -1)
firstName = o.optString("first_name")
lastName = o.optString("last_name")
deactivated = o.optString("deactivated", "")
isClosed = o.optBoolean("is_closed")
isCanAccessClosed = o.optBoolean("can_access_closed")
sex = o.optInt("sex")
screenName = o.optString("screen_name")
photo50 = o.optString("photo_50")
photo100 = o.optString("photo_100")
photo200 = o.optString("photo_200")
isOnline = o.optInt("online") == 1
isOnlineMobile = isOnline && o.optInt("online_mobile") == 1
status = o.optString("status")
lastSeen = 0
lastSeenPlatform = 0
isVerified = o.optInt("verified") == 1
o.optJSONObject("last_seen")?.let {
lastSeen = it.optInt("time")
lastSeenPlatform = it.optInt("platform")
}
}
fun isDeactivated() = deactivated.isNotEmpty()
override fun toString(): String {
return "$firstName $lastName"
}
}
@@ -1,43 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKVideo() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.VIDEO
// var id = o.optInt("id", -1)
// var ownerId = o.optInt("owner_id", -1)
// var title: String = o.optString("title")
// var description: String = o.optString("description")
// var duration = o.optInt("duration", -1)
// var photo130: String = o.optString("photo_130")
// var photo320: String = o.optString("photo_320")
// var photo640: String = o.optString("photo_640")
// var photo800: String = o.optString("photo_800")
// var photo1280: String = o.optString("photo_1280")
// var firstFrame130: String = o.optString("first_frame_130")
// var firstFrame320: String = o.optString("first_frame_320")
// var firstFrame640: String = o.optString("first_frame_640")
// var firstFrame800: String = o.optString("first_frame_800")
// var firstFrame1280: String = o.optString("first_frame_1280")
// var date = o.optInt("date")
// var views = o.optInt("views")
// var comments = o.optInt("comments")
// var player: String = o.optString("player")
// var isCanEdit = o.optInt("can_edit", 0) == 1
// var isCanAdd = o.optInt("can_add") == 1
// var isPrivate = o.optInt("is_private", 0) == 1
// var accessKey: String = o.optString("access_key")
// var isProcessing = o.optInt("processing", 0) == 1
// var isLive = o.optInt("live", 0) == 1
// var isUpcoming = o.optInt("upcoming", 0) == 1
// var isFavorite = o.optBoolean("favorite")
constructor(o: JSONObject) : this() {}
}
@@ -1,15 +0,0 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKWall() : VKModel() { //https://vk.com/dev/objects/post
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.WALL_POST
constructor(o: JSONObject) : this() {}
}
@@ -0,0 +1,41 @@
package com.meloda.fast.api.model
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Entity(tableName = "conversations")
@Parcelize
data class VkConversation(
@PrimaryKey(autoGenerate = false)
val id: Int,
val ownerId: Int?,
val title: String?,
val photo200: String?,
val type: String,
val callInProgress: Boolean,
val isPhantom: Boolean,
val lastConversationMessageId: Int,
val inRead: Int,
val outRead: Int,
val isMarkedUnread: Boolean,
val lastMessageId: Int,
val unreadCount: Int?,
val membersCount: Int?,
val isPinned: Boolean
) : Parcelable {
@Ignore
var lastMessage: VkMessage? = null
fun isChat() = type == "chat"
fun isUser() = type == "user"
fun isGroup() = type == "group"
fun isInUnread() = inRead != lastMessageId
fun isOutUnread() = outRead != lastMessageId
fun isUnread() = isInUnread() || isOutUnread()
}
@@ -0,0 +1,20 @@
package com.meloda.fast.api.model
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Entity(tableName = "groups")
@Parcelize
data class VkGroup(
@PrimaryKey(autoGenerate = false)
val id: Int,
val name: String,
val screenName: String,
val photo200: String?
): Parcelable {
override fun toString() = name.trim()
}
@@ -0,0 +1,88 @@
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")
@Parcelize
data class VkMessage(
@PrimaryKey(autoGenerate = false)
val id: Int,
val text: String? = null,
val isOut: Boolean,
val peerId: Int,
val fromId: Int,
val date: Int,
val randomId: Int,
val action: String? = null,
val actionMemberId: Int? = null,
val actionText: String? = null,
val actionConversationMessageId: Int? = null,
val actionMessage: String? = null,
val geoType: String? = null
) : Parcelable {
@IgnoredOnParcel
@Ignore
var forwards: List<VkMessage>? = null
@IgnoredOnParcel
@Ignore
var attachments: List<VkAttachment>? = null
fun isPeerChat() = peerId > 2_000_000_000
fun isUser() = fromId > 0
fun isGroup() = fromId < 0
fun isRead(conversation: VkConversation) = conversation.outRead < id
fun getPreparedAction(): Action? {
if (action == null) return null
return Action.parse(action)
}
fun changeId(id: Int) = VkMessage(
id = id,
text = text,
isOut = isOut,
peerId = peerId,
fromId = fromId,
date = date,
randomId = randomId,
action = action,
actionMemberId = actionMemberId,
actionText = actionText,
actionConversationMessageId = actionConversationMessageId,
actionMessage = actionMessage,
geoType = geoType
)
enum class Action(val value: String) {
CHAT_CREATE("chat_create"),
CHAT_PHOTO_UPDATE("chat_photo_update"),
CHAT_PHOTO_REMOVE("chat_photo_remove"),
CHAT_TITLE_UPDATE("chat_title_update"),
CHAT_PIN_MESSAGE("chat_pin_message"),
CHAT_UNPIN_MESSAGE("chat_unpin_message"),
CHAT_INVITE_USER("chat_invite_user"),
CHAT_INVITE_USER_BY_LINK("chat_invite_user_by_link"),
CHAT_KICK_USER("chat_kick_user"),
CHAT_SCREENSHOT("chat_screenshot"),
// TODO: 9/11/2021 catch this shit
CHAT_INVITE_USER_BY_CALL("chat_invite_user_by_call"),
CHAT_INVITE_USER_BY_CALL_LINK("chat_invite_user_by_call_join_link");
companion object {
fun parse(value: String) = values().first { it.value == value }
}
}
}
@@ -0,0 +1,21 @@
package com.meloda.fast.api.model
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Entity(tableName = "users")
@Parcelize
data class VkUser(
@PrimaryKey(autoGenerate = false)
val id: Int,
val firstName: String,
val lastName: String,
val online: Boolean,
val photo200: String?
) : Parcelable {
override fun toString() = "$firstName $lastName".trim()
}
@@ -0,0 +1,3 @@
package com.meloda.fast.api.model.attachments
abstract class VkAttachment
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkAudio(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkCall(
val initiatorId: Int
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkFile(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkGift(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkGraffiti(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkGroupCall(
val initiatorId: Int
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkLink(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkMiniApp(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkPhoto(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkPoll(
val id: Int
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkSticker(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkVideo(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkVoiceMessage(
val link: String
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkWall(
val id: Int
) : VkAttachment()
@@ -0,0 +1,5 @@
package com.meloda.fast.api.model.attachments
data class VkWallReply(
val id: Int
) : VkAttachment()
@@ -0,0 +1,170 @@
package com.meloda.fast.api.model.base
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.meloda.fast.api.model.VkConversation
import com.meloda.fast.api.model.VkMessage
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkConversation(
val peer: Peer,
@SerializedName("last_message_id")
val lastMessageId: Int,
@SerializedName("in_read")
val inRead: Int,
@SerializedName("out_read")
val outRead: Int,
@SerializedName("sort_id")
val sortId: SortId,
@SerializedName("last_conversation_message_id")
val lastConversationMessageId: Int,
@SerializedName("is_marked_unread")
val isMarkedUnread: Boolean,
val important: Boolean,
@SerializedName("push_settings")
val pushSettings: PushSettings,
@SerializedName("can_write")
val canWrite: CanWrite,
@SerializedName("can_send_money")
val canSendMoney: Boolean,
@SerializedName("can_receive_money")
val canReceiveMoney: Boolean,
@SerializedName("chat_settings")
val chatSettings: ChatSettings?,
@SerializedName("call_in_progress")
val callInProgress: CallInProgress?,
@SerializedName("unread_count")
val unreadCount: Int?
) : Parcelable {
fun asVkConversation(lastMessage: VkMessage? = null) = VkConversation(
id = peer.id,
title = chatSettings?.title,
photo200 = chatSettings?.photo?.photo200,
type = peer.type,
callInProgress = callInProgress != null,
isPhantom = chatSettings?.isDisappearing == true,
lastConversationMessageId = lastConversationMessageId,
inRead = inRead,
outRead = outRead,
isMarkedUnread = isMarkedUnread,
lastMessageId = lastMessageId,
unreadCount = unreadCount,
membersCount = chatSettings?.membersCount,
ownerId = chatSettings?.ownerId,
isPinned = sortId.majorId > 0
).apply { this.lastMessage = lastMessage }
@Parcelize
data class Peer(
val id: Int,
val type: String,
@SerializedName("local_id")
val localId: Int
) : Parcelable
@Parcelize
data class SortId(
@SerializedName("major_id")
val majorId: Int,
@SerializedName("minor_id")
val minorId: Int
) : Parcelable
@Parcelize
data class PushSettings(
@SerializedName("disabled_forever")
val disabledForever: Boolean,
@SerializedName("no_sound")
val noSound: Boolean,
@SerializedName("disabled_mentions")
val disabledMentions: Boolean,
@SerializedName("disabled_mass_mentions")
val disabledMassMentions: Boolean
) : Parcelable
@Parcelize
data class CanWrite(
val allowed: Boolean
) : Parcelable
@Parcelize
data class ChatSettings(
@SerializedName("owner_id")
val ownerId: Int,
val title: String,
val state: String,
val acl: Acl,
@SerializedName("members_count")
val membersCount: Int,
@SerializedName("friends_count")
val friendsCount: Int,
val photo: Photo?,
@SerializedName("admin_ids")
val adminsIds: List<Int>,
@SerializedName("active_ids")
val activeIds: List<Int>,
@SerializedName("is_group_channel")
val isGroupChannel: Boolean,
@SerializedName("is_disappearing")
val isDisappearing: Boolean,
@SerializedName("is_service")
val isService: Boolean,
val theme: String
) : Parcelable {
@Parcelize
data class Acl(
@SerializedName("can_change_info")
val canChangeInfo: Boolean,
@SerializedName("can_change_invite_link")
val canChangeInviteLink: Boolean,
@SerializedName("can_change_pin")
val canChangePin: Boolean,
@SerializedName("can_invite")
val canInvite: Boolean,
@SerializedName("can_promote_users")
val canPromoteUsers: Boolean,
@SerializedName("can_see_invite_link")
val canSeeInviteLink: Boolean,
@SerializedName("can_moderate")
val canModerate: Boolean,
@SerializedName("can_copy_chat")
val canCopyChat: Boolean,
@SerializedName("can_call")
val canCall: Boolean,
@SerializedName("can_use_mass_mentions")
val canUseMassMentions: Boolean,
@SerializedName("can_change_style")
val canChangeStyle: Boolean
) : Parcelable
@Parcelize
data class Photo(
@SerializedName("photo_50")
val photo50: String?,
@SerializedName("photo_100")
val photo100: String?,
@SerializedName("photo_200")
val photo200: String?,
@SerializedName("is_default_photo")
val isDefaultPhoto: Boolean
) : Parcelable
}
@Parcelize
data class CallInProgress(
val participants: Participants,
@SerializedName("join_link")
val joinLink: String
) : Parcelable {
@Parcelize
data class Participants(
val list: List<Int>,
val count: Int
) : Parcelable
}
}
@@ -0,0 +1,38 @@
package com.meloda.fast.api.model.base
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.meloda.fast.api.model.VkGroup
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkGroup(
val id: Int,
val name: String,
@SerializedName("screen_name")
val screenName: String,
@SerializedName("is_closed")
val isClosed: Int,
val type: String,
@SerializedName("is_admin")
val isAdmin: Int,
@SerializedName("is_member")
val isMember: Int,
@SerializedName("is_advertiser")
val isAdvertiser: Int,
@SerializedName("photo_50")
val photo50: String?,
@SerializedName("photo_100")
val photo100: String?,
@SerializedName("photo_200")
val photo200: String?
) : Parcelable {
fun asVkGroup() = VkGroup(
id = -id,
name = name,
screenName = screenName,
photo200 = photo200
)
}
@@ -0,0 +1,81 @@
package com.meloda.fast.api.model.base
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.meloda.fast.api.VkUtils
import com.meloda.fast.api.model.VkMessage
import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkMessage(
val date: Int,
@SerializedName("from_id")
val fromId: Int,
val id: Int,
val out: Int,
@SerializedName("peer_id")
val peerId: Int,
val text: String,
@SerializedName("conversation_message_id")
val conversationMessageId: Int,
@SerializedName("fwd_messages")
val fwdMessages: List<BaseVkMessage>? = listOf(),
val important: Boolean,
@SerializedName("random_id")
val randomId: Int,
val attachments: List<BaseVkAttachmentItem> = listOf(),
@SerializedName("is_hidden")
val isHidden: Boolean,
val payload: String,
val geo: Geo?,
val action: Action?,
val ttl: Int
) : Parcelable {
fun asVkMessage() = VkMessage(
id = id,
text = if (text.isBlank()) null else text,
isOut = out == 1,
peerId = peerId,
fromId = fromId,
date = date,
randomId = randomId,
action = action?.type,
actionMemberId = action?.memberId,
actionText = action?.text,
actionConversationMessageId = action?.conversationMessageId,
actionMessage = action?.message,
geoType = geo?.type
).also {
it.attachments = VkUtils.parseAttachments(attachments)
it.forwards = VkUtils.parseForwards(fwdMessages)
}
@Parcelize
data class Geo(
val type: String,
val coordinates: Coordinates,
val place: Place
) : Parcelable {
@Parcelize
data class Coordinates(val latitude: Float, val longitude: Float) : Parcelable
@Parcelize
data class Place(val country: String, val city: String, val title: String) : Parcelable
}
@Parcelize
data class Action(
val type: String,
@SerializedName("member_id")
val memberId: Int?,
val text: String?,
@SerializedName("conversation_message_id")
val conversationMessageId: Int?,
val message: String?
) : Parcelable
}
@@ -0,0 +1,58 @@
package com.meloda.fast.api.model.base
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.meloda.fast.api.model.VkUser
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkUser(
val id: Int,
@SerializedName("first_name")
val firstName: String,
@SerializedName("last_name")
val lastName: String,
@SerializedName("can_access_closed")
val canAccessClosed: Boolean,
@SerializedName("is_closed")
val isClosed: Boolean,
@SerializedName("can_invite_to_chats")
val canInviteToChats: Boolean,
val sex: Int?,
@SerializedName("photo_50")
val photo50: String?,
@SerializedName("photo_100")
val photo100: String?,
@SerializedName("photo_200")
val photo200: String?,
val online: Int?,
@SerializedName("online_info")
val onlineInfo: OnlineInfo?,
@SerializedName("screen_name")
val screenName: String
//...other fields
) : Parcelable {
@Parcelize
data class OnlineInfo(
val visible: Boolean,
val status: String,
@SerializedName("last_seen")
val lastSeen: Int?,
@SerializedName("is_online")
val isOnline: Boolean?,
@SerializedName("online_mobile")
val isOnlineMobile: Boolean?,
@SerializedName("app_id")
val appId: Int?
) : Parcelable
fun asVkUser() = VkUser(
id = id,
firstName = firstName,
lastName = lastName,
online = online == 1,
photo200 = photo200
)
}
@@ -0,0 +1,59 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkAttachmentItem(
val type: String,
val photo: BaseVkPhoto?,
val video: BaseVkVideo?,
val audio: BaseVkAudio?,
@SerializedName("doc")
val file: BaseVkFile?,
val link: BaseVkLink?,
@SerializedName("mini_app")
val miniApp: BaseVkMiniApp?,
@SerializedName("audio_message")
val voiceMessage: BaseVkVoiceMessage?,
val sticker: BaseVkSticker?,
val gift: BaseVkGift?,
val wall: BaseVkWall?,
val graffiti: BaseVkGraffiti?,
val poll: BaseVkPoll?,
@SerializedName("wall_reply")
val wallReply: BaseVkWallReply?,
val call: BaseVkCall?,
@SerializedName("group_call_in_progress")
val groupCall: BaseVkGroupCall?
) : Parcelable {
fun getPreparedType() = AttachmentType.parse(type)
enum class AttachmentType(val value: String) {
PHOTO("photo"),
VIDEO("video"),
AUDIO("audio"),
FILE("doc"),
LINK("link"),
VOICE("audio_message"),
MINI_APP("mini_app"),
STICKER("sticker"),
GIFT("gift"),
WALL("wall"),
GRAFFITI("graffiti"),
POLL("poll"),
WALL_REPLY("wall_reply"),
CALL("call"),
GROUP_CALL_IN_PROGRESS("group_call_in_progress")
;
companion object {
fun parse(value: String) = values().firstOrNull { it.value == value }
}
}
}
abstract class BaseVkAttachment : Parcelable
@@ -0,0 +1,71 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkAudio(
val id: Int,
val title: String,
val artist: String,
val duration: Int,
val url: String,
val date: Int,
@SerializedName("owner_id")
val ownerId: Int,
@SerializedName("access_key")
val accessKey: String,
@SerializedName("is_explicit")
val isExplicit: Boolean,
@SerializedName("is_focus_track")
val isFocusTrack: Boolean,
@SerializedName("is_licensed")
val isLicensed: Boolean,
@SerializedName("track_code")
val trackCode: String,
@SerializedName("genre_id")
val genreId: Int,
val album: Album,
@SerializedName("short_videos_allowed")
val shortVideosAllowed: Boolean,
@SerializedName("stories_allowed")
val storiesAllowed: Boolean,
@SerializedName("stories_cover_allowed")
val storiesCoverAllowed: Boolean
) : BaseVkAttachment() {
@Parcelize
data class Album(
val id: Int,
val title: String,
@SerializedName("owner_id")
val ownerId: Int,
@SerializedName("access_key")
val accessKey: String,
val thumb: Thumb
) : Parcelable {
@Parcelize
data class Thumb(
val width: Int,
val height: Int,
@SerializedName("photo_34")
val photo34: String,
@SerializedName("photo_68")
val photo68: String,
@SerializedName("photo_135")
val photo135: String,
@SerializedName("photo_270")
val photo270: String,
@SerializedName("photo_300")
val photo300: String,
@SerializedName("photo_600")
val photo600: String,
@SerializedName("photo_1200")
val photo1200: String
) : Parcelable
}
}
@@ -0,0 +1,17 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkCall(
@SerializedName("initiator_id")
val initiatorId: Int,
@SerializedName("receiver_id")
val receiverId: Int,
val state: String,
val time: Int,
val duration: Int,
val video: Boolean
) : Parcelable
@@ -0,0 +1,47 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkFile(
val id: Int,
@SerializedName("owner_id")
val ownerId: Int,
val title: String,
val size: Int,
val ext: String,
val date: Int,
val type: Int,
val url: String,
val preview: Preview?,
@SerializedName("is_licensed")
val isLicensed: Int,
@SerializedName("access_key")
val accessKey: String,
@SerializedName("web_preview_url")
val webPreviewUrl: String?
) : BaseVkAttachment() {
@Parcelize
data class Preview(
val photo: Photo?,
val video: Video?
) : Parcelable {
@Parcelize
data class Photo(val sizes: List<Size>) : Parcelable
@Parcelize
data class Video(
val src: String,
val width: Int,
val height: Int,
@SerializedName("file_size")
val fileSize: Int
) : Parcelable
}
}
@@ -0,0 +1,16 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkGift(
val id: Int,
@SerializedName("thumb_256")
val thumb256: String?,
@SerializedName("thumb_96")
val thumb96: String?,
@SerializedName("thumb_48")
val thumb48: String
) : Parcelable
@@ -0,0 +1,17 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkGraffiti(
val id: Int,
@SerializedName("owner_id")
val ownerId: Int,
val url: String,
val width: Int,
val height: Int,
@SerializedName("access_key")
val accessKey: String
) : Parcelable
@@ -0,0 +1,22 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkGroupCall(
@SerializedName("initiator_id")
val initiatorId: Int,
@SerializedName("join_link")
val joinLink: String,
val participants: Participants
) : Parcelable {
@Parcelize
data class Participants(
val list: List<Int>,
val count: Int
) : Parcelable
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api.model.base.attachments
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkLink(
val url: String,
val title: String,
val caption: String,
val photo: BaseVkPhoto,
val target: String,
@SerializedName("is_favorite")
val isFavorite: Boolean
) : BaseVkAttachment()
@@ -0,0 +1,67 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkMiniApp(
val title: String,
val description: String,
val app: App,
val images: List<Image>?,
@SerializedName("button_text")
val buttonText: String
) : Parcelable {
@Parcelize
data class App(
val type: String,
val id: Int,
val title: String,
@SerializedName("author_owner_id")
val authorOwnerId: Int,
@SerializedName("are_notifications_enabled")
val areNotificationsEnabled: Boolean,
@SerializedName("is_favorite")
val isFavorite: Boolean,
@SerializedName("is_installed")
val isInstalled: Boolean,
@SerializedName("track_code")
val trackCode: String,
@SerializedName("share_url")
val shareUrl: String,
@SerializedName("webview_url")
val webViewUrl: String,
@SerializedName("hide_tabbar")
val hideTabBar: Int,
@SerializedName("icon_75")
val icon75: String?,
@SerializedName("icon_139")
val icon139: String?,
@SerializedName("icon_150")
val icon150: String?,
@SerializedName("icon_278")
val icon278: String?,
@SerializedName("icon_576")
val icon576: String?,
@SerializedName("open_in_external_browser")
val openInExternalBrowser: Boolean,
@SerializedName("need_policy_confirmation")
val needPolicyConfirmation: Boolean,
@SerializedName("is_vkui_internal")
val isVkUiInternal: Boolean,
@SerializedName("has_vk_connect")
val hasVkConnect: Boolean,
@SerializedName("need_show_bottom_menu_tooltip_on_close")
val needShowBottomMenuTooltipOnClose: Boolean
) : Parcelable
@Parcelize
data class Image(
val height: Int,
val width: Int,
val url: String
) : Parcelable
}
@@ -0,0 +1,32 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkPhoto(
@SerializedName("album_id")
val albumId: Int,
val date: Int,
val id: Int,
@SerializedName("owner_id")
val ownerId: Int,
@SerializedName("has_tags")
val hasTags: Boolean,
@SerializedName("access_key")
val accessKey: String,
val sizes: List<Size>,
val text: String,
@SerializedName("user_id")
val userId: Int?
) : BaseVkAttachment()
@Parcelize
data class Size(
val height: Int,
val width: Int,
val type: String,
@SerializedName("url", alternate = ["src"])
val url: String,
) : Parcelable
@@ -0,0 +1,72 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkPoll(
val multiple: Boolean,
val id: Int,
val votes: Int,
val anonymous: Boolean,
val closed: Boolean,
@SerializedName("end_date")
val endDate: Int,
@SerializedName("is_board")
val isBoard: Boolean,
@SerializedName("can_vote")
val canVote: Boolean,
@SerializedName("can_edit")
val canEdit: Boolean,
@SerializedName("can_report")
val canReport: Boolean,
@SerializedName("can_share")
val canShare: Boolean,
val created: Int,
@SerializedName("owner_id")
val ownerId: Int,
val question: String,
@SerializedName("disable_unvote")
val disableUnVote: Boolean,
val friends: List<Friend>?,
@SerializedName("embed_hash")
val embedHash: String,
val answers: List<Answer>,
@SerializedName("author_id")
val authorId: Int,
val background: Background?
) : Parcelable {
@Parcelize
data class Friend(
val id: Int
) : Parcelable
@Parcelize
data class Answer(
val id: Int,
val rate: Double,
val text: String,
val votes: Int
) : Parcelable
@Parcelize
data class Background(
val angle: Int,
val color: String,
val id: Int,
val name: String,
val type: String,
val points: List<Point>
) : Parcelable {
@Parcelize
data class Point(
val color: String,
val position: Double
) : Parcelable
}
}
@@ -0,0 +1,35 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkSticker(
@SerializedName("product_id")
val productId: Int,
@SerializedName("sticker_id")
val stickerId: Int,
val images: List<Image>,
@SerializedName("images_with_background")
val imagesWithBackground: List<Image>,
@SerializedName("animation_url")
val animationUrl: String?,
val animations: List<Animation>?
) : Parcelable {
@Parcelize
data class Image(
val width: Int,
val height: Int,
val url: String
) : Parcelable
@Parcelize
data class Animation(
val type: String,
val url: String
) : Parcelable
}
@@ -0,0 +1,111 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkVideo(
val id: Int,
val title: String,
val width: Int,
val height: Int,
val duration: Int,
val date: Int,
val comments: Int,
val description: String,
val player: String,
val added: Int,
val type: String,
val views: Int,
@SerializedName("can_comment")
val canComment: Int,
@SerializedName("can_edit")
val canEdit: Int,
@SerializedName("can_like")
val canLike: Int,
@SerializedName("can_repost")
val canRepost: Int,
@SerializedName("can_subscribe")
val canSubscribe: Int,
@SerializedName("can_add_to_faves")
val canAddToFaves: Int,
@SerializedName("can_add")
val canAdd: Int,
@SerializedName("can_attach_link")
val canAttachLink: Int,
@SerializedName("access_key")
val accessKey: String,
@SerializedName("owner_id")
val ownerId: Int,
@SerializedName("ov_id")
val ovId: String,
@SerializedName("is_favorite")
val isFavorite: Boolean,
@SerializedName("track_code")
val trackCode: String,
val image: List<Image>,
@SerializedName("first_frame")
val firstFrame: List<FirstFrame>,
val files: File,
@SerializedName("timeline_thumbs")
val timelineThumbs: TimelineThumbs
//ads
) : BaseVkAttachment() {
@Parcelize
data class Image(
val height: Int,
val width: Int,
val url: String,
@SerializedName("with_padding")
val withPadding: Int
) : Parcelable
@Parcelize
data class FirstFrame(
val height: Int,
val width: Int,
val url: String
) : Parcelable
@Parcelize
data class File(
val mp4_240: String?,
val mp4_360: String?,
val mp4_480: String?,
val mp4_720: String?,
val mp4_1080: String?,
val mp4_1440: String?,
val hls: String,
@SerializedName("dash_uni")
val dashUni: String,
@SerializedName("dash_sep")
val dashSep: String,
@SerializedName("hls_ondemand")
val hlsOnDemand: String,
@SerializedName("dash_ondemand")
val dashOnDemand: String,
@SerializedName("failover_host")
val failOverHost: String
) : Parcelable
@Parcelize
data class TimelineThumbs(
@SerializedName("count_per_image")
val countPerImage: Int,
@SerializedName("count_per_row")
val countPerRow: Int,
@SerializedName("count_total")
val countTotal: Int,
@SerializedName("frame_height")
val frameHeight: Int,
@SerializedName("frame_width")
val frameWidth: Float,
val links: List<String>,
@SerializedName("is_uv")
val isUv: Boolean,
val frequency: Int
) : Parcelable
}
@@ -0,0 +1,23 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkVoiceMessage(
val id: Int,
@SerializedName("owner_id")
val ownerId: Int,
val duration: Int,
val waveform: List<Int>,
@SerializedName("link_ogg")
val linkOgg: String,
@SerializedName("link_mp3")
val linkMp3: String,
@SerializedName("access_key")
val accessKey: String,
@SerializedName("transcript_state")
val transcriptState: String,
val transcript: String
) : Parcelable
@@ -0,0 +1,76 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkWall(
val id: Int,
@SerializedName("from_id")
val fromId: Int,
@SerializedName("to_id")
val toId: Int,
val date: Int,
val text: String,
val attachments: List<BaseVkAttachmentItem>?,
@SerializedName("post_source")
val postSource: PostSource,
val comments: Comments,
val likes: Likes,
val reposts: Reposts,
val views: Views,
@SerializedName("is_favorite")
val isFavorite: Boolean,
val donut: Donut,
@SerializedName("access_key")
val accessKey: String,
@SerializedName("short_text_rate")
val shortTextRate: Double
) : Parcelable {
@Parcelize
data class PostSource(
val type: String,
val platform: String
) : Parcelable
@Parcelize
data class Comments(
val count: Int,
@SerializedName("can_post")
val canPost: Int,
@SerializedName("groups_can_post")
val groupsCanPost: Boolean
) : Parcelable
@Parcelize
data class Likes(
val count: Int,
@SerializedName("user_likes")
val userLikes: Int,
@SerializedName("can_like")
val canLike: Int,
@SerializedName("can_publish")
val canPublish: Int,
) : Parcelable
@Parcelize
data class Reposts(
val count: Int,
@SerializedName("user_reposted")
val userReposted: Int
) : Parcelable
@Parcelize
data class Views(
val count: Int
) : Parcelable
@Parcelize
data class Donut(
@SerializedName("is_donut")
val isDonut: Boolean
) : Parcelable
}
@@ -0,0 +1,39 @@
package com.meloda.fast.api.model.base.attachments
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class BaseVkWallReply(
val id: Int,
@SerializedName("from_id")
val fromId: Int,
val date: Int,
val text: String,
@SerializedName("post_id")
val postId: Int,
@SerializedName("owner_id")
val ownerId: Int,
@SerializedName("parents_stack")
val parentsStack: List<Int>,
val likes: Likes,
@SerializedName("reply_to_user")
val replyToUser: Int?,
@SerializedName("reply_to_comment")
val replyToComment: Int?
) : Parcelable {
@Parcelize
data class Likes(
val count: Int,
@SerializedName("can_like")
val canLike: Int,
@SerializedName("user_likes")
val userLikes: Int,
@SerializedName("can_publish")
val canPublish: Int
) : Parcelable
}
@@ -1,5 +1,6 @@
package com.meloda.fast.api.network
import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.VKConstants
import okhttp3.Interceptor
import okhttp3.Response
@@ -10,6 +11,12 @@ class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().url.newBuilder()
.addQueryParameter("v", URLEncoder.encode(VKConstants.API_VERSION, "utf-8"))
UserConfig.accessToken.let {
if (it.isNotBlank())
builder.addQueryParameter("access_token", URLEncoder.encode(it, "utf-8"))
}
return chain.proceed(chain.request().newBuilder().apply { url(builder.build()) }.build())
}
@@ -1,65 +0,0 @@
package com.meloda.fast.api.network
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.meloda.fast.api.network.datasource.AuthDataSource
import com.meloda.fast.api.network.repo.AuthRepo
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class VKModules {
@Singleton
@Provides
fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient = OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(authInterceptor)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build()
@Singleton
@Provides
fun provideGson(): Gson = GsonBuilder()
.setLenient()
.create()
@Singleton
@Provides
fun provideRetrofit(
client: OkHttpClient,
gson: Gson
): Retrofit = Retrofit.Builder()
.baseUrl("https://api.vk.com/")
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(ResultCallFactory())
.client(client)
.build()
@Provides
@Singleton
fun provideAuthInterceptor(): AuthInterceptor = AuthInterceptor()
@Provides
fun provideAuthRepo(retrofit: Retrofit): AuthRepo =
retrofit.create(AuthRepo::class.java)
@Provides
fun provideAuthDataSource(repo: AuthRepo): AuthDataSource =
AuthDataSource(repo)
}
@@ -6,12 +6,21 @@ object VKUrls {
const val API = "https://api.vk.com/method"
object Auth {
const val directAuth = "$OAUTH/token"
const val sendSms = "$API/auth.validatePhone"
const val DirectAuth = "$OAUTH/token"
const val SendSms = "$API/auth.validatePhone"
}
object Conversations {
const val get = "$API/messages.getConversations"
const val Get = "$API/messages.getConversations"
}
object Users {
const val GetById = "$API/users.get"
}
object Messages {
const val GetHistory = "$API/messages.getHistory"
const val Send = "$API/messages.send"
}
@@ -1,12 +0,0 @@
package com.meloda.fast.api.network.datasource
import com.meloda.fast.api.network.repo.AuthRepo
import javax.inject.Inject
class AuthDataSource @Inject constructor(
private val repo: AuthRepo
) : AuthRepo {
override suspend fun auth(param: Map<String, String?>) = repo.auth(param)
override suspend fun sendSms(validationSid: String) = repo.sendSms(validationSid)
}
@@ -8,10 +8,10 @@ import retrofit2.http.*
interface AuthRepo {
@GET(VKUrls.Auth.directAuth)
@GET(VKUrls.Auth.DirectAuth)
suspend fun auth(@QueryMap param: Map<String, String?>): Answer<ResponseAuthDirect>
@GET(VKUrls.Auth.sendSms)
@GET(VKUrls.Auth.SendSms)
suspend fun sendSms(@Query("sid") validationSid: String): Answer<ResponseSendSms>
}
@@ -1,18 +1,17 @@
package com.meloda.fast.api.network.repo
import com.meloda.fast.api.base.ApiResponse
import com.meloda.fast.api.network.Answer
import com.meloda.fast.api.network.VKUrls
import com.meloda.fast.api.network.response.GetConversationsResponse
import retrofit2.http.*
import com.meloda.fast.api.network.response.ConversationsGetResponse
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface ConversationsRepo {
@FormUrlEncoded
@POST(VKUrls.Conversations.get)
suspend fun getAllChats(
@Field("user_id") chatId: Int,
@Field("token") token: String
): Answer<GetConversationsResponse>
@POST(VKUrls.Conversations.Get)
suspend fun getAllChats(@FieldMap params: Map<String, String>): Answer<ApiResponse<ConversationsGetResponse>>
}
@@ -0,0 +1,21 @@
package com.meloda.fast.api.network.repo
import com.meloda.fast.api.base.ApiResponse
import com.meloda.fast.api.network.Answer
import com.meloda.fast.api.network.VKUrls
import com.meloda.fast.api.network.response.MessagesGetHistoryResponse
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface MessagesRepo {
@FormUrlEncoded
@POST(VKUrls.Messages.GetHistory)
suspend fun getHistory(@FieldMap params: Map<String, String>): Answer<ApiResponse<MessagesGetHistoryResponse>>
@FormUrlEncoded
@POST(VKUrls.Messages.Send)
suspend fun send(@FieldMap params: Map<String, String>): Answer<ApiResponse<Int>>
}
@@ -0,0 +1,19 @@
package com.meloda.fast.api.network.repo
import com.meloda.fast.api.base.ApiResponse
import com.meloda.fast.api.model.base.BaseVkUser
import com.meloda.fast.api.network.Answer
import com.meloda.fast.api.network.VKUrls
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface UsersRepo {
@FormUrlEncoded
@POST(VKUrls.Users.GetById)
suspend fun getById(
@FieldMap params: Map<String, String>?
): Answer<ApiResponse<List<BaseVkUser>>>
}
@@ -0,0 +1,26 @@
package com.meloda.fast.api.network.request
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class ConversationsGetRequest(
val count: Int? = null,
val offset: Int? = null,
val fields: String = "",
val filter: String = "all",
val extended: Boolean? = true,
val startMessageId: Int? = null
) : Parcelable {
val map
get() = mutableMapOf(
"fields" to fields,
"filter" to filter
).apply {
count?.let { this["count"] = it.toString() }
offset?.let { this["offset"] = it.toString() }
extended?.let { this["extended"] = it.toString() }
startMessageId?.let { this["start_message_id"] = it.toString() }
}
}
@@ -1 +0,0 @@
package com.meloda.fast.api.network.request
@@ -0,0 +1,58 @@
package com.meloda.fast.api.network.request
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class MessagesGetHistoryRequest(
val count: Int? = null,
val offset: Int? = null,
val peerId: Int,
val extended: Boolean? = null,
val startMessageId: Int? = null,
val rev: Boolean? = null,
val fields: String? = null,
) : Parcelable {
val map
get() = mutableMapOf(
"peer_id" to peerId.toString()
).apply {
count?.let { this["count"] = it.toString() }
offset?.let { this["offset"] = it.toString() }
extended?.let { this["extended"] = (if (it) 1 else 0).toString() }
startMessageId?.let { this["start_message_id"] = it.toString() }
rev?.let { this["rev"] = (if (it) 1 else 0).toString() }
fields?.let { this["fields"] = it }
}
}
@Parcelize
data class MessagesSendRequest(
val peerId: Int,
val randomId: Int = 0,
val message: String? = null,
val lat: Int? = null,
val lon: Int? = null,
val replyTo: Int? = null,
val stickerId: Int? = null,
val disableMentions: Boolean? = null,
val dontParseLinks: Boolean? = null
) : Parcelable {
val map
get() = mutableMapOf(
"peer_id" to peerId.toString(),
"random_id" to randomId.toString()
).apply {
message?.let { this["message"] = it }
lat?.let { this["lat"] = it.toString() }
lon?.let { this["lon"] = it.toString() }
replyTo?.let { this["reply_to"] = it.toString() }
stickerId?.let { this["sticker_id"] = it.toString() }
disableMentions?.let { this["disable_mentions"] = (if (it) 1 else 0).toString() }
dontParseLinks?.let { this["dont_parse_links"] = (if (it) 1 else 0).toString() }
}
}
@@ -0,0 +1,21 @@
package com.meloda.fast.api.network.request
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class UsersGetRequest(
val usersIds: List<Int>? = null,
val fields: String? = null,
val nomCase: String? = null
) : Parcelable {
val map
get() = mutableMapOf<String, String>()
.apply {
usersIds?.let { this["user_ids"] = it.joinToString { id -> id.toString() } }
fields?.let { this["fields"] = it }
nomCase?.let { this["nom_case"] = it }
}
}
@@ -1 +1,26 @@
package com.meloda.fast.api.network.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.meloda.fast.api.model.base.BaseVkConversation
import com.meloda.fast.api.model.base.BaseVkGroup
import com.meloda.fast.api.model.base.BaseVkMessage
import com.meloda.fast.api.model.base.BaseVkUser
import kotlinx.parcelize.Parcelize
@Parcelize
data class ConversationsGetResponse(
val count: Int,
val items: List<ConversationsResponseItems>,
@SerializedName("unread_count")
val unreadCount: Int?,
val profiles: List<BaseVkUser>?,
val groups: List<BaseVkGroup>?
) : Parcelable
@Parcelize
data class ConversationsResponseItems(
val conversation: BaseVkConversation,
@SerializedName("last_message")
val lastMessage: BaseVkMessage?
) : Parcelable
@@ -0,0 +1,17 @@
package com.meloda.fast.api.network.response
import android.os.Parcelable
import com.meloda.fast.api.model.base.BaseVkConversation
import com.meloda.fast.api.model.base.BaseVkGroup
import com.meloda.fast.api.model.base.BaseVkMessage
import com.meloda.fast.api.model.base.BaseVkUser
import kotlinx.parcelize.Parcelize
@Parcelize
data class MessagesGetHistoryResponse(
val count: Int,
val items: List<BaseVkMessage> = listOf(),
val conversations: List<BaseVkConversation>?,
val profiles: List<BaseVkUser>?,
val groups: List<BaseVkGroup>?
) : Parcelable
@@ -0,0 +1,5 @@
package com.meloda.fast.api.network.response
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@@ -1,33 +0,0 @@
package com.meloda.fast.base
import android.os.Bundle
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.meloda.fast.R
abstract class BaseFullscreenDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.AppTheme_FullScreenDialog)
}
override fun onStart() {
super.onStart()
dialog?.let { dialog ->
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.MATCH_PARENT
dialog.window?.let {
it.setLayout(width, height)
it.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
it.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
it.setWindowAnimations(R.style.AppTheme_Slide)
}
}
}
}
@@ -8,15 +8,15 @@ import android.widget.AdapterView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@Suppress("UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate", "CanBeParameter")
abstract class BaseAdapter<Item : BaseItem, VH : BaseHolder>(
@Suppress("MemberVisibilityCanBePrivate", "unused")
abstract class BaseAdapter<Item, VH : BaseHolder>(
var context: Context,
values: ArrayList<Item>,
values: MutableList<Item>,
diffUtil: DiffUtil.ItemCallback<Item>
) : ListAdapter<Item, VH>(diffUtil) {
val cleanValues = arrayListOf<Item>()
val values = arrayListOf<Item>()
val cleanValues = mutableListOf<Item>()
val values = mutableListOf<Item>()
init {
addAll(values)
@@ -24,18 +24,24 @@ abstract class BaseAdapter<Item : BaseItem, VH : BaseHolder>(
protected var inflater: LayoutInflater = LayoutInflater.from(context)
var itemClickListener: OnItemClickListener? = null
var itemLongClickListener: OnItemLongClickListener? = null
var itemClickListener: ((position: Int) -> Unit) = {}
var itemLongClickListener: ((position: Int) -> Boolean) = { false }
open fun destroy() {
itemClickListener = null
itemLongClickListener = null
}
open fun destroy() {}
override fun getItem(position: Int): Item {
return values[position]
}
fun getOrNull(position: Int): Item? {
return if (position >= 0 && position <= values.lastIndex) get(position) else null
}
fun getOrElse(position: Int, defaultValue: (Int) -> Item): Item {
return if (position >= 0 && position <= values.lastIndex) get(position)
else defaultValue(position)
}
fun add(position: Int, item: Item) {
values.add(position, item)
cleanValues.add(position, item)
@@ -94,39 +100,32 @@ abstract class BaseAdapter<Item : BaseItem, VH : BaseHolder>(
return inflater.inflate(resId, viewGroup, attachToRoot)
}
fun updateValues(arrayList: ArrayList<Item>) {
fun updateValues(list: MutableList<Item>) {
values.clear()
values += arrayList
values += list
}
fun updateValues(list: List<Item>) = updateValues(ArrayList(list))
override fun onBindViewHolder(holder: VH, position: Int) {
onBindItemViewHolder(holder, position)
}
protected fun initListeners(itemView: View, position: Int) {
if (itemView is AdapterView<*>) return
itemView.setOnClickListener {
itemClickListener?.onItemClick(position)
}
itemView.setOnLongClickListener {
itemLongClickListener?.onItemLongClick(position)
return@setOnLongClickListener itemClickListener == null
}
}
override fun getItemCount(): Int {
return values.size
}
val size get() = itemCount
private fun onBindItemViewHolder(holder: VH, position: Int) {
initListeners(holder.itemView, position)
holder.bind(position)
}
protected fun initListeners(itemView: View, position: Int) {
if (itemView is AdapterView<*>) return
itemView.setOnClickListener { itemClickListener.invoke(position) }
itemView.setOnLongClickListener { itemLongClickListener.invoke(position) }
}
override fun getItemCount(): Int {
return values.size
}
val lastPosition
get() = itemCount - 1
}
@@ -6,7 +6,9 @@ import androidx.viewbinding.ViewBinding
abstract class BaseHolder(v: View) : RecyclerView.ViewHolder(v) {
open fun bind(position: Int) {}
open fun bind(position: Int) {
bind(position, null)
}
open fun bind(position: Int, payloads: MutableList<Any>?) {}
@@ -1,38 +1,22 @@
package com.meloda.fast.common
import android.annotation.SuppressLint
import android.app.Application
import android.content.ClipboardManager
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.database.sqlite.SQLiteDatabase
import android.net.ConnectivityManager
import android.os.Handler
import android.view.inputmethod.InputMethodManager
import androidx.core.content.pm.PackageInfoCompat
import androidx.preference.PreferenceManager
import androidx.room.Room
import com.meloda.fast.BuildConfig
import com.meloda.fast.R
import com.meloda.fast.database.DatabaseHelper
import com.meloda.fast.database.AppDatabase
import com.meloda.fast.util.AndroidUtils
import dagger.hilt.android.HiltAndroidApp
import org.acra.ACRA
import org.acra.ReportingInteractionMode
import org.acra.annotation.ReportsCrashes
import java.util.*
@SuppressLint("NonConstantResourceId")
@ReportsCrashes(
mailTo = "lischenkodev@gmail.com",
mode = ReportingInteractionMode.DIALOG,
resDialogTitle = R.string.app_has_been_crashed,
resDialogText = R.string.empty,
resDialogTheme = R.style.AppTheme_Dialog,
resDialogPositiveButtonText = R.string.send_crash_report,
resDialogNegativeButtonText = R.string.ok
)
@HiltAndroidApp
class AppGlobal : Application() {
@@ -43,14 +27,11 @@ class AppGlobal : Application() {
lateinit var clipboardManager: ClipboardManager
lateinit var preferences: SharedPreferences
lateinit var locale: Locale
lateinit var handler: Handler
lateinit var resources: Resources
lateinit var packageName: String
lateinit var instance: AppGlobal
lateinit var dbHelper: DatabaseHelper
lateinit var database: SQLiteDatabase
lateinit var appDatabase: AppDatabase
lateinit var packageManager: PackageManager
@@ -59,10 +40,6 @@ class AppGlobal : Application() {
var screenWidth = 0
var screenHeight = 0
fun post(runnable: Runnable) {
handler.post(runnable)
}
}
override fun onCreate() {
@@ -73,12 +50,13 @@ class AppGlobal : Application() {
ACRA.init(this)
}
preferences = PreferenceManager.getDefaultSharedPreferences(this)
handler = Handler(mainLooper)
locale = Locale.getDefault()
appDatabase = Room.databaseBuilder(
this, AppDatabase::class.java, "cache"
)
.fallbackToDestructiveMigration()
.build()
dbHelper = DatabaseHelper(this)
database = dbHelper.writableDatabase
preferences = PreferenceManager.getDefaultSharedPreferences(this)
val info = packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES)
versionName = info.versionName
@@ -88,7 +66,6 @@ class AppGlobal : Application() {
Companion.packageName = packageName
Companion.packageManager = packageManager
screenWidth = AndroidUtils.getDisplayWidth()
screenHeight = AndroidUtils.getDisplayHeight()
@@ -0,0 +1,31 @@
package com.meloda.fast.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.meloda.fast.api.model.VkConversation
import com.meloda.fast.api.model.VkGroup
import com.meloda.fast.api.model.VkMessage
import com.meloda.fast.api.model.VkUser
import com.meloda.fast.database.dao.ConversationsDao
import com.meloda.fast.database.dao.GroupsDao
import com.meloda.fast.database.dao.MessagesDao
import com.meloda.fast.database.dao.UsersDao
@Database(
entities = [
VkConversation::class,
VkMessage::class,
VkUser::class,
VkGroup::class
],
version = 15,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun conversationsDao(): ConversationsDao
abstract fun messagesDao(): MessagesDao
abstract fun usersDao(): UsersDao
abstract fun groupsDao(): GroupsDao
}
@@ -1,115 +0,0 @@
package com.meloda.fast.database
import android.content.ContentValues
import android.database.Cursor
import android.os.Bundle
import com.meloda.fast.common.AppGlobal.Companion.database
import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS
import com.meloda.fast.database.DatabaseUtils.TABLE_FRIENDS
import com.meloda.fast.database.DatabaseUtils.TABLE_MESSAGES
import com.meloda.fast.database.DatabaseUtils.TABLE_USERS
import com.meloda.fast.database.storage.ChatsStorage
import com.meloda.fast.database.storage.GroupsStorage
import com.meloda.fast.database.storage.MessagesStorage
import com.meloda.fast.database.storage.UsersStorage
import com.meloda.fast.api.model.VKConversation
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.api.model.VKUser
import java.util.*
object CacheStorage {
val usersStorage = UsersStorage()
val messagesStorage = MessagesStorage()
val chatsStorage = ChatsStorage()
val groupsStorage = GroupsStorage()
fun selectCursor(tableName: String): Cursor {
return QueryBuilder.query()
.select("*").from(tableName)
.asCursor(database)
}
fun selectCursor(tableName: String, where: String): Cursor {
return QueryBuilder.query()
.select("*").from(tableName)
.where(where)
.asCursor(database)
}
fun selectCursor(tableName: String, columnName: String, value: Any): Cursor {
return QueryBuilder.query()
.select("*").from(tableName)
.where("$columnName=$value")
.asCursor(database)
}
fun selectCursor(tableName: String, columnName: String, ids: IntArray): Cursor {
val where = StringBuilder(5 * ids.size)
where.append("$columnName=${ids[0]}")
for (i in 1 until ids.size) {
where.append(" OR ")
where.append("$columnName=${ids[i]}")
}
return selectCursor(tableName, where.toString())
}
fun getInt(cursor: Cursor, columnName: String) =
cursor.getInt(cursor.getColumnIndexOrThrow(columnName))
fun getString(cursor: Cursor, columnName: String) =
cursor.getString(cursor.getColumnIndexOrThrow(columnName))
fun getBlob(cursor: Cursor, columnName: String) =
cursor.getBlob(cursor.getColumnIndexOrThrow(columnName))
fun <T> insert(tableName: String, values: ArrayList<T>) {
database.beginTransaction()
val contentValues = ContentValues()
for (value in values) {
when (tableName) {
TABLE_USERS -> {
usersStorage.cacheValue(contentValues, value as VKUser)
break
}
TABLE_FRIENDS -> {
usersStorage.cacheValue(
contentValues,
value as VKUser,
Bundle().apply { putBoolean("toFriends", true) })
break
}
TABLE_MESSAGES -> {
messagesStorage.cacheValue(contentValues, value as VKMessage)
break
}
TABLE_CHATS -> {
chatsStorage.cacheValue(contentValues, value as VKConversation)
break
}
}
database.insert(tableName, null, contentValues)
contentValues.clear()
}
database.setTransactionSuccessful()
database.endTransaction()
}
fun delete(tableName: String, whereClause: String, vararg whereArgs: String) {
database.delete(tableName, whereClause, whereArgs)
}
fun delete(tableName: String) {
database.delete(tableName, null, null)
}
}
@@ -1,29 +0,0 @@
package com.meloda.fast.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class DatabaseHelper constructor(context: Context) : SQLiteOpenHelper(
context,
DB_NAME,
null,
DB_VERSION
) {
companion object {
private const val DB_NAME = "cache.db"
private const val DB_VERSION = 1
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(DatabaseUtils.createUsersTable())
db.execSQL(DatabaseUtils.createGroupsTable())
db.execSQL(DatabaseUtils.createFriendsTable())
db.execSQL(DatabaseUtils.createMessagesTable())
db.execSQL(DatabaseUtils.createChatsTable())
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
@@ -1,95 +0,0 @@
package com.meloda.fast.database
object DatabaseKeys {
const val ID = "_id"
const val SORT_ID = "_sort_id"
const val USER_ID = "_user_id"
const val FIRST_NAME = "_first_name"
const val LAST_NAME = "_last_name"
const val DEACTIVATED = "_deactivated"
const val GENDER = "_gender"
const val SCREEN_NAME = "_screen_name"
const val PHOTOS = "_photos"
const val IS_ONLINE = "_is_online"
const val IS_ONLINE_MOBILE = "_is_online_mobile"
const val STATUS = "_status"
const val LAST_SEEN = "_last_seen"
const val MESSAGE_ID = "_message_id"
const val DATE = "_date"
const val PEER_ID = "_peer_id"
const val FROM_ID = "_from_id"
const val EDIT_TIME = "_edit_time"
const val IS_OUT = "_is_out"
const val TEXT = "_text"
const val RANDOM_ID = "_random_id"
const val CONVERSATION_MESSAGE_ID = "_conversation_message_id"
const val ATTACHMENTS = "_attachments"
const val FWD_MESSAGES = "_fwd_messages"
const val REPLY_MESSAGE_ID = "_reply_message_id"
const val ACTION = "_action"
const val IS_ALLOWED = "_is_allowed"
const val NOT_ALLOWED_REASON = "_not_allowed_reason"
const val IN_READ_MESSAGE_ID = "_in_read_message_id"
const val OUT_READ_MESSAGE_ID = "_out_read_message_id"
const val LAST_MESSAGE_ID = "_last_message_id"
const val UNREAD_COUNT = "_unread_count"
const val CONVERSATION_ID = "_conversation_id"
const val TYPE = "_type"
const val LOCAL_ID = "_local_id"
const val IS_NOTIFICATIONS_DISABLED = "_is_notifications_disabled"
const val MEMBERS_COUNT = "_members_count"
const val TITLE = "_title"
const val PINNED_MESSAGE_ID = "_pinned_message_id"
const val CHAT_STATE = "_chat_state"
const val IS_GROUP_CHANNEL = "_is_group_channel"
const val FRIEND_ID = "_friend_id"
const val GROUP_ID = "_group_id"
const val NAME = "_name"
const val IS_CLOSED = "_is_closed"
}

Some files were not shown because too many files have changed in this diff Show More