Removed unused classes
Using dagger for Retrofit2, OkHttp and Gson
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
||||||
|
|
||||||
|
val login: String = gradleLocalProperties(rootDir).getProperty("vklogin")
|
||||||
|
val password: String = gradleLocalProperties(rootDir).getProperty("vkpassword")
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
@@ -26,6 +31,10 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
getByName("debug") {
|
||||||
|
buildConfigField("String", "vkLogin", login)
|
||||||
|
buildConfigField("String", "vkPassword", password)
|
||||||
|
}
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ object UserConfig {
|
|||||||
userId = -1
|
userId = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLoggedIn(): Boolean {
|
fun isLoggedIn() = userId > 0 && accessToken.isNotBlank()
|
||||||
return userId > 0 && !TextUtils.isEmpty(accessToken)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import com.meloda.fast.concurrent.EventInfo
|
|||||||
import com.meloda.fast.concurrent.TaskManager
|
import com.meloda.fast.concurrent.TaskManager
|
||||||
import com.meloda.fast.api.VKApiKeys
|
import com.meloda.fast.api.VKApiKeys
|
||||||
import com.meloda.fast.api.model.VKMessage
|
import com.meloda.fast.api.model.VKMessage
|
||||||
import com.meloda.fast.api.util.VKUtil
|
import com.meloda.fast.api.VKUtil
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
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>()
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
package com.meloda.fast.api
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import com.meloda.fast.BuildConfig
|
|||||||
import com.meloda.fast.api.method.MessageMethodSetter
|
import com.meloda.fast.api.method.MessageMethodSetter
|
||||||
import com.meloda.fast.api.method.MethodSetter
|
import com.meloda.fast.api.method.MethodSetter
|
||||||
import com.meloda.fast.api.method.UserMethodSetter
|
import com.meloda.fast.api.method.UserMethodSetter
|
||||||
|
import com.meloda.fast.api.network.ErrorCodes
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
@@ -55,13 +56,14 @@ object VKApi {
|
|||||||
try {
|
try {
|
||||||
checkError(json, url)
|
checkError(json, url)
|
||||||
} catch (ex: VKException) {
|
} catch (ex: VKException) {
|
||||||
if (ex.code == ErrorCodes.TOO_MANY_REQUESTS) {
|
throw ex
|
||||||
Timer().schedule(object : TimerTask() {
|
// if (ex.code == ErrorCodes.TOO_MANY_REQUESTS) {
|
||||||
override fun run() {
|
// Timer().schedule(object : TimerTask() {
|
||||||
execute(url, cls)
|
// override fun run() {
|
||||||
}
|
// execute(url, cls)
|
||||||
}, 1000)
|
// }
|
||||||
} else throw ex
|
// }, 1000)
|
||||||
|
// } else throw ex
|
||||||
}
|
}
|
||||||
|
|
||||||
when (cls) {
|
when (cls) {
|
||||||
@@ -282,7 +284,7 @@ object VKApi {
|
|||||||
|
|
||||||
val code = error.optInt("error_code", -1)
|
val code = error.optInt("error_code", -1)
|
||||||
val message = error.optString("error_msg", "")
|
val message = error.optString("error_msg", "")
|
||||||
val e = VKException(url, message, code)
|
// val e = VKException(url, message, code)
|
||||||
|
|
||||||
//TODO: add checking invalid session
|
//TODO: add checking invalid session
|
||||||
if (code == 5 && message.contains("invalid session")) {
|
if (code == 5 && message.contains("invalid session")) {
|
||||||
@@ -291,16 +293,16 @@ object VKApi {
|
|||||||
// })
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code == ErrorCodes.CAPTCHA_NEEDED) {
|
// if (code == ErrorCodes.CAPTCHA_NEEDED) {
|
||||||
e.captchaImg = error.optString("captcha_img")
|
// e.captchaImg = error.optString("captcha_img")
|
||||||
e.captchaSid = error.optString("captcha_sid")
|
// e.captchaSid = error.optString("captcha_sid")
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// if (code == ErrorCodes.VALIDATION_REQUIRED) {
|
||||||
|
// e.redirectUri = error.optString("redirect_uri")
|
||||||
|
// }
|
||||||
|
|
||||||
if (code == ErrorCodes.VALIDATION_REQUIRED) {
|
// throw e
|
||||||
e.redirectUri = error.optString("redirect_uri")
|
|
||||||
}
|
|
||||||
|
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ package com.meloda.fast.api
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.meloda.fast.BuildConfig
|
import com.meloda.fast.BuildConfig
|
||||||
import com.meloda.fast.UserConfig
|
import com.meloda.fast.UserConfig
|
||||||
import com.meloda.fast.api.util.VKUtil
|
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
object VKAuth {
|
object VKAuth {
|
||||||
|
|
||||||
private const val TAG = "VKM.VKAuth"
|
private const val TAG = "VKM.VKAuth"
|
||||||
|
|
||||||
private const val settings = "notify," +
|
object GrantType {
|
||||||
|
const val PASSWORD = "password"
|
||||||
|
}
|
||||||
|
|
||||||
|
const val scope = "notify," +
|
||||||
"friends," +
|
"friends," +
|
||||||
"photos," +
|
"photos," +
|
||||||
"audio," +
|
"audio," +
|
||||||
@@ -30,17 +33,25 @@ object VKAuth {
|
|||||||
fun getDirectAuthUrl(
|
fun getDirectAuthUrl(
|
||||||
login: String,
|
login: String,
|
||||||
password: String,
|
password: String,
|
||||||
captchaSid: String? = null,
|
twoFa: Boolean = false,
|
||||||
captchaKey: String? = null
|
twoFaCode: String = "",
|
||||||
) = "https://oauth.vk.com/token?grant_type=password&" +
|
captcha: Pair<String, String>? = null
|
||||||
|
) = "https://oauth.vk.com/token?" +
|
||||||
|
"grant_type=password&" +
|
||||||
"client_id=${VKConstants.VK_APP_ID}&" +
|
"client_id=${VKConstants.VK_APP_ID}&" +
|
||||||
"scope=$settings&" +
|
|
||||||
"client_secret=${VKConstants.VK_SECRET}&" +
|
"client_secret=${VKConstants.VK_SECRET}&" +
|
||||||
"username=$login&" +
|
"username=$login&" +
|
||||||
"password=$password" +
|
"password=$password&" +
|
||||||
(if (captchaSid == null || captchaKey == null) "" else "&captcha_sid=$captchaSid&captcha_key=$captchaKey") +
|
"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")}"
|
"&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?" +
|
fun getOAuthUrl(settings: String) = "https://oauth.vk.com/authorize?" +
|
||||||
"client_id=${UserConfig.FAST_APP_ID}&" +
|
"client_id=${UserConfig.FAST_APP_ID}&" +
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
package com.meloda.fast.api
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class VKException(var url: String = "", override var message: String = "", var code: Int) :
|
class VKException(var url: String = "", var description: String = "", var error: String) :
|
||||||
IOException(message) {
|
IOException(description) {
|
||||||
var captchaSid: String? = null
|
|
||||||
var captchaImg: String? = null
|
var captcha: Pair<String, String>? = null
|
||||||
var redirectUri: String? = null
|
var redirectUri: String? = null
|
||||||
|
|
||||||
|
var validationSid: String? = null
|
||||||
|
|
||||||
|
var json: JSONObject? = null
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "code: $code, message: $message"
|
return "url: $url;\n\nerror: $error; description: $description;"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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>
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.meloda.fast.api
|
|
||||||
|
|
||||||
object VKUrls {
|
|
||||||
|
|
||||||
const val getConversations = "messages.getConversations"
|
|
||||||
|
|
||||||
}
|
|
||||||
+17
-1
@@ -1,7 +1,8 @@
|
|||||||
package com.meloda.fast.api.util
|
package com.meloda.fast.api
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.meloda.fast.api.model.*
|
import com.meloda.fast.api.model.*
|
||||||
|
import com.meloda.fast.api.network.VKErrors
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -12,6 +13,21 @@ object VKUtil {
|
|||||||
|
|
||||||
private const val TAG = "VKUtil"
|
private const val TAG = "VKUtil"
|
||||||
|
|
||||||
|
fun isValidationRequired(throwable: Throwable): Boolean {
|
||||||
|
if (throwable !is VKException) return false
|
||||||
|
return throwable.error == VKErrors.NEED_VALIDATION
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCaptchaRequired(throwable: Throwable): Boolean {
|
||||||
|
if (throwable !is VKException) return false
|
||||||
|
return throwable.error == VKErrors.NEED_CAPTCHA
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extractValidationSid(throwable: Throwable): String? {
|
||||||
|
if (throwable !is VKException) return null
|
||||||
|
return throwable.json?.optString("validation_sid")
|
||||||
|
}
|
||||||
|
|
||||||
fun extractPattern(string: String, pattern: String): String? {
|
fun extractPattern(string: String, pattern: String): String? {
|
||||||
val p = Pattern.compile(pattern)
|
val p = Pattern.compile(pattern)
|
||||||
val m = p.matcher(string)
|
val m = p.matcher(string)
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package com.meloda.fast.api.datasource
|
|
||||||
|
|
||||||
class MessagesDataSource constructor() {
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.meloda.fast.api.model
|
package com.meloda.fast.api.model
|
||||||
|
|
||||||
import android.util.ArrayMap
|
import android.util.ArrayMap
|
||||||
import com.meloda.fast.api.util.VKUtil
|
import com.meloda.fast.api.VKUtil
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
open class VKMessage() : VKModel() {
|
open class VKMessage() : VKModel() {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
|
import com.meloda.fast.api.VKApi
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
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"))
|
||||||
|
return chain.proceed(chain.request().newBuilder().apply { url(builder.build()) }.build())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.meloda.fast.api
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
object ErrorCodes {
|
object ErrorCodes {
|
||||||
const val UNKNOWN_ERROR = 1
|
const val UNKNOWN_ERROR = 1
|
||||||
@@ -40,3 +40,12 @@ object ErrorCodes {
|
|||||||
const val INVALID_DOC_TITLE = 1152
|
const val INVALID_DOC_TITLE = 1152
|
||||||
const val ACCESS_TO_DOC_DENIED = 1153
|
const val ACCESS_TO_DOC_DENIED = 1153
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object VKErrors {
|
||||||
|
const val UNKNOWN = "unknown_error"
|
||||||
|
|
||||||
|
const val NEED_VALIDATION = "need_validation"
|
||||||
|
const val NEED_CAPTCHA = "need_captcha"
|
||||||
|
const val INVALID_REQUEST = "invalid_request"
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.meloda.fast.api.VKException
|
||||||
|
import okhttp3.Request
|
||||||
|
import okio.IOException
|
||||||
|
import okio.Timeout
|
||||||
|
import org.json.JSONObject
|
||||||
|
import retrofit2.*
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
class ResultCallFactory : CallAdapter.Factory() {
|
||||||
|
override fun get(
|
||||||
|
returnType: Type,
|
||||||
|
annotations: Array<out Annotation>,
|
||||||
|
retrofit: Retrofit
|
||||||
|
): CallAdapter<*, *>? {
|
||||||
|
val rawReturnType: Class<*> = getRawType(returnType)
|
||||||
|
if (rawReturnType == Call::class.java) {
|
||||||
|
if (returnType is ParameterizedType) {
|
||||||
|
val callInnerType: Type = getParameterUpperBound(0, returnType)
|
||||||
|
if (getRawType(callInnerType) == Answer::class.java) {
|
||||||
|
if (callInnerType is ParameterizedType) {
|
||||||
|
val resultInnerType = getParameterUpperBound(0, callInnerType)
|
||||||
|
return ResultCallAdapter<Any?>(resultInnerType)
|
||||||
|
}
|
||||||
|
return ResultCallAdapter<Nothing>(Nothing::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class CallDelegate<In, Out>(protected val proxy: Call<In>) : Call<Out> {
|
||||||
|
|
||||||
|
override fun execute(): Response<Out> = throw NotImplementedError()
|
||||||
|
|
||||||
|
final override fun enqueue(callback: Callback<Out>) = enqueueImpl(callback)
|
||||||
|
|
||||||
|
final override fun clone(): Call<Out> = cloneImpl()
|
||||||
|
|
||||||
|
override fun cancel() = proxy.cancel()
|
||||||
|
|
||||||
|
override fun request(): Request = proxy.request()
|
||||||
|
|
||||||
|
override fun isExecuted() = proxy.isExecuted
|
||||||
|
|
||||||
|
override fun isCanceled() = proxy.isCanceled
|
||||||
|
|
||||||
|
abstract fun enqueueImpl(callback: Callback<Out>)
|
||||||
|
|
||||||
|
abstract fun cloneImpl(): Call<Out>
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResultCallAdapter<R>(private val type: Type) : CallAdapter<R, Call<Answer<R>>> {
|
||||||
|
|
||||||
|
override fun responseType() = type
|
||||||
|
|
||||||
|
override fun adapt(call: Call<R>): Call<Answer<R>> = ResultCall(call)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Answer<T>>(proxy) {
|
||||||
|
|
||||||
|
override fun enqueueImpl(callback: Callback<Answer<T>>) {
|
||||||
|
proxy.enqueue(ResultCallback(this, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cloneImpl(): ResultCall<T> {
|
||||||
|
return ResultCall(proxy.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResultCallback<T>(
|
||||||
|
private val proxy: ResultCall<T>,
|
||||||
|
private val callback: Callback<Answer<T>>
|
||||||
|
) : Callback<T> {
|
||||||
|
|
||||||
|
// TODO: 8/31/2021 parse VK errors
|
||||||
|
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||||
|
val result: Answer<T> = if (response.isSuccessful)
|
||||||
|
Answer.Success(response.body() as T)
|
||||||
|
else Answer.Error(IOException(response.errorBody()?.string() ?: ""))
|
||||||
|
|
||||||
|
if (result is Answer.Error) if (checkErrors(call, result)) return
|
||||||
|
|
||||||
|
callback.onResponse(proxy, Response.success(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<T>, error: Throwable) {
|
||||||
|
callback.onResponse(
|
||||||
|
proxy,
|
||||||
|
Response.success(Answer.Error(throwable = error))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkErrors(call: Call<T>, result: Answer.Error): Boolean {
|
||||||
|
val json = JSONObject(result.throwable.message ?: "{}")
|
||||||
|
|
||||||
|
return if (json.has("error")) {
|
||||||
|
val error = json.optString("error", "")
|
||||||
|
val description = json.optString("error_description", "")
|
||||||
|
|
||||||
|
val exception = VKException(
|
||||||
|
error = error,
|
||||||
|
description = description,
|
||||||
|
).also { it.json = json }
|
||||||
|
|
||||||
|
onFailure(call, exception)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun timeout(): Timeout {
|
||||||
|
return proxy.timeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Answer<out R> {
|
||||||
|
|
||||||
|
data class Success<out T>(val data: T) : Answer<T>()
|
||||||
|
data class Error(val throwable: Throwable) : Answer<Nothing>()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.meloda.fast.api.network.datasource.AuthDataSource
|
||||||
|
import com.meloda.fast.api.network.repo.AuthRepo
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Module
|
||||||
|
class VKModules {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(authInterceptor)
|
||||||
|
.followRedirects(true)
|
||||||
|
.followSslRedirects(true)
|
||||||
|
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}).build()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideGson(): Gson = GsonBuilder()
|
||||||
|
.setLenient()
|
||||||
|
.create()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideRetrofit(
|
||||||
|
client: OkHttpClient,
|
||||||
|
gson: Gson
|
||||||
|
): Retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("https://api.vk.com/")
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
|
.addCallAdapterFactory(ResultCallFactory())
|
||||||
|
.client(client)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAuthInterceptor(): AuthInterceptor = AuthInterceptor()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideAuthRepo(retrofit: Retrofit): AuthRepo =
|
||||||
|
retrofit.create(AuthRepo::class.java)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideAuthDataSource(repo: AuthRepo): AuthDataSource =
|
||||||
|
AuthDataSource(repo)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.meloda.fast.api.network
|
||||||
|
|
||||||
|
object VKUrls {
|
||||||
|
|
||||||
|
object Auth {
|
||||||
|
const val directAuth = "https://oauth.vk.com/token"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Conversations {
|
||||||
|
const val get = "messages.getConversations"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.meloda.fast.api.network.datasource
|
||||||
|
|
||||||
|
import com.meloda.fast.api.network.repo.AuthRepo
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AuthDataSource @Inject constructor(
|
||||||
|
private val repo: AuthRepo
|
||||||
|
) : AuthRepo {
|
||||||
|
override suspend fun auth(param: Map<String, String?>) = repo.auth(param)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.meloda.fast.api.network.repo
|
||||||
|
|
||||||
|
import com.meloda.fast.api.network.VKUrls
|
||||||
|
import com.meloda.fast.api.network.response.ResponseAuthDirect
|
||||||
|
import com.meloda.fast.api.network.Answer
|
||||||
|
import retrofit2.http.*
|
||||||
|
|
||||||
|
interface AuthRepo {
|
||||||
|
|
||||||
|
@GET(VKUrls.Auth.directAuth)
|
||||||
|
suspend fun auth(@QueryMap param: Map<String, String?>): Answer<ResponseAuthDirect>
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.meloda.fast.api.network.repo
|
||||||
|
|
||||||
|
import com.meloda.fast.api.network.Answer
|
||||||
|
import com.meloda.fast.api.network.VKUrls
|
||||||
|
import com.meloda.fast.api.network.response.GetConversationsResponse
|
||||||
|
import retrofit2.http.*
|
||||||
|
|
||||||
|
interface ConversationsRepo {
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(VKUrls.Conversations.get)
|
||||||
|
suspend fun getAllChats(
|
||||||
|
@Field("user_id") chatId: Int,
|
||||||
|
@Field("token") token: String
|
||||||
|
): Answer<GetConversationsResponse>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.meloda.fast.api.network.request
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class RequestAuthDirect(
|
||||||
|
@SerializedName("grant_type") val grantType: String,
|
||||||
|
@SerializedName("client_id") val clientId: String,
|
||||||
|
@SerializedName("client_secret") val clientSecret: String,
|
||||||
|
@SerializedName("username") val username: String,
|
||||||
|
@SerializedName("password") val password: String,
|
||||||
|
@SerializedName("scope") val scope: String,
|
||||||
|
@SerializedName("2fa_supported") val twoFaSupported: Boolean = true,
|
||||||
|
@SerializedName("force_sms") val twoFaForceSms: Boolean = false,
|
||||||
|
@SerializedName("code") val twoFaCode: String? = null,
|
||||||
|
@SerializedName("captcha_sid") val captchaSid: String? = null,
|
||||||
|
@SerializedName("captcha_key") val captchaKey: String? = null,
|
||||||
|
) : Parcelable {
|
||||||
|
val map
|
||||||
|
get() = mutableMapOf(
|
||||||
|
"grant_type" to grantType,
|
||||||
|
"client_id" to clientId,
|
||||||
|
"client_secret" to clientSecret,
|
||||||
|
"username" to username,
|
||||||
|
"password" to password,
|
||||||
|
"scope" to scope,
|
||||||
|
"2fa_supported" to if (twoFaSupported) "1" else "0",
|
||||||
|
"force_sms" to if (twoFaForceSms) "1" else "0"
|
||||||
|
)
|
||||||
|
.apply {
|
||||||
|
twoFaCode?.let { this["code"] = it }
|
||||||
|
captchaSid?.let { this["captcha_sid"] = it }
|
||||||
|
captchaKey?.let { this["captcha_key"] = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package com.meloda.fast.api.network.request
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.meloda.fast.api.model.request
|
package com.meloda.fast.api.network.request
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.meloda.fast.api.network.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ResponseAuthDirect(
|
||||||
|
@SerializedName("access_token") val accessToken: String? = null,
|
||||||
|
@SerializedName("user_id") val userId: Int? = null,
|
||||||
|
@SerializedName("trusted_hash") val twoFaHash: String? = null,
|
||||||
|
@SerializedName("validation_sid") val validationSid: String? = null
|
||||||
|
) : Parcelable
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.meloda.fast.api.model.response
|
package com.meloda.fast.api.network.response
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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>>
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,12 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.meloda.fast.base.viewmodel.BaseVM
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
abstract class BaseVMFragment<VM : BaseVM> : BaseFragment {
|
abstract class BaseVMFragment<VM : BaseViewModel> : BaseFragment {
|
||||||
|
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.meloda.fast.base.viewmodel
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.meloda.fast.api.Answer
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
abstract class BaseVM : ViewModel() {
|
|
||||||
|
|
||||||
protected val tasksEventChannel = Channel<VKEvent>()
|
|
||||||
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
|
||||||
|
|
||||||
protected fun <T> makeJob(
|
|
||||||
job: suspend () -> Answer<T>,
|
|
||||||
onAnswer: suspend (T) -> Unit = {},
|
|
||||||
onStart: (suspend () -> Unit)? = null,
|
|
||||||
onEnd: (suspend () -> Unit)? = null,
|
|
||||||
onError: (suspend (String) -> Unit)? = null
|
|
||||||
) = viewModelScope.launch {
|
|
||||||
onStart?.invoke()
|
|
||||||
when (val response = job()) {
|
|
||||||
is Answer.Success -> onAnswer(response.data)
|
|
||||||
is Answer.Error -> onError?.invoke(response.errorString) ?: tasksEventChannel.send(
|
|
||||||
ShowDialogInfoEvent(null, response.errorString)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
|
||||||
|
|
||||||
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.meloda.fast.base.viewmodel
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.meloda.fast.api.VKException
|
||||||
|
import com.meloda.fast.api.network.Answer
|
||||||
|
import com.meloda.fast.api.network.VKErrors
|
||||||
|
import com.meloda.fast.util.Utils
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
abstract class BaseViewModel : ViewModel() {
|
||||||
|
|
||||||
|
protected val tasksEventChannel = Channel<VKEvent>()
|
||||||
|
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
||||||
|
|
||||||
|
protected fun <T> makeJob(
|
||||||
|
job: suspend () -> Answer<T>,
|
||||||
|
onAnswer: suspend (T) -> Unit = {},
|
||||||
|
onStart: (suspend () -> Unit)? = null,
|
||||||
|
onEnd: (suspend () -> Unit)? = null,
|
||||||
|
onError: (suspend (Throwable) -> Unit)? = null
|
||||||
|
) = viewModelScope.launch {
|
||||||
|
onStart?.invoke()
|
||||||
|
when (val response = job()) {
|
||||||
|
is Answer.Success -> onAnswer(response.data)
|
||||||
|
is Answer.Error -> onError?.invoke(response.throwable)
|
||||||
|
}
|
||||||
|
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
||||||
|
|
||||||
|
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
||||||
|
|
||||||
|
protected suspend fun checkErrors(throwable: Throwable) {
|
||||||
|
// TODO: 8/31/2021 check illegal token
|
||||||
|
if (throwable is VKException) {
|
||||||
|
when (throwable.error) {
|
||||||
|
VKErrors.NEED_CAPTCHA -> {
|
||||||
|
throwable.captcha =
|
||||||
|
(throwable.json?.optString("captcha_sid")
|
||||||
|
?: "") to (throwable.json?.optString("captcha_img") ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
VKErrors.NEED_VALIDATION -> {
|
||||||
|
throwable.validationSid = throwable.json?.optString("validation_sid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasksEventChannel.send(ShowDialogInfoEvent(null, Log.getStackTraceString(throwable)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ import com.meloda.fast.database.DatabaseKeys.UNREAD_COUNT
|
|||||||
import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS
|
import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS
|
||||||
import com.meloda.fast.database.base.Storage
|
import com.meloda.fast.database.base.Storage
|
||||||
import com.meloda.fast.api.model.VKConversation
|
import com.meloda.fast.api.model.VKConversation
|
||||||
import com.meloda.fast.api.util.VKUtil
|
import com.meloda.fast.api.VKUtil
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import com.meloda.fast.database.DatabaseKeys.TYPE
|
|||||||
import com.meloda.fast.database.DatabaseUtils.TABLE_GROUPS
|
import com.meloda.fast.database.DatabaseUtils.TABLE_GROUPS
|
||||||
import com.meloda.fast.database.base.Storage
|
import com.meloda.fast.database.base.Storage
|
||||||
import com.meloda.fast.api.model.VKGroup
|
import com.meloda.fast.api.model.VKGroup
|
||||||
import com.meloda.fast.api.util.VKUtil
|
import com.meloda.fast.api.VKUtil
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
class GroupsStorage : Storage<VKGroup>() {
|
class GroupsStorage : Storage<VKGroup>() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.meloda.fast.database.DatabaseUtils.TABLE_USERS
|
|||||||
import com.meloda.fast.database.QueryBuilder
|
import com.meloda.fast.database.QueryBuilder
|
||||||
import com.meloda.fast.database.base.Storage
|
import com.meloda.fast.database.base.Storage
|
||||||
import com.meloda.fast.api.model.VKUser
|
import com.meloda.fast.api.model.VKUser
|
||||||
import com.meloda.fast.api.util.VKUtil
|
import com.meloda.fast.api.VKUtil
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
package com.meloda.fast.screens.login
|
package com.meloda.fast.screens.login
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
import android.webkit.CookieManager
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@@ -20,6 +18,7 @@ import coil.load
|
|||||||
import coil.transform.RoundedCornersTransformation
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.meloda.fast.BuildConfig
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.base.BaseVMFragment
|
import com.meloda.fast.base.BaseVMFragment
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
@@ -37,12 +36,12 @@ import java.util.*
|
|||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
class LoginFragment : BaseVMFragment<LoginViewModel>(R.layout.fragment_login) {
|
||||||
|
|
||||||
override val viewModel: LoginVM by viewModels()
|
override val viewModel: LoginViewModel by viewModels()
|
||||||
private val binding: FragmentLoginBinding by viewBinding()
|
private val binding: FragmentLoginBinding by viewBinding()
|
||||||
|
|
||||||
private var lastEmail: String = ""
|
private var lastLogin: String = ""
|
||||||
private var lastPassword: String = ""
|
private var lastPassword: String = ""
|
||||||
|
|
||||||
private var errorTimer: Timer? = null
|
private var errorTimer: Timer? = null
|
||||||
@@ -72,9 +71,9 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
|
|
||||||
when (event) {
|
when (event) {
|
||||||
is ShowError -> showErrorSnackbar(event.errorDescription)
|
is ShowError -> showErrorSnackbar(event.errorDescription)
|
||||||
is ShowCaptchaDialog -> showCaptchaDialog(event.captchaImage, event.captchaSid)
|
is CaptchaRequired -> showCaptchaDialog(event.captcha.first, event.captcha.second)
|
||||||
is GoToValidationEvent -> goToValidation(event.redirectUrl)
|
is ValidationRequired -> goToValidation()
|
||||||
is GoToMainEvent -> goToMain(event.haveAuthorized)
|
is SuccessAuth -> goToMain(event.haveAuthorized)
|
||||||
StartProgressEvent -> onProgressStarted()
|
StartProgressEvent -> onProgressStarted()
|
||||||
StopProgressEvent -> onProgressStopped()
|
StopProgressEvent -> onProgressStopped()
|
||||||
}
|
}
|
||||||
@@ -95,28 +94,11 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareViews() {
|
private fun prepareViews() {
|
||||||
prepareWebView()
|
|
||||||
prepareEmailEditText()
|
prepareEmailEditText()
|
||||||
preparePasswordEditText()
|
preparePasswordEditText()
|
||||||
prepareAuthButton()
|
prepareAuthButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
private fun prepareWebView() {
|
|
||||||
with(binding.webView) {
|
|
||||||
settings.javaScriptEnabled = true
|
|
||||||
settings.domStorageEnabled = true
|
|
||||||
settings.loadsImagesAutomatically = false
|
|
||||||
settings.userAgentString = "Chrome/41.0.2228.0 Safari/537.36"
|
|
||||||
clearCache(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val cookieManager = CookieManager.getInstance()
|
|
||||||
cookieManager.removeAllCookies(null)
|
|
||||||
cookieManager.flush()
|
|
||||||
cookieManager.setAcceptCookie(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun prepareEmailEditText() {
|
private fun prepareEmailEditText() {
|
||||||
binding.loginInput.addTextChangedListener {
|
binding.loginInput.addTextChangedListener {
|
||||||
if (!binding.loginLayout.error.isNullOrBlank()) binding.loginLayout.error = ""
|
if (!binding.loginLayout.error.isNullOrBlank()) binding.loginLayout.error = ""
|
||||||
@@ -150,23 +132,30 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareAuthButton() {
|
private fun prepareAuthButton() {
|
||||||
binding.auth.setOnClickListener {
|
binding.auth.setOnClickListener { validateDataAndAuth() }
|
||||||
if (binding.progress.isVisible) return@setOnClickListener
|
binding.auth.setOnLongClickListener {
|
||||||
|
validateDataAndAuth(BuildConfig.vkLogin to BuildConfig.vkPassword)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val loginString = binding.loginInput.text.toString().trim()
|
private fun validateDataAndAuth(data: Pair<String, String>? = null) {
|
||||||
val passwordString = binding.passwordInput.text.toString().trim()
|
if (binding.progress.isVisible) return
|
||||||
|
val loginString = data?.first ?: binding.loginInput.text.toString().trim()
|
||||||
|
val passwordString = data?.second ?: binding.passwordInput.text.toString().trim()
|
||||||
|
|
||||||
if (!validateInputData(loginString, passwordString)) return@setOnClickListener
|
if (!validateInputData(loginString, passwordString)) return
|
||||||
|
|
||||||
KeyboardUtils.hideKeyboardFrom(requireView().findFocus())
|
lastLogin = loginString
|
||||||
|
lastPassword = passwordString
|
||||||
|
|
||||||
lifecycleScope.launch {
|
KeyboardUtils.hideKeyboardFrom(requireView().findFocus())
|
||||||
viewModel.login(
|
|
||||||
binding.webView,
|
lifecycleScope.launch {
|
||||||
loginString,
|
viewModel.login(
|
||||||
passwordString
|
login = loginString,
|
||||||
)
|
password = passwordString
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +209,7 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
captchaInputLayout?.error = ""
|
captchaInputLayout?.error = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showCaptchaDialog(captchaImage: String, captchaSid: String) {
|
private fun showCaptchaDialog(captchaSid: String, captchaImage: String) {
|
||||||
val captchaBinding = DialogCaptchaBinding.inflate(layoutInflater, null, false)
|
val captchaBinding = DialogCaptchaBinding.inflate(layoutInflater, null, false)
|
||||||
captchaInputLayout = captchaBinding.captchaLayout
|
captchaInputLayout = captchaBinding.captchaLayout
|
||||||
|
|
||||||
@@ -250,11 +239,9 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.login(
|
viewModel.login(
|
||||||
webView = binding.webView,
|
login = lastLogin,
|
||||||
email = lastEmail,
|
|
||||||
password = lastPassword,
|
password = lastPassword,
|
||||||
captchaSid = captchaSid,
|
captcha = captchaSid to captchaCode
|
||||||
captchaKey = captchaCode
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,11 +259,11 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
snackbar.show()
|
snackbar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goToValidation(redirectUrl: String) {
|
private fun goToValidation() {
|
||||||
findNavController().navigate(
|
// findNavController().navigate(
|
||||||
R.id.toValidation,
|
// R.id.toValidation,
|
||||||
bundleOf("redirectUrl" to redirectUrl)
|
// bundleOf("redirectUrl" to redirectUrl)
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goToMain(haveAuthorized: Boolean) {
|
private fun goToMain(haveAuthorized: Boolean) {
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
package com.meloda.fast.screens.login
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.JavascriptInterface
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.meloda.fast.UserConfig
|
|
||||||
import com.meloda.fast.api.VKAuth
|
|
||||||
import com.meloda.fast.base.viewmodel.BaseVM
|
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.json.JSONObject
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
|
|
||||||
class LoginVM : BaseVM() {
|
|
||||||
|
|
||||||
private var isWebViewPrepared = false
|
|
||||||
|
|
||||||
suspend fun login(
|
|
||||||
webView: WebView,
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
captchaSid: String? = null,
|
|
||||||
captchaKey: String? = null
|
|
||||||
) {
|
|
||||||
sendEvent(StartProgressEvent)
|
|
||||||
|
|
||||||
val urlToGo = VKAuth.getDirectAuthUrl(email, password, captchaSid, captchaKey)
|
|
||||||
|
|
||||||
if (!isWebViewPrepared) {
|
|
||||||
isWebViewPrepared = true
|
|
||||||
|
|
||||||
webView.addJavascriptInterface(WebViewHandlerInterface(), "HtmlHandler")
|
|
||||||
|
|
||||||
webView.webViewClient = object : WebViewClient() {
|
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
|
||||||
webView.loadUrl(
|
|
||||||
"javascript:window.HtmlHandler.handleHtml" +
|
|
||||||
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webView.loadUrl(urlToGo)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MoveVariableDeclarationIntoWhen")
|
|
||||||
private fun checkResponse(response: JSONObject) {
|
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
|
||||||
if (response.has("error")) {
|
|
||||||
sendEvent(StopProgressEvent)
|
|
||||||
|
|
||||||
val errorString = response.optString("error")
|
|
||||||
val errorDescription = response.optString("error_description")
|
|
||||||
|
|
||||||
// TODO: 7/27/2021 use this with localized resources
|
|
||||||
// val errorType = response.optString("error_type")
|
|
||||||
|
|
||||||
when (errorString) {
|
|
||||||
"need_validation" -> {
|
|
||||||
val redirectUrl = response.optString("redirect_uri")
|
|
||||||
|
|
||||||
tasksEventChannel.send(GoToValidationEvent(redirectUrl))
|
|
||||||
}
|
|
||||||
"need_captcha" -> {
|
|
||||||
val captchaImage = response.optString("captcha_img")
|
|
||||||
val captchaSid = response.optString("captcha_sid")
|
|
||||||
|
|
||||||
Log.d("CAPTCHA", "captchaImage: $captchaImage")
|
|
||||||
|
|
||||||
tasksEventChannel.send(ShowCaptchaDialog(captchaImage, captchaSid))
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
tasksEventChannel.send(ShowError(errorDescription))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delay(1500)
|
|
||||||
sendEvent(StopProgressEvent)
|
|
||||||
|
|
||||||
val userId = response.optInt("user_id", -1)
|
|
||||||
val accessToken = response.optString("access_token")
|
|
||||||
|
|
||||||
UserConfig.accessToken = accessToken
|
|
||||||
UserConfig.userId = userId
|
|
||||||
|
|
||||||
tasksEventChannel.send(GoToMainEvent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getValidatedData(bundle: Bundle) {
|
|
||||||
val accessToken = bundle.getString("token") ?: ""
|
|
||||||
val userId = bundle.getInt("userId")
|
|
||||||
|
|
||||||
UserConfig.accessToken = accessToken
|
|
||||||
UserConfig.userId = userId
|
|
||||||
|
|
||||||
tasksEventChannel.send(GoToMainEvent())
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class WebViewHandlerInterface {
|
|
||||||
@JavascriptInterface
|
|
||||||
fun handleHtml(html: String) {
|
|
||||||
val doc = Jsoup.parse(html)
|
|
||||||
|
|
||||||
val responseString =
|
|
||||||
doc.select("pre[style=\"word-wrap: break-word; white-space: pre-wrap;\"]").first()
|
|
||||||
?.text() ?: ""
|
|
||||||
|
|
||||||
checkResponse(JSONObject(responseString))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ShowError(val errorDescription: String) : VKEvent()
|
|
||||||
data class ShowCaptchaDialog(val captchaImage: String, val captchaSid: String) : VKEvent()
|
|
||||||
data class GoToValidationEvent(val redirectUrl: String) : VKEvent()
|
|
||||||
data class GoToMainEvent(val haveAuthorized: Boolean = true) : VKEvent()
|
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
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.VKConstants
|
||||||
|
import com.meloda.fast.api.VKException
|
||||||
|
import com.meloda.fast.api.VKUtil
|
||||||
|
import com.meloda.fast.api.network.repo.AuthRepo
|
||||||
|
import com.meloda.fast.api.network.request.RequestAuthDirect
|
||||||
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
|
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
|
||||||
|
class LoginViewModel @Inject constructor(
|
||||||
|
private val repo: AuthRepo
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
suspend fun login(
|
||||||
|
login: String,
|
||||||
|
password: String,
|
||||||
|
twoFa: Boolean = false,
|
||||||
|
twoFaCode: String? = null,
|
||||||
|
captcha: Pair<String, String>? = null
|
||||||
|
) {
|
||||||
|
makeJob(
|
||||||
|
{
|
||||||
|
repo.auth(
|
||||||
|
RequestAuthDirect(
|
||||||
|
grantType = VKAuth.GrantType.PASSWORD,
|
||||||
|
clientId = VKConstants.VK_APP_ID,
|
||||||
|
clientSecret = VKConstants.VK_SECRET,
|
||||||
|
username = login,
|
||||||
|
password = password,
|
||||||
|
scope = VKAuth.scope,
|
||||||
|
twoFaForceSms = twoFa,
|
||||||
|
twoFaCode = twoFaCode,
|
||||||
|
captchaSid = captcha?.first,
|
||||||
|
captchaKey = captcha?.second
|
||||||
|
).map
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onAnswer = {
|
||||||
|
// TODO: 8/31/2021 do something
|
||||||
|
if (it.userId == null || it.accessToken == null) {
|
||||||
|
return@makeJob
|
||||||
|
}
|
||||||
|
|
||||||
|
UserConfig.userId = it.userId
|
||||||
|
UserConfig.accessToken = it.accessToken
|
||||||
|
|
||||||
|
sendEvent(SuccessAuth(haveAuthorized = true))
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
checkErrors(it)
|
||||||
|
|
||||||
|
if (it !is VKException) return@makeJob
|
||||||
|
|
||||||
|
if (VKUtil.isValidationRequired(it)) {
|
||||||
|
sendEvent(ValidationRequired(validationSid = it.validationSid))
|
||||||
|
} else if (VKUtil.isCaptchaRequired(it) && it.captcha != null) {
|
||||||
|
sendEvent(CaptchaRequired(it.captcha!!.first to it.captcha!!.second))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStart = { sendEvent(StartProgressEvent) },
|
||||||
|
onEnd = { sendEvent(StopProgressEvent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MoveVariableDeclarationIntoWhen")
|
||||||
|
private fun checkResponse(response: JSONObject) {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
if (response.has("error")) {
|
||||||
|
sendEvent(StopProgressEvent)
|
||||||
|
|
||||||
|
val errorString = response.optString("error")
|
||||||
|
val errorDescription = response.optString("error_description")
|
||||||
|
|
||||||
|
// TODO: 7/27/2021 use this with localized resources
|
||||||
|
// val errorType = response.optString("error_type")
|
||||||
|
|
||||||
|
when (errorString) {
|
||||||
|
"need_validation" -> {
|
||||||
|
val redirectUrl = response.optString("redirect_uri")
|
||||||
|
|
||||||
|
tasksEventChannel.send(ValidationRequired(redirectUrl))
|
||||||
|
}
|
||||||
|
"need_captcha" -> {
|
||||||
|
val captchaImage = response.optString("captcha_img")
|
||||||
|
val captchaSid = response.optString("captcha_sid")
|
||||||
|
|
||||||
|
Log.d("CAPTCHA", "captchaImage: $captchaImage")
|
||||||
|
|
||||||
|
tasksEventChannel.send(ShowCaptchaDialog(captchaImage, captchaSid))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
tasksEventChannel.send(ShowError(errorDescription))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delay(1500)
|
||||||
|
sendEvent(StopProgressEvent)
|
||||||
|
|
||||||
|
val userId = response.optInt("user_id", -1)
|
||||||
|
val accessToken = response.optString("access_token")
|
||||||
|
|
||||||
|
UserConfig.accessToken = accessToken
|
||||||
|
UserConfig.userId = userId
|
||||||
|
|
||||||
|
tasksEventChannel.send(SuccessAuth())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getValidatedData(bundle: Bundle) {
|
||||||
|
val accessToken = bundle.getString("token") ?: ""
|
||||||
|
val userId = bundle.getInt("userId")
|
||||||
|
|
||||||
|
UserConfig.accessToken = accessToken
|
||||||
|
UserConfig.userId = userId
|
||||||
|
|
||||||
|
tasksEventChannel.send(SuccessAuth())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ShowError(val errorDescription: String) : VKEvent()
|
||||||
|
data class ShowCaptchaDialog(val captchaImage: String, val captchaSid: String) : VKEvent()
|
||||||
|
|
||||||
|
data class ValidationRequired(
|
||||||
|
val redirectUrl: String? = null,
|
||||||
|
val validationSid: String? = null
|
||||||
|
) : VKEvent()
|
||||||
|
|
||||||
|
data class CaptchaRequired(val captcha: Pair<String, String>) : VKEvent()
|
||||||
|
|
||||||
|
data class SuccessAuth(val haveAuthorized: Boolean = true) : VKEvent()
|
||||||
@@ -13,9 +13,9 @@ import com.meloda.fast.extensions.NavigationExtensions.setupWithNavController
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainFragment : BaseVMFragment<MainVM>(R.layout.fragment_main) {
|
class MainFragment : BaseVMFragment<MainViewModel>(R.layout.fragment_main) {
|
||||||
|
|
||||||
override val viewModel: MainVM by viewModels()
|
override val viewModel: MainViewModel by viewModels()
|
||||||
private val binding: FragmentMainBinding by viewBinding()
|
private val binding: FragmentMainBinding by viewBinding()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.meloda.fast.screens.main
|
|
||||||
|
|
||||||
import com.meloda.fast.base.viewmodel.BaseVM
|
|
||||||
|
|
||||||
class MainVM : BaseVM()
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.meloda.fast.screens.main
|
||||||
|
|
||||||
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
|
|
||||||
|
class MainViewModel : BaseViewModel()
|
||||||
@@ -16,9 +16,9 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ConversationsFragment : BaseVMFragment<ConversationsVM>(R.layout.fragment_conversations) {
|
class ConversationsFragment : BaseVMFragment<ConversationsViewModel>(R.layout.fragment_conversations) {
|
||||||
|
|
||||||
override val viewModel: ConversationsVM by viewModels()
|
override val viewModel: ConversationsViewModel by viewModels()
|
||||||
private val binding: FragmentConversationsBinding by viewBinding()
|
private val binding: FragmentConversationsBinding by viewBinding()
|
||||||
|
|
||||||
private lateinit var adapter: ConversationsAdapter
|
private lateinit var adapter: ConversationsAdapter
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.meloda.fast.base.viewmodel.BaseVM
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ConversationsVM : BaseVM() {
|
class ConversationsViewModel : BaseViewModel() {
|
||||||
|
|
||||||
fun loadConversations() = viewModelScope.launch(Dispatchers.Default) {
|
fun loadConversations() = viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import com.meloda.fast.R
|
|||||||
import com.meloda.fast.api.model.*
|
import com.meloda.fast.api.model.*
|
||||||
import com.meloda.fast.common.AppGlobal
|
import com.meloda.fast.common.AppGlobal
|
||||||
import com.meloda.fast.api.OnResponseListener
|
import com.meloda.fast.api.OnResponseListener
|
||||||
import com.meloda.fast.api.util.VKUtil
|
import com.meloda.fast.api.VKUtil
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import com.meloda.fast.extensions.ContextExtensions.color
|
|||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.widget.CircleImageView
|
import com.meloda.fast.widget.CircleImageView
|
||||||
import com.meloda.fast.api.model.VKUser
|
import com.meloda.fast.api.model.VKUser
|
||||||
import com.meloda.fast.api.util.VKUtil
|
|
||||||
|
|
||||||
|
|
||||||
object ViewUtils {
|
object ViewUtils {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/image"
|
android:id="@+id/image"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="50dp"
|
android:layout_height="100dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
tools:src="@tools:sample/backgrounds/scenic" />
|
tools:src="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -65,7 +66,9 @@
|
|||||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@android:string/cancel"
|
android:text="@android:string/cancel"
|
||||||
app:elevation="0dp" />
|
android:textColor="?colorAction"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:rippleColor="?colorActionRipple" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/ok"
|
android:id="@+id/ok"
|
||||||
@@ -73,8 +76,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:backgroundTint="?colorAction"
|
||||||
android:text="@android:string/ok"
|
android:text="@android:string/ok"
|
||||||
app:elevation="0dp" />
|
android:textColor="?colorActionContentPrimary"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:rippleColor="?colorActionRipple" />
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -9,12 +9,6 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<WebView
|
|
||||||
android:id="@+id/webView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
Reference in New Issue
Block a user