diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a2968c5..72385b5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,5 @@
import de.fayard.refreshVersions.core.versionFor
-
plugins {
id("com.android.application")
kotlin("android")
@@ -68,6 +67,7 @@ android {
excludes += "META-INF/LICENSE.md"
excludes += "META-INF/LICENSE-notice.md"
excludes += "META-INF/LGPL2.1"
+ excludes += "META-INF/com/android/build/gradle/aar-metadata.properties"
excludes += "win32-x86/attach_hotspot_windows.dll"
excludes += "win32-x86-64/attach_hotspot_windows.dll"
}
@@ -119,9 +119,9 @@ dependencies {
implementation(Libs.lottie_compose)
- implementation(Libs.orbit_mvi_core)
- implementation(Libs.orbit_mvi_viewmodel)
- testImplementation(Libs.orbit_mvi_test)
+ implementation(Libs.mavericks_compose)
+ testImplementation(Libs.mavericks_testing)
+ testImplementation(Libs.mavericks_mocking)
kapt(Libs.room_compiler)
testImplementation(Libs.room_testing)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 90c6843..8b4f311 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -47,6 +47,9 @@
+
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/AppNavigation.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/AppNavigation.kt
index 7b1f435..0bcb952 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/app/AppNavigation.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/AppNavigation.kt
@@ -1,22 +1,20 @@
package com.ericampire.android.androidstudycase.app
import androidx.compose.material.ExperimentalMaterialApi
-import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.ericampire.android.androidstudycase.presentation.screen.explore.ui.ExploreScreen
import com.ericampire.android.androidstudycase.presentation.screen.home.ui.HomeScreen
+import com.ericampire.android.androidstudycase.presentation.screen.login.ui.LoginScreen
import com.ericampire.android.androidstudycase.presentation.screen.preview.ui.PreviewScreen
import com.ericampire.android.androidstudycase.util.Destination
import com.google.accompanist.pager.ExperimentalPagerApi
+@ExperimentalMaterialApi
fun NavGraphBuilder.addHomeScreen(navController: NavController) {
composable(Destination.Home.route) {
- HomeScreen(
- navController = navController,
- viewModel = hiltViewModel()
- )
+ HomeScreen(navController = navController)
}
}
@@ -24,18 +22,20 @@ fun NavGraphBuilder.addHomeScreen(navController: NavController) {
@ExperimentalPagerApi
fun NavGraphBuilder.addExploreScreen(navController: NavController) {
composable(Destination.Explore.route) {
- ExploreScreen(
- navController = navController,
- viewModel = hiltViewModel()
- )
+ ExploreScreen(navController = navController)
+ }
+}
+
+@ExperimentalMaterialApi
+@ExperimentalPagerApi
+fun NavGraphBuilder.addLoginScreen(navController: NavController) {
+ composable(Destination.Login.route) {
+ LoginScreen(navController = navController)
}
}
fun NavGraphBuilder.addPreviewScreen(navController: NavController) {
composable(Destination.Preview.route) {
- PreviewScreen(
- navController = navController,
- viewModel = hiltViewModel()
- )
+ PreviewScreen(navController = navController)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/di/CoroutineModule.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/di/CoroutineModule.kt
index 83f3cd9..5cc6dda 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/app/di/CoroutineModule.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/di/CoroutineModule.kt
@@ -9,13 +9,18 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
@Module
@InstallIn(SingletonComponent::class)
object CoroutineModule {
+ @Provides
+ fun provideCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob())
+
@Provides
@IoDispatcher
fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/di/ViewModelModule.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/di/ViewModelModule.kt
index 3bdceee..1400e94 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/app/di/ViewModelModule.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/di/ViewModelModule.kt
@@ -1,47 +1,30 @@
package com.ericampire.android.androidstudycase.app.di
-import com.ericampire.android.androidstudycase.domain.usecase.*
+import com.ericampire.android.androidstudycase.app.hilt.AssistedViewModelFactory
+import com.ericampire.android.androidstudycase.app.hilt.MavericksViewModelComponent
+import com.ericampire.android.androidstudycase.app.hilt.ViewModelKey
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewModel
import com.ericampire.android.androidstudycase.presentation.screen.home.business.HomeViewModel
-import com.ericampire.android.androidstudycase.presentation.screen.preview.business.PreviewViewModel
+import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.hilt.InstallIn
-import dagger.hilt.android.components.ViewModelComponent
+import dagger.multibindings.IntoMap
@Module
-@InstallIn(ViewModelComponent::class)
-object ViewModelModule {
+@InstallIn(MavericksViewModelComponent::class)
+interface ViewModelModule {
- @Provides
+ @Binds
+ @IntoMap
+ @ViewModelKey(HomeViewModel::class)
fun provideHomeViewModel(
- findFeaturedAnimatorUseCase: FindFeaturedAnimatorUseCase,
- findFeaturedBlogUseCase: FindFeaturedBlogUseCase
- ): HomeViewModel {
- return HomeViewModel(
- findFeaturedAnimatorUseCase = findFeaturedAnimatorUseCase,
- findFeaturedBlogUseCase = findFeaturedBlogUseCase
- )
- }
+ factory: HomeViewModel.Factory
+ ): AssistedViewModelFactory<*, *>
- @Provides
+ @Binds
+ @IntoMap
+ @ViewModelKey(ExploreViewModel::class)
fun provideExploreViewModel(
- findFeaturedAnimatorUseCase: FindFeaturedAnimatorUseCase,
- findFeaturedBlogUseCase: FindFeaturedBlogUseCase
- ): PreviewViewModel {
- return PreviewViewModel()
- }
-
- @Provides
- fun providePreview(
- findPopularLottieFileUseCase: FindPopularLottieFileUseCase,
- findRecentLottieFileUseCase: FindRecentLottieFileUseCase,
- findFeaturedLottieFileUseCase: FindFeaturedLottieFileUseCase
- ): ExploreViewModel {
- return ExploreViewModel(
- findPopularLottieFileUseCase = findPopularLottieFileUseCase,
- findRecentLottieFileUseCase = findRecentLottieFileUseCase,
- findFeaturedLottieFileUseCase = findFeaturedLottieFileUseCase
- )
- }
+ factory: ExploreViewModel.Factory
+ ): AssistedViewModelFactory<*, *>
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/AssistedViewModelFactory.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/AssistedViewModelFactory.kt
new file mode 100644
index 0000000..831d8e5
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/AssistedViewModelFactory.kt
@@ -0,0 +1,9 @@
+package com.ericampire.android.androidstudycase.app.hilt
+
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksViewModel
+
+
+interface AssistedViewModelFactory, S : MavericksState> {
+ fun create(state: S): VM
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/HiltMavericksViewModelFactory.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/HiltMavericksViewModelFactory.kt
new file mode 100644
index 0000000..f683dcd
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/HiltMavericksViewModelFactory.kt
@@ -0,0 +1,59 @@
+package com.ericampire.android.androidstudycase.app.hilt
+
+
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import dagger.hilt.DefineComponent
+import dagger.hilt.EntryPoint
+import dagger.hilt.EntryPoints
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+inline fun , S : MavericksState> hiltMavericksViewModelFactory() =
+ HiltMavericksViewModelFactory(VM::class.java)
+
+class HiltMavericksViewModelFactory, S : MavericksState>(
+ private val viewModelClass: Class>
+) : MavericksViewModelFactory {
+
+ override fun create(viewModelContext: ViewModelContext, state: S): VM {
+ // We want to create the ViewModelComponent. In order to do that, we need to get its parent: ActivityComponent.
+ val componentBuilder =
+ EntryPoints.get(viewModelContext.app(), CreateMavericksViewModelComponent::class.java)
+ .mavericksViewModelComponentBuilder()
+ val viewModelComponent = componentBuilder.build()
+ val viewModelFactoryMap = EntryPoints.get(
+ viewModelComponent,
+ HiltMavericksEntryPoint::class.java
+ ).viewModelFactories
+ val viewModelFactory = viewModelFactoryMap[viewModelClass]
+
+ @Suppress("UNCHECKED_CAST")
+ val castedViewModelFactory = viewModelFactory as? AssistedViewModelFactory
+ return castedViewModelFactory?.create(state) as VM
+ }
+}
+
+
+@MavericksViewModelScoped
+@DefineComponent(parent = SingletonComponent::class)
+interface MavericksViewModelComponent
+
+@DefineComponent.Builder
+interface MavericksViewModelComponentBuilder {
+ fun build(): MavericksViewModelComponent
+}
+
+@EntryPoint
+@InstallIn(SingletonComponent::class)
+interface CreateMavericksViewModelComponent {
+ fun mavericksViewModelComponentBuilder(): MavericksViewModelComponentBuilder
+}
+
+@EntryPoint
+@InstallIn(MavericksViewModelComponent::class)
+interface HiltMavericksEntryPoint {
+ val viewModelFactories: Map>, AssistedViewModelFactory<*, *>>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/MavericksViewModelScoped.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/MavericksViewModelScoped.kt
new file mode 100644
index 0000000..991e902
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/MavericksViewModelScoped.kt
@@ -0,0 +1,6 @@
+package com.ericampire.android.androidstudycase.app.hilt
+
+import javax.inject.Scope
+
+@Scope
+annotation class MavericksViewModelScoped
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/ViewModelKey.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/ViewModelKey.kt
new file mode 100644
index 0000000..ac17f4f
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/ViewModelKey.kt
@@ -0,0 +1,13 @@
+package com.ericampire.android.androidstudycase.app.hilt
+
+import com.airbnb.mvrx.MavericksViewModel
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+/**
+ * A [MapKey] for populating a map of ViewModels and their factories.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION)
+@MapKey
+annotation class ViewModelKey(val value: KClass>)
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/ViewModelScopedClass.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/ViewModelScopedClass.kt
new file mode 100644
index 0000000..7582f8d
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/hilt/ViewModelScopedClass.kt
@@ -0,0 +1,13 @@
+package com.ericampire.android.androidstudycase.app.hilt
+
+import java.util.concurrent.atomic.AtomicInteger
+import javax.inject.Inject
+
+@MavericksViewModelScoped
+class ViewModelScopedClass @Inject constructor() {
+ val id = instanceId.incrementAndGet()
+
+ companion object {
+ private val instanceId = AtomicInteger(0)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/initializer/MavericksInitializer.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/initializer/MavericksInitializer.kt
new file mode 100644
index 0000000..5f8fb4d
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/initializer/MavericksInitializer.kt
@@ -0,0 +1,15 @@
+package com.ericampire.android.androidstudycase.app.initializer
+
+import android.content.Context
+import androidx.startup.Initializer
+import com.airbnb.mvrx.Mavericks
+
+class MavericksInitializer : Initializer {
+ override fun create(context: Context) {
+ Mavericks.initialize(context)
+ }
+
+ override fun dependencies(): MutableList>> {
+ return mutableListOf()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/app/room/AppDatabase.kt b/app/src/main/java/com/ericampire/android/androidstudycase/app/room/AppDatabase.kt
index 429d1db..1937ed5 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/app/room/AppDatabase.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/app/room/AppDatabase.kt
@@ -13,7 +13,7 @@ import com.ericampire.android.androidstudycase.domain.entity.User
@Database(
entities = [Blog::class, Animator::class, Lottiefile::class, User::class],
- version = 2,
+ version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/custom/LoadingAnimation.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/custom/LoadingAnimation.kt
new file mode 100644
index 0000000..0c36a6f
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/custom/LoadingAnimation.kt
@@ -0,0 +1,199 @@
+package com.ericampire.android.androidstudycase.presentation.custom
+
+import androidx.compose.animation.core.*
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.ericampire.android.androidstudycase.presentation.theme.AndroidStudyCaseTheme
+import com.ericampire.android.androidstudycase.presentation.theme.AppColor
+
+
+@Composable
+fun LoadingAnimation(
+ modifier: Modifier = Modifier,
+ waveColor: Color = AppColor.WhiteTransparent,
+ arcColor: Color = Color.White
+) {
+ var currentRotation by remember { mutableStateOf(0f) }
+ val rotation = remember { Animatable(currentRotation) }
+
+ LaunchedEffect(true) {
+ rotation.animateTo(
+ targetValue = currentRotation + 360f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(2000, easing = LinearEasing),
+ repeatMode = RepeatMode.Restart
+ ),
+ block = {
+ currentRotation = value
+ }
+ )
+ }
+
+ Surface(
+ modifier = modifier.size(200.dp),
+ color = Color.Transparent,
+ content = {
+ Box(
+ contentAlignment = Alignment.Center,
+ content = {
+ Canvas(modifier = Modifier.size(100.dp)) {
+ drawCircle(
+ style = Stroke(width = 10f),
+ color = waveColor
+ )
+
+ drawArc(
+ color = arcColor,
+ startAngle = rotation.value,
+ sweepAngle = 30f,
+ useCenter = false,
+ style = Stroke(width = 10f),
+ )
+ }
+
+ Surface(
+ modifier = Modifier.size(90.dp),
+ shape = CircleShape,
+ color = Color.Transparent,
+ content = {
+ WaveView(
+ waveColor = waveColor,
+ timeSpec = 5000,
+ init = true,
+ initValue = 1f,
+ targetValue = 0f
+ )
+ WaveView(
+ waveColor = waveColor,
+ timeSpec = 5000,
+ init = true,
+ initValue = 0f,
+ targetValue = 1f,
+ waveWidth = 200
+ )
+ WaveView(
+ waveColor = waveColor,
+ timeSpec = 2000,
+ init = true,
+ initValue = 0f,
+ targetValue = 1f,
+ waveWidth = 350,
+ dxTimeSpec = 2000
+ )
+ }
+ )
+ }
+ )
+ }
+ )
+}
+
+@Composable
+fun WaveView(
+ modifier: Modifier = Modifier,
+ timeSpec: Long,
+ initValue: Float,
+ targetValue: Float,
+ init: Boolean,
+ waveWidth: Int = 250,
+ dxTimeSpec: Int = 4000,
+ waveColor: Color,
+) {
+
+ val deltaXAnim = rememberInfiniteTransition()
+ val dx by deltaXAnim.animateFloat(
+ initialValue = initValue,
+ targetValue = targetValue,
+ animationSpec = infiniteRepeatable(
+ animation = tween(dxTimeSpec, easing = LinearEasing)
+ )
+ )
+
+ val dy by deltaXAnim.animateFloat(
+ initialValue = 100f,
+ targetValue = 0f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(4000, easing = LinearEasing),
+ repeatMode = RepeatMode.Reverse
+ )
+ )
+
+ val screenWidthPx = with(LocalDensity.current) {
+ (LocalConfiguration.current.screenHeightDp * density) - 80.dp.toPx()
+ }
+ val animTranslate by animateFloatAsState(
+ targetValue = if (init) 0f else screenWidthPx,
+ animationSpec = TweenSpec(if (init) 0 else timeSpec.toInt(), easing = LinearEasing)
+ )
+
+ val waveHeight by animateFloatAsState(
+ targetValue = if (init) 80f else 0f,
+ animationSpec = TweenSpec(if (init) 0 else timeSpec.toInt(), easing = LinearEasing)
+ )
+
+ val path = Path()
+
+ Canvas(
+ modifier = modifier.fillMaxSize(),
+ onDraw = {
+ translate(top = animTranslate) {
+ drawPath(path = path, color = waveColor)
+ path.reset()
+ val halfWaveWidth = waveWidth / 2
+ path.moveTo(-waveWidth + (waveWidth * dx), dy.dp.toPx())
+
+ for (i in -waveWidth..(size.width.toInt() + waveWidth) step waveWidth) {
+ path.relativeQuadraticBezierTo(
+ halfWaveWidth.toFloat() / 2,
+ -waveHeight,
+ halfWaveWidth.toFloat(),
+ 0f
+ )
+ path.relativeQuadraticBezierTo(
+ halfWaveWidth.toFloat() / 2,
+ waveHeight,
+ halfWaveWidth.toFloat(),
+ 0f
+ )
+ }
+
+ path.lineTo(size.width, size.height)
+ path.lineTo(0f, size.height)
+ path.close()
+ }
+ }
+ )
+}
+
+@Preview
+@Composable
+fun LoadingAnimationPreview() {
+ AndroidStudyCaseTheme() {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.fillMaxSize(),
+ content = {
+ LoadingAnimation(
+ waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
+ arcColor = MaterialTheme.colors.primaryVariant
+ )
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreEffect.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreEffect.kt
deleted file mode 100644
index 36a85fb..0000000
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreEffect.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.ericampire.android.androidstudycase.presentation.screen.explore.business
-
-sealed interface ExploreEffect {
- data class ShowErrorMessage(val message: String) : ExploreEffect
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewModel.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewModel.kt
index 7f97673..3f12713 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewModel.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewModel.kt
@@ -1,33 +1,29 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.business
-import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.ericampire.android.androidstudycase.app.hilt.AssistedViewModelFactory
+import com.ericampire.android.androidstudycase.app.hilt.hiltMavericksViewModelFactory
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedLottieFileUseCase
import com.ericampire.android.androidstudycase.domain.usecase.FindPopularLottieFileUseCase
import com.ericampire.android.androidstudycase.domain.usecase.FindRecentLottieFileUseCase
import com.ericampire.android.androidstudycase.util.Result
+import com.ericampire.android.androidstudycase.util.data
import com.ericampire.android.androidstudycase.util.mvi.BaseViewModel
-import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import org.orbitmvi.orbit.Container
-import org.orbitmvi.orbit.syntax.simple.intent
-import org.orbitmvi.orbit.syntax.simple.postSideEffect
-import org.orbitmvi.orbit.syntax.simple.reduce
-import org.orbitmvi.orbit.viewmodel.container
-import javax.inject.Inject
-@HiltViewModel
-class ExploreViewModel @Inject constructor(
+class ExploreViewModel @AssistedInject constructor(
+ @Assisted initialState: ExploreViewState,
private val findPopularLottieFileUseCase: FindPopularLottieFileUseCase,
private val findRecentLottieFileUseCase: FindRecentLottieFileUseCase,
private val findFeaturedLottieFileUseCase: FindFeaturedLottieFileUseCase
-) : BaseViewModel() {
-
- override val container: Container
- get() = container(initialState = ExploreViewState())
+) : BaseViewModel(initialState) {
init {
viewModelScope.launch {
@@ -41,21 +37,21 @@ class ExploreViewModel @Inject constructor(
}
}
- private fun Flow>>.fetchData() = intent(registerIdling = false) {
- collect { result ->
- when (result) {
- is Result.Error -> {
- val errorMessage = result.exception.localizedMessage ?: "Unknown Error"
- reduce { state.copy(isLoading = false) }
- postSideEffect(ExploreEffect.ShowErrorMessage(errorMessage))
- }
- Result.Loading -> reduce {
- state.copy(isLoading = true)
- }
- is Result.Success -> reduce {
- state.copy(files = result.data, isLoading = false)
- }
+ private fun Flow>>.fetchData() {
+ viewModelScope.launch {
+ map {
+ it.data ?: emptyList()
+ }.execute {
+ copy(files = it)
}
}
}
+
+ @AssistedFactory
+ interface Factory : AssistedViewModelFactory {
+ override fun create(state: ExploreViewState): ExploreViewModel
+ }
+
+ companion object : MavericksViewModelFactory
+ by hiltMavericksViewModelFactory()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewState.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewState.kt
index af0a64e..ec34fe9 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewState.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/business/ExploreViewState.kt
@@ -1,8 +1,10 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.business
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
data class ExploreViewState(
- val files: List = emptyList(),
- val isLoading: Boolean = false
-)
+ val files: Async> = Uninitialized,
+) : MavericksState
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/ExploreScreen.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/ExploreScreen.kt
index 357c471..1263caa 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/ExploreScreen.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/ExploreScreen.kt
@@ -1,6 +1,6 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.ui
-import android.widget.Toast
+import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -18,45 +18,43 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
import com.ericampire.android.androidstudycase.R
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
-import com.ericampire.android.androidstudycase.presentation.custom.LoadingView
+import com.ericampire.android.androidstudycase.presentation.custom.LoadingAnimation
import com.ericampire.android.androidstudycase.presentation.custom.TopActionBar
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreAction
-import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreEffect
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewModel
+import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewState
import com.ericampire.android.androidstudycase.presentation.theme.AppColor
import com.google.accompanist.pager.ExperimentalPagerApi
+import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
+import timber.log.Timber
@ExperimentalMaterialApi
@ExperimentalPagerApi
@Composable
fun ExploreScreen(
navController: NavController,
- viewModel: ExploreViewModel
+ viewModel: ExploreViewModel = mavericksViewModel()
) {
val coroutineScope = rememberCoroutineScope()
- val state by viewModel.container.stateFlow.collectAsState()
+ val state by viewModel.collectAsState(ExploreViewState::files)
val context = LocalContext.current
val tabItems = stringArrayResource(id = R.array.explore_item)
val pagerState = rememberPagerState(pageCount = tabItems.size)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collect {
- when (it) {
- is ExploreEffect.ShowErrorMessage -> {
- Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
- }
- }
- }
- }
-
LaunchedEffect(viewModel) {
viewModel.submitAction(ExploreAction.FindRecentFile)
}
@@ -122,38 +120,70 @@ fun ExploreScreen(
)
},
content = { contentPadding ->
- Box(
- modifier = Modifier
- .padding(contentPadding)
- .fillMaxSize(),
- contentAlignment = Alignment.Center,
- content = {
- if (state.isLoading) {
- LoadingView()
+ Crossfade(modifier = Modifier.padding(contentPadding), targetState = state) {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ content = {
+ when (it) {
+ Uninitialized -> {
+ LoadingAnimation(
+ waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
+ arcColor = MaterialTheme.colors.primaryVariant
+ )
+ }
+ is Loading -> {
+ LoadingAnimation(
+ waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
+ arcColor = MaterialTheme.colors.primaryVariant
+ )
+ }
+ is Success -> {
+ val animations = it.invoke()
+ HorizontalPager(state = pagerState) {
+ ExploreContent(files = animations)
+ }
+ }
+ is Fail -> {
+ Timber.e(it.error.localizedMessage)
+ }
+ }
}
- if (state.files.isNotEmpty()) {
- ExploreContent(files = state.files)
- }
- }
- )
+ )
+ }
}
)
}
@ExperimentalMaterialApi
@Composable
-fun ExploreContent(
+private fun ExploreContent(
modifier: Modifier = Modifier,
files: List
) {
LazyColumn(
modifier = modifier.fillMaxSize(),
content = {
+
+ item {
+ Divider(
+ modifier = Modifier
+ .background(MaterialTheme.colors.surface)
+ .height(18.dp)
+ )
+ }
+
items(items = files, key = { it.toString() }) { lottieFile ->
LottieFileItemView(
lottiefile = lottieFile,
onClick = {}
)
+
+ Divider(
+ modifier = Modifier
+ .background(MaterialTheme.colors.surface)
+ .height(18.dp)
+ )
}
}
)
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/LottieFileItem.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/LottieFileItem.kt
index 0d5a6d4..3dae165 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/LottieFileItem.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/explore/ui/LottieFileItem.kt
@@ -6,14 +6,16 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.*
+import androidx.compose.material.icons.rounded.ChatBubble
+import androidx.compose.material.icons.rounded.CreateNewFolder
+import androidx.compose.material.icons.rounded.Favorite
+import androidx.compose.material.icons.rounded.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -72,12 +74,10 @@ fun LottieFileItemView(
Text(
text = lottiefile.name,
style = MaterialTheme.typography.h6,
- textAlign = TextAlign.Center,
)
Text(
text = lottiefile.createdBy?.name ?: "",
style = MaterialTheme.typography.caption,
- textAlign = TextAlign.Center,
)
}
)
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeAction.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeAction.kt
index d285d5d..0da54af 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeAction.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeAction.kt
@@ -1,3 +1,5 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
-sealed interface HomeAction
\ No newline at end of file
+sealed interface HomeAction {
+ object FetchData : HomeAction
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeEffect.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeEffect.kt
deleted file mode 100644
index 5eafec2..0000000
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeEffect.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.ericampire.android.androidstudycase.presentation.screen.home.business
-
-sealed interface HomeEffect
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewModel.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewModel.kt
index 8e61ae6..8e7b8bf 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewModel.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewModel.kt
@@ -1,30 +1,68 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
-import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.ericampire.android.androidstudycase.app.hilt.AssistedViewModelFactory
+import com.ericampire.android.androidstudycase.app.hilt.hiltMavericksViewModelFactory
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedAnimatorUseCase
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedBlogUseCase
+import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedLottieFileUseCase
+import com.ericampire.android.androidstudycase.domain.usecase.FindUsersUseCase
+import com.ericampire.android.androidstudycase.util.data
import com.ericampire.android.androidstudycase.util.mvi.BaseViewModel
-import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
-import org.orbitmvi.orbit.Container
-import org.orbitmvi.orbit.viewmodel.container
-import javax.inject.Inject
-@HiltViewModel
-class HomeViewModel @Inject constructor(
+class HomeViewModel @AssistedInject constructor(
+ @Assisted initialState: HomeViewState,
private val findFeaturedBlogUseCase: FindFeaturedBlogUseCase,
- private val findFeaturedAnimatorUseCase: FindFeaturedAnimatorUseCase
-) : BaseViewModel() {
+ private val findFeaturedAnimatorUseCase: FindFeaturedAnimatorUseCase,
+ private val findUsersUseCase: FindUsersUseCase,
+ private val findFeaturedLottieFileUseCase: FindFeaturedLottieFileUseCase
+) : BaseViewModel(initialState) {
- override val container: Container
- get() = container(initialState = HomeViewState())
init {
viewModelScope.launch {
pendingAction.collectLatest { action ->
-
+ when (action) {
+ HomeAction.FetchData -> fetchData()
+ }
}
}
}
+
+ private fun fetchData() {
+ val userFlow = findUsersUseCase(Unit)
+ val storiesFlow = findFeaturedBlogUseCase(Unit)
+ val animatorsFlow = findFeaturedAnimatorUseCase(Unit)
+ val animationsFlow = findFeaturedLottieFileUseCase(Unit)
+
+ combine(
+ userFlow,
+ storiesFlow,
+ animationsFlow,
+ animatorsFlow
+ ) { user, stories, anim, animators ->
+ HomeContentData(
+ user = user.data?.firstOrNull(),
+ blog = stories.data ?: emptyList(),
+ featuredAnimators = animators.data ?: emptyList(),
+ featuredLottieFile = anim.data ?: emptyList(),
+ )
+ }.execute {
+ copy(contentData = it)
+ }
+ }
+
+ @AssistedFactory
+ interface Factory : AssistedViewModelFactory {
+ override fun create(state: HomeViewState): HomeViewModel
+ }
+
+ companion object : MavericksViewModelFactory
+ by hiltMavericksViewModelFactory()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewState.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewState.kt
index 3d7e4d0..0422c7c 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewState.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/business/HomeViewState.kt
@@ -1,7 +1,20 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+import com.ericampire.android.androidstudycase.domain.entity.Animator
import com.ericampire.android.androidstudycase.domain.entity.Blog
+import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
+import com.ericampire.android.androidstudycase.domain.entity.User
data class HomeViewState(
- val blog: List = emptyList()
-)
+ val contentData: Async = Uninitialized
+) : MavericksState
+
+data class HomeContentData(
+ val blog: List = emptyList(),
+ val featuredAnimators: List = emptyList(),
+ val featuredLottieFile: List = emptyList(),
+ val user: User? = null,
+) : MavericksState
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/FeaturedLottieFileView.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/FeaturedLottieFileView.kt
index fec6709..72c53eb 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/FeaturedLottieFileView.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/FeaturedLottieFileView.kt
@@ -45,7 +45,7 @@ fun FeaturedLottieFileView(
Box(
modifier = modifier
.background(Color.White)
- .height(200.dp)
+ .height(170.dp)
.width(170.dp),
content = {
LottieAnimation(
@@ -71,7 +71,9 @@ fun FeaturedLottieFileView(
Text(
text = lottiefile.createdBy?.name ?: "",
maxLines = 1,
- style = MaterialTheme.typography.caption,
+ style = MaterialTheme.typography.caption.copy(
+ color = Color.Gray
+ ),
)
}
)
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/HomeScreen.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/HomeScreen.kt
index b028dd8..59e7f9d 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/HomeScreen.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/home/ui/HomeScreen.kt
@@ -1,13 +1,206 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.ui
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.compose.collectAsState
+import com.airbnb.mvrx.compose.mavericksViewModel
+import com.ericampire.android.androidstudycase.R
+import com.ericampire.android.androidstudycase.presentation.custom.CustomImageView
+import com.ericampire.android.androidstudycase.presentation.custom.LoadingAnimation
+import com.ericampire.android.androidstudycase.presentation.custom.TopActionBar
+import com.ericampire.android.androidstudycase.presentation.screen.home.business.HomeAction
+import com.ericampire.android.androidstudycase.presentation.screen.home.business.HomeContentData
import com.ericampire.android.androidstudycase.presentation.screen.home.business.HomeViewModel
+import com.ericampire.android.androidstudycase.presentation.screen.home.business.HomeViewState
+import com.ericampire.android.androidstudycase.presentation.theme.AppColor
+import com.ericampire.android.androidstudycase.util.Destination
+import timber.log.Timber
+@ExperimentalMaterialApi
@Composable
fun HomeScreen(
navController: NavController,
- viewModel: HomeViewModel
+ viewModel: HomeViewModel = mavericksViewModel()
) {
-}
\ No newline at end of file
+ val state by viewModel.collectAsState(HomeViewState::contentData)
+
+ LaunchedEffect(viewModel) {
+ viewModel.submitAction(HomeAction.FetchData)
+ }
+
+ Scaffold(
+ topBar = {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(color = AppColor.Black001),
+ content = {
+ TopActionBar()
+ }
+ )
+ },
+ content = { contentPadding ->
+ Crossfade(modifier = Modifier.padding(contentPadding), targetState = state) {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ content = {
+ when (it) {
+ Uninitialized -> {
+ LoadingAnimation(
+ waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
+ arcColor = MaterialTheme.colors.primaryVariant
+ )
+ }
+ is Loading -> {
+ LoadingAnimation(
+ waveColor = MaterialTheme.colors.primary.copy(alpha = 0.5f),
+ arcColor = MaterialTheme.colors.primaryVariant
+ )
+ }
+ is Success -> {
+ HomeContent(
+ state = it.invoke(),
+ onLoginClick = {
+ navController.navigate(Destination.Login.route)
+ }
+ )
+ }
+ is Fail -> {
+ Timber.e(it.error.localizedMessage)
+ }
+ }
+ }
+ )
+ }
+ }
+ )
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun HomeContent(
+ modifier: Modifier = Modifier,
+ state: HomeContentData,
+ onLoginClick: () -> Unit
+) {
+ LazyColumn(
+ state = rememberLazyListState(),
+ modifier = modifier
+ .fillMaxSize()
+ .animateContentSize(),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ content = {
+ item {
+ if (state.user == null) {
+ UnLoggedUserHeaderView(onLoginClick = onLoginClick)
+ } else {
+ LoggedUserHeaderView(user = state.user)
+ }
+ }
+
+ item {
+ Text(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ maxLines = 1,
+ text = stringResource(id = R.string.txt_featured_animation),
+ style = MaterialTheme.typography.h4.copy(
+ color = MaterialTheme.colors.onSurface
+ ),
+ )
+ }
+
+ item {
+ LazyRow(
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ content = {
+ items(items = state.featuredLottieFile, key = { it.toString() }) { animation ->
+ FeaturedLottieFileView(
+ lottiefile = animation,
+ onClick = {
+ // Todo:
+ }
+ )
+ }
+ }
+ )
+ }
+ item {
+ Text(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ maxLines = 1,
+ text = stringResource(id = R.string.txt_featured_animator),
+ style = MaterialTheme.typography.h4.copy(
+ color = MaterialTheme.colors.onSurface
+ ),
+ )
+ }
+
+ item {
+ LazyRow(
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ content = {
+ items(items = state.featuredAnimators) { animator ->
+ CustomImageView(
+ modifier = Modifier
+ .size(70.dp)
+ .clip(CircleShape),
+ data = animator.avatarUrl,
+ )
+ }
+ }
+ )
+ }
+
+ item {
+ Text(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ maxLines = 1,
+ text = stringResource(id = R.string.txt_latest_story),
+ style = MaterialTheme.typography.h4.copy(
+ color = MaterialTheme.colors.onSurface
+ ),
+ )
+ }
+
+ items(items = state.blog) { blog ->
+ BlogItemView(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ blog = blog,
+ onClick = { }
+ )
+ }
+
+ item {
+ BrowseAllItemView()
+ }
+ }
+ )
+}
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/login/business/LoginViewModel.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/login/business/LoginViewModel.kt
new file mode 100644
index 0000000..6abead9
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/login/business/LoginViewModel.kt
@@ -0,0 +1,10 @@
+package com.ericampire.android.androidstudycase.presentation.screen.login.business
+
+import androidx.lifecycle.ViewModel
+import com.ericampire.android.androidstudycase.domain.usecase.SaveUserUseCase
+import javax.inject.Inject
+
+class LoginViewModel @Inject constructor(
+ private val saveUserUseCase: SaveUserUseCase
+) : ViewModel() {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/login/ui/LoginScreen.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/login/ui/LoginScreen.kt
new file mode 100644
index 0000000..8c863ee
--- /dev/null
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/login/ui/LoginScreen.kt
@@ -0,0 +1,11 @@
+package com.ericampire.android.androidstudycase.presentation.screen.login.ui
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavController
+
+@Composable
+fun LoginScreen(
+ navController: NavController,
+) {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/main/ui/MainScreen.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/main/ui/MainScreen.kt
index 86937f8..30d0794 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/main/ui/MainScreen.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/main/ui/MainScreen.kt
@@ -19,6 +19,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.ericampire.android.androidstudycase.app.addExploreScreen
import com.ericampire.android.androidstudycase.app.addHomeScreen
+import com.ericampire.android.androidstudycase.app.addLoginScreen
import com.ericampire.android.androidstudycase.app.addPreviewScreen
import com.ericampire.android.androidstudycase.util.Destination
import com.google.accompanist.insets.navigationBarsPadding
@@ -84,6 +85,7 @@ fun MainScreen() {
addHomeScreen(navController = navController)
addPreviewScreen(navController = navController)
addExploreScreen(navController = navController)
+ addLoginScreen(navController = navController)
}
)
},
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewEffect.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewEffect.kt
deleted file mode 100644
index 77f62df..0000000
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewEffect.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.ericampire.android.androidstudycase.presentation.screen.preview.business
-
-sealed interface PreviewEffect
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewViewModel.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewViewModel.kt
index 99a60d1..c388032 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewViewModel.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/business/PreviewViewModel.kt
@@ -1,27 +1,12 @@
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
-import androidx.lifecycle.viewModelScope
-import com.ericampire.android.androidstudycase.util.mvi.BaseViewModel
+import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import org.orbitmvi.orbit.Container
-import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@HiltViewModel
class PreviewViewModel @Inject constructor(
-) : BaseViewModel() {
+) : ViewModel() {
- override val container: Container
- get() = container(initialState = PreviewViewState())
-
- init {
- viewModelScope.launch {
- pendingAction.collectLatest { action ->
-
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/ui/PreviewScreen.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/ui/PreviewScreen.kt
index a48cea6..784740b 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/ui/PreviewScreen.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/screen/preview/ui/PreviewScreen.kt
@@ -11,14 +11,10 @@ import com.budiyev.android.codescanner.CodeScanner
import com.budiyev.android.codescanner.CodeScannerView
import com.ericampire.android.androidstudycase.R
import com.ericampire.android.androidstudycase.presentation.custom.LottiePreviewDialog
-import com.ericampire.android.androidstudycase.presentation.screen.preview.business.PreviewViewModel
@Composable
-fun PreviewScreen(
- navController: NavController,
- viewModel: PreviewViewModel
-) {
+fun PreviewScreen(navController: NavController) {
var codeScanner: CodeScanner? = null
var lottieFileUrl by remember { mutableStateOf("") }
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/theme/Color.kt b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/theme/Color.kt
index a358ff4..2b16236 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/presentation/theme/Color.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/presentation/theme/Color.kt
@@ -14,4 +14,5 @@ object AppColor {
val Black001 = Color(0xFF222222)
val BlackOverlay = Color(0x4D000000)
val BlackOverlay001 = Color(0x1A000000)
+ val WhiteTransparent = Color(0x80FFFFFF)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ericampire/android/androidstudycase/util/Destination.kt b/app/src/main/java/com/ericampire/android/androidstudycase/util/Destination.kt
index a953ca4..32c95e0 100644
--- a/app/src/main/java/com/ericampire/android/androidstudycase/util/Destination.kt
+++ b/app/src/main/java/com/ericampire/android/androidstudycase/util/Destination.kt
@@ -7,4 +7,5 @@ sealed class Destination(val route: String, @StringRes val resourceId: Int) {
object Home : Destination("home", R.string.txt_home)
object Explore : Destination("explore", R.string.txt_explore)
object Preview : Destination("preview", R.string.txt_preview)
+ object Login : Destination("login", R.string.txt_login)
}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e5f8fdc..1a664b8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,2 +1,3 @@
+ Featured Animations
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Apps.kt b/buildSrc/src/main/kotlin/Apps.kt
index 4672ac3..9e4b1d3 100644
--- a/buildSrc/src/main/kotlin/Apps.kt
+++ b/buildSrc/src/main/kotlin/Apps.kt
@@ -1,5 +1,5 @@
object Apps {
- const val compileSdk = 30
+ const val compileSdk = 31
const val buildToolsVersion = "30.0.3"
const val applicationId = "com.ericampire.android.androidstudycase"
diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt
index b917ee0..3c1315f 100644
--- a/buildSrc/src/main/kotlin/Libs.kt
+++ b/buildSrc/src/main/kotlin/Libs.kt
@@ -18,7 +18,7 @@ object Libs {
const val activity_compose = "androidx.activity:activity-compose:_"
const val startup_runtime = "androidx.startup:startup-runtime:_"
- const val navigation_compose = "androidx.navigation:navigation-compose:2.4.0-alpha06"
+ const val navigation_compose = "androidx.navigation:navigation-compose:2.4.0-alpha08"
const val appcompat = "androidx.appcompat:appcompat:_"
const val preference_ktx = "androidx.preference:preference-ktx:_"
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index 423e189..8e74804 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -36,8 +36,6 @@ dependencies {
api(platform(Libs.kotlin_coroutine_bom))
api(Libs.kotlin_coroutine_core)
- api(Libs.ktor_client_android)
-
testImplementation(Libs.junit_jupiter_api)
testImplementation(Libs.junit_jupiter_engine)
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/animator/RemoteAnimatorDataSource.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/animator/RemoteAnimatorDataSource.kt
index 25cddfa..a58636b 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/animator/RemoteAnimatorDataSource.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/animator/RemoteAnimatorDataSource.kt
@@ -17,8 +17,11 @@ class RemoteAnimatorDataSource @Inject constructor(
override fun findAll(): Flow>> {
return flow {
try {
- val data = httpClient.get(ApiUrl.Animator.featured)
- emit(Result.Success(data.animatorAnimatorData.featuredAnimators.results))
+ val result = httpClient.get(ApiUrl.Animator.featured)
+ val identifiableData = result.data.animators.results.mapIndexed { index, animator ->
+ animator.copy(id = index.toLong())
+ }
+ emit(Result.Success(identifiableData))
} catch (e: Exception) {
emit(Result.Error(e))
}
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/blog/RemoteBlogDataSource.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/blog/RemoteBlogDataSource.kt
index 5a5557c..f0a97b2 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/blog/RemoteBlogDataSource.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/blog/RemoteBlogDataSource.kt
@@ -17,12 +17,14 @@ class RemoteBlogDataSource @Inject constructor(
return flow {
try {
val data = httpClient.get(ApiUrl.Blog.latest)
- emit(Result.Success(data.blogBlogData.blogPage.blogs))
+ val identifiableData = data.data.blogs.results.mapIndexed { index, blog ->
+ blog.copy(id = index.toLong())
+ }
+ emit(Result.Success(identifiableData))
} catch (e: Exception) {
emit(Result.Error(e))
}
}
-
}
override suspend fun save(blog: Blog) {
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/LocalLottieFileDataSource.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/LocalLottieFileDataSource.kt
index 8df0b6d..3fd06eb 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/LocalLottieFileDataSource.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/LocalLottieFileDataSource.kt
@@ -11,19 +11,19 @@ class LocalLottieFileDataSource @Inject constructor(
private val lottieFileDao: LottieFilesDao
) : LottieFileDataSource {
override fun findRecent(): Flow>> {
- return lottieFileDao.findRecent().map {
+ return lottieFileDao.findByType("recent").map {
Result.Success(it)
}
}
override fun findPopular(): Flow>> {
- return lottieFileDao.findPopular().map {
+ return lottieFileDao.findByType("popular").map {
Result.Success(it)
}
}
override fun findFeatured(): Flow>> {
- return lottieFileDao.findFeatured().map {
+ return lottieFileDao.findByType("featured").map {
Result.Success(it)
}
}
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/RemoteLottieFileDataSource.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/RemoteLottieFileDataSource.kt
index 94ac6af..8016cc7 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/RemoteLottieFileDataSource.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/datasource/lottiefiles/RemoteLottieFileDataSource.kt
@@ -7,6 +7,7 @@ import com.ericampire.android.androidstudycase.util.Result
import io.ktor.client.*
import io.ktor.client.request.*
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
@@ -14,11 +15,14 @@ class RemoteLottieFileDataSource @Inject constructor(
private val httpClient: HttpClient
) : LottieFileDataSource {
- private fun find(url: String): Flow>> {
+ private fun find(
+ url: String,
+ action: suspend FlowCollector>>.(LottieFilesApiResponse) -> Unit
+ ): Flow>> {
return flow {
try {
val data = httpClient.get(url)
- emit(Result.Success(data.lottieFilesLottieFilesData.page.results))
+ action(data)
} catch (e: Exception) {
emit(Result.Error(e))
}
@@ -26,15 +30,24 @@ class RemoteLottieFileDataSource @Inject constructor(
}
override fun findRecent(): Flow>> {
- return find(ApiUrl.LottieFile.recent)
+ return find(ApiUrl.LottieFile.recent) { response ->
+ val animations = response.lottieFilesLottieFilesData.recent?.results ?: emptyList()
+ emit(Result.Success(animations))
+ }
}
override fun findPopular(): Flow>> {
- return find(ApiUrl.LottieFile.popular)
+ return find(ApiUrl.LottieFile.popular) { response ->
+ val animations = response.lottieFilesLottieFilesData.popular?.results ?: emptyList()
+ emit(Result.Success(animations))
+ }
}
override fun findFeatured(): Flow>> {
- return find(ApiUrl.LottieFile.featured)
+ return find(ApiUrl.LottieFile.featured) { response ->
+ val animations = response.lottieFilesLottieFilesData.featured?.results ?: emptyList()
+ emit(Result.Success(animations))
+ }
}
override suspend fun save(lottiefile: Lottiefile) {
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/di/RepositoryModule.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/di/RepositoryModule.kt
index 10ba4d6..f87e61c 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/di/RepositoryModule.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/di/RepositoryModule.kt
@@ -15,10 +15,13 @@ import com.ericampire.android.androidstudycase.domain.repository.AnimatorReposit
import com.ericampire.android.androidstudycase.domain.repository.BlogRepository
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
import com.ericampire.android.androidstudycase.domain.repository.UserRepository
+import com.ericampire.android.androidstudycase.util.IoDispatcher
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
@Module
@@ -29,10 +32,14 @@ object RepositoryModule {
fun provideAnimatorRepository(
localDataSource: LocalAnimatorDataSource,
remoteDataSource: RemoteAnimatorDataSource,
+ @IoDispatcher coroutineDispatcher: CoroutineDispatcher,
+ coroutineScope: CoroutineScope
): AnimatorRepository {
return AnimatorRepositoryImpl(
localDataSource = localDataSource,
- remoteDataSource = remoteDataSource
+ remoteDataSource = remoteDataSource,
+ coroutineDispatcher = coroutineDispatcher,
+ coroutineScope = coroutineScope
)
}
@@ -49,10 +56,14 @@ object RepositoryModule {
fun provideLottieFileRepository(
localDataSource: LocalLottieFileDataSource,
remoteDataSource: RemoteLottieFileDataSource,
+ @IoDispatcher coroutineDispatcher: CoroutineDispatcher,
+ coroutineScope: CoroutineScope
): LottieFileRepository {
return LottieFileRepositoryImpl(
localDataSource = localDataSource,
- remoteDataSource = remoteDataSource
+ remoteDataSource = remoteDataSource,
+ coroutineDispatcher = coroutineDispatcher,
+ coroutineScope = coroutineScope
)
}
@@ -60,10 +71,14 @@ object RepositoryModule {
fun provideBlogRepository(
localDataSource: LocalBlogDataSource,
remoteDataSource: RemoteBlogDataSource,
+ @IoDispatcher coroutineDispatcher: CoroutineDispatcher,
+ coroutineScope: CoroutineScope
) : BlogRepository {
return BlogRepositoryImpl(
localDataSource = localDataSource,
- remoteDataSource = remoteDataSource
+ remoteDataSource = remoteDataSource,
+ coroutineDispatcher = coroutineDispatcher,
+ coroutineScope = coroutineScope
)
}
}
\ No newline at end of file
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryImpl.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryImpl.kt
index d6699bc..74949c8 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryImpl.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/AnimatorRepositoryImpl.kt
@@ -3,15 +3,21 @@ package com.ericampire.android.androidstudycase.data.repository
import com.ericampire.android.androidstudycase.data.datasource.animator.AnimatorDataSource
import com.ericampire.android.androidstudycase.domain.entity.Animator
import com.ericampire.android.androidstudycase.domain.repository.AnimatorRepository
+import com.ericampire.android.androidstudycase.util.IoDispatcher
import com.ericampire.android.androidstudycase.util.Result
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import javax.inject.Inject
class AnimatorRepositoryImpl @Inject constructor(
private val remoteDataSource: AnimatorDataSource,
private val localDataSource: AnimatorDataSource,
+ private val coroutineScope: CoroutineScope,
+ @IoDispatcher private val coroutineDispatcher: CoroutineDispatcher
) : AnimatorRepository {
override fun findAll(): Flow>> {
refreshData()
@@ -19,7 +25,7 @@ class AnimatorRepositoryImpl @Inject constructor(
}
private fun refreshData() {
- suspend {
+ coroutineScope.launch(coroutineDispatcher) {
remoteDataSource.findAll().collect {
if (it is Result.Success) {
it.data.forEach { animator ->
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryImpl.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryImpl.kt
index 6253384..413e238 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryImpl.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/BlogRepositoryImpl.kt
@@ -3,14 +3,20 @@ package com.ericampire.android.androidstudycase.data.repository
import com.ericampire.android.androidstudycase.data.datasource.blog.BlogDataSource
import com.ericampire.android.androidstudycase.domain.entity.Blog
import com.ericampire.android.androidstudycase.domain.repository.BlogRepository
+import com.ericampire.android.androidstudycase.util.IoDispatcher
import com.ericampire.android.androidstudycase.util.Result
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import javax.inject.Inject
class BlogRepositoryImpl @Inject constructor(
private val remoteDataSource: BlogDataSource,
private val localDataSource: BlogDataSource,
+ private val coroutineScope: CoroutineScope,
+ @IoDispatcher private val coroutineDispatcher: CoroutineDispatcher
) : BlogRepository {
override fun findAll(): Flow>> {
refreshData()
@@ -18,7 +24,7 @@ class BlogRepositoryImpl @Inject constructor(
}
private fun refreshData() {
- suspend {
+ coroutineScope.launch(coroutineDispatcher) {
remoteDataSource.findAll().collect {
if (it is Result.Success) {
it.data.forEach { blog ->
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryImpl.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryImpl.kt
index 20ee702..6026b86 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryImpl.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/repository/LottieFileRepositoryImpl.kt
@@ -3,41 +3,44 @@ package com.ericampire.android.androidstudycase.data.repository
import com.ericampire.android.androidstudycase.data.datasource.lottiefiles.LottieFileDataSource
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
import com.ericampire.android.androidstudycase.domain.repository.LottieFileRepository
+import com.ericampire.android.androidstudycase.util.IoDispatcher
import com.ericampire.android.androidstudycase.util.Result
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import javax.inject.Inject
class LottieFileRepositoryImpl @Inject constructor(
private val localDataSource: LottieFileDataSource,
private val remoteDataSource: LottieFileDataSource,
+ private val coroutineScope: CoroutineScope,
+ @IoDispatcher private val coroutineDispatcher: CoroutineDispatcher
) : LottieFileRepository {
override fun findRecent(): Flow>> {
- val recentFiles = remoteDataSource.findRecent()
- refreshData(recentFiles)
- return recentFiles
+ refreshData(remoteDataSource.findRecent(), "recent")
+ return localDataSource.findRecent()
}
- private fun refreshData(data: Flow>>) {
- suspend {
+ private fun refreshData(data: Flow>>, type: String) {
+ coroutineScope.launch(coroutineDispatcher) {
data.collect {
if (it is Result.Success) {
- it.data.forEach { file -> localDataSource.save(file) }
+ it.data.forEach { file -> localDataSource.save(file.copy(type = type)) }
}
}
}
}
override fun findPopular(): Flow>> {
- val files = remoteDataSource.findPopular()
- refreshData(files)
- return files
+ refreshData(remoteDataSource.findPopular(), "popular")
+ return localDataSource.findPopular()
}
override fun findFeatured(): Flow>> {
- val files = remoteDataSource.findFeatured()
- refreshData(files)
- return files
+ refreshData(remoteDataSource.findFeatured(), "featured")
+ return localDataSource.findFeatured()
}
}
\ No newline at end of file
diff --git a/data/src/main/java/com/ericampire/android/androidstudycase/data/room/LottieFilesDao.kt b/data/src/main/java/com/ericampire/android/androidstudycase/data/room/LottieFilesDao.kt
index b148139..11eea80 100644
--- a/data/src/main/java/com/ericampire/android/androidstudycase/data/room/LottieFilesDao.kt
+++ b/data/src/main/java/com/ericampire/android/androidstudycase/data/room/LottieFilesDao.kt
@@ -12,12 +12,6 @@ interface LottieFilesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(lottiefile: Lottiefile)
- @Query("SELECT * FROM Lottiefile")
- fun findPopular(): Flow>
-
- @Query("SELECT * FROM Lottiefile")
- fun findFeatured(): Flow>
-
- @Query("SELECT * FROM Lottiefile")
- fun findRecent(): Flow>
+ @Query("SELECT * FROM Lottiefile WHERE type = :type")
+ fun findByType(type: String): Flow>
}
\ No newline at end of file
diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts
index e1eddfb..3fc0217 100644
--- a/domain/build.gradle.kts
+++ b/domain/build.gradle.kts
@@ -1,9 +1,7 @@
-import de.fayard.refreshVersions.core.versionFor
-
plugins {
id("com.android.library")
id("kotlin-android")
- kotlin("plugin.serialization") version "1.5.20"
+ kotlin("plugin.serialization") version "1.5.21"
kotlin("kapt")
}
@@ -41,6 +39,9 @@ dependencies {
api(Libs.ktor_client_core)
api(Libs.ktor_serialization)
+ api(Libs.ktor_client_android)
+ api(Libs.ktor_client_cio)
+
api(Libs.joda_time)
testImplementation(Libs.junit_jupiter_api)
diff --git a/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Animator.kt b/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Animator.kt
index cd0fb26..998ba57 100644
--- a/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Animator.kt
+++ b/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Animator.kt
@@ -1,5 +1,6 @@
package com.ericampire.android.androidstudycase.domain.entity
+import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.SerialName
@@ -9,8 +10,10 @@ import kotlinx.serialization.Serializable
@Serializable
@Entity
data class Animator(
+ @ColumnInfo(name = "id_animator")
@PrimaryKey
- var name: String = "",
+ val id: Long? = null,
+ val name: String = "",
var avatarUrl: String = "",
)
@@ -19,10 +22,11 @@ data class FeaturedAnimators(val results: List)
@Serializable
data class AnimatorData(
- val featuredAnimators: FeaturedAnimators
+ @SerialName("featuredAnimators")
+ val animators: FeaturedAnimators
)
@Serializable
data class AnimatorApiResponse(
- @SerialName("data") val animatorAnimatorData: AnimatorData
+ @SerialName("data") val data: AnimatorData
)
\ No newline at end of file
diff --git a/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/BlogPage.kt b/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/BlogPage.kt
index dea6a32..a588d61 100644
--- a/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/BlogPage.kt
+++ b/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/BlogPage.kt
@@ -2,20 +2,14 @@ package com.ericampire.android.androidstudycase.domain.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
-import androidx.room.TypeConverters
-import com.ericampire.android.androidstudycase.domain.util.DateSerializer
-import com.ericampire.android.androidstudycase.util.room.DateConverter
-import kotlinx.serialization.Contextual
-import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import java.util.*
@Serializable
data class BlogPage(
val currentPage: Int,
val from: Int,
val perPage: Int,
- val blogs: List,
+ val results: List,
val to: Int,
val total: Int,
val totalPages: Int
@@ -23,20 +17,19 @@ data class BlogPage(
@Serializable
data class BlogData(
- @SerialName("blogs") val blogPage: BlogPage
+ val blogs: BlogPage
)
@Serializable
data class BlogApiResponse(
- @SerialName("data") val blogBlogData: BlogData
+ val data: BlogData
)
@Serializable
@Entity
data class Blog(
-// @TypeConverters(DateConverter::class)
-// @Serializable(with = DateSerializer::class)
- var postedAt: String = "",
- @PrimaryKey val imageUrl: String = "",
- var title: String
+ @PrimaryKey val id: Long? = null,
+ val postedAt: String = "",
+ val imageUrl: String = "",
+ val title: String
)
\ No newline at end of file
diff --git a/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Lottiefile.kt b/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Lottiefile.kt
index ff5cf7e..e3afd95 100644
--- a/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Lottiefile.kt
+++ b/domain/src/main/java/com/ericampire/android/androidstudycase/domain/entity/Lottiefile.kt
@@ -13,10 +13,14 @@ data class LottieFilesApiResponse(
val lottieFilesLottieFilesData: LottieFilesData
)
+/**
+ * You can avoid this duplication using @JsonNames annotation
+ */
@Serializable
data class LottieFilesData(
- @SerialName("recent")
- val page: LottieFilesPage
+ val recent: LottieFilesPage? = null,
+ val featured: LottieFilesPage? = null,
+ val popular: LottieFilesPage? = null,
)
@@ -40,6 +44,7 @@ data class Lottiefile(
var lottieUrl: String = "",
var gifUrl: String? = "",
var videoUrl: String? = "",
+ var type: String = "", // For making difference between recent, popular and featured
var imageUrl: String? = "",
@ColumnInfo(name = "file_name") var name: String = "",
var createdAt: String = "",
diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml
index ee4289a..132e9f7 100644
--- a/i18n/src/main/res/values/strings.xml
+++ b/i18n/src/main/res/values/strings.xml
@@ -14,4 +14,6 @@
Go to Explore
Login
Hello Stranger
+ Featured Animators
+ Latest Stories
\ No newline at end of file
diff --git a/util/build.gradle.kts b/util/build.gradle.kts
index e82a728..adeabf3 100644
--- a/util/build.gradle.kts
+++ b/util/build.gradle.kts
@@ -40,7 +40,7 @@ dependencies {
api(platform(Libs.kotlin_coroutine_bom))
api(Libs.kotlin_coroutine_core)
- api(Libs.orbit_mvi_core)
+ api(Libs.mavericks_core)
testImplementation(Libs.junit_jupiter_api)
testImplementation(Libs.junit_jupiter_engine)
diff --git a/util/src/main/java/com/ericampire/android/androidstudycase/util/mvi/BaseViewModel.kt b/util/src/main/java/com/ericampire/android/androidstudycase/util/mvi/BaseViewModel.kt
index d2acafc..71b8a77 100644
--- a/util/src/main/java/com/ericampire/android/androidstudycase/util/mvi/BaseViewModel.kt
+++ b/util/src/main/java/com/ericampire/android/androidstudycase/util/mvi/BaseViewModel.kt
@@ -1,12 +1,13 @@
package com.ericampire.android.androidstudycase.util.mvi
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
-import org.orbitmvi.orbit.ContainerHost
-abstract class BaseViewModel : ContainerHost, ViewModel() {
+abstract class BaseViewModel(
+ initialState: S
+) : MavericksViewModel(initialState) {
protected val pendingAction = MutableSharedFlow()
fun submitAction(action: A) {
diff --git a/versions.properties b/versions.properties
index b8b7605..ff3305a 100644
--- a/versions.properties
+++ b/versions.properties
@@ -11,14 +11,13 @@ plugin.android=7.0.2
version.androidx.activity=1.3.0-alpha08
version.androidx.preference=1.1.1
version.androidx.appcompat=1.3.0
-version.androidx.compose.compiler=1.0.1
-version.androidx.compose.material-icons-extended=1.0.1
-
-version.androidx.compose.material=1.0.1
-version.androidx.compose.runtime=1.0.1
-version.google.accompanist=0.16.1
-version.androidx.compose.ui=1.0.1
-version.androidx.compose.ui-viewbinding=1.0.1
+version.androidx.compose.compiler=1.0.2
+version.androidx.compose.material-icons-extended=1.0.2
+version.androidx.compose.material=1.0.2
+version.androidx.compose.runtime=1.0.2
+version.google.accompanist=0.18.0
+version.androidx.compose.ui=1.0.2
+version.androidx.compose.ui-viewbinding=1.0.2
version.androidx.core=1.5.0
version.androidx.room=2.3.0
version.androidx.startup=1.0.0