diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt index c72e0ea6..d92f1773 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt @@ -222,7 +222,6 @@ fun LobstersPostsScreen( ) { dest -> UserProfile( username = dest.username, - getProfile = viewModel::getUserProfile, contentPadding = contentPadding, openUserProfile = { clawBackStack.add(User(it)) }, ) diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt index 149a4489..f4b69b5b 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt @@ -161,17 +161,6 @@ constructor( suspend fun getLinkMetadata(url: String) = withContext(ioDispatcher) { linkMetadataRepository.getLinkMetadata(url) } - suspend fun getUserProfile(username: String) = - withContext(ioDispatcher) { - when (val result = api.getUser(username)) { - is Success -> result.value - is Failure.NetworkFailure -> throw result.error - is Failure.UnknownFailure -> throw result.error - is Failure.HttpFailure -> throw result.toError() - is Failure.ApiFailure -> throw IOException("API returned an invalid response") - } - } - suspend fun importPosts(input: InputStream) = dataTransferRepository.importPosts(input) suspend fun exportPostsAsJson(output: OutputStream) = diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 09fdaf3f..351998f0 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -25,6 +25,8 @@ android { namespace = "dev.msfjarvis.claw.common" } +whetstone.addOns.compose = true + androidComponents { beforeVariants { (it as HasUnitTestBuilder).enableUnitTest = false } } anvil { generateDaggerFactories.set(true) } @@ -40,6 +42,7 @@ dependencies { api(libs.androidx.compose.ui) api(libs.dagger) api(libs.javax.inject) + api(projects.api) api(projects.core) api(projects.database.core) api(projects.model) diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt index 9da087ac..9005d237 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfile.kt @@ -20,17 +20,14 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.unit.dp -import com.github.michaelbull.result.coroutines.runSuspendCatching -import com.github.michaelbull.result.fold -import dev.msfjarvis.claw.common.NetworkState +import com.deliveryhero.whetstone.compose.injectedViewModel import dev.msfjarvis.claw.common.NetworkState.Error import dev.msfjarvis.claw.common.NetworkState.Loading import dev.msfjarvis.claw.common.NetworkState.Success @@ -44,35 +41,25 @@ import dev.msfjarvis.claw.model.User @Composable fun UserProfile( username: String, - getProfile: suspend (username: String) -> User, - openUserProfile: (String) -> Unit, contentPadding: PaddingValues, + openUserProfile: (String) -> Unit, modifier: Modifier = Modifier, + viewModel: UserProfileViewModel = injectedViewModel(), ) { - val user by - produceState(Loading) { - runSuspendCatching { getProfile(username) } - .fold( - success = { profile -> value = Success(profile) }, - failure = { - value = Error(error = it, description = "Failed to load profile for $username") - }, - ) - } - when (user) { + LaunchedEffect(username) { viewModel.loadProfile(username) } + when (val state = viewModel.userProfile) { is Success<*> -> { UserProfileInternal( - user = (user as Success).data, + user = (state as Success).data, openUserProfile = openUserProfile, modifier = modifier.padding(contentPadding), ) } is Error -> { - val error = user as Error Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) { NetworkError( - label = error.description, - error = error.error, + label = state.description, + error = state.error, modifier = Modifier.align(Alignment.Center), ) } diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfileViewModel.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfileViewModel.kt new file mode 100644 index 00000000..5fb520d4 --- /dev/null +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/user/UserProfileViewModel.kt @@ -0,0 +1,64 @@ +/* + * Copyright © Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.common.user + +import android.app.Application +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.AndroidViewModel +import com.deliveryhero.whetstone.app.ApplicationScope +import com.deliveryhero.whetstone.viewmodel.ContributesViewModel +import com.github.michaelbull.result.coroutines.runSuspendCatching +import com.github.michaelbull.result.fold +import com.slack.eithernet.ApiResult +import com.slack.eithernet.ApiResult.Failure +import com.squareup.anvil.annotations.optional.ForScope +import dev.msfjarvis.claw.api.LobstersApi +import dev.msfjarvis.claw.api.toError +import dev.msfjarvis.claw.common.NetworkState +import dev.msfjarvis.claw.common.NetworkState.Error +import dev.msfjarvis.claw.common.NetworkState.Loading +import dev.msfjarvis.claw.common.NetworkState.Success +import dev.msfjarvis.claw.core.injection.IODispatcher +import dev.msfjarvis.claw.model.User +import java.io.IOException +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +@ContributesViewModel +class UserProfileViewModel +@Inject +constructor( + private val api: LobstersApi, + @IODispatcher private val ioDispatcher: CoroutineDispatcher, + @ForScope(ApplicationScope::class) context: Context, +) : AndroidViewModel(context as Application) { + + var userProfile by mutableStateOf(Loading) + + suspend fun loadProfile(username: String) { + userProfile = + runSuspendCatching { + withContext(ioDispatcher) { + when (val result = api.getUser(username)) { + is ApiResult.Success -> result.value + is Failure.NetworkFailure -> throw result.error + is Failure.UnknownFailure -> throw result.error + is Failure.HttpFailure -> throw result.toError() + is Failure.ApiFailure -> throw IOException("API returned an invalid response") + } + } + } + .fold( + success = { profile -> Success(profile) }, + failure = { Error(error = it, description = "Failed to load profile for $username") }, + ) + } +}