From ce659dea1f42ba6b1ab75d038e93376f49331da0 Mon Sep 17 00:00:00 2001 From: stslex Date: Sat, 29 Jul 2023 21:37:37 +0300 Subject: [PATCH 1/3] Init data store --- app/build.gradle.kts | 1 + .../stslex/aproselection/SelectApplication.kt | 4 +- .../stslex/aproselection/DiKoinModuleTest.kt | 4 +- core/datastore/.gitignore | 1 + core/datastore/build.gradle.kts | 10 ++++ core/datastore/consumer-rules.pro | 0 core/datastore/proguard-rules.pro | 21 ++++++++ .../core/datastore/ExampleInstrumentedTest.kt | 24 ++++++++++ core/datastore/src/main/AndroidManifest.xml | 2 + .../core/datastore/AppDataStore.kt | 13 +++++ .../core/datastore/AppDataStoreImpl.kt | 48 +++++++++++++++++++ .../core/datastore/CoreDataStoreModule.kt | 9 ++++ .../core/datastore/ExampleUnitTest.kt | 17 +++++++ gradle/libs.versions.toml | 4 ++ settings.gradle.kts | 1 + 15 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 core/datastore/.gitignore create mode 100644 core/datastore/build.gradle.kts create mode 100644 core/datastore/consumer-rules.pro create mode 100644 core/datastore/proguard-rules.pro create mode 100644 core/datastore/src/androidTest/java/com/stslex/aproselection/core/datastore/ExampleInstrumentedTest.kt create mode 100644 core/datastore/src/main/AndroidManifest.xml create mode 100644 core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStore.kt create mode 100644 core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStoreImpl.kt create mode 100644 core/datastore/src/main/java/com/stslex/aproselection/core/datastore/CoreDataStoreModule.kt create mode 100644 core/datastore/src/test/java/com/stslex/aproselection/core/datastore/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4aa0656..489e6a0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { implementation(project(":core:ui")) + implementation(project(":core:datastore")) implementation(project(":core:network")) implementation(project(":feature:auth")) } \ No newline at end of file diff --git a/app/src/main/java/com/stslex/aproselection/SelectApplication.kt b/app/src/main/java/com/stslex/aproselection/SelectApplication.kt index ca5e214..c5f0446 100644 --- a/app/src/main/java/com/stslex/aproselection/SelectApplication.kt +++ b/app/src/main/java/com/stslex/aproselection/SelectApplication.kt @@ -1,6 +1,7 @@ package com.stslex.aproselection import android.app.Application +import com.stslex.aproselection.core.datastore.coreDataStoreModule import com.stslex.aproselection.core.network.di.ModuleCoreNetwork.moduleCoreNetwork import com.stslex.aproselection.feature.auth.di.ModuleFeatureAuth.moduleFeatureAuth import org.koin.android.ext.koin.androidContext @@ -16,7 +17,8 @@ class SelectApplication : Application() { androidContext(applicationContext) modules( moduleFeatureAuth, - moduleCoreNetwork + moduleCoreNetwork, + coreDataStoreModule ) } } diff --git a/app/src/test/java/com/stslex/aproselection/DiKoinModuleTest.kt b/app/src/test/java/com/stslex/aproselection/DiKoinModuleTest.kt index f00e806..2194669 100644 --- a/app/src/test/java/com/stslex/aproselection/DiKoinModuleTest.kt +++ b/app/src/test/java/com/stslex/aproselection/DiKoinModuleTest.kt @@ -1,6 +1,7 @@ package com.stslex.aproselection import android.content.Context +import com.stslex.aproselection.core.datastore.coreDataStoreModule import com.stslex.aproselection.core.network.di.ModuleCoreNetwork import com.stslex.aproselection.core.network.di.ModuleCoreNetwork.moduleCoreNetwork import com.stslex.aproselection.feature.auth.di.ModuleFeatureAuth.moduleFeatureAuth @@ -19,7 +20,8 @@ class DiKoinModuleTest : KoinTest { androidContext(Mockito.mock(Context::class.java)) modules( moduleFeatureAuth, - moduleCoreNetwork + moduleCoreNetwork, + coreDataStoreModule ) checkModules() } diff --git a/core/datastore/.gitignore b/core/datastore/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/datastore/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts new file mode 100644 index 0000000..6067b9d --- /dev/null +++ b/core/datastore/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("aproselection.android.library") +} + +android.namespace = "com.stslex.aproselection.core.datastore" + +dependencies { + implementation(libs.androidx.datastore.preferences) + implementation(libs.androidx.datastore) +} \ No newline at end of file diff --git a/core/datastore/consumer-rules.pro b/core/datastore/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/core/datastore/proguard-rules.pro b/core/datastore/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/core/datastore/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/datastore/src/androidTest/java/com/stslex/aproselection/core/datastore/ExampleInstrumentedTest.kt b/core/datastore/src/androidTest/java/com/stslex/aproselection/core/datastore/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1e74b74 --- /dev/null +++ b/core/datastore/src/androidTest/java/com/stslex/aproselection/core/datastore/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.stslex.aproselection.core.datastore + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.stslex.aproselection.core.datastore.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/datastore/src/main/AndroidManifest.xml b/core/datastore/src/main/AndroidManifest.xml new file mode 100644 index 0000000..568741e --- /dev/null +++ b/core/datastore/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file 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 new file mode 100644 index 0000000..03f651d --- /dev/null +++ b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStore.kt @@ -0,0 +1,13 @@ +package com.stslex.aproselection.core.datastore + +import kotlinx.coroutines.flow.Flow + +interface AppDataStore { + + val uuid: Flow + val token: Flow + + suspend fun setUuid(uuid: String) + + suspend fun setToken(token: String) +} \ No newline at end of file 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 new file mode 100644 index 0000000..9bb0662 --- /dev/null +++ b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/AppDataStoreImpl.kt @@ -0,0 +1,48 @@ +package com.stslex.aproselection.core.datastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class AppDataStoreImpl( + private val context: Context +) : AppDataStore { + + 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 Context.dataStore: DataStore by preferencesDataStore(DATA_STORE_KEY) + } + + override val uuid: Flow + get() = context.dataStore.data.map { prefs -> + prefs[UUID_KEY].orEmpty() + } + + override val token: Flow + get() = context.dataStore.data.map { prefs -> + prefs[TOKEN_KEY].orEmpty() + } + + override suspend fun setUuid(uuid: String) { + context.dataStore.updateData { prefs -> + prefs.toMutablePreferences().apply { + set(UUID_KEY, uuid) + } + } + } + + + override suspend fun setToken(token: String) { + context.dataStore.updateData { prefs -> + prefs.toMutablePreferences().apply { + set(TOKEN_KEY, token) + } + } + } +} \ No newline at end of file diff --git a/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/CoreDataStoreModule.kt b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/CoreDataStoreModule.kt new file mode 100644 index 0000000..dd72393 --- /dev/null +++ b/core/datastore/src/main/java/com/stslex/aproselection/core/datastore/CoreDataStoreModule.kt @@ -0,0 +1,9 @@ +package com.stslex.aproselection.core.datastore + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val coreDataStoreModule = module { + singleOf(::AppDataStoreImpl) { bind() } +} \ No newline at end of file diff --git a/core/datastore/src/test/java/com/stslex/aproselection/core/datastore/ExampleUnitTest.kt b/core/datastore/src/test/java/com/stslex/aproselection/core/datastore/ExampleUnitTest.kt new file mode 100644 index 0000000..908cc47 --- /dev/null +++ b/core/datastore/src/test/java/com/stslex/aproselection/core/datastore/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.stslex.aproselection.core.datastore + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec287d5..c8db7f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ espresso = "3.5.1" room = "2.5.2" ksp = "1.8.22-1.0.11" mockito = "2.19.0" +datastore = "1.0.0" [libraries] android-desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } @@ -96,6 +97,9 @@ androidx-room-paging = { group = "androidx.room", name = "room-paging", version. androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } +androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } + [plugins] kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index d72f1c5..4088e18 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,4 @@ include(":app") include(":core:network") include(":feature:auth") include(":core:ui") +include(":core:datastore") From 34cee2aa80cb20aa187a2fb1ba499d9b2b61a868 Mon Sep 17 00:00:00 2001 From: stslex Date: Sat, 29 Jul 2023 22:01:31 +0300 Subject: [PATCH 2/3] init auth network client --- core/network/build.gradle.kts | 1 + .../core/network/client/NetworkClientImpl.kt | 64 ++++++++++++++++++- .../network/clients/auth/AuthNetworkClient.kt | 7 ++ .../clients/auth/AuthNetworkClientImpl.kt | 35 ++++++++++ .../clients/auth/model/TokenResponse.kt | 10 +++ .../auth/model/UserAuthResponseModel.kt | 16 +++++ .../clients/auth/model/UserAuthSendModel.kt | 12 ++++ 7 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/TokenResponse.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthResponseModel.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthSendModel.kt diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 8d429d6..9f18b16 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -6,6 +6,7 @@ plugins { } dependencies { + implementation(project(":core:datastore")) implementation(libs.bundles.ktor) implementation(libs.bundles.okhttp) } 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 31cd302..b7aa353 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 @@ -1,8 +1,13 @@ 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 io.ktor.client.HttpClient +import io.ktor.client.call.body import io.ktor.client.engine.android.Android +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.HttpResponseValidator import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.logging.DEFAULT @@ -10,13 +15,44 @@ 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.bearerAuth +import io.ktor.client.request.get +import io.ktor.http.HttpStatusCode import io.ktor.http.URLProtocol +import io.ktor.http.appendPathSegments import io.ktor.http.encodedPath import io.ktor.serialization.kotlinx.json.json +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json -class NetworkClientImpl : NetworkClient { +class NetworkClientImpl( + private val dataStore: AppDataStore +) : NetworkClient { + + private val scope = CoroutineScope(SupervisorJob()) + private val _token = MutableStateFlow("") + private val token = _token.asStateFlow() + private val _uuid = MutableStateFlow("") + private val uuid = _uuid.asStateFlow() + + init { + dataStore.token + .onEach { + _token.emit(it) + } + .launchIn(scope) + + dataStore.uuid + .onEach { + _uuid.emit(it) + } + .launchIn(scope) + } @OptIn(ExperimentalSerializationApi::class) override val client: HttpClient @@ -39,6 +75,17 @@ class NetworkClientImpl : NetworkClient { } ) } + + HttpResponseValidator { + handleResponseExceptionWithRequest { exception, request -> + val clientException = exception as? ClientRequestException + ?: return@handleResponseExceptionWithRequest + val exceptionResponse = clientException.response + if (exceptionResponse.status == HttpStatusCode.Unauthorized) { + regenerateToken() + } + } + } } override val apiClient: HttpClient @@ -48,8 +95,21 @@ class NetworkClientImpl : NetworkClient { 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", uuid.value) } - bearerAuth(BuildConfig.API_KEY) + bearerAuth(token = token.value) } } + + private suspend fun regenerateToken() { + val token = client + .get { + url.appendPathSegments("token") + } + .body() + .token + dataStore.setToken(token) + } } \ 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 e133342..8d8c160 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 @@ -1,8 +1,15 @@ package com.stslex.aproselection.core.network.clients.auth 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 kotlinx.coroutines.flow.Flow interface AuthNetworkClient { suspend fun getHello(username: String): HelloRequestModel + + fun auth(user: UserAuthSendModel): Flow + + fun register(user: UserAuthSendModel): Flow } \ 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 7148185..2fe126c 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 @@ -2,10 +2,16 @@ package com.stslex.aproselection.core.network.clients.auth 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 io.ktor.client.call.body import io.ktor.client.request.get +import io.ktor.client.request.setBody import io.ktor.http.appendPathSegments import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext class AuthNetworkClientImpl( @@ -22,4 +28,33 @@ class AuthNetworkClientImpl( } .body() } + + + override fun auth( + user: UserAuthSendModel + ): Flow = flow { + val result = networkClient + .apiClient + .get { + url.appendPathSegments("passport/auth") + setBody(user) + } + .body() + emit(result) + } + .flowOn(Dispatchers.IO) + + override fun register( + user: UserAuthSendModel + ): Flow = flow { + val result = networkClient + .apiClient + .get { + url.appendPathSegments("passport/register") + setBody(user) + } + .body() + emit(result) + } + .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/TokenResponse.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/TokenResponse.kt new file mode 100644 index 0000000..86ea4a9 --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/TokenResponse.kt @@ -0,0 +1,10 @@ +package com.stslex.aproselection.core.network.clients.auth.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TokenResponse( + @SerialName("token") + val token: String +) \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthResponseModel.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthResponseModel.kt new file mode 100644 index 0000000..86d892d --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthResponseModel.kt @@ -0,0 +1,16 @@ +package com.stslex.aproselection.core.network.clients.auth.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserAuthResponseModel( + @SerialName("uuid") + val uuid: String, + @SerialName("username") + val username: String, + @SerialName("nickname") + val nickname: String, + @SerialName("token") + val token: String +) diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthSendModel.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthSendModel.kt new file mode 100644 index 0000000..f0dd86f --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/clients/auth/model/UserAuthSendModel.kt @@ -0,0 +1,12 @@ +package com.stslex.aproselection.core.network.clients.auth.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserAuthSendModel( + @SerialName("username") + val username: String, + @SerialName("password") + val password: String +) From a65e6d34345c451d93c8bba2d20e80d1193c9dcf Mon Sep 17 00:00:00 2001 From: stslex Date: Sat, 29 Jul 2023 22:55:37 +0300 Subject: [PATCH 3/3] init registration --- .../stslex/aproselection/ui/MainActivity.kt | 21 +++++++++ .../core/network/client/NetworkClient.kt | 2 + .../core/network/client/NetworkClientImpl.kt | 7 ++- .../clients/auth/AuthNetworkClientImpl.kt | 9 ++-- feature/auth/build.gradle.kts | 1 + .../feature/auth/data/model/AuthMapper.kt | 12 +++++ .../feature/auth/data/model/UserModel.kt | 7 +++ .../auth/data/repository/AuthRepository.kt | 5 ++- .../data/repository/AuthRepositoryImpl.kt | 44 +++++++++++++++---- .../auth/domain/interactor/AuthInteractor.kt | 5 ++- .../domain/interactor/AuthInteractorImpl.kt | 19 ++++++-- .../feature/auth/ui/AuthScreen.kt | 36 ++++++++++----- .../feature/auth/ui/AuthViewModel.kt | 14 ++++-- .../feature/auth/ui/navigation/AuthRouter.kt | 2 +- 14 files changed, 148 insertions(+), 36 deletions(-) create mode 100644 feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/AuthMapper.kt create mode 100644 feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/UserModel.kt diff --git a/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt b/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt index 6a44d46..9a3d5cb 100644 --- a/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt +++ b/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt @@ -1,14 +1,35 @@ package com.stslex.aproselection.ui import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.lifecycle.lifecycleScope +import com.stslex.aproselection.core.datastore.AppDataStore +import com.stslex.aproselection.core.network.client.NetworkClient import com.stslex.aproselection.core.ui.theme.AppTheme +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.koin.android.ext.android.inject class MainActivity : ComponentActivity() { + val dataStore by inject() + val networkClient by inject() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + dataStore.token + .catch { + Log.e(it.message, javaClass.simpleName, it) + } + .onEach { + if (it.isBlank()) { + networkClient.regenerateToken() + } + } + .launchIn(lifecycleScope) setContent { AppTheme { InitialApp() 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 a5b97db..83fedd9 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 @@ -7,4 +7,6 @@ interface NetworkClient { val client: HttpClient val apiClient: HttpClient + + suspend fun regenerateToken() } \ 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 b7aa353..c10685f 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 @@ -16,9 +16,11 @@ import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.bearerAuth import io.ktor.client.request.get +import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode 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.CoroutineScope @@ -98,13 +100,14 @@ class NetworkClientImpl( headers.append("API_KEY", BuildConfig.API_KEY) headers.append("DEVICE_ID", "test") headers.append("uuid", uuid.value) + contentType(ContentType.Application.Json) } bearerAuth(token = token.value) } } - private suspend fun regenerateToken() { - val token = client + override suspend fun regenerateToken() { + val token = apiClient .get { url.appendPathSegments("token") } 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 2fe126c..2ee7d23 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 @@ -6,6 +6,7 @@ import com.stslex.aproselection.core.network.clients.auth.model.UserAuthResponse import com.stslex.aproselection.core.network.clients.auth.model.UserAuthSendModel import io.ktor.client.call.body import io.ktor.client.request.get +import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.http.appendPathSegments import kotlinx.coroutines.Dispatchers @@ -35,9 +36,9 @@ class AuthNetworkClientImpl( ): Flow = flow { val result = networkClient .apiClient - .get { + .post { url.appendPathSegments("passport/auth") - setBody(user) + setBody(user) } .body() emit(result) @@ -49,9 +50,9 @@ class AuthNetworkClientImpl( ): Flow = flow { val result = networkClient .apiClient - .get { + .post { url.appendPathSegments("passport/register") - setBody(user) + setBody(user) } .body() emit(result) diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index 4c98180..99a9ff4 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -5,6 +5,7 @@ plugins { dependencies { implementation(project(":core:ui")) + implementation(project(":core:datastore")) implementation(project(":core:network")) } diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/AuthMapper.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/AuthMapper.kt new file mode 100644 index 0000000..bd9ba83 --- /dev/null +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/AuthMapper.kt @@ -0,0 +1,12 @@ +package com.stslex.aproselection.feature.auth.data.model + +import com.stslex.aproselection.core.network.clients.auth.model.UserAuthResponseModel + +object AuthMapper { + + fun UserAuthResponseModel.toData(): UserModel = UserModel( + uuid = uuid, + username = username, + nickname = nickname + ) +} \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/UserModel.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/UserModel.kt new file mode 100644 index 0000000..3619855 --- /dev/null +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/data/model/UserModel.kt @@ -0,0 +1,7 @@ +package com.stslex.aproselection.feature.auth.data.model + +data class UserModel( + val uuid: String, + val username: String, + val nickname: String, +) 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 5b77795..69942ef 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 @@ -1,8 +1,11 @@ package com.stslex.aproselection.feature.auth.data.repository +import com.stslex.aproselection.feature.auth.data.model.UserModel import kotlinx.coroutines.flow.Flow interface AuthRepository { - fun getHello(username: String): Flow + fun auth(username: String, password: String): Flow + + fun register(username: String, password: String): Flow } \ 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 8760b92..a46d124 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,17 +1,45 @@ package com.stslex.aproselection.feature.auth.data.repository +import com.stslex.aproselection.core.datastore.AppDataStore 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 +import com.stslex.aproselection.feature.auth.data.model.UserModel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach class AuthRepositoryImpl( - private val networkClient: AuthNetworkClient + private val networkClient: AuthNetworkClient, + private val dataSource: AppDataStore ) : AuthRepository { - override fun getHello(username: String): Flow = flow { - val helloResponse = networkClient - .getHello(username = username) - .text - emit(helloResponse) - } + + override fun auth( + username: String, + password: String + ): Flow = networkClient.auth( + UserAuthSendModel(username, password) + ) + .onEach { response -> + dataSource.setToken(response.token) + dataSource.setUuid(response.uuid) + } + .map { user -> + user.toData() + } + + override 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() + } } \ 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 0629d66..e5a2355 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 @@ -1,8 +1,11 @@ package com.stslex.aproselection.feature.auth.domain.interactor +import com.stslex.aproselection.feature.auth.data.model.UserModel import kotlinx.coroutines.flow.Flow interface AuthInteractor { - fun getHello(username: String): Flow + fun auth(username: String, password: String): Flow + + fun register(username: String, password: String): Flow } \ 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 d696509..e62839b 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 @@ -1,5 +1,6 @@ package com.stslex.aproselection.feature.auth.domain.interactor +import com.stslex.aproselection.feature.auth.data.model.UserModel import com.stslex.aproselection.feature.auth.data.repository.AuthRepository import kotlinx.coroutines.flow.Flow @@ -7,9 +8,19 @@ class AuthInteractorImpl( private val repository: AuthRepository ) : AuthInteractor { - override fun getHello( - username: String - ): Flow = repository.getHello( - username = username + override fun auth( + username: String, + password: String + ): Flow = repository.auth( + username = username, + password = password + ) + + override fun register( + username: String, + password: String + ): Flow = 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/AuthScreen.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt index 3c29bac..5da2af8 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt @@ -25,13 +25,17 @@ import com.stslex.aproselection.core.ui.navigation.NavigationScreen fun AuthScreen( text: String, navigate: (NavigationScreen) -> Unit, - setUsername: (String) -> Unit, + auth: (String, String) -> Unit, modifier: Modifier = Modifier, ) { var inputUsername by remember { mutableStateOf("") } + var inputPassword by remember { + mutableStateOf("") + } + Box( modifier = modifier .fillMaxSize() @@ -52,9 +56,19 @@ fun AuthScreen( maxLines = 1, ) Divider(Modifier.padding(16.dp)) + TextField( + value = inputPassword, + onValueChange = { value -> + if (inputPassword != value) { + inputPassword = value + } + }, + maxLines = 1, + ) + Divider(Modifier.padding(16.dp)) ElevatedButton( onClick = { - setUsername(inputUsername) + auth(inputUsername, inputPassword) inputUsername = "" } ) { @@ -73,12 +87,12 @@ fun AuthScreen( } } -@Preview(device = "id:pixel_6", showSystemUi = true, showBackground = true) -@Composable -fun AuthScreenPreview() { - AuthScreen( - text = "text", - navigate = {}, - setUsername = {} - ) -} \ No newline at end of file +//@Preview(device = "id:pixel_6", showSystemUi = true, showBackground = true) +//@Composable +//fun AuthScreenPreview() { +// AuthScreen( +// text = "text", +// navigate = {}, +// setUsername = {} +// ) +//} \ 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 4fc3873..ecb9ab3 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 @@ -19,14 +19,20 @@ class AuthViewModel( val text: StateFlow get() = _text.asStateFlow() - fun setUsername(username: String) { + fun auth(username: String, password: String) { _text.value = "..." - interactor.getHello(username) + + interactor + .register( + username = username, + password = password + ) .catch { throwable -> + _text.emit("Error: ${throwable.localizedMessage}") handleError(throwable) } - .onEach { receivedText -> - _text.emit(receivedText) + .onEach { userModel -> + _text.emit("Success: uuid--${userModel.uuid}") } .launchIn(viewModelScope) } diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt index 557bbd3..3273528 100644 --- a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt @@ -28,7 +28,7 @@ fun NavGraphBuilder.authRouter( AuthScreen( text = text, navigate = navigate, - setUsername = viewModel::setUsername, + auth = viewModel::auth, modifier = modifier ) }