From 503f8d8489be17d2571f6d11b628e50933e1e777 Mon Sep 17 00:00:00 2001 From: stslex Date: Tue, 29 Aug 2023 21:52:46 +0300 Subject: [PATCH] refactor authentication --- .idea/.gitignore | 3 + .idea/appInsightsSettings.xml | 45 ++++ .idea/codeStyles/Project.xml | 123 +++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/compiler.xml | 6 + .idea/deploymentTargetDropDown.xml | 13 + .idea/gradle.xml | 43 ++++ .idea/inspectionProfiles/Project_Default.xml | 41 +++ .idea/kotlinc.xml | 6 + .idea/migrations.xml | 10 + .idea/misc.xml | 10 + .idea/vcs.xml | 6 + .../controller/AuthControllerImpl.kt | 12 +- .../core/datastore/AppDataStore.kt | 6 +- .../core/datastore/AppDataStoreImpl.kt | 24 +- .../core/datastore/UserCredential.kt | 6 + .../core/network/client/NetworkClient.kt | 4 +- .../core/network/client/NetworkClientImpl.kt | 52 ++-- .../network/clients/auth/AuthNetworkClient.kt | 2 +- .../clients/auth/AuthNetworkClientImpl.kt | 39 ++- .../clients/auth/model/UserAuthMapper.kt | 8 + .../ui/components/buttons/SwipeableButton.kt | 233 ++++++++++++++++++ .../stslex/aproselection/core/ui/ext/UiExt.kt | 7 +- .../auth/data/repository/AuthRepository.kt | 2 +- .../data/repository/AuthRepositoryImpl.kt | 23 +- .../auth/domain/interactor/AuthInteractor.kt | 2 +- .../domain/interactor/AuthInteractorImpl.kt | 12 +- .../feature/auth/ui/AuthViewModel.kt | 40 +-- 28 files changed, 677 insertions(+), 106 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/appInsightsSettings.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/migrations.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 core/datastore/src/main/java/com/stslex/aproselection/core/datastore/UserCredential.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthMapper.kt create mode 100644 core/ui/src/main/java/com/stslex/aproselection/core/ui/components/buttons/SwipeableButton.kt diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..3dc2238 --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,45 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..7643783 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..d016070 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..1105b2c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,43 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..44ca2d9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6e4b8a0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/stslex/aproselection/controller/AuthControllerImpl.kt b/app/src/main/java/com/stslex/aproselection/controller/AuthControllerImpl.kt index bdbdfb8..0665798 100644 --- a/app/src/main/java/com/stslex/aproselection/controller/AuthControllerImpl.kt +++ b/app/src/main/java/com/stslex/aproselection/controller/AuthControllerImpl.kt @@ -1,7 +1,6 @@ package com.stslex.aproselection.controller import com.stslex.aproselection.core.datastore.AppDataStore -import com.stslex.aproselection.core.network.client.NetworkClient import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -12,7 +11,6 @@ import kotlin.coroutines.coroutineContext class AuthControllerImpl( private val dataStore: AppDataStore, - private val networkClient: NetworkClient, ) : AuthController { private val _isAuth = MutableStateFlow(null) @@ -22,15 +20,7 @@ class AuthControllerImpl( override suspend fun init() { dataStore.token .onEach { token -> - if (token.isBlank()) { - networkClient.regenerateToken() - } - } - .launchIn(CoroutineScope(coroutineContext)) - - dataStore.uuid - .onEach { uuid -> - _isAuth.emit(uuid.isNotBlank()) + _isAuth.emit(token.isNotBlank()) } .launchIn(CoroutineScope(coroutineContext)) } diff --git a/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStore.kt b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStore.kt index d0df066..1caa121 100644 --- a/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStore.kt +++ b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStore.kt @@ -4,13 +4,13 @@ import kotlinx.coroutines.flow.StateFlow interface AppDataStore { - val uuid: StateFlow val token: StateFlow - - suspend fun setUuid(uuid: String) + val credential: StateFlow suspend fun setToken(token: String) + suspend fun setCredential(credential: UserCredential) + suspend fun clear() suspend fun init() diff --git a/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStoreImpl.kt b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStoreImpl.kt index b4d4784..a869fef 100644 --- a/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStoreImpl.kt +++ b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStoreImpl.kt @@ -23,31 +23,35 @@ class AppDataStoreImpl( companion object { private const val DATA_STORE_KEY = "app_data_store" - private val UUID_KEY = stringPreferencesKey("UUID_KEY") private val TOKEN_KEY = stringPreferencesKey("TOKEN_KEY") + private val USERNAME_KEY = stringPreferencesKey("USERNAME_KEY") + private val PASSWORD_KEY = stringPreferencesKey("PASSWORD_KEY") private val Context.dataStore: DataStore by preferencesDataStore(DATA_STORE_KEY) } - private val _uuid = MutableStateFlow("") - override val uuid: StateFlow - get() = _uuid.asStateFlow() - private val _token = MutableStateFlow("") override val token: StateFlow get() = _token.asStateFlow() - override suspend fun setUuid(uuid: String) { + private val _credential = MutableStateFlow(UserCredential()) + override val credential: StateFlow + get() = _credential.asStateFlow() + + override suspend fun setToken(token: String) { + _token.emit(token) context.dataStore.updateData { prefs -> prefs.toMutablePreferences().apply { - set(UUID_KEY, uuid) + set(TOKEN_KEY, token) } } } - override suspend fun setToken(token: String) { + override suspend fun setCredential(credential: UserCredential) { + _credential.emit(credential) context.dataStore.updateData { prefs -> prefs.toMutablePreferences().apply { - set(TOKEN_KEY, token) + set(USERNAME_KEY, credential.username) + set(PASSWORD_KEY, credential.password) } } } @@ -58,7 +62,6 @@ class AppDataStoreImpl( Logger.exception(error) } .onEach { prefs -> - _uuid.value = prefs[UUID_KEY].orEmpty() _token.value = prefs[TOKEN_KEY].orEmpty() } .flowOn(Dispatchers.IO) @@ -66,7 +69,6 @@ class AppDataStoreImpl( } override suspend fun clear() { - setUuid("") setToken("") } } \ No newline at end of file diff --git a/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/UserCredential.kt b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/UserCredential.kt new file mode 100644 index 0000000..5744a27 --- /dev/null +++ b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/UserCredential.kt @@ -0,0 +1,6 @@ +package com.stslex.aproselection.core.datastore + +data class UserCredential( + val username: String = "", + val password: String = "" +) diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClient.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClient.kt index 6de8039..83ba85d 100644 --- a/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClient.kt +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClient.kt @@ -1,10 +1,12 @@ package com.stslex.aproselection.core.network.client +import com.stslex.aproselection.core.network.clients.auth.model.UserAuthResponseModel import io.ktor.client.HttpClient +import kotlinx.coroutines.flow.SharedFlow interface NetworkClient { val apiClient: HttpClient - suspend fun regenerateToken(): String + suspend fun auth(): UserAuthResponseModel } \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClientImpl.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClientImpl.kt index d22d67c..cda5568 100644 --- a/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClientImpl.kt +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/client/NetworkClientImpl.kt @@ -2,7 +2,8 @@ package com.stslex.aproselection.core.network.client import com.stslex.aproselection.core.datastore.AppDataStore import com.stslex.aproselection.core.network.BuildConfig -import com.stslex.aproselection.core.network.clients.auth.model.TokenResponse +import com.stslex.aproselection.core.network.clients.auth.model.UserAuthResponseModel +import com.stslex.aproselection.core.network.clients.auth.model.UserAuthSendModel import com.stslex.aproselection.core.network.model.ApiError import com.stslex.aproselection.core.network.model.ApiErrorRespond import com.stslex.aproselection.core.network.model.ApiErrorType @@ -22,13 +23,16 @@ import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.HttpRequest -import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.URLProtocol import io.ktor.http.appendPathSegments import io.ktor.http.contentType import io.ktor.http.encodedPath import io.ktor.serialization.kotlinx.json.json +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json @@ -51,10 +55,6 @@ class NetworkClientImpl( val token = dataStore.token.value BearerTokens(token, token) } - refreshTokens { - val token = regenerateToken() - BearerTokens(token, token) - } } } @@ -80,9 +80,7 @@ class NetworkClientImpl( host = BuildConfig.API_SERVER_HOST encodedPath = BuildConfig.API_VERSION protocol = URLProtocol.HTTP - headers.append("API_KEY", BuildConfig.API_KEY) - headers.append("DEVICE_ID", "test") - headers.append("uuid", dataStore.uuid.value) + headers.append(API_KEY_HEADER, BuildConfig.API_KEY) contentType(ContentType.Application.Json) } } @@ -102,19 +100,35 @@ class NetworkClientImpl( ) when (apiError.type) { - is ApiErrorType.Unauthorized.Token -> regenerateToken() + is ApiErrorType.Unauthorized.Token -> { + dataStore.setToken(auth().token) + } + else -> throw apiError } } - override suspend fun regenerateToken(): String { - val token = apiClient - .get { - url.appendPathSegments("token") - } - .body() - .token - dataStore.setToken(token) - return token + override suspend fun auth(): UserAuthResponseModel = withContext(Dispatchers.IO) { + val credential = dataStore.credential.value + val username = credential.username + val password = credential.password + if (username.isBlank() || password.isBlank()) { + throw ApiError( + message = "need auth", + type = ApiErrorType.Authentication.InvalidPassword + ) + } + val user = UserAuthSendModel( + username = dataStore.credential.value.username, + password = dataStore.credential.value.password + ) + apiClient.post { + url.appendPathSegments("passport/auth") + setBody(user) + }.body() + } + + companion object { + private const val API_KEY_HEADER = "x-api-key" } } \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClient.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClient.kt index 8d8c160..5e30945 100644 --- a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClient.kt +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClient.kt @@ -11,5 +11,5 @@ interface AuthNetworkClient { fun auth(user: UserAuthSendModel): Flow - fun register(user: UserAuthSendModel): Flow + suspend fun register(user: UserAuthSendModel) } \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClientImpl.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClientImpl.kt index 748dd9d..b1b0b82 100644 --- a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClientImpl.kt +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/AuthNetworkClientImpl.kt @@ -1,9 +1,11 @@ package com.stslex.aproselection.core.network.clients.auth +import com.stslex.aproselection.core.datastore.AppDataStore import com.stslex.aproselection.core.network.client.NetworkClient import com.stslex.aproselection.core.network.clients.auth.model.HelloRequestModel import com.stslex.aproselection.core.network.clients.auth.model.UserAuthResponseModel import com.stslex.aproselection.core.network.clients.auth.model.UserAuthSendModel +import com.stslex.aproselection.core.network.clients.auth.model.toStorage import io.ktor.client.call.body import io.ktor.client.request.get import io.ktor.client.request.post @@ -16,7 +18,8 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext class AuthNetworkClientImpl( - private val networkClient: NetworkClient + private val networkClient: NetworkClient, + private val dataStore: AppDataStore ) : AuthNetworkClient { override suspend fun getHello( @@ -33,28 +36,20 @@ class AuthNetworkClientImpl( override fun auth( user: UserAuthSendModel ): Flow = flow { - val result = networkClient - .apiClient - .post { - url.appendPathSegments("passport/auth") - setBody(user) - } - .body() - emit(result) - } - .flowOn(Dispatchers.IO) + dataStore.setCredential(user.toStorage()) + emit(networkClient.auth()) + }.flowOn(Dispatchers.IO) - override fun register( + override suspend fun register( user: UserAuthSendModel - ): Flow = flow { - val result = networkClient - .apiClient - .post { - url.appendPathSegments("passport/register") - setBody(user) - } - .body() - emit(result) + ) { + withContext(Dispatchers.IO) { + networkClient + .apiClient + .post { + url.appendPathSegments("passport/register") + setBody(user) + } + } } - .flowOn(Dispatchers.IO) } \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthMapper.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthMapper.kt new file mode 100644 index 0000000..76d3fa3 --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthMapper.kt @@ -0,0 +1,8 @@ +package com.stslex.aproselection.core.network.clients.auth.model + +import com.stslex.aproselection.core.datastore.UserCredential + +fun UserAuthSendModel.toStorage() = UserCredential( + username = username, + password = password +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/stslex/aproselection/core/ui/components/buttons/SwipeableButton.kt b/core/ui/src/main/java/com/stslex/aproselection/core/ui/components/buttons/SwipeableButton.kt new file mode 100644 index 0000000..0bf5feb --- /dev/null +++ b/core/ui/src/main/java/com/stslex/aproselection/core/ui/components/buttons/SwipeableButton.kt @@ -0,0 +1,233 @@ +package com.stslex.aproselection.core.ui.components.buttons + +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.stslex.aproselection.core.ui.ext.toPx +import com.stslex.aproselection.core.ui.theme.AppTheme +import kotlin.math.PI +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.roundToInt +import kotlin.math.sin +import kotlin.math.sqrt + +@Composable +fun SwipeableButton( + modifier: Modifier = Modifier, +) { + val stepsButtonCount = 10 + val stepsContainerSize = 10 + val containerSizeInit = 100.dp.toPx + val containerSizeStep = 10.dp.toPx + + val stepsCircleSize = 20 + val circleSizeInit = 4.dp.toPx + val circleSizeStep = 1.dp.toPx + + var containerInputSize by remember { + mutableFloatStateOf(0f) + } + + val containerSize by remember { + derivedStateOf { + containerSizeInit + (containerInputSize * stepsContainerSize) * containerSizeStep + } + } + + var circleInputSize by remember { + mutableFloatStateOf(0f) + } + + val circleSize by remember { + derivedStateOf { + circleSizeInit + (circleInputSize * stepsCircleSize) * circleSizeStep + } + } + + var inputCount by remember { + mutableFloatStateOf(0f) + } + + val buttonsCount by remember { + derivedStateOf { + (inputCount * stepsButtonCount).roundToInt() + } + } + + val radDiff = 360f / buttonsCount + + + var dragAmountX by remember { + mutableFloatStateOf(0f) + } + + var dragAmountY by remember { + mutableFloatStateOf(0f) + } + + val dragAmount by remember { + derivedStateOf { + val min = + if ( + (dragAmountX < 0 && dragAmountY > 0) || + (dragAmountY < 0 && dragAmountX > 0) + ) { + -1 + } else { + 1 + } + min * sqrt(abs(dragAmountX * dragAmountY)) + } + } + + + Box( + Modifier + .fillMaxSize() + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + dragAmountX = change.position.x + dragAmountY = change.position.y + } + } + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 32.dp), + ) { + Text( + modifier = Modifier.padding(8.dp), + text = "x = $dragAmountX \ny = $dragAmountY \na = $dragAmount", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground + ) + Text( + modifier = Modifier + .padding(8.dp), + text = "count = $buttonsCount", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground + ) + Slider( + modifier = Modifier.padding(8.dp), + value = inputCount, + onValueChange = { + inputCount = it + }, + steps = stepsButtonCount + ) + Text( + modifier = Modifier + .padding(8.dp), + text = "containerSize = $containerSize Px", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground + ) + Slider( + modifier = Modifier.padding(8.dp), + value = containerInputSize, + onValueChange = { + containerInputSize = it + }, + steps = stepsContainerSize + ) + Text( + modifier = Modifier + .padding(8.dp), + text = "circleSize = $circleSize Px", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground + ) + Slider( + modifier = Modifier.padding(8.dp), + value = circleInputSize, + onValueChange = { + circleInputSize = it + }, + steps = stepsCircleSize + ) + Box( + modifier = Modifier + .weight(1f) + .size(with(LocalDensity.current) { containerSize.toDp() }) + .align(Alignment.CenterHorizontally) + .drawBehind { + val radius = containerSize * .5f + + for (index in 0 until buttonsCount) { + + val radian = (index * radDiff + dragAmount) * (PI.toFloat() / 180f) + + val xRad = sin(radian) + val x = xRad * radius + center.x + + val yRad = cos(radian) + val y = yRad * radius + center.y + + val offset = Offset( + x = x, + y = y + ) + + drawCircle( + color = Color.White, + center = offset, + radius = circleSize + ) + } + + drawCircle( + color = Color.White, + center = center, + radius = radius, + style = Stroke(1.dp.toPx()) + ) + }, + ) + } + + + } + +} + +@Preview +@Composable +fun SwipeableButtonPreview() { + AppTheme(true) { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + ) { + SwipeableButton( + modifier = Modifier.align(Alignment.Center) + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/stslex/aproselection/core/ui/ext/UiExt.kt b/core/ui/src/main/java/com/stslex/aproselection/core/ui/ext/UiExt.kt index d28219d..8041506 100644 --- a/core/ui/src/main/java/com/stslex/aproselection/core/ui/ext/UiExt.kt +++ b/core/ui/src/main/java/com/stslex/aproselection/core/ui/ext/UiExt.kt @@ -23,4 +23,9 @@ fun Modifier.noRippleClick( val Dp.toPx: Float @ReadOnlyComposable @Composable - get() = with(LocalDensity.current) { toPx() } \ No newline at end of file + get() = with(LocalDensity.current) { toPx() } + +val Dp.roundToPx: Int + @ReadOnlyComposable + @Composable + get() = with(LocalDensity.current) { roundToPx() } \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepository.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepository.kt index 69942ef..614fdd5 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepository.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepository.kt @@ -7,5 +7,5 @@ interface AuthRepository { fun auth(username: String, password: String): Flow - fun register(username: String, password: String): Flow + suspend fun register(username: String, password: String) } \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepositoryImpl.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepositoryImpl.kt index abab613..af65a7a 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepositoryImpl.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/repository/AuthRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.stslex.aproselection.feature.auth.data.repository import com.stslex.aproselection.core.datastore.AppDataStore +import com.stslex.aproselection.core.datastore.UserCredential import com.stslex.aproselection.core.network.clients.auth.AuthNetworkClient import com.stslex.aproselection.core.network.clients.auth.model.UserAuthSendModel import com.stslex.aproselection.feature.auth.data.model.AuthMapper.toData @@ -22,23 +23,21 @@ class AuthRepositoryImpl( ) .onEach { response -> dataSource.setToken(response.token) - dataSource.setUuid(response.uuid) + dataSource.setCredential( + UserCredential( + username = username, + password = password + ) + ) } .map { user -> user.toData() } - override fun register( + override suspend fun register( username: String, password: String - ): Flow = networkClient.register( - UserAuthSendModel(username, password) - ) - .onEach { response -> - dataSource.setToken(response.token) - dataSource.setUuid(response.uuid) - } - .map { user -> - user.toData() - } + ) { + networkClient.register(UserAuthSendModel(username, password)) + } } \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractor.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractor.kt index e5a2355..0948123 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractor.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractor.kt @@ -7,5 +7,5 @@ interface AuthInteractor { fun auth(username: String, password: String): Flow - fun register(username: String, password: String): Flow + suspend fun register(username: String, password: String) } \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractorImpl.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractorImpl.kt index e62839b..82cbbdb 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractorImpl.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/domain/interactor/AuthInteractorImpl.kt @@ -16,11 +16,13 @@ class AuthInteractorImpl( password = password ) - override fun register( + override suspend fun register( username: String, password: String - ): Flow = repository.register( - username = username, - password = password - ) + ) { + repository.register( + username = username, + password = password + ) + } } \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt index 0ddea0d..3d84ae9 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch class AuthViewModel( private val interactor: AuthInteractor, @@ -89,26 +90,29 @@ class AuthViewModel( val state = screenState.value setLoadingState(ScreenLoadingState.Loading) - interactor - .register( - username = state.username, - password = state.password - ) - .catch { throwable -> - _screenEvents.emit(ScreenEvent.Error(throwable)) - setLoadingState(ScreenLoadingState.Content) - handleError(throwable) - } - .onEach { - _screenState.update { currentState -> - currentState.copy( - screenLoadingState = ScreenLoadingState.Content, - authFieldsState = AuthFieldsState.AUTH + viewModelScope.launch { + runCatching { + interactor + .register( + username = state.username, + password = state.password ) - } - _screenEvents.emit(ScreenEvent.SuccessRegistered) } - .launchIn(viewModelScope) + .onFailure { throwable -> + _screenEvents.emit(ScreenEvent.Error(throwable)) + setLoadingState(ScreenLoadingState.Content) + handleError(throwable) + } + .onSuccess { + _screenState.update { currentState -> + currentState.copy( + screenLoadingState = ScreenLoadingState.Content, + authFieldsState = AuthFieldsState.AUTH + ) + } + _screenEvents.emit(ScreenEvent.SuccessRegistered) + } + } } private fun auth() {