mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 11:38:40 +01:00
chore(identity): add identity module
This commit is contained in:
parent
49706c603d
commit
e941dc0f21
@ -36,11 +36,12 @@ kotlin {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.ktorfit.lib)
|
||||
api(libs.ktorfit.lib)
|
||||
implementation(libs.ktor.serialization)
|
||||
implementation(libs.ktor.contentnegotiation)
|
||||
implementation(libs.ktor.json)
|
||||
implementation(libs.ktor.logging)
|
||||
implementation(projects.coreUtils)
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
|
@ -1,10 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.provider
|
||||
|
||||
import android.util.Log
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
|
||||
internal actual val defaultLogger: Logger = object : Logger {
|
||||
override fun log(message: String) {
|
||||
Log.d("com.github.diegoberaldin.raccoonforlemmy", message)
|
||||
}
|
||||
}
|
@ -16,4 +16,8 @@ val coreApiModule = module {
|
||||
val provider: ServiceProvider = get()
|
||||
provider.communityService
|
||||
}
|
||||
single {
|
||||
val provider: ServiceProvider = get()
|
||||
provider.authService
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LoginForm(
|
||||
@SerialName("username_or_email") val username: String,
|
||||
@SerialName("password") val password: String,
|
||||
@SerialName("totp_2fa_token") val totp2faToken: String? = null,
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LoginResponse(
|
||||
@SerialName("jwt") val token: String? = null,
|
||||
@SerialName("registration_created") val registrationCreated: Boolean,
|
||||
@SerialName("verify_email_sent") val verifyEmailSent: Boolean,
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.provider
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.AuthService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.CommunityService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.PostService
|
||||
import de.jensklingenberg.ktorfit.Ktorfit
|
||||
@ -26,6 +27,9 @@ internal class DefaultServiceProvider : ServiceProvider {
|
||||
override lateinit var communityService: CommunityService
|
||||
private set
|
||||
|
||||
override lateinit var authService: AuthService
|
||||
private set
|
||||
|
||||
private val baseUrl: String get() = "https://$currentInstance/api/$VERSION/"
|
||||
private val client = HttpClient {
|
||||
install(Logging) {
|
||||
@ -54,5 +58,6 @@ internal class DefaultServiceProvider : ServiceProvider {
|
||||
.build()
|
||||
postService = ktorfit.create()
|
||||
communityService = ktorfit.create()
|
||||
authService = ktorfit.create()
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.provider
|
||||
|
||||
import com.github.diegoberaldin.racconforlemmy.core_utils.Log
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
|
||||
internal expect val defaultLogger: Logger
|
||||
internal val defaultLogger = object : Logger {
|
||||
override fun log(message: String) {
|
||||
Log.d(message)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.provider
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.AuthService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.CommunityService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.PostService
|
||||
|
||||
@ -8,6 +9,7 @@ interface ServiceProvider {
|
||||
val currentInstance: String
|
||||
val postService: PostService
|
||||
val communityService: CommunityService
|
||||
val authService: AuthService
|
||||
|
||||
fun changeInstance(value: String)
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.service
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.dto.LoginForm
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.dto.LoginResponse
|
||||
import de.jensklingenberg.ktorfit.Response
|
||||
import de.jensklingenberg.ktorfit.http.Body
|
||||
import de.jensklingenberg.ktorfit.http.POST
|
||||
|
||||
interface AuthService {
|
||||
@POST("user/login")
|
||||
suspend fun login(@Body form: LoginForm): Response<LoginResponse>
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_api.provider
|
||||
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
import platform.Foundation.NSLog
|
||||
|
||||
internal actual val defaultLogger: Logger = object : Logger {
|
||||
override fun log(message: String) {
|
||||
NSLog(message)
|
||||
}
|
||||
}
|
@ -8,35 +8,35 @@ internal class DefaultTemporaryKeyStore(
|
||||
private val settings: Settings,
|
||||
) : TemporaryKeyStore {
|
||||
|
||||
override suspend fun containsKey(key: String): Boolean = settings.keys.contains(key)
|
||||
override fun containsKey(key: String): Boolean = settings.keys.contains(key)
|
||||
|
||||
override suspend fun save(key: String, value: Boolean) {
|
||||
override fun save(key: String, value: Boolean) {
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Boolean): Boolean = settings[key, default]
|
||||
override fun get(key: String, default: Boolean): Boolean = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: String) {
|
||||
override fun save(key: String, value: String) {
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: String): String = settings[key, default]
|
||||
override fun get(key: String, default: String): String = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: Int) {
|
||||
override fun save(key: String, value: Int) {
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Int): Int = settings[key, default]
|
||||
override fun get(key: String, default: Int): Int = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: Float) {
|
||||
override fun save(key: String, value: Float) {
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Float): Float = settings[key, default]
|
||||
override fun get(key: String, default: Float): Float = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: Double) {
|
||||
override fun save(key: String, value: Double) {
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Double): Double = settings[key, default]
|
||||
override fun get(key: String, default: Double): Double = settings[key, default]
|
||||
}
|
||||
|
@ -3,4 +3,5 @@ package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
||||
object KeyStoreKeys {
|
||||
const val UITheme = "uiTheme"
|
||||
const val Locale = "locale"
|
||||
const val AuthToken = "auth"
|
||||
}
|
@ -10,7 +10,7 @@ interface TemporaryKeyStore {
|
||||
* @param key Key to check
|
||||
* @return true if the key store contains the key, false otherwise
|
||||
*/
|
||||
suspend fun containsKey(key: String): Boolean
|
||||
fun containsKey(key: String): Boolean
|
||||
|
||||
/**
|
||||
* Save a boolean value in the keystore under a given key.
|
||||
@ -18,7 +18,7 @@ interface TemporaryKeyStore {
|
||||
* @param key Key
|
||||
* @param value Value
|
||||
*/
|
||||
suspend fun save(key: String, value: Boolean)
|
||||
fun save(key: String, value: Boolean)
|
||||
|
||||
/**
|
||||
* Retrieve a boolean value from the key store given its key.
|
||||
@ -27,7 +27,7 @@ interface TemporaryKeyStore {
|
||||
* @param default Default value
|
||||
* @return value saved in the keystore or the default one
|
||||
*/
|
||||
suspend fun get(key: String, default: Boolean): Boolean
|
||||
fun get(key: String, default: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Save a string value in the keystore under a given key.
|
||||
@ -35,7 +35,7 @@ interface TemporaryKeyStore {
|
||||
* @param key Key
|
||||
* @param value Value
|
||||
*/
|
||||
suspend fun save(key: String, value: String)
|
||||
fun save(key: String, value: String)
|
||||
|
||||
/**
|
||||
* Retrieve a string value from the key store given its key.
|
||||
@ -44,7 +44,7 @@ interface TemporaryKeyStore {
|
||||
* @param default Default value
|
||||
* @return value saved in the keystore or the default one
|
||||
*/
|
||||
suspend fun get(key: String, default: String): String
|
||||
operator fun get(key: String, default: String): String
|
||||
|
||||
/**
|
||||
* Save an integer value in the keystore under a given key.
|
||||
@ -52,7 +52,7 @@ interface TemporaryKeyStore {
|
||||
* @param key Key
|
||||
* @param value Value
|
||||
*/
|
||||
suspend fun save(key: String, value: Int)
|
||||
fun save(key: String, value: Int)
|
||||
|
||||
/**
|
||||
* Retrieve an integer value from the key store given its key.
|
||||
@ -61,7 +61,7 @@ interface TemporaryKeyStore {
|
||||
* @param default Default value
|
||||
* @return value saved in the keystore or the default one
|
||||
*/
|
||||
suspend fun get(key: String, default: Int): Int
|
||||
fun get(key: String, default: Int): Int
|
||||
|
||||
/**
|
||||
* Save a floating point value in the keystore under a given key.
|
||||
@ -69,7 +69,7 @@ interface TemporaryKeyStore {
|
||||
* @param key Key
|
||||
* @param value Value
|
||||
*/
|
||||
suspend fun save(key: String, value: Float)
|
||||
fun save(key: String, value: Float)
|
||||
|
||||
/**
|
||||
* Retrieve a floating point value from the key store given its key.
|
||||
@ -78,7 +78,7 @@ interface TemporaryKeyStore {
|
||||
* @param default Default value
|
||||
* @return value saved in the keystore or the default one
|
||||
*/
|
||||
suspend fun get(key: String, default: Float): Float
|
||||
fun get(key: String, default: Float): Float
|
||||
|
||||
/**
|
||||
* Save a floating point (double precision) value in the keystore under a given key.
|
||||
@ -86,7 +86,7 @@ interface TemporaryKeyStore {
|
||||
* @param key Key
|
||||
* @param value Value
|
||||
*/
|
||||
suspend fun save(key: String, value: Double)
|
||||
fun save(key: String, value: Double)
|
||||
|
||||
/**
|
||||
* Retrieve a floating point (double precision) value from the key store given its key.
|
||||
@ -95,5 +95,5 @@ interface TemporaryKeyStore {
|
||||
* @param default Default value
|
||||
* @return value saved in the keystore or the default one
|
||||
*/
|
||||
suspend fun get(key: String, default: Double): Double
|
||||
fun get(key: String, default: Double): Double
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
package com.github.diegoberaldin.racconforlemmy.core_utils
|
||||
|
||||
actual object Log {
|
||||
|
||||
private const val TAG = "com.github.diegoberaldin.racconforlemmy"
|
||||
actual fun d(message: String) {
|
||||
android.util.Log.d(TAG, message)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.github.diegoberaldin.racconforlemmy.core_utils
|
||||
|
||||
expect object Log {
|
||||
fun d(message: String)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.github.diegoberaldin.racconforlemmy.core_utils
|
||||
|
||||
import platform.Foundation.NSLog
|
||||
|
||||
actual object Log {
|
||||
actual fun d(message: String) {
|
||||
NSLog(message)
|
||||
}
|
||||
}
|
57
domain-identity/build.gradle.kts
Normal file
57
domain-identity/build.gradle.kts
Normal file
@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.compose)
|
||||
alias(libs.plugins.native.cocoapods)
|
||||
}
|
||||
|
||||
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
|
||||
kotlin {
|
||||
targetHierarchy.default()
|
||||
|
||||
android {
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
cocoapods {
|
||||
summary = "Some description for the Shared Module"
|
||||
homepage = "Link to the Shared Module homepage"
|
||||
version = "1.0"
|
||||
ios.deploymentTarget = "14.1"
|
||||
framework {
|
||||
baseName = "domain-identity"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.koin.core)
|
||||
implementation(compose.foundation)
|
||||
implementation(projects.corePreferences)
|
||||
implementation(projects.coreApi)
|
||||
implementation(projects.coreUtils)
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.github.diegoberaldin.raccoonforlemmy.domain_identity"
|
||||
compileSdk = 33
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
42
domain-identity/domain-identity.podspec
Normal file
42
domain-identity/domain-identity.podspec
Normal file
@ -0,0 +1,42 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'domain-identity'
|
||||
spec.version = '1.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
|
||||
spec.vendored_frameworks = "build/cocoapods/framework/domain-identity.framework"
|
||||
spec.libraries = "c++"
|
||||
spec.module_name = "#{spec.name}_umbrella"
|
||||
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':domain-identity',
|
||||
'PRODUCT_MODULE_NAME' => 'domain-identity',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build domain-identity',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
|
||||
echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
end
|
39
domain-identity/domain_identity.podspec
Normal file
39
domain-identity/domain_identity.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'domain_identity'
|
||||
spec.version = '1.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/domain-identity.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':domain-identity',
|
||||
'PRODUCT_MODULE_NAME' => 'domain-identity',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build domain_identity',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
@ -0,0 +1,30 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.usecase.DefaultLoginUseCase
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.usecase.LoginUseCase
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.AuthRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.DefaultApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.DefaultAuthRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.DefaultIdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.IdentityRepository
|
||||
import org.koin.dsl.module
|
||||
|
||||
val coreIdentityModule = module {
|
||||
single<ApiConfigurationRepository> {
|
||||
DefaultApiConfigurationRepository(get())
|
||||
}
|
||||
single<IdentityRepository> {
|
||||
DefaultIdentityRepository(get())
|
||||
}
|
||||
single<AuthRepository> {
|
||||
DefaultAuthRepository(get())
|
||||
}
|
||||
single<LoginUseCase> {
|
||||
DefaultLoginUseCase(
|
||||
apiConfigurationRepository = get(),
|
||||
authRepository = get(),
|
||||
identityRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
|
||||
|
||||
interface ApiConfigurationRepository {
|
||||
fun getInstance(): String
|
||||
|
||||
fun changeInstance(value: String)
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.dto.LoginResponse
|
||||
|
||||
interface AuthRepository {
|
||||
suspend fun login(
|
||||
username: String,
|
||||
password: String,
|
||||
totp2faToken: String? = null,
|
||||
): Result<LoginResponse>
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.provider.ServiceProvider
|
||||
|
||||
internal class DefaultApiConfigurationRepository(
|
||||
private val serviceProvider: ServiceProvider,
|
||||
) : ApiConfigurationRepository {
|
||||
|
||||
override fun getInstance() = serviceProvider.currentInstance
|
||||
|
||||
override fun changeInstance(value: String) {
|
||||
serviceProvider.changeInstance(value)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.dto.LoginForm
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.dto.LoginResponse
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.service.AuthService
|
||||
|
||||
internal class DefaultAuthRepository(
|
||||
private val authService: AuthService,
|
||||
) : AuthRepository {
|
||||
override suspend fun login(
|
||||
username: String,
|
||||
password: String,
|
||||
totp2faToken: String?,
|
||||
): Result<LoginResponse> = runCatching {
|
||||
val data = LoginForm(
|
||||
username = username,
|
||||
password = password,
|
||||
totp2faToken = totp2faToken,
|
||||
)
|
||||
val response = authService.login(data)
|
||||
if (!response.isSuccessful) {
|
||||
// TODO: better API error handling
|
||||
val error = response.errorBody().toString()
|
||||
throw Exception(error)
|
||||
}
|
||||
response.body() ?: throw Exception("No reponse from login endpoint")
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.KeyStoreKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class DefaultIdentityRepository(
|
||||
private val keyStore: TemporaryKeyStore,
|
||||
) : IdentityRepository {
|
||||
|
||||
override val authToken = MutableStateFlow<String?>(null)
|
||||
|
||||
init {
|
||||
val previousToken = keyStore[KeyStoreKeys.AuthToken, ""]
|
||||
.takeIf { it.isNotEmpty() }
|
||||
authToken.value = previousToken
|
||||
}
|
||||
|
||||
override fun storeToken(value: String) {
|
||||
authToken.value = value
|
||||
keyStore.save(KeyStoreKeys.AuthToken, value)
|
||||
}
|
||||
|
||||
override fun clearToken() {
|
||||
authToken.value = null
|
||||
keyStore.save(KeyStoreKeys.AuthToken, "")
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface IdentityRepository {
|
||||
|
||||
val authToken: StateFlow<String?>
|
||||
|
||||
fun storeToken(value: String)
|
||||
|
||||
fun clearToken()
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.usecase
|
||||
|
||||
import com.github.diegoberaldin.racconforlemmy.core_utils.Log
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.AuthRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.IdentityRepository
|
||||
|
||||
internal class DefaultLoginUseCase(
|
||||
private val authRepository: AuthRepository,
|
||||
private val apiConfigurationRepository: ApiConfigurationRepository,
|
||||
private val identityRepository: IdentityRepository,
|
||||
) : LoginUseCase {
|
||||
|
||||
override suspend fun login(
|
||||
instance: String,
|
||||
username: String,
|
||||
password: String,
|
||||
totp2faToken: String?,
|
||||
): Result<Unit> {
|
||||
val oldInstance = apiConfigurationRepository.getInstance()
|
||||
apiConfigurationRepository.changeInstance(instance)
|
||||
|
||||
val response = authRepository.login(
|
||||
username = username,
|
||||
password = password,
|
||||
totp2faToken = totp2faToken
|
||||
)
|
||||
return response.onFailure {
|
||||
Log.d("Login failure: ${it.message}")
|
||||
}.map {
|
||||
val auth = it.token
|
||||
if (auth == null) {
|
||||
apiConfigurationRepository.changeInstance(oldInstance)
|
||||
} else {
|
||||
identityRepository.storeToken(auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
identityRepository.clearToken()
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.usecase
|
||||
|
||||
interface LoginUseCase {
|
||||
suspend fun login(
|
||||
instance: String,
|
||||
username: String,
|
||||
password: String,
|
||||
totp2faToken: String? = null,
|
||||
): Result<Unit>
|
||||
|
||||
suspend fun logout()
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_post.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.provider.ServiceProvider
|
||||
|
||||
class ApiConfigurationRepository(
|
||||
private val serviceProvider: ServiceProvider,
|
||||
) {
|
||||
|
||||
fun getInstance() = serviceProvider.currentInstance
|
||||
|
||||
fun changeInstance(value: String) {
|
||||
serviceProvider.changeInstance(value)
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.CommunityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.PostsRepository
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val postsRepositoryModule = module {
|
||||
singleOf(::ApiConfigurationRepository)
|
||||
singleOf(::PostsRepository)
|
||||
singleOf(::CommunityRepository)
|
||||
}
|
@ -52,6 +52,7 @@ kotlin {
|
||||
implementation(projects.coreArchitecture)
|
||||
implementation(projects.coreUtils)
|
||||
implementation(projects.coreMd)
|
||||
implementation(projects.domainIdentity)
|
||||
implementation(projects.domainPost.data)
|
||||
implementation(projects.domainPost.repository)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ actual val homeTabModule = module {
|
||||
mvi = DefaultMviModel(HomeScreenMviModel.UiState()),
|
||||
postsRepository = get(),
|
||||
apiConfigRepository = get(),
|
||||
identityRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
fun ListingTypeBottomSheet(
|
||||
isLogged: Boolean = false,
|
||||
onDismiss: (ListingType) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
@ -44,11 +45,13 @@ fun ListingTypeBottomSheet(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
val values = listOf(
|
||||
ListingType.Subscribed,
|
||||
ListingType.Local,
|
||||
ListingType.All,
|
||||
)
|
||||
val values = buildList {
|
||||
if (isLogged) {
|
||||
this += ListingType.Subscribed
|
||||
}
|
||||
this += ListingType.All
|
||||
this += ListingType.Local
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xxxs)
|
||||
|
@ -79,7 +79,9 @@ object HomeTab : Tab {
|
||||
sortType = uiState.sortType,
|
||||
onSelectListingType = {
|
||||
bottomSheetChannel.trySend @Composable {
|
||||
ListingTypeBottomSheet { type ->
|
||||
ListingTypeBottomSheet(
|
||||
isLogged = uiState.isLogged
|
||||
) { type ->
|
||||
model.reduce(HomeScreenMviModel.Intent.ChangeListing(type))
|
||||
bottomSheetChannel.trySend(null)
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import com.seiko.imageloader.rememberImagePainter
|
||||
|
||||
@Composable
|
||||
internal fun PostCardImage(post: PostModel) {
|
||||
val imageUrl = post.thumbnailUrl
|
||||
if (!imageUrl.isNullOrEmpty()) {
|
||||
val imageUrl = post.thumbnailUrl.orEmpty()
|
||||
if (imageUrl.isNotEmpty()) {
|
||||
val painter = rememberImagePainter(imageUrl)
|
||||
Image(
|
||||
modifier = Modifier.fillMaxWidth().heightIn(min = 200.dp),
|
||||
|
@ -5,16 +5,21 @@ import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviMode
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.data.ListingType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.data.SortType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.PostsRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class HomeScreenModel(
|
||||
private val mvi: DefaultMviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect>,
|
||||
private val postsRepository: PostsRepository,
|
||||
private val apiConfigRepository: ApiConfigurationRepository,
|
||||
private val identityRepository: IdentityRepository,
|
||||
) : ScreenModel,
|
||||
MviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect> by mvi {
|
||||
|
||||
@ -31,7 +36,16 @@ class HomeScreenModel(
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
mvi.updateState { it.copy(instance = apiConfigRepository.getInstance()) }
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
instance = apiConfigRepository.getInstance()
|
||||
)
|
||||
}
|
||||
identityRepository.authToken.map { !it.isNullOrEmpty() }.onEach { isLogged ->
|
||||
mvi.updateState {
|
||||
it.copy(isLogged = isLogged)
|
||||
}
|
||||
}.launchIn(mvi.scope)
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ interface HomeScreenMviModel :
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val instance: String = "",
|
||||
val isLogged: Boolean = false,
|
||||
val listingType: ListingType = ListingType.Local,
|
||||
val sortType: SortType = SortType.Active,
|
||||
val posts: List<PostModel> = emptyList(),
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature_home.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.postsRepositoryModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_post.repository.di.postsRepositoryModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_home.viewmodel.HomeScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_home.viewmodel.HomeScreenMviModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -15,6 +15,7 @@ actual val homeTabModule = module {
|
||||
mvi = DefaultMviModel(HomeScreenMviModel.UiState()),
|
||||
postsRepository = get(),
|
||||
apiConfigRepository = get(),
|
||||
identityRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -36,4 +36,5 @@ include(":core-api")
|
||||
include(":domain-post")
|
||||
include(":domain-post:repository")
|
||||
include(":domain-post:data")
|
||||
include(":domain-identity")
|
||||
include(":core-md")
|
||||
|
@ -58,6 +58,7 @@ kotlin {
|
||||
implementation(projects.coreAppearance)
|
||||
implementation(projects.corePreferences)
|
||||
implementation(projects.coreApi)
|
||||
implementation(projects.domainIdentity)
|
||||
|
||||
api(projects.resources)
|
||||
api(projects.featureHome)
|
||||
|
@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.di.coreApiModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.di.coreAppearanceModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.di.coreIdentityModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.di.corePreferencesModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_inbox.inboxTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_profile.profileTabModule
|
||||
@ -16,6 +17,7 @@ val sharedHelperModule = module {
|
||||
coreAppearanceModule,
|
||||
corePreferencesModule,
|
||||
coreApiModule,
|
||||
coreIdentityModule,
|
||||
localizationModule,
|
||||
homeTabModule,
|
||||
inboxTabModule,
|
||||
|
@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_api.di.coreApiModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.di.coreAppearanceModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_identity.di.coreIdentityModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.di.corePreferencesModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_home.di.homeTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_inbox.inboxTabModule
|
||||
@ -17,6 +18,7 @@ fun initKoin() {
|
||||
coreAppearanceModule,
|
||||
corePreferencesModule,
|
||||
coreApiModule,
|
||||
coreIdentityModule,
|
||||
localizationModule,
|
||||
homeTabModule,
|
||||
inboxTabModule,
|
||||
|
Loading…
x
Reference in New Issue
Block a user