chore(identity): add identity module

This commit is contained in:
Diego Beraldin 2023-07-28 23:59:08 +02:00
parent 49706c603d
commit e941dc0f21
42 changed files with 464 additions and 71 deletions

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -16,4 +16,8 @@ val coreApiModule = module {
val provider: ServiceProvider = get()
provider.communityService
}
single {
val provider: ServiceProvider = get()
provider.authService
}
}

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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>
}

View File

@ -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)
}
}

View File

@ -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]
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -0,0 +1,5 @@
package com.github.diegoberaldin.racconforlemmy.core_utils
expect object Log {
fun d(message: String)
}

View File

@ -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)
}
}

View 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
}
}

View 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

View 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

View File

@ -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(),
)
}
}

View File

@ -0,0 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.domain_identity.repository
interface ApiConfigurationRepository {
fun getInstance(): String
fun changeInstance(value: String)
}

View File

@ -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>
}

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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, "")
}
}

View File

@ -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()
}

View File

@ -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()
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -14,6 +14,7 @@ actual val homeTabModule = module {
mvi = DefaultMviModel(HomeScreenMviModel.UiState()),
postsRepository = get(),
apiConfigRepository = get(),
identityRepository = get(),
)
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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),

View File

@ -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()
}

View File

@ -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(),

View File

@ -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(),
)
}
}

View File

@ -36,4 +36,5 @@ include(":core-api")
include(":domain-post")
include(":domain-post:repository")
include(":domain-post:data")
include(":domain-identity")
include(":core-md")

View File

@ -58,6 +58,7 @@ kotlin {
implementation(projects.coreAppearance)
implementation(projects.corePreferences)
implementation(projects.coreApi)
implementation(projects.domainIdentity)
api(projects.resources)
api(projects.featureHome)

View File

@ -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,

View File

@ -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,