Move from java/ to kotlin/ directory

Android 12 dynamic color usage on login screen
This commit is contained in:
2021-08-31 02:18:29 +03:00
parent 2453e534ae
commit 1209c37e24
135 changed files with 140 additions and 57 deletions
@@ -0,0 +1,6 @@
package com.meloda.fast.api
sealed class Answer<out R> {
data class Success<out T>(val data: T) : Answer<T>()
data class Error(val errorString: String) : Answer<Nothing>()
}
@@ -0,0 +1,42 @@
package com.meloda.fast.api
object ErrorCodes {
const val UNKNOWN_ERROR = 1
const val APP_DISABLED = 2
const val UNKNOWN_METHOD = 3
const val INVALID_SIGNATURE = 4
const val USER_AUTHORIZATION_FAILED = 5
const val TOO_MANY_REQUESTS = 6
const val NO_RIGHTS = 7
const val BAD_REQUEST = 8
const val TOO_MANY_SIMILAR_ACTIONS = 9
const val INTERNAL_SERVER_ERROR = 10
const val IN_TEST_MODE = 11
const val EXECUTE_CODE_COMPILE_ERROR = 12
const val EXECUTE_CODE_RUNTIME_ERROR = 13
const val CAPTCHA_NEEDED = 14
const val ACCESS_DENIED = 15
const val REQUIRES_REQUESTS_OVER_HTTPS = 16
const val VALIDATION_REQUIRED = 17
const val USER_BANNED_OR_DELETED = 18
const val ACTION_PROHIBITED = 20
const val ACTION_ALLOWED_ONLY_FOR_STANDALONE = 21
const val METHOD_OFF = 23
const val CONFIRMATION_REQUIRED = 24
const val PARAMETER_IS_NOT_SPECIFIED = 100
const val INCORRECT_APP_ID = 101
const val OUT_OF_LIMITS = 103
const val INCORRECT_USER_ID = 113
const val INCORRECT_TIMESTAMP = 150
const val ACCESS_TO_ALBUM_DENIED = 200
const val ACCESS_TO_AUDIO_DENIED = 201
const val ACCESS_TO_GROUP_DENIED = 203
const val ALBUM_IS_FULL = 300
const val ACTION_DENIED = 500
const val PERMISSION_DENIED = 600
const val CANNOT_SEND_MESSAGE_BLACK_LIST = 900
const val CANNOT_SEND_MESSAGE_GROUP = 901
const val INVALID_DOC_ID = 1150
const val INVALID_DOC_TITLE = 1152
const val ACCESS_TO_DOC_DENIED = 1153
}
@@ -0,0 +1,9 @@
package com.meloda.fast.api
interface OnResponseListener<T> {
fun onResponse(response: T)
fun onError(t: Throwable)
}
@@ -0,0 +1,2 @@
package com.meloda.fast.api
@@ -0,0 +1,25 @@
package com.meloda.fast.api
data class Resource<out T> constructor(
val status: Status,
val responseData: T?,
val message: String?
) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(responseData: T?): Resource<T> =
Resource(Status.SUCCESS, responseData, null)
fun <T> error(message: String?, responseBody: T? = null): Resource<T> =
Resource(Status.ERROR, responseBody, message)
fun <T> loading(responseData: T? = null): Resource<T> =
Resource(Status.LOADING, responseData, null)
}
}
@@ -0,0 +1,504 @@
package com.meloda.fast.api
import android.os.Handler
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.BuildConfig
import com.meloda.fast.api.method.MessageMethodSetter
import com.meloda.fast.api.method.MethodSetter
import com.meloda.fast.api.method.UserMethodSetter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
import kotlin.collections.ArrayList
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@Suppress("UNCHECKED_CAST")
object VKApi {
private const val TAG = "VKM:VKApi"
const val BASE_URL = "https://api.vk.com/method/"
const val API_VERSION = "5.132"
var language: String = ""
var token: String = ""
private lateinit var handler: Handler
fun init(language: String, token: String, handler: Handler) {
VKApi.language = language
VKApi.token = token
VKApi.handler = handler
}
@WorkerThread
@Suppress("UNCHECKED_CAST")
fun <T> execute(url: String, cls: Class<T>?): ArrayList<T>? {
if (BuildConfig.DEBUG) {
Log.w(TAG, "url: $url")
}
val buffer = com.meloda.fast.net.HttpRequest[url].asString()
if (BuildConfig.DEBUG) {
Log.i(TAG, "response: $buffer")
}
val json = JSONObject(buffer)
try {
checkError(json, url)
} catch (ex: VKException) {
if (ex.code == ErrorCodes.TOO_MANY_REQUESTS) {
Timer().schedule(object : TimerTask() {
override fun run() {
execute(url, cls)
}
}, 1000)
} else throw ex
}
when (cls) {
null -> return null
com.meloda.fast.api.model.VKLongPollServer::class.java -> {
json.optJSONObject("response")?.let {
return arrayListOf(com.meloda.fast.api.model.VKLongPollServer(it)) as ArrayList<T>?
}
}
Boolean::class.java -> {
val value = json.optInt("response") == 1
return arrayListOf(value) as ArrayList<T>?
}
Long::class.java -> {
val value = json.optLong("response")
return arrayListOf(value) as ArrayList<T>?
}
Int::class.java -> {
val value = json.optInt("response")
return arrayListOf(value) as ArrayList<T>?
}
}
val response = json.opt("response") ?: return null
val array = optItems(json) ?: return null
val models = ArrayList<T>(array.length())
when (cls) {
com.meloda.fast.api.model.VKUser::class.java -> {
json.optJSONObject("response")?.let { r ->
com.meloda.fast.api.model.VKUser.friendsCount = r.optInt("count")
}
for (i in 0 until array.length()) {
models.add(com.meloda.fast.api.model.VKUser(array.optJSONObject(i)) as T)
}
}
com.meloda.fast.api.model.VKMessage::class.java -> {
response as JSONObject
if (url.contains("messages.getHistory")) {
com.meloda.fast.api.model.VKMessage.lastHistoryCount = response.optInt("count")
response.optJSONArray("profiles")?.let {
val profiles = arrayListOf<com.meloda.fast.api.model.VKUser>()
for (j in 0 until it.length()) {
profiles.add(com.meloda.fast.api.model.VKUser(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKMessage.profiles = profiles
}
response.optJSONArray("groups")?.let {
val groups = arrayListOf<com.meloda.fast.api.model.VKGroup>()
for (j in 0 until it.length()) {
groups.add(com.meloda.fast.api.model.VKGroup(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKMessage.groups = groups
}
response.optJSONArray("conversations")?.let {
val conversations = arrayListOf<com.meloda.fast.api.model.VKConversation>()
for (j in 0 until it.length()) {
conversations.add(
com.meloda.fast.api.model.VKConversation(
it.optJSONObject(
j
)
)
)
}
com.meloda.fast.api.model.VKMessage.conversations = conversations
}
}
for (i in 0 until array.length()) {
var source = array.optJSONObject(i)
if (source.has("message")) {
source = source.optJSONObject("message")
}
val message = com.meloda.fast.api.model.VKMessage(source)
models.add(message as T)
}
}
com.meloda.fast.api.model.VKGroup::class.java -> {
for (i in 0 until array.length()) {
models.add(com.meloda.fast.api.model.VKGroup(array.optJSONObject(i)) as T)
}
}
com.meloda.fast.api.model.VKModel::class.java -> {
if (url.contains("messages.getHistoryAttachments")) {
return com.meloda.fast.api.model.VKAttachments.parse(array) as ArrayList<T>
}
}
com.meloda.fast.api.model.VKConversation::class.java -> {
if (url.contains("getConversationsById")) {
for (i in 0 until array.length()) {
val source = array.optJSONObject(i)
models.add(com.meloda.fast.api.model.VKConversation(source) as T)
}
return models
}
json.optJSONObject("response")?.let { r ->
com.meloda.fast.api.model.VKConversation.conversationsCount = r.optInt("count")
}
for (i in 0 until array.length()) {
response as JSONObject
val source = array.optJSONObject(i)
val oConversation = source.optJSONObject("conversation") ?: return null
val oLastMessage = source.optJSONObject("last_message") ?: return null
val conversation = com.meloda.fast.api.model.VKConversation(oConversation).also {
it.lastMessage = com.meloda.fast.api.model.VKMessage(oLastMessage)
}
response.optJSONArray("profiles")?.let {
val profiles = arrayListOf<com.meloda.fast.api.model.VKUser>()
for (j in 0 until it.length()) {
profiles.add(com.meloda.fast.api.model.VKUser(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKConversation.profiles = profiles
}
response.optJSONArray("groups")?.let {
val groups = arrayListOf<com.meloda.fast.api.model.VKGroup>()
for (j in 0 until it.length()) {
groups.add(com.meloda.fast.api.model.VKGroup(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKConversation.groups = groups
}
models.add(conversation as T)
}
}
}
return models
}
fun <E> execute(url: String, cls: Class<E>, listener: OnResponseListener<E>?) {
com.meloda.fast.concurrent.TaskManager.execute {
try {
val models = execute(url, cls) ?: return@execute
// listener?.onResponse(models)
} catch (e: Exception) {
e.printStackTrace()
listener?.onError(e)
// it.resumeWithException(e)
}
}
}
suspend fun <E> suspendExecute(url: String, cls: Class<E>): Flow<E> {
return suspendCoroutine {
try {
val models = execute(url, cls)?.asFlow() ?: return@suspendCoroutine
it.resume(models)
} catch (e: Exception) {
e.printStackTrace()
it.resumeWithException(e)
}
}
}
fun <E> executeArray(url: String, cls: Class<E>, listener: OnResponseListener<ArrayList<E>>) {
com.meloda.fast.concurrent.TaskManager.execute {
try {
val models = execute(url, cls)
handler.post { listener.onResponse(models as ArrayList<E>) }
} catch (e: Exception) {
e.printStackTrace()
listener.onError(e)
}
}
}
private fun optItems(source: JSONObject): JSONArray? {
val response = source.opt("response")
return when (response) {
is JSONArray -> response
is JSONObject -> response.optJSONArray("items")
else -> null
}
}
private fun checkError(json: JSONObject, url: String) {
if (json.has("error")) {
val error = json.optJSONObject("error") ?: return
val code = error.optInt("error_code", -1)
val message = error.optString("error_msg", "")
val e = VKException(url, message, code)
//TODO: add checking invalid session
if (code == 5 && message.contains("invalid session")) {
// context?.startActivity(Intent(context, DropUserDataActivity::class.java).apply {
// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// })
}
if (code == ErrorCodes.CAPTCHA_NEEDED) {
e.captchaImg = error.optString("captcha_img")
e.captchaSid = error.optString("captcha_sid")
}
if (code == ErrorCodes.VALIDATION_REQUIRED) {
e.redirectUri = error.optString("redirect_uri")
}
throw e
}
}
fun users(): VKUsers {
return VKUsers()
}
fun friends(): VKFriends {
return VKFriends()
}
fun messages(): VKMessages {
return VKMessages()
}
fun groups(): VKGroups {
return VKGroups()
}
fun account(): VKAccounts {
return VKAccounts()
}
class VKFriends {
fun get(): MethodSetter {
return MethodSetter("friends.get")
}
}
class VKUsers {
fun get(): UserMethodSetter {
return UserMethodSetter("users.get")
}
}
class VKMessages {
fun get(): MessageMethodSetter {
return MessageMethodSetter("messages.get")
}
fun getConversations(): MessageMethodSetter {
return MessageMethodSetter("messages.getConversations")
}
fun getConversationsById(): MessageMethodSetter {
return MessageMethodSetter("messages.getConversationsById")
}
fun getById(): MessageMethodSetter {
return MessageMethodSetter("messages.getById")
}
fun search(): MessageMethodSetter {
return MessageMethodSetter("messages.search")
}
fun getHistory(): MessageMethodSetter {
return MessageMethodSetter("messages.getHistory")
}
fun getHistoryAttachments(): MessageMethodSetter {
return MessageMethodSetter("messages.getHistoryAttachments")
}
fun send(): MessageMethodSetter {
return MessageMethodSetter("messages.send")
}
fun sendSticker(): MessageMethodSetter {
return MessageMethodSetter("messages.sendSticker")
}
fun delete(): MessageMethodSetter {
return MessageMethodSetter("messages.delete")
}
fun deleteDialog(): MessageMethodSetter {
return MessageMethodSetter("messages.deleteDialog")
}
fun restore(): MessageMethodSetter {
return MessageMethodSetter("messages.restore")
}
fun markAsRead(): MessageMethodSetter {
return MessageMethodSetter("messages.markAsRead")
}
fun markAsImportant(): MessageMethodSetter {
return MessageMethodSetter("messages.markAsImportant")
}
fun getLongPollServer(): MessageMethodSetter {
return MessageMethodSetter("messages.getLongPollServer")
}
/**
* Returns updates in user's private messages.
* To speed up handling of private messages,
* it can be useful to cache previously loaded messages on
* a user's mobile device/desktop, to prevent re-receipt at each call.
* With this method, you can synchronize a local copy of
* the message list with the actual version.
*
*
* Result:
* Returns an object that contains the following fields:
* 1 — history: An array similar to updates field returned
* from the Long Poll server,
* with these exceptions:
* - For events with code 4 (addition of a new message),
* there are no fields except the first three.
* - There are no events with codes 8, 9 (friend goes online/offline)
* or with codes 61, 62 (typing during conversation/chat).
*
*
* 2 — messages: An array of private message objects that were found
* among events with code 4 (addition of a new message)
* from the history field.
* Each object of message contains a set of fields described here.
* The first array element is the total number of messages
*/
fun getLongPollHistory(): MessageMethodSetter {
return MessageMethodSetter("messages.getLongPollHistory")
}
fun getChat(): MessageMethodSetter {
return MessageMethodSetter("messages.getChat")
}
fun createChat(): MessageMethodSetter {
return MessageMethodSetter("messages.createChat")
}
fun editChat(): MessageMethodSetter {
return MessageMethodSetter("messages.editChat")
}
val chatUsers: MessageMethodSetter
get() = MessageMethodSetter("messages.getChatUsers")
fun setActivity(): MessageMethodSetter {
return MessageMethodSetter("messages.setActivity").type(true)
}
fun addChatUser(): MessageMethodSetter {
return MessageMethodSetter("messages.addChatUser")
}
fun removeChatUser(): MessageMethodSetter {
return MessageMethodSetter("messages.removeChatUser")
}
}
class VKGroups {
fun getById(): MethodSetter {
return MethodSetter("groups.getById")
}
fun join(): MethodSetter {
return MethodSetter("groups.join")
}
}
class VKAccounts {
fun setOffline(): MethodSetter {
return MethodSetter("account.setOffline")
}
fun setOnline(): MethodSetter {
return MethodSetter("account.setOnline")
}
}
class SuccessCallback<E>(
private val listener: OnResponseListener<E>?,
private val response: E
) : Runnable {
override fun run() {
listener?.onResponse(response)
}
}
class SuccessArrayCallback<E>(
private val listener: OnResponseListener<ArrayList<E>>?,
private val response: ArrayList<E>
) : Runnable {
override fun run() {
listener?.onResponse(response)
}
}
class ErrorCallback<E>(
private val listener: OnResponseListener<E>?,
private val exception: Exception
) : Runnable {
override fun run() {
listener?.onError(exception)
}
}
}
@@ -0,0 +1,14 @@
package com.meloda.fast.api
enum class VKApiKeys(val value: String) {
READ_MESSAGE("_read_message"),
RESTORE_MESSAGE("_restore_message"),
NEW_MESSAGE("_new_message"),
EDIT_MESSAGE("_edit_message"),
DELETE_MESSAGE("_delete_message"),
UPDATE_MESSAGE("_update_message"),
UPDATE_CONVERSATION("_update_conversation"),
UPDATE_USER("_update_user"),
UPDATE_GROUP("_update_group")
}
@@ -0,0 +1,84 @@
package com.meloda.fast.api
import android.util.Log
import com.meloda.fast.BuildConfig
import com.meloda.fast.api.util.VKUtil
import java.net.URLEncoder
object VKAuth {
private const val TAG = "VKM.VKAuth"
const val settings =
"notify," +
"friends," +
"photos," +
"audio," +
"video," +
"docs," +
"status," +
"notes," +
"pages," +
"wall," +
"groups," +
"messages," +
"offline," +
"notifications"
const val redirectUrl = "https://oauth.vk.com/blank.html"
fun getDirectAuthUrl(
login: String,
password: String,
captchaSid: String? = null,
captchaKey: String? = null
): String {
return "https://oauth.vk.com/token?grant_type=password&" +
"client_id=${VKConstants.VK_APP_ID}&" +
"scope=$settings&" +
"client_secret=${VKConstants.VK_APP_SECRET}&" +
"username=$login&" +
"password=$password" +
(if (captchaSid == null || captchaKey == null) "" else "&captcha_sid=$captchaSid&captcha_key=$captchaKey") +
"&v=${VKApi.API_VERSION}"
// return "https://oauth.vk.com/token?grant_type=password&" +
// "client_id=2274003&" +
// "scope=notify,friends,photos,audio,video,docs,notes,pages,status,offers,questions,wall,groups,messages,email,notifications,stats,ads,market,offline&" +
// "client_secret=hHbZxrka2uZ6jB1inYsH&" +
// "username=$login&" +
// "password=$password" +
// (if (captcha.isEmpty()) "" else "&$captcha") +
// "&v=${VKApi.API_VERSION}"
}
fun getUrl(api_id: String, settings: String): String {
return "https://oauth.vk.com/authorize?" +
"client_id=$api_id&" +
"display=mobile&" +
"scope=$settings&" +
"redirect_uri=${
URLEncoder.encode(
redirectUrl,
"utf-8"
)
}&" +
"response_type=token&" +
"v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
}
fun parseRedirectUrl(url: String): Pair<String, Int> {
val accessToken = VKUtil.extractPattern(url, "access_token=(.*?)&") ?: ""
val userId = VKUtil.extractPattern(url, "user_id=(\\d*)")?.toIntOrNull() ?: -1
if (BuildConfig.DEBUG) {
Log.i(TAG, "access_token=$accessToken")
Log.i(TAG, "user_id=$userId")
}
if (accessToken.isEmpty() || userId == -1) throw Exception(
"Failed to parse redirect url: $url"
)
return accessToken to userId
}
}
@@ -0,0 +1,32 @@
package com.meloda.fast.api
object VKConstants {
const val GROUP_FIELDS = "description,members_count,counters,status,verified"
const val USER_FIELDS =
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex"
const val VK_APP_ID = "2274003"
const val VK_APP_SECRET = "hHbZxrka2uZ6jB1inYsH"
const val VK_ME_ID = "6146827"
const val VK_ME_SECRET = "qVxWRF1CwHERuIrKBnqe"
/*
const val ACTION_CHAT_CREATE = "chat_create"
const val ACTION_PHOTO_UPDATE = "chat_photo_update"
const val ACTION_PHOTO_REMOVE = "chat_photo_remove"
const val ACTION_TITLE_UPDATE = "chat_title_update"
const val ACTION_PIN_MESSAGE = "chat_pin_message"
const val ACTION_UNPIN_MESSAGE = "chat_unpin_message"
const val ACTION_INVITE_USER = "chat_invite_user"
const val ACTION_INVITE_USER_BY_LINK = "chat_invite_user_by_link"
const val ACTION_KICK_USER = "chat_kick_user"
const val ACTION_SCREENSHOT = "chat_screenshot"
const val ACTION_INVITE_USER_BY_CALL = "chat_invite_user_by_call"
const val ACTION_INVITE_USER_BY_CALL_JOIN_LINK = "chat_invite_user_by_call_link"
*/
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api
import java.io.IOException
class VKException(var url: String = "", override var message: String = "", var code: Int) :
IOException(message) {
var captchaSid: String? = null
var captchaImg: String? = null
var redirectUri: String? = null
override fun toString(): String {
return "code: $code, message: $message"
}
}
@@ -0,0 +1,17 @@
package com.meloda.fast.api
import com.meloda.fast.api.model.response.GetConversationsResponse
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface VKRepo {
@FormUrlEncoded
@POST(VKUrls.getConversations)
suspend fun getAllChats(
@Field("user_id") chatId: Int,
@Field("token") token: String
): Answer<GetConversationsResponse>
}
@@ -0,0 +1,7 @@
package com.meloda.fast.api
object VKUrls {
const val getConversations = "messages.getConversations"
}
@@ -0,0 +1,4 @@
package com.meloda.fast.api.datasource
class MessagesDataSource constructor() {
}
@@ -0,0 +1,97 @@
package com.meloda.fast.api.datasource.base
import android.util.Log
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.JsonSyntaxException
import com.meloda.fast.api.Resource
import com.meloda.fast.api.model.ApiResponse
import com.meloda.fast.api.ErrorCodes
import com.meloda.fast.api.VKException
import okhttp3.ResponseBody
import retrofit2.HttpException
class BaseDataSource {
private val TAG = BaseDataSource::class.simpleName
//TODO: move to resources
private val DEFAULT_ERROR = "Internal server error"
protected suspend fun <T> getResult(apiCall: suspend () -> ApiResponse<T>): Resource<T> {
try {
val response = apiCall()
return if (response.isSuccessful) {
Resource.success(response.response)
} else {
Log.d(TAG, "Server response unsuccessful")
if (response.error != null) {
Log.w(TAG, "Unsuccessful response with code 2XX")
Resource.error(response.error.message, response.response)
} else {
Log.e(TAG, "Unsuccessful result without error!")
Resource.error(DEFAULT_ERROR)
}
}
} catch (e: HttpException) {
Log.e(TAG, "Error while executing request ${e.message}")
val errorBody = e.response()?.errorBody() ?: return Resource.error(DEFAULT_ERROR)
val errorResponse = parseErrorBody<T>(errorBody) ?: return Resource.error(DEFAULT_ERROR)
return Resource.error(errorResponse.message)
} catch (e: Exception) {
Log.e(TAG, "Error while executing request ${e.message}")
return Resource.error(DEFAULT_ERROR)
}
}
private fun <T> parseErrorBody(responseBody: ResponseBody?): Exception? {
if (responseBody == null) return null
val jsonResponse: JsonObject?
try {
jsonResponse = JsonParser.parseString(responseBody.string()) as? JsonObject
if (jsonResponse == null) {
Log.d(TAG, "Response body is empty while parsing error body.")
return null
}
} catch (e: JsonSyntaxException) {
Log.e(TAG, "Error while parsing json ${e.message}")
return null
} catch (e: java.lang.Exception) {
Log.e(TAG, "Unknown error ${e.message}")
return null
}
if (jsonResponse.has("error")) {
val error = jsonResponse["error"].asJsonObject
val message = error["error_msg"].asString
val code = error["error_code"].asInt
val e = VKException("", message, code)
//TODO: add checking invalid session
if (code == 5 && message.contains("invalid session")) {
// context?.startActivity(Intent(context, DropUserDataActivity::class.java).apply {
// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// })
}
if (code == ErrorCodes.CAPTCHA_NEEDED) {
e.captchaImg = error["captcha_img"].asString
e.captchaSid = error["captcha_sid"].asString
}
if (code == ErrorCodes.VALIDATION_REQUIRED) {
e.redirectUri = error["redirect_uri"].asString
}
return e
}
return null
}
}
@@ -0,0 +1,205 @@
package com.meloda.fast.api.method
import com.meloda.fast.util.ArrayUtils
class MessageMethodSetter(name: String) : MethodSetter(name) {
fun out(value: Boolean): MessageMethodSetter {
put("out", value)
return this
}
fun timeOffset(value: Int): MessageMethodSetter {
put("time_offset", value)
return this
}
fun filters(value: Int): MessageMethodSetter {
put("filters", value)
return this
}
fun previewLength(value: Int): MessageMethodSetter {
put("preview_length", value)
return this
}
fun lastMessageId(value: Int): MessageMethodSetter {
put("last_message_id", value)
return this
}
fun unread(value: Boolean): MessageMethodSetter {
put("unread", value)
return this
}
fun messageIds(vararg ids: Int): MessageMethodSetter {
put("message_ids", ArrayUtils.asString(ids))
return this
}
fun messageIds(ids: ArrayList<Int>): MessageMethodSetter {
put("message_ids", ArrayUtils.asString(ids))
return this
}
fun q(query: String): MessageMethodSetter {
put("q", query)
return this
}
fun startMessageId(id: Int): MessageMethodSetter {
put("start_message_id", id)
return this
}
fun peerId(value: Int): MessageMethodSetter {
put("peer_id", value)
return this
}
fun peerIds(vararg values: Int): MessageMethodSetter {
put("peer_ids", com.meloda.fast.util.ArrayUtils.asString(values))
return this
}
fun reversed(value: Boolean): MessageMethodSetter {
put("rev", value)
return this
}
fun domain(value: String): MessageMethodSetter {
put("domain", value)
return this
}
fun chatId(value: Int): MessageMethodSetter {
put("chat_id", value)
return this
}
fun message(message: String): MessageMethodSetter {
put("message", message)
return this
}
fun randomId(value: Int): MessageMethodSetter {
put("random_id", value)
return this
}
fun lat(lat: Double): MessageMethodSetter {
put("lat", lat)
return this
}
fun longitude(value: Long): MessageMethodSetter {
put("LONG", value)
return this
}
fun attachment(attachments: Collection<String>): MessageMethodSetter {
put("attachment", com.meloda.fast.util.ArrayUtils.asString(attachments))
return this
}
fun attachment(vararg attachments: String): MessageMethodSetter {
put("attachment", com.meloda.fast.util.ArrayUtils.asString(*attachments))
return this
}
fun forwardMessages(ids: Collection<String>): MessageMethodSetter {
put("forward_messages", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun forwardMessages(vararg ids: Int): MessageMethodSetter {
put("forward_messages", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun stickerId(value: Int): MessageMethodSetter {
put("sticker_id", value)
return this
}
fun messageId(value: Int): MessageMethodSetter {
put("message_id", value)
return this
}
fun important(value: Boolean): MessageMethodSetter {
put("important", value)
return this
}
fun ts(value: Long): MessageMethodSetter {
put("ts", value)
return this
}
fun pts(value: Int): MessageMethodSetter {
put("pts", value)
return this
}
fun msgsLimit(limit: Int): MessageMethodSetter {
put("msgs_limit", limit)
return this
}
fun onlines(onlines: Boolean): MessageMethodSetter {
put("onlines", onlines)
return this
}
fun maxMsgId(id: Int): MessageMethodSetter {
put("max_msg_id", id)
return this
}
fun chatIds(vararg ids: Int): MessageMethodSetter {
put("max_msg_id", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun chatIds(ids: Collection<Int>): MessageMethodSetter {
put("max_msg_id", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun title(title: String): MessageMethodSetter {
put("title", title)
return this
}
fun type(typing: Boolean): MessageMethodSetter {
if (typing) {
put("type", "typing")
}
return this
}
fun mediaType(type: String): MessageMethodSetter {
put("media_type", type)
return this
}
fun photoSizes(value: Boolean): MessageMethodSetter {
return put("photo_sizes", value) as MessageMethodSetter
}
fun filter(value: String): MessageMethodSetter {
return put("filter", value) as MessageMethodSetter
}
fun extended(value: Boolean): MessageMethodSetter {
return put("extended", value) as MessageMethodSetter
}
fun markConversationAsRead(asRead: Boolean): MessageMethodSetter {
put("mark_conversation_as_read", asRead)
return this
}
}
@@ -0,0 +1,169 @@
package com.meloda.fast.api.method
import android.util.ArrayMap
import android.util.Log
import com.meloda.fast.BuildConfig
import com.meloda.fast.api.OnResponseListener
import com.meloda.fast.api.VKApi
import kotlinx.coroutines.flow.Flow
import java.net.URLEncoder
@Suppress("UNCHECKED_CAST")
open class MethodSetter(private val name: String) {
private val params: ArrayMap<String, String> = ArrayMap()
fun put(key: String, value: Any): MethodSetter {
params[key] = value.toString()
return this
}
fun put(key: String, value: String): MethodSetter {
params[key] = value
return this
}
fun put(key: String, value: Int): MethodSetter {
params[key] = value.toString()
return this
}
fun put(key: String, value: Long): MethodSetter {
params[key] = value.toString()
return this
}
fun put(key: String, value: Boolean): MethodSetter {
params[key] = if (value) "1" else "0"
return this
}
private fun getSignedUrl(): String {
if (!params.containsKey("access_token")) {
params["access_token"] = VKApi.token
}
if (!params.containsKey("v")) {
params["v"] = VKApi.API_VERSION
}
if (!params.containsKey("lang")) {
params["lang"] = VKApi.language
}
return "${VKApi.BASE_URL}$name?${retrieveParams()}"
}
private fun retrieveParams(): String {
val builder = StringBuilder()
for (i in 0 until params.size) {
val key = params.keyAt(i)
val value = params.valueAt(i)
if (builder.isNotEmpty()) {
builder.append("&")
}
builder.append(key)
builder.append("=")
builder.append(URLEncoder.encode(value, "UTF-8"))
}
val params = builder.toString()
if (BuildConfig.DEBUG) {
Log.i("MethodSetter", "retrieved params: $params")
}
return params
}
suspend fun <E> executeSuspend(cls: Class<E>): Flow<E> {
return VKApi.suspendExecute(getSignedUrl(), cls)
}
fun <E> execute(cls: Class<E>): ArrayList<E>? {
return VKApi.execute(getSignedUrl(), cls)
}
fun <E> executeArray(cls: Class<E>, listener: OnResponseListener<ArrayList<E>>) {
VKApi.executeArray(getSignedUrl(), cls, listener)
}
fun <E> execute(cls: Class<E>, listener: OnResponseListener<E>?) {
VKApi.execute(getSignedUrl(), cls, listener)
}
fun userId(value: Int): MethodSetter {
return put("user_id", value)
}
fun userIds(vararg ids: Int): MethodSetter {
return put("user_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun userIds(ids: ArrayList<Int>): MethodSetter {
return put("user_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun ownerId(value: Int): MethodSetter {
return put("owner_id", value)
}
fun groupId(value: Int): MethodSetter {
return put("group_id", value)
}
fun groupIds(vararg ids: Int): MethodSetter {
return put("group_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun groupIds(ids: ArrayList<Int>): MethodSetter {
return put("group_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun fields(values: String): MethodSetter {
return put("fields", values)
}
fun count(value: Int): MethodSetter {
return put("count", value)
}
fun sort(value: Int): MethodSetter {
put("sort", value)
return this
}
/**
*
* hints — сортировать по рейтингу, аналогично тому, как друзья сортируются в разделе Мои друзья
* random — возвращает друзей в случайном порядке.
* mobile — возвращает выше тех друзей, у которых установлены мобильные приложения.
* name — сортировать по имени (долго)
*
*/
fun order(value: String): MethodSetter {
put("order", value)
return this
}
fun offset(value: Int = 0): MethodSetter {
return put("offset", value)
}
fun nameCase(value: String): MethodSetter {
return put("name_case", value)
}
fun captchaSid(value: String): MethodSetter {
return put("captcha_sid", value)
}
fun captchaKey(value: String): MethodSetter {
return put("captcha_key", value)
}
}
@@ -0,0 +1,44 @@
package com.meloda.fast.api.method
class UserMethodSetter(name: String) : MethodSetter(name) {
fun extended(extended: Boolean): UserMethodSetter {
put("extended", extended)
return this
}
fun type(type: String): UserMethodSetter {
put("type", type)
return this
}
fun comment(comment: String): UserMethodSetter {
put("comment", comment)
return this
}
fun latitude(latitude: Float): UserMethodSetter {
put("latitude", latitude)
return this
}
fun longitude(longitude: Float): UserMethodSetter {
put("longitude", longitude)
return this
}
fun accuracy(accuracy: Int): UserMethodSetter {
put("accuracy", accuracy)
return this
}
fun timeout(timeout: Int): UserMethodSetter {
put("timeout", timeout)
return this
}
fun radius(radius: Int): UserMethodSetter {
put("radius", radius)
return this
}
}
@@ -0,0 +1,12 @@
package com.meloda.fast.api.model
data class ApiResponse<T> constructor(
val isSuccessful: Boolean,
val error: Error?,
val response: T?
)
data class Error constructor(
val code: Long,
val message: String
)
@@ -0,0 +1,76 @@
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
}
}
}
}
@@ -0,0 +1,31 @@
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")
}
}
@@ -0,0 +1,31 @@
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
}
}
}
@@ -0,0 +1,38 @@
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 }
}
}
}
@@ -0,0 +1,15 @@
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() {}
}
@@ -0,0 +1,156 @@
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 }
}
}
}
@@ -0,0 +1,101 @@
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 }
}
}
}
@@ -0,0 +1,15 @@
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() {}
}
@@ -0,0 +1,25 @@
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")
}
}
@@ -0,0 +1,29 @@
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")
}
}
@@ -0,0 +1,56 @@
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 }
}
}
}
@@ -0,0 +1,57 @@
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")
}
}
}
@@ -0,0 +1,14 @@
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: использовать
}
@@ -0,0 +1,19 @@
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")
}
}
@@ -0,0 +1,164 @@
package com.meloda.fast.api.model
import android.util.ArrayMap
import com.meloda.fast.api.util.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()
}
}
}
@@ -0,0 +1,47 @@
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 }
}
}
}
@@ -0,0 +1,14 @@
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
}
}
@@ -0,0 +1,40 @@
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
}
}
}
@@ -0,0 +1,18 @@
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")
}
@@ -0,0 +1,58 @@
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")
// }
}
@@ -0,0 +1,44 @@
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")
}
}
@@ -0,0 +1,80 @@
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"
}
}
@@ -0,0 +1,43 @@
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() {}
}
@@ -0,0 +1,15 @@
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,21 @@
package com.meloda.fast.api.model.request
import com.google.gson.annotations.SerializedName
class RequestMessagesGetConversations(
@SerializedName("offset")
private val offset: Int = 0,
@SerializedName("count")
private val count: Int = 0,
//values = all, unread
@SerializedName("filter")
private val filter: String = "",
@SerializedName("extended")
private val extended: Boolean = false,
@SerializedName("fields")
private var fields: String = ""
)
@@ -0,0 +1,13 @@
package com.meloda.fast.api.model.response
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
class MessagesResponse(
val count: Int
) {
}
@Parcelize
data class GetConversationsResponse(val a: String) : Parcelable
// TODO: 7/12/2021 use hilt for this like in LIR and make simple conversations' screen
@@ -0,0 +1,13 @@
package com.meloda.fast.api.service
import com.meloda.fast.api.model.ApiResponse
import com.meloda.fast.api.model.request.RequestMessagesGetConversations
import retrofit2.http.GET
import retrofit2.http.QueryMap
interface MessagesService {
@GET("messages.getConversations")
suspend fun getConversations(@QueryMap params: RequestMessagesGetConversations): ApiResponse<Map<String, Any>>
}
@@ -0,0 +1,382 @@
package com.meloda.fast.api.util
import androidx.annotation.WorkerThread
import com.meloda.fast.api.model.*
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
object VKUtil {
private const val TAG = "VKUtil"
fun extractPattern(string: String, pattern: String): String? {
val p = Pattern.compile(pattern)
val m = p.matcher(string)
return if (!m.find()) null else m.toMatchResult().group(1)
}
private const val pattern_string_profile_id = "^(id)?(\\d{1,10})$"
private val pattern_profile_id = Pattern.compile(pattern_string_profile_id)
fun parseProfileId(text: String): String? {
val m = pattern_profile_id.matcher(text)
return if (!m.find()) null else m.group(2)
}
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.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.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
}