Removed unused classes

Using dagger for Retrofit2, OkHttp and Gson
This commit is contained in:
2021-08-31 06:32:39 +03:00
parent fb6dbc6646
commit f8b00e320f
48 changed files with 670 additions and 457 deletions
@@ -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.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
@@ -55,13 +56,14 @@ object VKApi {
try {
checkError(json, url)
} catch (ex: VKException) {
if (ex.code == ErrorCodes.TOO_MANY_REQUESTS) {
Timer().schedule(object : TimerTask() {
override fun run() {
execute(url, cls)
}
}, 1000)
} else throw ex
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) {
@@ -282,7 +284,7 @@ object VKApi {
val code = error.optInt("error_code", -1)
val message = error.optString("error_msg", "")
val e = VKException(url, message, code)
// val e = VKException(url, message, code)
//TODO: add checking invalid session
if (code == 5 && message.contains("invalid session")) {
@@ -291,16 +293,16 @@ object VKApi {
// })
}
if (code == ErrorCodes.CAPTCHA_NEEDED) {
e.captchaImg = error.optString("captcha_img")
e.captchaSid = error.optString("captcha_sid")
}
// 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")
// }
if (code == ErrorCodes.VALIDATION_REQUIRED) {
e.redirectUri = error.optString("redirect_uri")
}
throw e
// throw e
}
}
@@ -3,14 +3,17 @@ package com.meloda.fast.api
import android.util.Log
import com.meloda.fast.BuildConfig
import com.meloda.fast.UserConfig
import com.meloda.fast.api.util.VKUtil
import java.net.URLEncoder
object VKAuth {
private const val TAG = "VKM.VKAuth"
private const val settings = "notify," +
object GrantType {
const val PASSWORD = "password"
}
const val scope = "notify," +
"friends," +
"photos," +
"audio," +
@@ -30,17 +33,25 @@ object VKAuth {
fun getDirectAuthUrl(
login: String,
password: String,
captchaSid: String? = null,
captchaKey: String? = null
) = "https://oauth.vk.com/token?grant_type=password&" +
twoFa: Boolean = false,
twoFaCode: String = "",
captcha: Pair<String, String>? = null
) = "https://oauth.vk.com/token?" +
"grant_type=password&" +
"client_id=${VKConstants.VK_APP_ID}&" +
"scope=$settings&" +
"client_secret=${VKConstants.VK_SECRET}&" +
"username=$login&" +
"password=$password" +
(if (captchaSid == null || captchaKey == null) "" else "&captcha_sid=$captchaSid&captcha_key=$captchaKey") +
"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}&" +
@@ -1,15 +1,20 @@
package com.meloda.fast.api
import org.json.JSONObject
import java.io.IOException
class VKException(var url: String = "", override var message: String = "", var code: Int) :
IOException(message) {
var captchaSid: String? = null
var captchaImg: String? = null
class VKException(var url: String = "", var description: String = "", var error: String) :
IOException(description) {
var captcha: Pair<String, String>? = null
var redirectUri: String? = null
var validationSid: String? = null
var json: JSONObject? = null
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"
}
@@ -1,7 +1,8 @@
package com.meloda.fast.api.util
package com.meloda.fast.api
import androidx.annotation.WorkerThread
import com.meloda.fast.api.model.*
import com.meloda.fast.api.network.VKErrors
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
@@ -12,6 +13,21 @@ object VKUtil {
private const val TAG = "VKUtil"
fun isValidationRequired(throwable: Throwable): Boolean {
if (throwable !is VKException) return false
return throwable.error == VKErrors.NEED_VALIDATION
}
fun isCaptchaRequired(throwable: Throwable): Boolean {
if (throwable !is VKException) return false
return throwable.error == VKErrors.NEED_CAPTCHA
}
fun 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)
@@ -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
import android.util.ArrayMap
import com.meloda.fast.api.util.VKUtil
import com.meloda.fast.api.VKUtil
import org.json.JSONObject
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())
}
}
@@ -1,4 +1,4 @@
package com.meloda.fast.api
package com.meloda.fast.api.network
object ErrorCodes {
const val UNKNOWN_ERROR = 1
@@ -39,4 +39,13 @@ object ErrorCodes {
const val INVALID_DOC_ID = 1150
const val INVALID_DOC_TITLE = 1152
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,4 +1,4 @@
package com.meloda.fast.api.model.request
package com.meloda.fast.api.network.request
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,4 +1,4 @@
package com.meloda.fast.api.model.response
package com.meloda.fast.api.network.response
import android.os.Parcelable
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>>
}