api refactored and cleaned

This commit is contained in:
2021-08-31 16:20:09 +03:00
parent c3208fd95e
commit b09ae2049c
40 changed files with 129 additions and 2107 deletions
@@ -1,160 +0,0 @@
package com.meloda.fast
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.concurrent.EventInfo
import com.meloda.fast.concurrent.TaskManager
import com.meloda.fast.api.VKApiKeys
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.api.VKUtil
import org.json.JSONArray
@Suppress("UNCHECKED_CAST")
object VKLongPollParser {
@WorkerThread
fun parse(updates: JSONArray) {
if (updates.length() == 0) {
return
}
for (i in 0 until updates.length()) {
val item = updates.optJSONArray(i)
when (item.optInt(0)) {
2 -> messageSetFlags(item)
3 -> messageClearFlags(item)
4 -> messageEvent(item)
5 -> messageEdit(item)
}
}
}
fun parseEvent(eventInfo: EventInfo<*>, onMessagesListener: OnMessagesListener) {
when (eventInfo.key) {
VKApiKeys.NEW_MESSAGE.value -> onMessagesListener.onNewMessage(eventInfo.data as VKMessage)
VKApiKeys.EDIT_MESSAGE.value -> onMessagesListener.onEditMessage(eventInfo.data as VKMessage)
VKApiKeys.RESTORE_MESSAGE.value -> onMessagesListener.onRestoredMessage(eventInfo.data as VKMessage)
VKApiKeys.DELETE_MESSAGE.value -> {
val array = eventInfo.data as Array<Int>
onMessagesListener.onDeleteMessage(array[0], array[1])
}
VKApiKeys.READ_MESSAGE.value -> {
val array = eventInfo.data as Array<Int>
onMessagesListener.onReadMessage(array[0], array[1])
}
}
}
private const val TAG = "VKLongPollParser"
private fun messageEvent(item: JSONArray) {
val message = VKUtil.parseLongPollMessage(item)
TaskManager.execute {
if (message.isFromUser()) {
// VKUtil.searchUser(message.fromId)?.let { message.fromUser = it }
} else {
// VKUtil.searchGroup(message.fromId)?.let { message.fromGroup = it }
}
// MemoryCache.getConversationById(message.peerId)?.let {
// it.lastMessage = message
// it.lastMessageId = message.messageId
//
// MemoryCache.put(it)
// }
//
// MemoryCache.put(message)
val info = EventInfo(VKApiKeys.NEW_MESSAGE.name, message)
sendEvent(info)
}
}
private fun messageEdit(item: JSONArray) {
val message = VKUtil.parseLongPollMessage(item)
val info = EventInfo(VKApiKeys.EDIT_MESSAGE.name, message)
// MemoryCache.put(message)
sendEvent(info)
}
private fun messageDelete(item: JSONArray) {
val messageId = item.optInt(1)
val peerId = item.optInt(3)
val info = EventInfo(VKApiKeys.DELETE_MESSAGE.name, arrayOf(peerId, messageId))
// MemoryCache.deleteMessage(messageId)
sendEvent(info)
}
private fun messageRestored(item: JSONArray) {
val message = VKUtil.parseLongPollMessage(item)
val info = EventInfo(VKApiKeys.RESTORE_MESSAGE.name, message)
// MemoryCache.put(message)
sendEvent(info)
}
private fun messageRead(item: JSONArray) {
val messageId = item.optInt(1)
val peerId = item.optInt(3)
val info = EventInfo(VKApiKeys.READ_MESSAGE.name, arrayOf(peerId, messageId))
// MemoryCache.edit(MemoryCache.getMessageById(messageId)?.apply { isRead = true })
sendEvent(info)
}
private fun messageClearFlags(item: JSONArray) {
val id = item.optInt(1)
val flags = item.optInt(2)
if (VKUtil.isMessageHasFlag(flags, "cancel_spam")) {
Log.i(TAG, "Message with id $id: Not spam")
}
if (VKUtil.isMessageHasFlag(flags, "deleted")) {
messageRestored(item)
}
if (VKUtil.isMessageHasFlag(flags, "important")) {
Log.i(TAG, "Message with id $id: Not Important")
}
if (VKUtil.isMessageHasFlag(flags, "unread")) {
messageRead(item)
}
}
private fun messageSetFlags(item: JSONArray) {
val id = item.optInt(1)
val flags = item.optInt(2)
if (VKUtil.isMessageHasFlag(flags, "delete_for_all")) {
messageDelete(item)
}
if (VKUtil.isMessageHasFlag(flags, "deleted")) {
messageDelete(item)
}
if (VKUtil.isMessageHasFlag(flags, "spam")) {
Log.i(TAG, "Message with id $id: Spam")
}
if (VKUtil.isMessageHasFlag(flags, "important")) {
Log.i(TAG, "Message with id $id: Important")
}
}
private fun sendEvent(eventInfo: EventInfo<*>) {
TaskManager.sendEvent(eventInfo)
}
interface OnMessagesListener {
fun onNewMessage(message: VKMessage)
fun onEditMessage(message: VKMessage)
fun onRestoredMessage(message: VKMessage)
fun onDeleteMessage(peerId: Int, messageId: Int)
fun onReadMessage(peerId: Int, messageId: Int)
}
}
@@ -1,9 +0,0 @@
package com.meloda.fast.api
interface OnResponseListener<T> {
fun onResponse(response: T)
fun onError(t: Throwable)
}
@@ -1,6 +1,5 @@
package com.meloda.fast
package com.meloda.fast.api
import android.text.TextUtils
import com.meloda.fast.common.AppGlobal
object UserConfig {
@@ -1,506 +0,0 @@
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 com.meloda.fast.api.network.ErrorCodes
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) {
throw ex
// 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)
}
}
}
@@ -1,14 +0,0 @@
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")
}
@@ -1,84 +0,0 @@
package com.meloda.fast.api
import android.util.Log
import com.meloda.fast.BuildConfig
import com.meloda.fast.UserConfig
import java.net.URLEncoder
object VKAuth {
private const val TAG = "VKM.VKAuth"
object GrantType {
const val PASSWORD = "password"
}
const val scope = "notify," +
"friends," +
"photos," +
"audio," +
"video," +
"docs," +
"status," +
"notes," +
"pages," +
"wall," +
"groups," +
"messages," +
"offline," +
"notifications"
private const val redirectUrl = "https://oauth.vk.com/blank.html"
fun getDirectAuthUrl(
login: String,
password: String,
twoFa: Boolean = false,
twoFaCode: String = "",
captcha: Pair<String, String>? = null
) = "https://oauth.vk.com/token?" +
"grant_type=password&" +
"client_id=${VKConstants.VK_APP_ID}&" +
"client_secret=${VKConstants.VK_SECRET}&" +
"username=$login&" +
"password=$password&" +
"scope=$scope&" +
"2fa_supported=1&" +
"force_sms=${if (twoFa) "1" else "0"}" +
(if (twoFa) "code=$twoFaCode" else "") +
(if (captcha == null) "" else "&captcha_sid=${captcha.first}&captcha_key=${captcha.second}") +
"&v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
fun getSendSmsCodeUrl(sid: String) = "https://api.vk.com/method/auth.validatePhone?" +
"sid=$sid&" +
"&v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
fun getOAuthUrl(settings: String) = "https://oauth.vk.com/authorize?" +
"client_id=${UserConfig.FAST_APP_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
}
}
@@ -7,6 +7,30 @@ object VKConstants {
const val USER_FIELDS =
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex"
const val API_VERSION = "5.132"
const val VK_APP_ID = "2274003"
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
object Auth {
const val SCOPE = "notify," +
"friends," +
"photos," +
"audio," +
"video," +
"docs," +
"status," +
"notes," +
"pages," +
"wall," +
"groups," +
"messages," +
"offline," +
"notifications"
object GrantType {
const val PASSWORD = "password"
}
}
}
@@ -7,8 +7,8 @@ import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
// TODO: 8/31/2021 review
object VKUtil {
private const val TAG = "VKUtil"
@@ -23,26 +23,6 @@ object VKUtil {
return throwable.error == VKErrors.NEED_CAPTCHA
}
fun extractValidationSid(throwable: Throwable): String? {
if (throwable !is VKException) return null
return throwable.json?.optString("validation_sid")
}
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
@@ -292,7 +272,7 @@ object VKUtil {
message.text = text
// val fromId =
// if (isMessageHasFlag(flags, "outbox")) com.meloda.fast.UserConfig.userId
// if (isMessageHasFlag(flags, "outbox")) com.meloda.fast.api.UserConfig.userId
// else peerId
message.fromId = peerId
@@ -357,7 +337,7 @@ object VKUtil {
val editTime = array.optInt(10)
message.editTime = editTime
// val out = fromId == com.meloda.fast.UserConfig.userId
// val out = fromId == com.meloda.fast.api.UserConfig.userId
// message.isOut = out
//
// if (message.isFromUser()) {
@@ -1,205 +0,0 @@
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
}
}
@@ -1,169 +0,0 @@
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)
}
}
@@ -1,44 +0,0 @@
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
}
}
@@ -1,6 +1,6 @@
package com.meloda.fast.api.network
import com.meloda.fast.api.VKApi
import com.meloda.fast.api.VKConstants
import okhttp3.Interceptor
import okhttp3.Response
import java.net.URLEncoder
@@ -9,7 +9,7 @@ class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().url.newBuilder()
.addQueryParameter("v", URLEncoder.encode(VKApi.API_VERSION, "utf-8"))
.addQueryParameter("v", URLEncoder.encode(VKConstants.API_VERSION, "utf-8"))
return chain.proceed(chain.request().newBuilder().apply { url(builder.build()) }.build())
}
@@ -9,7 +9,7 @@ import com.meloda.fast.base.viewmodel.VKEvent
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
abstract class BaseVMFragment<VM : BaseViewModel> : BaseFragment {
abstract class BaseViewModelFragment<VM : BaseViewModel> : BaseFragment {
constructor() : super()
@@ -9,3 +9,5 @@ data class ShowDialogInfoEvent(
object StartProgressEvent : VKEvent()
object StopProgressEvent : VKEvent()
abstract class VKEvent
@@ -1,3 +0,0 @@
package com.meloda.fast.base.viewmodel
abstract class VKEvent
@@ -1,105 +0,0 @@
package com.meloda.fast.common
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.meloda.fast.R
object FragmentSwitcher {
fun getCurrentFragment(fragmentManager: FragmentManager): Fragment? {
val fragments = fragmentManager.fragments
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
for (fragment in fragments) {
if (fragment.isVisible) {
return fragment
}
}
return null
}
fun addFragments(
fragmentManager: FragmentManager,
containerId: Int,
fragments: Collection<Fragment>
) {
val transaction = fragmentManager.beginTransaction()
for (fragment in fragments) {
transaction.add(containerId, fragment, fragment.javaClass.simpleName)
}
transaction.commitNow()
}
fun showFragment(fragmentManager: FragmentManager, tag: String) {
showFragment(fragmentManager, tag, false)
}
fun showFragment(
fragmentManager: FragmentManager,
tag: String,
hideOthers: Boolean,
containerId: Int = R.id.fragmentContainer
) {
val fragments = fragmentManager.fragments
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
var fragmentToShow: Fragment? = null
for (fragment in fragments) {
if (fragment.tag != null && fragment.tag == tag) {
fragmentToShow = fragment
break
}
}
val transaction = fragmentManager.beginTransaction()
if (fragmentToShow == null) {
throw NullPointerException("Required fragment is null")
} else {
transaction.show(fragmentToShow)
}
if (hideOthers) {
for (fragment in fragments) {
if (fragment.tag != null && fragment.tag == tag) continue
transaction.hide(fragment)
}
}
transaction.commit()
}
fun clearFragments(fragmentManager: FragmentManager) {
val fragments = fragmentManager.fragments
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
val transaction = fragmentManager.beginTransaction()
for (fragment in fragments) {
transaction.remove(fragment)
}
transaction.commitNow()
}
fun hideFragments(fragmentManager: FragmentManager) {
val fragments = fragmentManager.fragments
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
val transaction = fragmentManager.beginTransaction()
for (fragment in fragments) {
transaction.hide(fragment)
}
transaction.commitNow()
}
}
@@ -1,81 +0,0 @@
package com.meloda.fast.common
import android.util.Log
import androidx.collection.arrayMapOf
import com.meloda.fast.concurrent.TaskManager
import com.meloda.fast.BuildConfig
import com.meloda.fast.model.NewUpdateInfo
import com.meloda.fast.net.HttpRequest
import org.json.JSONArray
import org.json.JSONObject
object UpdateManager {
interface OnUpdateListener {
fun onNewUpdate(updateInfo: NewUpdateInfo)
fun onNoUpdates()
}
private const val checkLink = "https://melodev.procsec.top/vkm/project_vkm_ota.json"
private const val PRODUCT_NAME = "project_vkm"
private const val BRANCH = "alpha"
private const val OFFSET = 0
private const val TAG = "UpdateManager"
fun checkUpdates(onUpdateListener: OnUpdateListener) {
TaskManager.execute {
val newLink = "https://temply.procsec.top/prop/deploy/api/method/getOTA"
val params = arrayMapOf<String, String>()
params["product"] = PRODUCT_NAME
params["branch"] = BRANCH
params["offset"] = OFFSET.toString()
params["code"] = AppGlobal.versionCode.toString()
if (BuildConfig.DEBUG) {
Log.d(TAG, "Request started")
}
HttpRequest[newLink, params].asString().let {
AppGlobal.post {
if (BuildConfig.DEBUG) {
Log.d(TAG, "response: $it")
}
val response: Any = if (it == "[]") JSONArray(it) else JSONObject(it)
val newUpdateInfo: NewUpdateInfo? =
if (response is JSONArray) null else NewUpdateInfo(response as JSONObject)
if (response is JSONArray || newUpdateInfo?.version?.isEmpty() == true || newUpdateInfo?.version == AppGlobal.versionName) {
onUpdateListener.onNoUpdates()
return@post
} else {
newUpdateInfo?.let { onUpdateListener.onNewUpdate(it) }
}
}
}
// HttpRequest[checkLink].asString().let {
// val response = JSONObject(it)
//
// val updateInfo = UpdateInfo(response)
//
// AppGlobal.handler.post {
// if (updateInfo.version.isEmpty() || updateInfo.version == AppGlobal.versionName) {
// onUpdateListener.onNoUpdates()
// return@post
// }
//
// if (AppGlobal.versionName != updateInfo.version) {
// onUpdateListener.onNewUpdate(updateInfo)
// }
// }
// }
}
}
}
@@ -1,3 +0,0 @@
package com.meloda.fast.concurrent
class EventInfo<T> constructor(var key: String, var data: T? = null)
@@ -1,12 +0,0 @@
package com.meloda.fast.concurrent
import android.os.Process
class LowThread(runnable: Runnable?) : Thread(runnable) {
override fun run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
super.run()
}
}
@@ -1,30 +0,0 @@
package com.meloda.fast.concurrent
object TaskManager {
private const val TAG = "TaskManager"
private val listeners = arrayListOf<OnEventListener>()
fun addOnEventListener(listener: OnEventListener) {
listeners.add(listener)
}
fun removeOnEventListener(listener: OnEventListener?) {
listeners.remove(listener)
}
fun execute(runnable: Runnable?) {
LowThread(runnable).start()
}
fun sendEvent(eventInfo: EventInfo<*>) {
for (listener in listeners) {
listener.onNewEvent(eventInfo)
}
}
interface OnEventListener {
fun onNewEvent(info: EventInfo<*>)
}
}
@@ -5,9 +5,10 @@ import android.database.Cursor
import android.os.Bundle
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.UserConfig
import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.VKUtil
import com.meloda.fast.api.model.VKUser
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.CacheStorage.selectCursor
import com.meloda.fast.database.DatabaseKeys.DEACTIVATED
import com.meloda.fast.database.DatabaseKeys.FIRST_NAME
import com.meloda.fast.database.DatabaseKeys.FRIEND_ID
@@ -25,8 +26,6 @@ import com.meloda.fast.database.DatabaseUtils.TABLE_FRIENDS
import com.meloda.fast.database.DatabaseUtils.TABLE_USERS
import com.meloda.fast.database.QueryBuilder
import com.meloda.fast.database.base.Storage
import com.meloda.fast.api.model.VKUser
import com.meloda.fast.api.VKUtil
import org.json.JSONObject
@WorkerThread
@@ -78,7 +77,7 @@ class UsersStorage : Storage<VKUser>() {
}
override fun getAllValues(): ArrayList<VKUser> {
val cursor = selectCursor(TABLE_USERS)
val cursor = CacheStorage.selectCursor(TABLE_USERS)
val users = ArrayList<VKUser>()
while (cursor.moveToNext()) users.add(parseValue(cursor))
@@ -1,8 +1,9 @@
package com.meloda.fast.extensions
import android.content.Intent
import android.util.SparseArray
import androidx.core.util.forEach
import androidx.collection.SparseArrayCompat
import androidx.collection.forEach
import androidx.collection.set
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData
@@ -11,7 +12,7 @@ import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.api.UserConfig
/**
* Manages the various graphs needed for a [BottomNavigationView].
@@ -28,7 +29,7 @@ object NavigationExtensions {
): LiveData<NavController> {
// Map of tags
val graphIdToTagMap = SparseArray<String>()
val graphIdToTagMap = SparseArrayCompat<String>()
// Result. Mutable live data with the selected controlled
val selectedNavController = MutableLiveData<NavController>()
@@ -68,7 +69,7 @@ object NavigationExtensions {
// Now connect selecting an item with swapping Fragments
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId] ?: ""
var isOnFirstFragment = selectedItemTag == firstFragmentTag
setOnItemSelectedListener { item ->
@@ -1,30 +0,0 @@
package com.meloda.fast.model
import org.json.JSONObject
class NewUpdateInfo() {
var id: Int = 0
var version: String = ""
var code: Int = 0
var time: Int = 0
var changelog: String = ""
// var branchId: Int = 0
// var branchName: String = ""
var downloadLink: String = ""
// var state: Boolean = true
constructor(o: JSONObject) : this() {
id = o.optInt("id")
version = o.optString("version")
code = o.optInt("code")
time = o.optInt("time")
changelog = o.optString("changelog")
downloadLink = o.optString("download")
}
}
@@ -1,21 +0,0 @@
package com.meloda.fast.model
import org.json.JSONObject
class UpdateInfo() {
var version: String = ""
var code: Int = 0
var changelog: String = ""
var downloadLink: String = ""
var date: Int = 0
constructor(o: JSONObject) : this() {
version = o.optString("lastVersionName")
code = o.optInt("lastVersionCode")
changelog = o.optString("changelog")
downloadLink = o.optString("downloadLink")
date = o.optInt("buildDate")
}
}
@@ -1,114 +0,0 @@
package com.meloda.fast.net
import androidx.collection.ArrayMap
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
class HttpRequest(
private val url: String,
private val method: String,
private val params: ArrayMap<String, String> = ArrayMap<String, String>()
) {
companion object {
const val GET = "GET"
const val POST = "POST"
operator fun get(
url: String,
params: ArrayMap<String, String> = ArrayMap()
): HttpRequest {
return HttpRequest(url, GET, params)
}
operator fun get(url: String): HttpRequest {
return Companion[url, ArrayMap()]
}
}
private var connection: HttpURLConnection? = null
@Throws(IOException::class)
fun asString(): String {
val input = getStream()
val content = com.meloda.fast.io.EasyStreams.read(input)
connection?.disconnect()
return content
}
@Throws(IOException::class)
fun asBytes(): ByteArray {
val input = getStream()
val content = com.meloda.fast.io.EasyStreams.readBytes(input)
connection?.disconnect()
return content
}
@Throws(IOException::class)
fun getStream(): InputStream {
if (connection == null) {
connection = createConnection()
}
var input = connection!!.inputStream
val encoding = connection!!.getHeaderField("Content-Encoding")
if ("gzip".equals(encoding, ignoreCase = true)) {
input = com.meloda.fast.io.EasyStreams.gzip(input)
}
return input
}
@Throws(UnsupportedEncodingException::class)
private fun getParams(): String {
val buffer = StringBuilder()
for (i in 0 until params.size) {
val key = params.keyAt(i)
val value = params.valueAt(i)
buffer.append(key).append("=")
buffer.append(URLEncoder.encode(value, "UTF-8"))
buffer.append("&")
}
return buffer.toString()
}
@Throws(UnsupportedEncodingException::class)
private fun getUrl(): String {
return if (params.isNotEmpty() && "GET".equals(method, ignoreCase = true)) {
url + "?" + getParams()
} else url
}
@Throws(IOException::class)
private fun createConnection(): HttpURLConnection? {
connection = URL(getUrl()).openConnection() as HttpURLConnection
connection!!.readTimeout = 60000
connection!!.connectTimeout = 60000
connection!!.useCaches = true
connection!!.doInput = true
connection!!.doOutput =
!GET.equals(method, ignoreCase = true)
connection!!.requestMethod = method
connection!!.setRequestProperty("Accept-Encoding", "gzip")
return connection
}
override fun toString(): String {
try {
return asString()
} catch (e: IOException) {
e.printStackTrace()
}
return ""
}
}
@@ -1,17 +0,0 @@
package com.meloda.fast.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.meloda.fast.api.OnResponseListener
open class DownloadUpdateReceiver : BroadcastReceiver() {
var listener: OnResponseListener<Any?>? = null
override fun onReceive(context: Context?, intent: Intent?) {
listener?.onResponse(null)
}
}
@@ -19,7 +19,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputLayout
import com.meloda.fast.BuildConfig
import com.meloda.fast.R
import com.meloda.fast.base.BaseVMFragment
import com.meloda.fast.base.BaseViewModelFragment
import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent
import com.meloda.fast.base.viewmodel.VKEvent
@@ -36,7 +36,7 @@ import java.util.*
import kotlin.concurrent.schedule
@AndroidEntryPoint
class LoginFragment : BaseVMFragment<LoginViewModel>(R.layout.fragment_login) {
class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_login) {
override val viewModel: LoginViewModel by viewModels()
private val binding: FragmentLoginBinding by viewBinding()
@@ -1,10 +1,8 @@
package com.meloda.fast.screens.login
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.viewModelScope
import com.meloda.fast.UserConfig
import com.meloda.fast.api.VKAuth
import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.VKConstants
import com.meloda.fast.api.VKException
import com.meloda.fast.api.VKUtil
@@ -15,10 +13,7 @@ import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent
import com.meloda.fast.base.viewmodel.VKEvent
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject
import javax.inject.Inject
@HiltViewModel
@@ -36,12 +31,12 @@ class LoginViewModel @Inject constructor(
{
repo.auth(
RequestAuthDirect(
grantType = VKAuth.GrantType.PASSWORD,
grantType = VKConstants.Auth.GrantType.PASSWORD,
clientId = VKConstants.VK_APP_ID,
clientSecret = VKConstants.VK_SECRET,
username = login,
password = password,
scope = VKAuth.scope,
scope = VKConstants.Auth.SCOPE,
twoFaForceSms = true,
twoFaCode = twoFaCode,
captchaSid = captcha?.first,
@@ -64,6 +59,8 @@ class LoginViewModel @Inject constructor(
checkErrors(it)
if (it !is VKException) return@makeJob
twoFaCode?.let { sendEvent(CodeSent) }
if (VKUtil.isValidationRequired(it)) {
it.validationSid?.let { sid ->
sendEvent(ValidationRequired(validationSid = sid))
@@ -1,75 +0,0 @@
package com.meloda.fast.screens.login
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.viewbinding.library.fragment.viewBinding
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.os.bundleOf
import androidx.navigation.fragment.findNavController
import com.meloda.fast.R
import com.meloda.fast.api.VKAuth
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.databinding.FragmentValidationBinding
class ValidationFragment : BaseFragment(R.layout.fragment_validation) {
private val binding: FragmentValidationBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val redirectUrl = getRedirectUrl()
binding.webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
Log.d("Fast::Validation", "onPageStarted: url: $url")
parseUrl(url ?: "")
}
}
binding.webView.settings.domStorageEnabled = true
binding.webView.clearCache(true)
binding.webView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
val manager = CookieManager.getInstance()
manager.removeAllCookies(null)
manager.flush()
manager.setAcceptCookie(true)
binding.webView.loadUrl(redirectUrl)
}
private fun getRedirectUrl() = requireArguments().getString("redirectUrl", "")
private fun parseUrl(url: String) {
if (url.startsWith("https://oauth.vk.com/blank.html#success=1")) {
if (!url.contains("error=")) {
val data = VKAuth.parseRedirectUrl(url)
val accessToken = data.first
val userId = data.second
parentFragmentManager.setFragmentResult(
"validation",
bundleOf(
"accessToken" to accessToken,
"userId" to userId
)
)
findNavController().navigate(R.id.toLogin)
}
} else {
Log.d("Fast::Validation", "parseUrl: $url")
}
}
}
@@ -6,14 +6,14 @@ import android.viewbinding.library.fragment.viewBinding
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.base.BaseVMFragment
import com.meloda.fast.api.UserConfig
import com.meloda.fast.base.BaseViewModelFragment
import com.meloda.fast.databinding.FragmentMainBinding
import com.meloda.fast.extensions.NavigationExtensions.setupWithNavController
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainFragment : BaseVMFragment<MainViewModel>(R.layout.fragment_main) {
class MainFragment : BaseViewModelFragment<MainViewModel>(R.layout.fragment_main) {
override val viewModel: MainViewModel by viewModels()
private val binding: FragmentMainBinding by viewBinding()
@@ -6,7 +6,7 @@ import android.viewbinding.library.fragment.viewBinding
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import com.meloda.fast.R
import com.meloda.fast.base.BaseVMFragment
import com.meloda.fast.base.BaseViewModelFragment
import com.meloda.fast.base.viewmodel.StartProgressEvent
import com.meloda.fast.base.viewmodel.StopProgressEvent
import com.meloda.fast.base.viewmodel.VKEvent
@@ -16,7 +16,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlin.math.roundToInt
@AndroidEntryPoint
class ConversationsFragment : BaseVMFragment<ConversationsViewModel>(R.layout.fragment_conversations) {
class ConversationsFragment : BaseViewModelFragment<ConversationsViewModel>(R.layout.fragment_conversations) {
override val viewModel: ConversationsViewModel by viewModels()
private val binding: FragmentConversationsBinding by viewBinding()
@@ -5,17 +5,13 @@ import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.collection.arrayMapOf
import com.meloda.fast.UserConfig
import com.meloda.fast.VKLongPollParser
import com.meloda.fast.api.VKApi
import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.model.VKLongPollServer
import com.meloda.fast.concurrent.LowThread
import com.meloda.fast.net.HttpRequest
import com.meloda.fast.util.AndroidUtils
import org.json.JSONArray
import org.json.JSONObject
// TODO: 8/31/2021 rewrite
@Deprecated("Absolutely obsolete")
class LongPollService : Service() {
private var thread: Thread? = null
@@ -26,7 +22,7 @@ class LongPollService : Service() {
running = false
thread = LowThread(Updater())
// thread = LowThread(Updater())
}
override fun onBind(intent: Intent?): IBinder? {
@@ -75,8 +71,9 @@ class LongPollService : Service() {
}
try {
if (server == null) {
server = VKApi.messages().getLongPollServer()
.execute(VKLongPollServer::class.java)!![0]
server = null
// server = VKApi.messages().getLongPollServer()
// .execute(VKLongPollServer::class.java)!![0]
}
val response = getResponse(server)
@@ -92,7 +89,7 @@ class LongPollService : Service() {
Log.i(TAG, "updates: $updates")
server.ts = tsResponse
server?.ts = tsResponse
if (updates.length() != 0) {
process(updates)
@@ -110,23 +107,23 @@ class LongPollService : Service() {
}
@Throws(Exception::class)
private fun getResponse(server: VKLongPollServer): JSONObject {
val params = arrayMapOf<String, String>()
params["act"] = "a_check"
params["key"] = server.key
params["ts"] = server.ts.toString()
params["wait"] = "10"
params["mode"] = "490"
params["version"] = "9"
val buffer = HttpRequest["https://" + server.server, params].asString()
return JSONObject(buffer)
private fun getResponse(server: VKLongPollServer?): JSONObject {
return JSONObject("")
// val params = arrayMapOf<String, String>()
// params["act"] = "a_check"
// params["key"] = server.key
// params["ts"] = server.ts.toString()
// params["wait"] = "10"
// params["mode"] = "490"
// params["version"] = "9"
//
// val buffer = HttpRequest["https://" + server.server, params].asString()
//
// return JSONObject(buffer)
}
@WorkerThread
private fun process(updates: JSONArray) {
VKLongPollParser.parse(updates)
}
}
@@ -2,18 +2,15 @@ package com.meloda.fast.util
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.annotation.WorkerThread
import androidx.core.content.ContextCompat
import com.meloda.fast.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.api.VKUtil
import com.meloda.fast.api.model.*
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.extensions.ContextExtensions.color
import com.meloda.fast.extensions.ContextExtensions.drawable
import com.meloda.fast.extensions.DrawableExtensions.tint
import com.meloda.fast.extensions.StringExtensions.lowerCase
import com.meloda.fast.R
import com.meloda.fast.api.model.*
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.api.OnResponseListener
import com.meloda.fast.api.VKUtil
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.abs
@@ -81,41 +78,6 @@ object VKUtils {
// )
// }
@Deprecated("")
@WorkerThread
fun searchUser(id: Int, onResponseListener: OnResponseListener<VKUser>? = null): VKUser? {
return if (VKUtil.isGroupId(id) || VKUtil.isChatId(id)) {
null
} else {
// MemoryCache.getUserById(id)?.let { return it }
//
// if (BuildConfig.DEBUG) {
// Log.d(VKUtil.TAG, "User with id $id not found")
// }
//
// TaskManager.loadUser(VKApiKeys.UPDATE_USER, id, onResponseListener)
return null
}
}
@Deprecated("")
@WorkerThread
fun searchGroup(id: Int, onResponseListener: OnResponseListener<VKGroup>? = null): VKGroup? {
return if (!VKUtil.isGroupId(id) || VKUtil.isChatId(id)) {
null
} else {
// MemoryCache.getGroupById(abs(id))?.let { return it }
//
// if (BuildConfig.DEBUG) {
// Log.d(VKUtil.TAG, "Group with id $id not found")
// }
//
// TaskManager.loadGroup(VKApiKeys.UPDATE_GROUP, abs(id), onResponseListener)
return null
}
}
fun getAttachmentText(context: Context, attachments: List<VKModel>): String {
val resId: Int
@@ -158,7 +120,7 @@ object VKUtils {
if (resId == -1) "Unknown attachments" else context.getString(
resId,
attachments.size
).toLowerCase(Locale.getDefault())
).lowerCase()
} else {
context.getString(R.string.message_attachments_many)
}
@@ -226,10 +188,9 @@ object VKUtils {
@Deprecated("need to rewrite")
fun getActionText(
context: Context,
lastMessage: VKMessage,
onResponseListener: OnResponseListener<String>
lastMessage: VKMessage
) {
TaskManager.execute {
lastMessage.action?.let {
var result = ""
@@ -280,8 +241,7 @@ object VKUtils {
)
}
AppGlobal.post { onResponseListener.onResponse(result) }
}
}
}
@@ -1,70 +0,0 @@
package com.meloda.fast.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.api.VKApi
import com.meloda.fast.api.VKConstants
import com.meloda.fast.api.model.VKConversation
import com.meloda.fast.api.model.VKMessage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ChatsViewModel : ViewModel() {
val chatsAnswer = MutableLiveData<ChatsAnswer?>()
data class ChatsAnswer(var status: Status, var message: String? = "") {
companion object {
val SUCCESS get() = ChatsAnswer(Status.SUCCESS)
val FAIL get() = ChatsAnswer(Status.FAIL)
val LOADING get() = ChatsAnswer(Status.LOADING)
}
enum class Status {
SUCCESS, FAIL, LOADING
}
}
fun loadChats() = viewModelScope.launch(Dispatchers.IO) {
chatsAnswer.postValue(ChatsAnswer.LOADING)
try {
val chats = VKApi.messages()
.getConversations()
.filter("all")
.extended(true)
.fields(VKConstants.USER_FIELDS + ',' + VKConstants.GROUP_FIELDS)
.offset(0)
.count(30)
.executeSuspend(VKConversation::class.java)
// CacheStorage.chatsStorage.insertValues(chats)
val lastMessages = arrayListOf<VKMessage>()
chats.collect {
lastMessages.add(it.lastMessage)
}
CacheStorage.messagesStorage.insertValues(lastMessages)
CacheStorage.usersStorage.insertValues(VKConversation.profiles)
CacheStorage.groupsStorage.insertValues(VKConversation.groups)
//
// chatsAnswer.value = ChatsAnswer.SUCCESS
chatsAnswer.postValue(ChatsAnswer.SUCCESS)
withContext(Dispatchers.Main) {
// adapter.updateValues(chats.toList())
// adapter.notifyDataSetChanged()
}
} catch (e: Exception) {
chatsAnswer.postValue(ChatsAnswer.FAIL.also { it.message = e.message })
// chatsAnswer.value = ChatsAnswer.FAIL.also { it.message = e.message }
}
}
}
@@ -8,6 +8,7 @@ import android.util.AttributeSet
import android.view.ViewTreeObserver
import androidx.appcompat.widget.AppCompatImageView
// TODO: 8/31/2021 extend ShapeableImageView and set corners for half of size
class CircleImageView : AppCompatImageView {
companion object {
@@ -1,141 +0,0 @@
package com.meloda.fast.widget
import android.app.Activity
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.core.content.res.ResourcesCompat
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.theme.overlay.MaterialThemeOverlay
import com.meloda.fast.R
import com.meloda.fast.util.AndroidUtils
class Toolbar : MaterialToolbar {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
MaterialThemeOverlay.wrap(
context,
attrs,
defStyleAttr,
com.google.android.material.R.style.Widget_MaterialComponents_Toolbar
),
attrs,
defStyleAttr
)
private fun init() {
//...
}
override fun setTitle(resId: Int) {
title = context.getString(resId)
}
override fun setTitle(title: CharSequence?) {
findViewById<TextView>(R.id.toolbarTitle).text = title
}
override fun setTitleTextColor(color: Int) {
findViewById<TextView>(R.id.toolbarTitle).setTextColor(color)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
init()
}
override fun setNavigationIcon(icon: Drawable?) {
findViewById<ImageButton>(R.id.toolbarNavigationIcon).setImageDrawable(icon)
}
override fun setNavigationIcon(@DrawableRes resId: Int) {
findViewById<ImageButton>(R.id.toolbarNavigationIcon).setImageResource(resId)
}
// fun setNavigationIconTintList(tintList: ColorStateList?) {
// findViewById<ImageButton>(R.id.toolbarNavigationIcon).drawable?.setTintList(tintList)
// }
//
// fun setNavigationIconTint(@ColorInt tintColor: Int) {
// findViewById<ImageButton>(R.id.toolbarNavigationIcon).drawable?.setTint(tintColor)
// }
fun setNavigationClickListener(listener: OnClickListener?) {
findViewById<View>(R.id.toolbarNavigation).setOnClickListener(listener)
}
fun setNavigationOnBackClickListener(activity: Activity) {
findViewById<View>(R.id.toolbarNavigation).setOnClickListener { activity.onBackPressed() }
}
fun setNavigationVisibility(visible: Boolean) {
findViewById<View>(R.id.toolbarNavigation).visibility = if (visible) VISIBLE else GONE
}
fun tintNavigationIcon(@ColorInt color: Int) {
findViewById<ImageButton>(R.id.toolbarNavigationIcon).imageTintList =
ColorStateList.valueOf(color)
}
fun setAvatarIcon(icon: Drawable?) {
findViewById<ImageView>(R.id.toolbarAvatar).setImageDrawable(icon)
}
fun setAvatarIcon(@DrawableRes resId: Int) {
// findViewById<SimpleDraweeView>(R.id.toolbarAvatar).setActualImageResource(resId)
}
fun setAvatarClickListener(listener: OnClickListener?) {
findViewById<View>(R.id.toolbarAvatar).setOnClickListener(listener)
}
fun setAvatarVisibility(visible: Boolean) {
findViewById<View>(R.id.toolbarAvatar).visibility = if (visible) VISIBLE else GONE
}
// fun getAvatar(): SimpleDraweeView {
// return findViewById(R.id.toolbarAvatar)
// }
fun setTitleMode(titleMode: TitleMode) {
val title = findViewById<TextView>(R.id.toolbarTitle)
when (titleMode) {
TitleMode.SIMPLE -> {
title.gravity = Gravity.CENTER
title.typeface = ResourcesCompat.getFont(context, R.font.google_sans_medium)
title.setTextColor(AndroidUtils.getThemeAttrColor(context, R.attr.colorAccent))
title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f)
}
TitleMode.HINT -> {
title.gravity = Gravity.CENTER_VERTICAL and Gravity.START
title.typeface = ResourcesCompat.getFont(context, R.font.google_sans_regular)
title.setTextColor(
AndroidUtils.getThemeAttrColor(
context,
R.attr.textColorSecondary
)
)
title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
}
}
}
enum class TitleMode {
SIMPLE, HINT
}
}
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</layout>
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.meloda.fast.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.appbar.MaterialToolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
@@ -63,4 +63,4 @@
</RelativeLayout>
</com.meloda.fast.widget.Toolbar>
</com.google.android.material.appbar.MaterialToolbar>
-18
View File
@@ -17,24 +17,6 @@
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/toValidation"
app:destination="@id/validationFragment" />
</fragment>
<fragment
android:id="@+id/validationFragment"
android:name="com.meloda.fast.screens.login.ValidationFragment"
android:label="ValidationFragment"
tools:layout="@layout/fragment_validation">
<action
android:id="@+id/toLogin"
app:destination="@id/loginFragment"
app:popUpTo="@id/validationFragment"
app:popUpToInclusive="true" />
</fragment>
-18
View File
@@ -29,24 +29,6 @@
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/toValidation"
app:destination="@id/validationFragment" />
</fragment>
<fragment
android:id="@+id/validationFragment"
android:name="com.meloda.fast.screens.login.ValidationFragment"
android:label="ValidationFragment"
tools:layout="@layout/fragment_validation">
<action
android:id="@+id/toLogin"
app:destination="@id/loginFragment"
app:popUpTo="@id/validationFragment"
app:popUpToInclusive="true" />
</fragment>
</navigation>