Upstream changes (#23)

This commit is contained in:
2024-07-11 02:12:32 +03:00
committed by GitHub
parent 8a6378f509
commit 3503ecffab
906 changed files with 23577 additions and 24115 deletions
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,15 @@
package com.meloda.app.fast.network
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ApiResponse<T>(
@Json(name = "error") val error: RestApiError?,
@Json(name = "response") val response: T?
) {
val isSuccessful get() = error == null && response != null
fun requireResponse(): T = requireNotNull(response)
fun requireError(): RestApiError = requireNotNull(error)
}
@@ -0,0 +1,126 @@
package com.meloda.app.fast.network
import com.slack.eithernet.ApiResult
fun <Success : Any, Error : Any, SuccessDomain : Any, ErrorDomain : Any>
ApiResult<Success, Error>.mapResult(
successMapper: (Success) -> SuccessDomain,
errorMapper: (Error?) -> ErrorDomain?
): ApiResult<SuccessDomain, ErrorDomain> {
if (BuildConfig.DEBUG) printStackTraceIfAny()
return when (this) {
is ApiResult.Success -> {
ApiResult.success(successMapper(value))
}
is ApiResult.Failure.NetworkFailure -> {
ApiResult.networkFailure(error)
}
is ApiResult.Failure.UnknownFailure -> {
ApiResult.unknownFailure(error)
}
is ApiResult.Failure.HttpFailure -> {
ApiResult.httpFailure(code, errorMapper(error))
}
is ApiResult.Failure.ApiFailure -> {
ApiResult.apiFailure(errorMapper(error))
}
}
}
fun <Success : Any, Error : Any, SuccessDomain : Any, ErrorDomain : Any>
ApiResult<Success, Error>.mapOAuthResult(
successMapper: (Success) -> SuccessDomain,
errorMapper: (Error?) -> ErrorDomain?
): ApiResult<SuccessDomain, ErrorDomain> {
if (BuildConfig.DEBUG) printStackTraceIfAny()
return when (this) {
is ApiResult.Success -> {
ApiResult.success(successMapper(value))
}
is ApiResult.Failure.NetworkFailure -> {
ApiResult.networkFailure(error)
}
is ApiResult.Failure.UnknownFailure -> {
ApiResult.unknownFailure(error)
}
is ApiResult.Failure.HttpFailure -> {
ApiResult.httpFailure(code, errorMapper(error))
}
is ApiResult.Failure.ApiFailure -> {
ApiResult.apiFailure(errorMapper(error))
}
}
}
fun <Success : ApiResponse<*>, SuccessDomain : Any, ErrorDomain : Any>
ApiResult<Success, RestApiError>.mapApiResult(
successMapper: (Success) -> SuccessDomain,
errorMapper: (RestApiError?) -> ErrorDomain?
): ApiResult<SuccessDomain, ErrorDomain> {
if (BuildConfig.DEBUG) printStackTraceIfAny()
return when (this) {
is ApiResult.Success -> {
if (value.isSuccessful) {
ApiResult.success(successMapper(value))
} else {
ApiResult.apiFailure(errorMapper(value.error))
}
}
is ApiResult.Failure.NetworkFailure -> ApiResult.networkFailure(error)
is ApiResult.Failure.UnknownFailure -> ApiResult.unknownFailure(error)
is ApiResult.Failure.HttpFailure -> ApiResult.httpFailure(code, errorMapper(error))
is ApiResult.Failure.ApiFailure -> ApiResult.apiFailure(errorMapper(error))
}
}
fun <T : Any, R : ApiResponse<T>> ApiResult<R, RestApiError>.mapApiDefault(): ApiResult<T, RestApiErrorDomain> =
mapResult(
successMapper = { response -> response.requireResponse() },
errorMapper = { error -> error?.toDomain() }
)
//@OptIn(ExperimentalContracts::class)
//inline fun <R : Any, E : Any, C> OAuthResponse<R, E>.fold(
// onSuccess: (value: R) -> C,
// onFailure: (failure: E) -> C,
//): C {
// contract {
// callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
// callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
// }
// return when (this) {
// is OAuthResponse.Success -> onSuccess(response)
// is OAuthResponse.Error -> onFailure(error)
// }
//}
fun <Success : Any, Error : Any> ApiResult<Success, Error>.isSuccess(): Boolean =
this is ApiResult.Success
fun <Success : Any, Error : Any> ApiResult<Success, Error>.printStackTraceIfAny() {
val throwable = when (this) {
is ApiResult.Failure.NetworkFailure -> error
is ApiResult.Failure.UnknownFailure -> error
else -> null
}
throwable?.printStackTrace()
}
fun ApiResult.Failure.HttpFailure<*>?.tryCastToRestErrorDomain() =
this?.error as? RestApiErrorDomain
fun ApiResult.Failure.ApiFailure<*>?.tryCastToRestErrorDomain() =
this?.error as? RestApiErrorDomain
@@ -0,0 +1,10 @@
package com.meloda.app.fast.network
import java.lang.reflect.Type
interface JsonConverter {
fun fromJson(clazz: Class<*>, jsonString: String): Any?
fun fromJson(type: Type, jsonString: String): Any?
}
@@ -0,0 +1,17 @@
package com.meloda.app.fast.network
import com.squareup.moshi.Moshi
import java.lang.reflect.Type
internal class MoshiConverter(private val moshi: Moshi) : JsonConverter {
@kotlin.jvm.Throws(RuntimeException::class)
override fun fromJson(clazz: Class<*>, jsonString: String): Any? {
return moshi.adapter(clazz).fromJson(jsonString)
}
@kotlin.jvm.Throws(RuntimeException::class)
override fun fromJson(type: Type, jsonString: String): Any? {
return moshi.adapter<Any>(type).fromJson(jsonString)
}
}
@@ -0,0 +1,141 @@
package com.meloda.app.fast.network
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// TODO: 09/05/2024, Danil Nikolaev: reimplement as sealed class
@JsonClass(generateAdapter = true)
open class OAuthError(
@Json(name = "error") open val error: String,
@Json(name = "error_description") open val errorDescription: String?,
@Json(name = "error_type") open val errorType: String?
)
@JsonClass(generateAdapter = true)
data class ValidationRequiredError(
@Json(name = "error") override val error: String, // "need_validation"
@Json(name = "error_description") override val errorDescription: String, // "sms sent, use code param" if sms method; "use app code" if 2fa app
@Json(name = "validation_type") val validationType: String, // 2fa_app, 2sa_sms
@Json(name = "validation_sid") val validationSid: String,
@Json(name = "phone_mask") val phoneMask: String, // "+7 *** *** ** 50"
@Json(name = "redirect_uri") val redirectUri: String,
@Json(name = "validation_resend") val validationResend: String?, // Приходит, если для отправки кода нужно вызвать метод auth.validatePhone
@Json(name = "cant_get_code_open_restore") val restoreIfCannotGetCode: Boolean?
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = null
)
@JsonClass(generateAdapter = true)
data class CaptchaRequiredError(
@Json(name = "error") override val error: String, // "need_captcha"
@Json(name = "captcha_sid") val captchaSid: String,
@Json(name = "captcha_img") val captchaImage: String,
@Json(name = "captcha_ts") val captchaTs: Double?,
@Json(name = "captcha_ratio") val captchaRatio: Double?,
@Json(name = "captcha_track") val captchaTrack: String?,
@Json(name = "is_refresh_enabled") val isRefreshEnabled: Boolean?,
@Json(name = "is_sound_captcha_available") val isSoundCaptchaAvailable: Boolean?
) : OAuthError(
error = error,
errorDescription = null,
errorType = null
)
@JsonClass(generateAdapter = true)
data class UserBannedError(
@Json(name = "error") override val error: String, // need_validation
@Json(name = "error_description") override val errorDescription: String, // user has been banned
@Json(name = "ban_info") val banInfo: BanInfo
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = null
) {
@JsonClass(generateAdapter = true)
data class BanInfo(
@Json(name = "member_name") val memberName: String,
@Json(name = "message") val message: String,
@Json(name = "access_token") val accessToken: String,
@Json(name = "restore_url") val restoreUrl: String
)
}
@JsonClass(generateAdapter = true)
data class InvalidCredentialsError(
@Json(name = "error") override val error: String, // "invalid_client"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "username_or_password_is_incorrect"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
@JsonClass(generateAdapter = true)
data class WrongTwoFaCode(
@Json(name = "error") override val error: String, // "invalid_request"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "wrong_otp"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
@JsonClass(generateAdapter = true)
data class WrongTwoFaCodeFormat(
@Json(name = "error") override val error: String, // "invalid_request"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "otp_format_is_incorrect"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
fun OAuthError.toDomain(): OAuthErrorDomain? = when (this) {
is ValidationRequiredError -> {
OAuthErrorDomain.ValidationRequiredError(
description = errorDescription,
validationType = ValidationType.parse(validationType),
validationSid = validationSid,
phoneMask = phoneMask,
redirectUri = redirectUri,
validationResend = validationResend,
restoreIfCannotGetCode = restoreIfCannotGetCode
)
}
is CaptchaRequiredError -> {
OAuthErrorDomain.CaptchaRequiredError(
captchaSid = captchaSid,
captchaImageUrl = captchaImage
)
}
is UserBannedError -> {
OAuthErrorDomain.UserBannedError(
memberName = banInfo.memberName,
message = banInfo.message,
accessToken = banInfo.accessToken,
restoreUrl = banInfo.restoreUrl
)
}
is InvalidCredentialsError -> {
OAuthErrorDomain.InvalidCredentialsError
}
is WrongTwoFaCode -> {
OAuthErrorDomain.WrongTwoFaCode
}
is WrongTwoFaCodeFormat -> {
OAuthErrorDomain.WrongTwoFaCodeFormat
}
else -> null
}
@@ -0,0 +1,31 @@
package com.meloda.app.fast.network
sealed class OAuthErrorDomain {
data class ValidationRequiredError(
val description: String,
val validationType: ValidationType,
val validationSid: String,
val phoneMask: String,
val redirectUri: String,
val validationResend: String?,
val restoreIfCannotGetCode: Boolean?
) : OAuthErrorDomain()
data class CaptchaRequiredError(
val captchaSid: String,
val captchaImageUrl: String
) : OAuthErrorDomain()
data class UserBannedError(
val memberName: String,
val message: String,
val accessToken: String,
val restoreUrl: String
) : OAuthErrorDomain()
data object InvalidCredentialsError : OAuthErrorDomain()
data object WrongTwoFaCode : OAuthErrorDomain()
data object WrongTwoFaCodeFormat : OAuthErrorDomain()
data object UnknownError : OAuthErrorDomain()
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.network
sealed interface OAuthResponse<out R : Any, out E : Any> {
data class Success<out R : Any>(val response: R) : OAuthResponse<R, Nothing>
data class Error<out E : Any>(val error: E?) : OAuthResponse<Nothing, E>
}
@@ -0,0 +1,196 @@
package com.meloda.app.fast.network
import android.util.Log
import com.squareup.moshi.Moshi
import okhttp3.Request
import okio.Timeout
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class OAuthResultCallFactory(private val moshi: Moshi) : 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) == OAuthResponse::class.java) {
if (callInnerType is ParameterizedType) {
val resultInnerType = getParameterUpperBound(0, callInnerType)
return ResultCallAdapter<Any, OAuthError>(resultInnerType, moshi)
}
return ResultCallAdapter<Nothing, Nothing>(Nothing::class.java, moshi)
}
}
}
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 : Any, E : OAuthError>(
private val type: Type,
private val moshi: Moshi
) : CallAdapter<R, Call<OAuthResponse<R, E>>> {
override fun responseType() = type
override fun adapt(call: Call<R>): Call<OAuthResponse<R, E>> = ResultCall(call, moshi)
}
internal class ResultCall<R : Any, E : OAuthError>(
proxy: Call<R>,
private val moshi: Moshi
) : CallDelegate<R, OAuthResponse<R, E>>(proxy) {
override fun enqueueImpl(callback: Callback<OAuthResponse<R, E>>) {
proxy.enqueue(ResultCallback(this, callback, moshi))
}
override fun cloneImpl(): ResultCall<R, E> {
return ResultCall(proxy.clone(), moshi)
}
private class ResultCallback<R : Any, E : OAuthError>(
private val proxy: ResultCall<R, E>,
private val callback: Callback<OAuthResponse<R, E>>,
private val moshi: Moshi
) : Callback<R> {
@Suppress("UNCHECKED_CAST")
override fun onResponse(call: Call<R>, response: Response<R>) {
when {
response.isSuccessful -> {
val baseBody = response.body()
baseBody?.let {
callback.onResponse(
proxy,
Response.success(OAuthResponse.Success(baseBody))
)
}
}
else -> {
val errorBodyString = response.errorBody()?.string()
val baseError: OAuthError = moshi.adapter(OAuthError::class.java)
.fromJson(errorBodyString.orEmpty()) ?: return
val error: OAuthError? = when (baseError.error) {
"invalid_client" -> {
moshi.adapter(InvalidCredentialsError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"need_captcha" -> {
moshi.adapter(CaptchaRequiredError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"invalid_request" -> {
when (val type = baseError.errorType) {
"wrong_otp" -> {
moshi.adapter(WrongTwoFaCode::class.java)
.fromJson(errorBodyString.orEmpty())
}
"otp_format_is_incorrect" -> {
moshi.adapter(WrongTwoFaCodeFormat::class.java)
.fromJson(errorBodyString.orEmpty())
}
else -> {
Log.d(
"ResultCallback",
"onResponse: invalid_request; error_type: $type"
)
error("Unknown type: $type")
}
}
}
"need_validation" -> {
when (val description = baseError.errorDescription) {
"user has been banned" -> {
moshi.adapter(UserBannedError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"sms sent, use code param",
"use app code" -> {
moshi.adapter(ValidationRequiredError::class.java)
.fromJson(errorBodyString.orEmpty())
}
else -> {
Log.d(
"ResultCallback",
"onResponse: need_validation; description: $description"
)
error("Unknown description: $description")
}
}
}
else -> null
}
error?.let {
callback.onResponse(
proxy,
Response.success(OAuthResponse.Error(error) as OAuthResponse<R, E>)
)
}
}
}
}
override fun onFailure(call: Call<R>, error: Throwable) {
val b = error
// TODO: 12/04/2024, Danil Nikolaev: handle
// callback.onResponse(
// proxy,
// Response.success(OAuthAnswer.Error((throwable = error)))
// )
}
}
override fun timeout(): Timeout {
return proxy.timeout()
}
}
@@ -0,0 +1,74 @@
package com.meloda.app.fast.network
import android.util.Log
import com.slack.eithernet.ApiException
import com.slack.eithernet.errorType
import com.slack.eithernet.toType
import com.squareup.moshi.JsonDataException
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
/**
* конвертер пытается перевести string с сервера в SuccessType
* если не получается, то в ErrorType и выбрасывает [ApiException]
*
* допускает Unit как SuccessType в случае невозможности каста ответа в ErrorType
*/
class ResponseConverterFactory(private val converter: JsonConverter) : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
val (errorType, b) = annotations.errorType() ?: return null
val errorRaw = getRawType(errorType.toType())
return ResponseBodyConverter(
successType = type,
errorRaw = errorRaw,
converter = converter,
)
}
class ResponseBodyConverter(
private val successType: Type,
private val errorRaw: Class<*>,
private val converter: JsonConverter,
) : Converter<ResponseBody, Any?> {
override fun convert(value: ResponseBody): Any? {
val string = value.string()
kotlin.runCatching {
converter.fromJson(successType, string)
}.fold(
onSuccess = { successModel ->
return successModel
},
onFailure = { failure ->
if(failure is JsonDataException) {
throw failure
}
val isUnit = successType == Unit::class.java
kotlin.runCatching {
converter.fromJson(errorRaw, string)
}.fold(
onSuccess = { errorModel ->
Log.d("ResponseBodyConverter", "convert: $errorModel")
throw ApiException(errorModel)
},
onFailure = { exception ->
if (!isUnit) {
throw exception
} else {
return Unit
}
}
)
}
)
}
}
}
@@ -0,0 +1,17 @@
package com.meloda.app.fast.network
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class RestApiError(
// @Json(name = "code") val code: Int?,
// @Json(name = "message") val message: String?,
@Json(name = "error_code") val errorCode: Int,
@Json(name = "error_msg") val errorMsg: String
) {
fun toDomain(): RestApiErrorDomain = RestApiErrorDomain(
code = errorCode,
message = errorMsg
)
}
@@ -0,0 +1,6 @@
package com.meloda.app.fast.network
data class RestApiErrorDomain(
val code: Int,
val message: String
)
@@ -0,0 +1,9 @@
package com.meloda.app.fast.network;
enum class ValidationType(val value: String) {
APP("2fa_app"), SMS("2fa_sms");
companion object {
fun parse(value: String): ValidationType = entries.first { it.value == value }
}
}
@@ -0,0 +1,60 @@
package com.meloda.app.fast.network
@Suppress("unused")
object VkErrorCodes {
const val UnknownError = 1
const val AppDisabled = 2
const val UnknownMethod = 3
const val InvalidSignature = 4
const val UserAuthorizationFailed = 5
const val TooManyRequests = 6
const val NoRights = 7
const val BadRequest = 8
const val TooManySimilarActions = 9
const val InternalServerError = 10
const val InTestMode = 11
const val ExecuteCodeCompileError = 12
const val ExecuteCodeRuntimeError = 13
const val CaptchaNeeded = 14
const val AccessDenied = 15
const val RequiresRequestsOverHttps = 16
const val ValidationRequired = 17
const val UserBannedOrDeleted = 18
const val ActionProhibited = 20
const val ActionAllowedOnlyForStandalone = 21
const val MethodOff = 23
const val ConfirmationRequired = 24
const val ParameterIsNotSpecified = 100
const val IncorrectAppId = 101
const val OutOfLimits = 103
const val IncorrectUserId = 113
const val IncorrectTimestamp = 150
const val AccessToAlbumDenied = 200
const val AccessToAudioDenied = 201
const val AccessToGroupDenied = 203
const val AlbumIsFull = 300
const val ActionDenied = 500
const val PermissionDenied = 600
const val CannotSendMessageBlackList = 900
const val CannotSendMessageGroup = 901
const val InvalidDocId = 1150
const val InvalidDocTitle = 1152
const val AccessToDocDenied = 1153
const val AccessTokenExpired = 1117
}
object VkOAuthErrors {
const val UNKNOWN = "unknown_error"
const val NEED_VALIDATION = "need_validation"
const val NEED_CAPTCHA = "need_captcha"
const val INVALID_CLIENT = "invalid_client"
const val INVALID_REQUEST = "invalid_request"
}
object VkErrorTypes {
const val WRONG_OTP_FORMAT = "otp_format_is_incorrect"
const val WRONG_OTP = "wrong_otp"
}
@@ -0,0 +1,94 @@
package com.meloda.app.fast.network.di
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.meloda.app.fast.common.AppConstants
import com.meloda.app.fast.common.AuthInterceptor
import com.meloda.app.fast.network.JsonConverter
import com.meloda.app.fast.network.MoshiConverter
import com.meloda.app.fast.network.OAuthResultCallFactory
import com.meloda.app.fast.network.ResponseConverterFactory
import com.meloda.app.fast.network.service.account.AccountService
import com.meloda.app.fast.network.service.audios.AudiosService
import com.meloda.app.fast.network.service.auth.AuthService
import com.meloda.app.fast.network.service.conversations.ConversationsService
import com.meloda.app.fast.network.service.files.FilesService
import com.meloda.app.fast.network.service.friends.FriendsService
import com.meloda.app.fast.network.service.longpoll.LongPollService
import com.meloda.app.fast.network.service.messages.MessagesService
import com.meloda.app.fast.network.service.oauth.OAuthService
import com.meloda.app.fast.network.service.photos.PhotosService
import com.meloda.app.fast.network.service.users.UsersService
import com.meloda.app.fast.network.service.videos.VideosService
import com.slack.eithernet.ApiResultCallAdapterFactory
import com.slack.eithernet.ApiResultConverterFactory
import com.squareup.moshi.Moshi
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.core.module.dsl.singleOf
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
import org.koin.dsl.bind
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit
val networkModule = module {
single { Moshi.Builder().build() }
singleOf(::MoshiConverter) bind JsonConverter::class
single { ChuckerCollector(get()) }
single { ChuckerInterceptor.Builder(get()).collector(get()).build() }
singleOf(::AuthInterceptor)
single {
OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(get<AuthInterceptor>())
.addInterceptor(get<ChuckerInterceptor>())
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()
}
single {
Retrofit.Builder()
.baseUrl("${AppConstants.URL_API}/")
.addConverterFactory(ApiResultConverterFactory)
.addCallAdapterFactory(ApiResultCallAdapterFactory)
.addConverterFactory(ResponseConverterFactory(get<JsonConverter>()))
.addConverterFactory(MoshiConverterFactory.create(get()))
.client(get())
.build()
}
singleOf(::OAuthResultCallFactory)
single<Retrofit>(named("oauth")) {
Retrofit.Builder()
.baseUrl("${AppConstants.URL_OAUTH}/")
.addCallAdapterFactory(get<OAuthResultCallFactory>())
.addConverterFactory(MoshiConverterFactory.create(get()))
.client(get())
.build()
}
single { service(AccountService::class.java) }
single { service(AudiosService::class.java) }
single { service(AuthService::class.java) }
single { service(ConversationsService::class.java) }
single { service(FilesService::class.java) }
single { service(LongPollService::class.java) }
single { service(MessagesService::class.java) }
single { service(OAuthService::class.java) }
// single { get<Retrofit>(named("oauth")).create(OAuthService::class.java) }
single { service(PhotosService::class.java) }
single { service(UsersService::class.java) }
single { service(VideosService::class.java) }
single { service(FriendsService::class.java) }
}
private fun <T> Scope.service(className: Class<T>): T = get<Retrofit>().create(className)
@@ -0,0 +1,21 @@
package com.meloda.app.fast.network.service.account
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.QueryMap
interface AccountService {
@GET(AccountUrls.SET_ONLINE)
suspend fun setOnline(
@QueryMap params: Map<String, String>
): ApiResult<ApiResponse<Any>, RestApiError>
@POST(AccountUrls.SET_OFFLINE)
suspend fun setOffline(
@QueryMap params: Map<String, String>
): ApiResult<ApiResponse<Any>, RestApiError>
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.network.service.account
import com.meloda.app.fast.common.AppConstants
object AccountUrls {
const val SET_ONLINE = "${AppConstants.URL_API}/account.setOnline"
const val SET_OFFLINE = "${AppConstants.URL_API}/account.setOffline"
}
@@ -0,0 +1,32 @@
package com.meloda.app.fast.network.service.audios
import com.meloda.app.fast.model.api.data.VkAudioData
import com.meloda.app.fast.model.api.responses.AudiosGetUploadServerResponse
import com.meloda.app.fast.model.api.responses.AudiosUploadResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import okhttp3.MultipartBody
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Url
interface AudiosService {
@POST(AudiosUrls.GET_UPLOAD_SERVER)
suspend fun getUploadServer(): ApiResult<ApiResponse<AudiosGetUploadServerResponse>, RestApiError>
@Multipart
@POST
suspend fun upload(
@Url url: String,
@Part file: MultipartBody.Part
): ApiResult<AudiosUploadResponse, RestApiError>
@FormUrlEncoded
@POST(AudiosUrls.SAVE)
suspend fun save(@FieldMap map: Map<String, String>): ApiResult<ApiResponse<VkAudioData>, RestApiError>
}
@@ -0,0 +1,10 @@
package com.meloda.app.fast.network.service.audios
import com.meloda.app.fast.common.AppConstants
object AudiosUrls {
const val GET_UPLOAD_SERVER = "${AppConstants.URL_API}/audio.getUploadServer"
const val SAVE = "${AppConstants.URL_API}/audio.save"
}
@@ -0,0 +1,14 @@
package com.meloda.app.fast.network.service.auth
import com.meloda.app.fast.model.api.responses.SendSmsResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.GET
import retrofit2.http.Query
interface AuthService {
@GET(AuthUrls.SEND_SMS)
suspend fun sendSms(@Query("sid") validationSid: String): ApiResult<ApiResponse<SendSmsResponse>, RestApiError>
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.network.service.auth
import com.meloda.app.fast.common.AppConstants
object AuthUrls {
const val SEND_SMS = "${AppConstants.URL_API}/auth.validatePhone"
}
@@ -0,0 +1,37 @@
package com.meloda.app.fast.network.service.conversations
import com.meloda.app.fast.model.api.responses.ConversationsDeleteResponse
import com.meloda.app.fast.model.api.responses.ConversationsGetResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface ConversationsService {
@FormUrlEncoded
@POST(ConversationsUrls.GET)
suspend fun getConversations(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<ConversationsGetResponse>, RestApiError>
@FormUrlEncoded
@POST(ConversationsUrls.DELETE)
suspend fun delete(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<ConversationsDeleteResponse>, RestApiError>
@FormUrlEncoded
@POST(ConversationsUrls.PIN)
suspend fun pin(@FieldMap params: Map<String, String>): ApiResult<ApiResponse<Int>, RestApiError>
@FormUrlEncoded
@POST(ConversationsUrls.UNPIN)
suspend fun unpin(@FieldMap params: Map<String, String>): ApiResult<ApiResponse<Int>, RestApiError>
@FormUrlEncoded
@POST(ConversationsUrls.REORDER_PINNED)
suspend fun reorderPinned(@FieldMap params: Map<String, String>): ApiResult<Unit, RestApiError>
}
@@ -0,0 +1,12 @@
package com.meloda.app.fast.network.service.conversations
import com.meloda.app.fast.common.AppConstants
object ConversationsUrls {
const val GET = "${AppConstants.URL_API}/messages.getConversations"
const val DELETE = "${AppConstants.URL_API}/messages.deleteConversation"
const val PIN = "${AppConstants.URL_API}/messages.pinConversation"
const val UNPIN = "${AppConstants.URL_API}/messages.unpinConversation"
const val REORDER_PINNED = "${AppConstants.URL_API}/messages.reorderPinnedConversations"
}
@@ -0,0 +1,38 @@
package com.meloda.app.fast.network.service.files
import com.meloda.app.fast.model.api.responses.FilesGetMessagesUploadServerResponse
import com.meloda.app.fast.model.api.responses.FilesSaveFileResponse
import com.meloda.app.fast.model.api.responses.FilesUploadFileResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import okhttp3.MultipartBody
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Url
interface FilesService {
@FormUrlEncoded
@POST(FilesUrls.GET_MESSAGES_UPLOAD_SERVER)
suspend fun getUploadServer(
@FieldMap map: Map<String, String>
): ApiResult<ApiResponse<FilesGetMessagesUploadServerResponse>, RestApiError>
@Multipart
@POST
suspend fun upload(
@Url url: String,
@Part file: MultipartBody.Part
): ApiResult<FilesUploadFileResponse, RestApiError>
@FormUrlEncoded
@POST(FilesUrls.SAVE)
suspend fun save(
@FieldMap map: Map<String, String>
): ApiResult<ApiResponse<FilesSaveFileResponse>, RestApiError>
}
@@ -0,0 +1,10 @@
package com.meloda.app.fast.network.service.files
import com.meloda.app.fast.common.AppConstants
object FilesUrls {
const val GET_MESSAGES_UPLOAD_SERVER = "${AppConstants.URL_API}/docs.getMessagesUploadServer"
const val SAVE = "${AppConstants.URL_API}/docs.save"
}
@@ -0,0 +1,24 @@
package com.meloda.app.fast.network.service.friends
import com.meloda.app.fast.model.api.responses.GetFriendsResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface FriendsService {
@FormUrlEncoded
@POST(FriendsUrls.GET)
suspend fun getFriends(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<GetFriendsResponse>, RestApiError>
@FormUrlEncoded
@POST(FriendsUrls.GET_ONLINE)
suspend fun getOnlineFriends(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<List<Int>>, RestApiError>
}
@@ -0,0 +1,9 @@
package com.meloda.app.fast.network.service.friends
import com.meloda.app.fast.common.AppConstants
object FriendsUrls {
const val GET = "${AppConstants.URL_API}/friends.get"
const val GET_ONLINE = "${AppConstants.URL_API}/friends.getOnline"
}
@@ -0,0 +1,17 @@
package com.meloda.app.fast.network.service.longpoll
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.GET
import retrofit2.http.QueryMap
import retrofit2.http.Url
interface LongPollService {
@GET
suspend fun getResponse(
@Url serverUrl: String,
@QueryMap params: Map<String, String>
): ApiResult<LongPollUpdates, RestApiError>
}
@@ -0,0 +1,93 @@
package com.meloda.app.fast.network.service.messages
import com.meloda.app.fast.model.api.data.VkLongPollData
import com.meloda.app.fast.model.api.responses.MessagesGetByIdResponse
import com.meloda.app.fast.model.api.responses.MessagesGetHistoryResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface MessagesService {
@FormUrlEncoded
@POST(MessagesUrls.GET_HISTORY)
suspend fun getHistory(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<MessagesGetHistoryResponse>, RestApiError>
@FormUrlEncoded
@POST(MessagesUrls.GET_BY_ID)
suspend fun getById(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<MessagesGetByIdResponse>, RestApiError>
@FormUrlEncoded
@POST(MessagesUrls.SEND)
suspend fun send(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<Int>, RestApiError>
@FormUrlEncoded
@POST(MessagesUrls.GET_LONG_POLL_SERVER)
suspend fun getLongPollServer(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<VkLongPollData>, RestApiError>
@FormUrlEncoded
@POST(MessagesUrls.MARK_AS_READ)
suspend fun markAsRead(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<Int>, RestApiError>
// @FormUrlEncoded
// @POST(MessagesUrls.MarkAsImportant)
// suspend fun markAsImportant(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<List<Int>>, RestApiError>
//
// @FormUrlEncoded
// @POST(MessagesUrls.Pin)
// suspend fun pin(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<VkMessageData>, RestApiError>
//
// @FormUrlEncoded
// @POST(MessagesUrls.Unpin)
// suspend fun unpin(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<Unit>, RestApiError>
//
// @FormUrlEncoded
// @POST(MessagesUrls.Delete)
// suspend fun delete(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<Unit>, RestApiError>
//
// @FormUrlEncoded
// @POST(MessagesUrls.Edit)
// suspend fun edit(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<Int>, RestApiError>
//
//
// @FormUrlEncoded
// @POST(MessagesUrls.GetChat)
// suspend fun getChat(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<VkChatData>, RestApiError>
//
// @FormUrlEncoded
// @POST(MessagesUrls.GetConversationMembers)
// suspend fun getConversationMembers(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<MessagesGetConversationMembersResponse>, RestApiError>
//
// @FormUrlEncoded
// @POST(MessagesUrls.RemoveChatUser)
// suspend fun removeChatUser(
// @FieldMap params: Map<String, String>
// ): ApiResult<ApiResponse<Int>, RestApiError>
}
@@ -0,0 +1,21 @@
package com.meloda.app.fast.network.service.messages
import com.meloda.app.fast.common.AppConstants
object MessagesUrls {
const val GET_HISTORY = "${AppConstants.URL_API}/messages.getHistory"
const val SEND = "${AppConstants.URL_API}/messages.send"
const val MARK_AS_IMPORTANT = "${AppConstants.URL_API}/messages.markAsImportant"
const val GET_LONG_POLL_SERVER = "${AppConstants.URL_API}/messages.getLongPollServer"
const val GET_LONG_POLL_HISTORY = "${AppConstants.URL_API}/messages.getLongPollHistory"
const val PIN = "${AppConstants.URL_API}/messages.pin"
const val UNPIN = "${AppConstants.URL_API}/messages.unpin"
const val DELETE = "${AppConstants.URL_API}/messages.delete"
const val EDIT = "${AppConstants.URL_API}/messages.edit"
const val GET_BY_ID = "${AppConstants.URL_API}/messages.getById"
const val MARK_AS_READ = "${AppConstants.URL_API}/messages.markAsRead"
const val GET_CHAT = "${AppConstants.URL_API}/messages.getChat"
const val GET_CONVERSATIONS_MEMBERS = "${AppConstants.URL_API}/messages.getConversationMembers"
const val REMOVE_CHAT_USER = "${AppConstants.URL_API}/messages.removeChatUser"
}
@@ -0,0 +1,16 @@
package com.meloda.app.fast.network.service.oauth
import com.meloda.app.fast.model.api.responses.AuthDirectResponse
import com.slack.eithernet.ApiResult
import com.slack.eithernet.DecodeErrorBody
import retrofit2.http.GET
import retrofit2.http.QueryMap
interface OAuthService {
@DecodeErrorBody
@GET(OAuthUrls.DIRECT_AUTH)
suspend fun auth(
@QueryMap param: Map<String, String?>
): ApiResult<AuthDirectResponse, AuthDirectResponse>
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.network.service.oauth
import com.meloda.app.fast.common.AppConstants
object OAuthUrls {
const val DIRECT_AUTH = "${AppConstants.URL_OAUTH}/token"
}
@@ -0,0 +1,10 @@
package com.meloda.app.fast.network.service.photos
import com.meloda.app.fast.common.AppConstants
object PhotoUrls {
const val GET_MESSAGES_UPLOAD_SERVER = "${AppConstants.URL_API}/photos.getMessagesUploadServer"
const val SAVE_MESSAGE_PHOTO = "${AppConstants.URL_API}/photos.saveMessagesPhoto"
}
@@ -0,0 +1,37 @@
package com.meloda.app.fast.network.service.photos
import com.meloda.app.fast.model.api.data.VkPhotoData
import com.meloda.app.fast.model.api.responses.PhotosGetMessagesUploadServerResponse
import com.meloda.app.fast.model.api.responses.PhotosUploadPhotoResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import okhttp3.MultipartBody
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Url
interface PhotosService {
@FormUrlEncoded
@POST(PhotoUrls.GET_MESSAGES_UPLOAD_SERVER)
suspend fun getUploadServer(
@FieldMap map: Map<String, String>
): ApiResult<ApiResponse<PhotosGetMessagesUploadServerResponse>, RestApiError>
@Multipart
@POST
suspend fun upload(
@Url url: String,
@Part photo: MultipartBody.Part
): ApiResult<PhotosUploadPhotoResponse, RestApiError>
@FormUrlEncoded
@POST(PhotoUrls.SAVE_MESSAGE_PHOTO)
suspend fun save(
@FieldMap map: Map<String, String>
): ApiResult<ApiResponse<List<VkPhotoData>>, RestApiError>
}
@@ -0,0 +1,18 @@
package com.meloda.app.fast.network.service.users
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface UsersService {
@FormUrlEncoded
@POST(UsersUrls.GET_BY_ID)
suspend fun getById(
@FieldMap params: Map<String, String>?
): ApiResult<ApiResponse<List<VkUserData>>, RestApiError>
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.network.service.users
import com.meloda.app.fast.common.AppConstants
object UsersUrls {
const val GET_BY_ID = "${AppConstants.URL_API}/users.get"
}
@@ -0,0 +1,25 @@
package com.meloda.app.fast.network.service.videos
import com.meloda.app.fast.model.api.responses.VideosSaveResponse
import com.meloda.app.fast.model.api.responses.VideosUploadResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import okhttp3.MultipartBody
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Url
interface VideosService {
@POST(VideosUrls.SAVE)
suspend fun save(): ApiResult<ApiResponse<VideosSaveResponse>, RestApiError>
@Multipart
@POST
suspend fun upload(
@Url url: String,
@Part file: MultipartBody.Part
): ApiResult<VideosUploadResponse, RestApiError>
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.network.service.videos
import com.meloda.app.fast.common.AppConstants
object VideosUrls {
const val SAVE = "${AppConstants.URL_API}/video.save"
}