domain module

This commit is contained in:
2024-08-11 17:40:13 +03:00
parent 648850f1c8
commit 0500969d81
77 changed files with 282 additions and 267 deletions
+4 -18
View File
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.fast.android.library)
alias(libs.plugins.fast.android.library.compose)
// alias(libs.plugins.fast.jvm.library)
// alias(libs.plugins.fast.koin)
}
android {
@@ -8,23 +9,8 @@ android {
}
dependencies {
implementation(libs.core.ktx)
implementation(libs.preference.ktx)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.bundles.nanokt)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.koin.android)
implementation(libs.koin.androidx.compose)
implementation(libs.koin.androidx.compose.navigation)
implementation(libs.coil.compose)
implementation(libs.nanokt.jvm)
implementation(libs.nanokt.android)
implementation(libs.nanokt)
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlin.serialization)
}
@@ -1,21 +0,0 @@
package dev.meloda.fast.common.di
import coil.ImageLoader
import dev.meloda.fast.common.LongPollController
import dev.meloda.fast.common.LongPollControllerImpl
import dev.meloda.fast.common.provider.ResourceProvider
import dev.meloda.fast.common.provider.ResourceProviderImpl
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
val commonModule = module {
single {
ImageLoader.Builder(get())
.crossfade(true)
.build()
}
singleOf(::LongPollControllerImpl) bind LongPollController::class
singleOf(::ResourceProviderImpl) bind ResourceProvider::class
}
@@ -1,10 +1,5 @@
package dev.meloda.fast.common.extensions
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineScope
@@ -21,18 +16,6 @@ import kotlinx.coroutines.flow.update
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
fun Context.restartApp() {
(this as? Activity)?.let { activity ->
activity.finishAffinity()
activity.startActivity(
Intent(
this,
Class.forName("dev.meloda.fast.MainActivity")
)
)
}
}
inline fun <T> Iterable<T>.findWithIndex(predicate: (T) -> Boolean): Pair<Int, T>? {
val value = firstOrNull(predicate) ?: return null
return indexOf(value).let { index -> if (index == -1) null else index to value }
@@ -115,13 +98,3 @@ fun <T> Any.toList(mapper: (old: Any) -> T): List<T> {
else -> emptyList()
}
}
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
fun isSdkAtLeast(sdkInt: Int, action: (() -> Unit)? = null): Boolean {
return if (Build.VERSION.SDK_INT >= sdkInt) {
action?.invoke()
true
} else {
false
}
}
@@ -4,14 +4,10 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.widget.Toast
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
object AndroidUtils {
@@ -96,61 +92,61 @@ object AndroidUtils {
})
}
fun getInstallPackageIntent(
context: Context,
providerPath: String,
fileToRead: File,
): Intent {
val intent = Intent(Intent.ACTION_VIEW)
// fun getInstallPackageIntent(
// context: Context,
// providerPath: String,
// fileToRead: File,
// ): Intent {
// val intent = Intent(Intent.ACTION_VIEW)
//
// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
// intent.data = FileProvider.getUriForFile(
// context,
// "dev.meloda.fast$providerPath",
// fileToRead
// )
//
// return intent
// }
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
intent.data = FileProvider.getUriForFile(
context,
"dev.meloda.fast$providerPath",
fileToRead
)
// fun getImageToShare(context: Context, existingFile: File): Uri? {
// val imageFolder = File(context.cacheDir, "images")
//
// return try {
// imageFolder.mkdirs()
//
// val copyToFile = File(imageFolder, "shared_image.png")
// if (copyToFile.exists()) {
// copyToFile.delete()
// }
//
// val file = existingFile.copyTo(copyToFile)
// FileProvider.getUriForFile(context, "dev.meloda.fast.provider", file)
// } catch (e: Exception) {
// e.printStackTrace()
// null
// }
// }
return intent
}
fun getImageToShare(context: Context, existingFile: File): Uri? {
val imageFolder = File(context.cacheDir, "images")
return try {
imageFolder.mkdirs()
val copyToFile = File(imageFolder, "shared_image.png")
if (copyToFile.exists()) {
copyToFile.delete()
}
val file = existingFile.copyTo(copyToFile)
FileProvider.getUriForFile(context, "dev.meloda.fast.provider", file)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun getImageToShare(context: Context, bitmap: Bitmap): Uri? {
val imageFolder = File(context.cacheDir, "images")
return try {
imageFolder.mkdirs()
val file = File(imageFolder, "shared_image.png")
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, outputStream)
outputStream.flush()
outputStream.close()
FileProvider.getUriForFile(context, "dev.meloda.fast.fileprovider", file)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
// fun getImageToShare(context: Context, bitmap: Bitmap): Uri? {
// val imageFolder = File(context.cacheDir, "images")
//
// return try {
// imageFolder.mkdirs()
//
// val file = File(imageFolder, "shared_image.png")
// val outputStream = FileOutputStream(file)
// bitmap.compress(Bitmap.CompressFormat.PNG, 90, outputStream)
// outputStream.flush()
// outputStream.close()
// FileProvider.getUriForFile(context, "dev.meloda.fast.fileprovider", file)
// } catch (e: Exception) {
// e.printStackTrace()
// null
// }
// }
fun showShareSheet(context: Context, content: ShareContent) {
val intent = Intent(Intent.ACTION_SEND).apply {
+3 -3
View File
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.fast.android.library)
// alias(libs.plugins.fast.koin)
}
android {
@@ -13,9 +14,8 @@ dependencies {
api(projects.core.network)
api(projects.core.database)
implementation(libs.koin.android)
// TODO: 05/05/2024, Danil Nikolaev: research, maybe remove
// TODO: 11/08/2024, Danil Nikolaev: remove?
implementation(libs.retrofit)
implementation(libs.eithernet)
implementation(libs.koin.android)
}
@@ -1,7 +1,6 @@
package dev.meloda.fast.network.interceptor
package dev.meloda.fast.data
import androidx.core.net.toUri
import dev.meloda.fast.common.UserConfig
import okhttp3.Interceptor
import okhttp3.Response
import java.net.URLEncoder
@@ -1,24 +1,16 @@
package dev.meloda.fast.common
package dev.meloda.fast.data
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.Delegates
import dev.meloda.fast.datastore.AppSettings
object UserConfig {
private const val ARG_CURRENT_USER_ID = "current_user_id"
private var preferences: SharedPreferences by Delegates.notNull()
fun init(preferences: SharedPreferences) {
this.preferences = preferences
}
var currentUserId: Int = -1
get() = preferences.getInt(ARG_CURRENT_USER_ID, -1)
get() = AppSettings.getInt(ARG_CURRENT_USER_ID, -1)
set(value) {
field = value
preferences.edit { putInt(ARG_CURRENT_USER_ID, value) }
AppSettings.edit { putInt(ARG_CURRENT_USER_ID, value) }
}
var userId: Int = -1
@@ -1,10 +1,8 @@
package dev.meloda.fast.data.di
import dev.meloda.fast.common.di.commonModule
import dev.meloda.fast.data.AccessTokenInterceptor
import dev.meloda.fast.data.api.account.AccountRepository
import dev.meloda.fast.data.api.account.AccountRepositoryImpl
import dev.meloda.fast.data.api.account.AccountUseCase
import dev.meloda.fast.data.api.account.AccountUseCaseImpl
import dev.meloda.fast.data.api.audios.AudiosRepository
import dev.meloda.fast.data.api.auth.AuthRepository
import dev.meloda.fast.data.api.auth.AuthRepositoryImpl
@@ -22,29 +20,26 @@ import dev.meloda.fast.data.api.oauth.OAuthRepositoryImpl
import dev.meloda.fast.data.api.photos.PhotosRepository
import dev.meloda.fast.data.api.users.UsersRepository
import dev.meloda.fast.data.api.users.UsersRepositoryImpl
import dev.meloda.fast.data.api.users.UsersUseCase
import dev.meloda.fast.data.api.users.UsersUseCaseImpl
import dev.meloda.fast.data.api.videos.VideosRepository
import dev.meloda.fast.data.db.AccountsRepository
import dev.meloda.fast.data.db.AccountsRepositoryImpl
import dev.meloda.fast.data.db.GetCurrentAccountUseCase
import dev.meloda.fast.database.di.databaseModule
import dev.meloda.fast.datastore.di.dataStoreModule
import dev.meloda.fast.network.di.networkModule
import okhttp3.Interceptor
import org.koin.core.module.dsl.singleOf
import org.koin.core.qualifier.named
import org.koin.dsl.bind
import org.koin.dsl.module
val dataModule = module {
includes(
commonModule,
databaseModule,
dataStoreModule,
networkModule,
)
singleOf(::AccountRepositoryImpl) bind AccountRepository::class
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
singleOf(::AudiosRepository)
@@ -63,12 +58,15 @@ val dataModule = module {
singleOf(::PhotosRepository)
singleOf(::UsersRepositoryImpl) bind UsersRepository::class
singleOf(::UsersUseCaseImpl) bind UsersUseCase::class
singleOf(::VideosRepository)
singleOf(::AccountsRepositoryImpl) bind AccountsRepository::class
singleOf(::GetCurrentAccountUseCase)
singleOf(::FriendsRepositoryImpl) bind FriendsRepository::class
// TODO: 11/08/2024, Danil Nikolaev: find a better solution
single<Interceptor>(named("token_interceptor")) {
AccessTokenInterceptor()
}
}
+1
View File
@@ -0,0 +1 @@
/build
+18
View File
@@ -0,0 +1,18 @@
plugins {
alias(libs.plugins.fast.android.library)
// alias(libs.plugins.fast.android.koin)
}
android {
namespace = "dev.meloda.fast.domain"
}
dependencies {
api(projects.core.data)
api(projects.core.model)
// TODO: 11/08/2024, Danil Nikolaev: remove?
implementation(libs.kotlinx.coroutines.core)
implementation(libs.koin.core)
implementation(libs.eithernet)
}
@@ -1,4 +1,4 @@
package dev.meloda.fast.data.api.account
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,7 @@
package dev.meloda.fast.data.api.account
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.account.AccountRepository
import dev.meloda.fast.data.mapToState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@@ -0,0 +1,12 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.model.api.responses.ValidatePhoneResponse
import kotlinx.coroutines.flow.Flow
interface AuthUseCase {
fun validatePhone(
validationSid: String
): Flow<State<ValidatePhoneResponse>>
}
@@ -0,0 +1,18 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.auth.AuthRepository
import dev.meloda.fast.data.mapToState
import dev.meloda.fast.model.api.responses.ValidatePhoneResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class AuthUseCaseImpl(private val repository: AuthRepository) : AuthUseCase {
override fun validatePhone(validationSid: String): Flow<State<ValidatePhoneResponse>> = flow {
emit(State.Loading)
val newState = repository.validatePhone(validationSid).mapToState()
emit(newState)
}
}
@@ -1,4 +1,4 @@
package dev.meloda.fast.data.api.conversations
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.model.api.domain.VkConversation
@@ -0,0 +1,118 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.conversations.ConversationsRepository
import dev.meloda.fast.data.mapToState
import dev.meloda.fast.model.api.domain.VkConversation
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
class ConversationsUseCaseImpl(
private val repository: ConversationsRepository,
) : ConversationsUseCase {
// override fun getConversations(
// count: Int?,
// offset: Int?,
// fields: String,
// filter: String,
// extended: Boolean?,
// startMessageId: Int?
// ): Flow<dev.meloda.fast.network.State<ConversationsResponseDomain>> = flow {
// emit(dev.meloda.fast.network.State.Loading)
//
// val newState = conversationsRepository.getConversations(
// params = ConversationsGetRequest(
// count = count,
// offset = offset,
// fields = fields,
// filter = filter,
// extended = extended,
// startMessageId = startMessageId
// )
// ).fold(
// onSuccess = { response -> dev.meloda.fast.network.State.Success(response.toDomain()) },
// onNetworkFailure = { dev.meloda.fast.network.State.Error.ConnectionError },
// onUnknownFailure = { dev.meloda.fast.network.State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
// }
//
//
// override fun pin(peerId: Int): Flow<dev.meloda.fast.network.State<Unit>> = flow {
// emit(dev.meloda.fast.network.State.Loading)
//
// val newState = conversationsRepository.pin(
// ConversationsPinRequest(peerId = peerId)
// ).fold(
// onSuccess = { dev.meloda.fast.network.State.Success(Unit) },
// onNetworkFailure = { dev.meloda.fast.network.State.Error.ConnectionError },
// onUnknownFailure = { dev.meloda.fast.network.State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
// }
//
// override fun unpin(peerId: Int): Flow<dev.meloda.fast.network.State<Unit>> = flow {
// emit(dev.meloda.fast.network.State.Loading)
//
// val newState = conversationsRepository.unpin(
// ConversationsUnpinRequest(peerId = peerId)
// ).fold(
// onSuccess = { dev.meloda.fast.network.State.Success(Unit) },
// onNetworkFailure = { dev.meloda.fast.network.State.Error.ConnectionError },
// onUnknownFailure = { dev.meloda.fast.network.State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
// }
//
// override suspend fun storeConversations(conversations: List<VkConversationDomain>) {
// conversationsDao.insertAll(conversations.map(VkConversationDomain::mapToDb))
// }
//
// override suspend fun storeGroups(groups: List<VkGroupDomain>) {
// groupsDao.insertAll(groups.map(VkGroupDomain::mapToDB))
// }
override fun getConversations(
count: Int?,
offset: Int?
): Flow<State<List<VkConversation>>> = flow {
emit(State.Loading)
val newState = repository.getConversations(count, offset).mapToState()
emit(newState)
}
override suspend fun storeConversations(
conversations: List<VkConversation>
) = withContext(Dispatchers.IO) {
repository.storeConversations(conversations)
}
override fun delete(peerId: Int): Flow<State<Int>> = flow {
emit(State.Loading)
val newState = repository.delete(peerId = peerId).mapToState()
emit(newState)
}
override fun changePinState(peerId: Int, pin: Boolean): Flow<State<Int>> = flow {
emit(State.Loading)
val newState = if (pin) {
repository.pin(peerId)
} else {
repository.unpin(peerId)
}.mapToState()
emit(newState)
}
}
@@ -1,4 +1,4 @@
package dev.meloda.fast.data.api.friends
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.model.FriendsInfo
@@ -0,0 +1,42 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.friends.FriendsRepository
import dev.meloda.fast.data.mapToState
import dev.meloda.fast.model.FriendsInfo
import dev.meloda.fast.model.api.domain.VkUser
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class FriendsUseCaseImpl(private val repository: FriendsRepository) :
FriendsUseCase {
override fun getAllFriends(count: Int?, offset: Int?): Flow<State<FriendsInfo>> = flow {
emit(State.Loading)
val newState = repository.getAllFriends(count, offset).mapToState()
emit(newState)
}
override fun getFriends(
count: Int?, offset: Int?
): Flow<State<List<VkUser>>> = flow {
emit(State.Loading)
val newState = repository.getFriends(count, offset).mapToState()
emit(newState)
}
override fun getOnlineFriends(
count: Int?, offset: Int?
): Flow<State<List<Int>>> = flow {
emit(State.Loading)
val newState = repository.getOnlineFriends(count, offset).mapToState()
emit(newState)
}
override suspend fun storeUsers(users: List<VkUser>) {
repository.storeUsers(users)
}
}
@@ -1,11 +1,14 @@
package dev.meloda.fast.data.db
package dev.meloda.fast.domain
import dev.meloda.fast.common.UserConfig
import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.db.AccountsRepository
import dev.meloda.fast.model.database.AccountEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class GetCurrentAccountUseCase(private val accountsRepository: AccountsRepository) {
class GetCurrentAccountUseCase(
private val accountsRepository: AccountsRepository
) {
suspend operator fun invoke(): AccountEntity? = withContext(Dispatchers.IO) {
accountsRepository.getAccountById(UserConfig.currentUserId)
@@ -1,12 +1,13 @@
package dev.meloda.fast.data
package dev.meloda.fast.domain
import android.util.Log
import dev.meloda.fast.common.UserConfig
import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.asInt
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.toList
import dev.meloda.fast.data.api.messages.MessagesUseCase
import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.data.processState
import dev.meloda.fast.model.ApiEvent
import dev.meloda.fast.model.InteractionType
import dev.meloda.fast.model.LongPollEvent
@@ -1,5 +1,6 @@
package dev.meloda.fast.data
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.model.api.data.LongPollUpdates
import dev.meloda.fast.model.api.data.VkLongPollData
import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,8 @@
package dev.meloda.fast.data
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.longpoll.LongPollRepository
import dev.meloda.fast.data.mapToState
import dev.meloda.fast.model.api.data.LongPollUpdates
import dev.meloda.fast.model.api.data.VkLongPollData
import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,7 @@
package dev.meloda.fast.data.api.messages
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.messages.MessagesHistoryInfo
import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
import dev.meloda.fast.model.api.domain.VkMessage
@@ -0,0 +1,110 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.messages.MessagesHistoryInfo
import dev.meloda.fast.data.api.messages.MessagesRepository
import dev.meloda.fast.data.mapToState
import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
import dev.meloda.fast.model.api.domain.VkMessage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class MessagesUseCaseImpl(
private val repository: MessagesRepository
) : MessagesUseCase {
override fun getMessagesHistory(
conversationId: Int,
count: Int?,
offset: Int?
): Flow<State<MessagesHistoryInfo>> = flow {
emit(State.Loading)
val newState = repository.getHistory(
conversationId = conversationId,
offset = offset,
count = count
).mapToState()
emit(newState)
}
override fun getById(
messageIds: List<Int>,
extended: Boolean?,
fields: String?
): Flow<State<List<VkMessage>>> = flow {
emit(State.Loading)
val newState = repository.getById(
messagesIds = messageIds,
extended = extended,
fields = fields
).mapToState()
emit(newState)
}
override fun sendMessage(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): Flow<State<Int>> = flow {
emit(State.Loading)
val newState = repository.send(
peerId = peerId,
randomId = randomId,
message = message,
replyTo = replyTo,
attachments = attachments
).mapToState()
emit(newState)
}
override fun markAsRead(
peerId: Int,
startMessageId: Int
): Flow<State<Int>> = flow {
emit(State.Loading)
val newState = repository.markAsRead(
peerId = peerId,
startMessageId = startMessageId
).mapToState()
emit(newState)
}
override fun getHistoryAttachments(
peerId: Int,
count: Int?,
offset: Int?,
attachmentTypes: List<String>,
conversationMessageId: Int
): Flow<State<List<VkAttachmentHistoryMessage>>> = flow {
emit(State.Loading)
val newState = repository.getHistoryAttachments(
peerId = peerId,
count = count,
offset = offset,
attachmentTypes = attachmentTypes,
conversationMessageId = conversationMessageId
).mapToState()
emit(newState)
}
override suspend fun storeMessage(message: VkMessage) {
repository.storeMessages(listOf(message))
}
override suspend fun storeMessages(messages: List<VkMessage>) {
repository.storeMessages(messages)
}
}
@@ -0,0 +1,17 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.model.AuthInfo
import kotlinx.coroutines.flow.Flow
interface OAuthUseCase {
fun auth(
login: String,
password: String,
forceSms: Boolean,
validationCode: String?,
captchaSid: String?,
captchaKey: String?
): Flow<State<AuthInfo>>
}
@@ -0,0 +1,124 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.oauth.OAuthRepository
import dev.meloda.fast.model.AuthInfo
import dev.meloda.fast.network.OAuthErrorDomain
import dev.meloda.fast.network.ValidationType
import dev.meloda.fast.network.VkOAuthError
import dev.meloda.fast.network.VkOAuthErrorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class OAuthUseCaseImpl(
private val oAuthRepository: OAuthRepository
) : OAuthUseCase {
override fun auth(
login: String,
password: String,
forceSms: Boolean,
validationCode: String?,
captchaSid: String?,
captchaKey: String?
): Flow<State<AuthInfo>> = flow {
emit(State.Loading)
val response = oAuthRepository.auth(
login = login,
password = password,
validationCode = validationCode,
captchaSid = captchaSid,
captchaKey = captchaKey,
forceSms = forceSms
)
val error = response.error?.let(VkOAuthError::parse)
val errorType = response.errorType?.let(VkOAuthErrorType::parse)
val newState = when (error) {
null -> {
State.Success(
AuthInfo(
userId = response.userId,
accessToken = response.accessToken,
validationHash = response.validationHash
)
)
}
VkOAuthError.FLOOD_CONTROL -> {
State.Error.OAuthError(OAuthErrorDomain.TooManyTriesError)
}
VkOAuthError.NEED_VALIDATION -> {
if (response.banInfo != null) {
val info = requireNotNull(response.banInfo)
State.Error.OAuthError(
OAuthErrorDomain.UserBannedError(
memberName = info.memberName,
message = info.message,
accessToken = info.accessToken,
restoreUrl = info.restoreUrl
)
)
} else {
State.Error.OAuthError(
OAuthErrorDomain.ValidationRequiredError(
description = response.errorDescription.orEmpty(),
validationType = response.validationType.orEmpty()
.let(ValidationType::parse),
validationSid = response.validationSid.orEmpty(),
phoneMask = response.phoneMask.orEmpty(),
redirectUri = response.redirectUri.orEmpty(),
validationResend = response.validationResend,
restoreIfCannotGetCode = response.restoreIfCannotGetCode
)
)
}
}
VkOAuthError.NEED_CAPTCHA -> {
State.Error.OAuthError(
OAuthErrorDomain.CaptchaRequiredError(
captchaSid = response.captchaSid.orEmpty(),
captchaImageUrl = response.captchaImage.orEmpty()
)
)
}
VkOAuthError.INVALID_CLIENT -> {
State.Error.OAuthError(OAuthErrorDomain.InvalidCredentialsError)
}
VkOAuthError.INVALID_REQUEST -> {
when (errorType) {
null -> State.Error.OAuthError(OAuthErrorDomain.UnknownError)
VkOAuthErrorType.WRONG_OTP -> {
State.Error.OAuthError(OAuthErrorDomain.WrongValidationCode)
}
VkOAuthErrorType.WRONG_OTP_FORMAT -> {
State.Error.OAuthError(OAuthErrorDomain.WrongValidationCodeFormat)
}
VkOAuthErrorType.PASSWORD_BRUTEFORCE_ATTEMPT -> {
State.Error.OAuthError(OAuthErrorDomain.TooManyTriesError)
}
VkOAuthErrorType.USERNAME_OR_PASSWORD_IS_INCORRECT -> {
State.Error.OAuthError(OAuthErrorDomain.InvalidCredentialsError)
}
}
}
VkOAuthError.UNKNOWN -> {
State.Error.OAuthError(OAuthErrorDomain.UnknownError)
}
}
emit(newState)
}
}
@@ -1,4 +1,4 @@
package dev.meloda.fast.data.api.users
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.model.api.domain.VkUser
@@ -1,6 +1,7 @@
package dev.meloda.fast.data.api.users
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.users.UsersRepository
import dev.meloda.fast.data.mapToState
import dev.meloda.fast.model.api.domain.VkUser
import kotlinx.coroutines.flow.Flow
@@ -0,0 +1,19 @@
package dev.meloda.fast.domain.di
import dev.meloda.fast.data.di.dataModule
import dev.meloda.fast.domain.AccountUseCase
import dev.meloda.fast.domain.AccountUseCaseImpl
import dev.meloda.fast.domain.GetCurrentAccountUseCase
import dev.meloda.fast.domain.UsersUseCase
import dev.meloda.fast.domain.UsersUseCaseImpl
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
val domainModule = module {
includes(dataModule)
singleOf(::UsersUseCaseImpl) bind UsersUseCase::class
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
singleOf(::GetCurrentAccountUseCase)
}
@@ -0,0 +1,7 @@
package dev.meloda.fast.model
data class AuthInfo(
val userId: Int?,
val accessToken: String?,
val validationHash: String?
)
@@ -2,12 +2,14 @@ package dev.meloda.fast.network.di
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.slack.eithernet.ApiResultCallAdapterFactory
import com.slack.eithernet.ApiResultConverterFactory
import com.squareup.moshi.Moshi
import dev.meloda.fast.common.AppConstants
import dev.meloda.fast.network.JsonConverter
import dev.meloda.fast.network.MoshiConverter
import dev.meloda.fast.network.OAuthResultCallFactory
import dev.meloda.fast.network.ResponseConverterFactory
import dev.meloda.fast.network.interceptor.AccessTokenInterceptor
import dev.meloda.fast.network.interceptor.LanguageInterceptor
import dev.meloda.fast.network.interceptor.VersionInterceptor
import dev.meloda.fast.network.service.account.AccountService
@@ -22,9 +24,7 @@ import dev.meloda.fast.network.service.oauth.OAuthService
import dev.meloda.fast.network.service.photos.PhotosService
import dev.meloda.fast.network.service.users.UsersService
import dev.meloda.fast.network.service.videos.VideosService
import com.slack.eithernet.ApiResultCallAdapterFactory
import com.slack.eithernet.ApiResultConverterFactory
import com.squareup.moshi.Moshi
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.core.module.dsl.singleOf
@@ -41,14 +41,13 @@ val networkModule = module {
singleOf(::MoshiConverter) bind JsonConverter::class
single { ChuckerCollector(get()) }
single { ChuckerInterceptor.Builder(get()).collector(get()).build() }
singleOf(::AccessTokenInterceptor)
singleOf(::VersionInterceptor)
singleOf(::LanguageInterceptor)
single {
OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(get<AccessTokenInterceptor>())
.addInterceptor(get(named("token_interceptor")) as Interceptor)
.addInterceptor(get<VersionInterceptor>())
.addInterceptor(get<LanguageInterceptor>())
.addInterceptor(get<ChuckerInterceptor>())
+6 -1
View File
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.fast.android.library)
alias(libs.plugins.fast.android.library.compose)
alias(libs.plugins.kotlin.serialization)
}
android {
@@ -8,7 +9,7 @@ android {
}
dependencies {
implementation(projects.core.common)
api(projects.core.common)
api(projects.core.model)
implementation(libs.haze)
@@ -17,5 +18,9 @@ dependencies {
implementation(platform(libs.compose.bom))
implementation(libs.bundles.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlin.serialization)
implementation(libs.koin.androidx.compose.navigation)
debugImplementation(libs.compose.ui.tooling)
}
@@ -1,4 +1,4 @@
package dev.meloda.fast.common.extensions
package dev.meloda.fast.ui.extensions
import android.os.Bundle
import android.os.Parcelable
@@ -1,4 +1,4 @@
package dev.meloda.fast.common.extensions.navigation
package dev.meloda.fast.ui.extensions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember