Setup Presentation Module

This commit is contained in:
2021-09-04 15:36:05 +02:00
parent e532ae454c
commit c00c3db51c
39 changed files with 602 additions and 68 deletions
@@ -8,31 +8,20 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.ericampire.android.androidstudycase.ui.theme.AndroidStudyCaseTheme import com.ericampire.android.androidstudycase.presentation.screen.main.ui.MainScreen
import com.ericampire.android.androidstudycase.presentation.theme.AndroidStudyCaseTheme
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
AndroidStudyCaseTheme { AndroidStudyCaseTheme(darkTheme = true) {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) { Surface(color = MaterialTheme.colors.background) {
Greeting("Android") MainScreen()
} }
} }
} }
} }
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
AndroidStudyCaseTheme {
Greeting("Android")
}
} }
@@ -0,0 +1,38 @@
package com.ericampire.android.androidstudycase.app
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.business.HomeViewModel
import com.ericampire.android.androidstudycase.presentation.screen.home.ui.HomeScreen
import com.ericampire.android.androidstudycase.presentation.screen.preview.ui.PreviewScreen
import com.ericampire.android.androidstudycase.util.Destination
fun NavGraphBuilder.addHomeScreen(navController: NavController) {
composable(Destination.Home.route) {
HomeScreen(
navController = navController,
viewModel = hiltViewModel()
)
}
}
fun NavGraphBuilder.addExploreScreen(navController: NavController) {
composable(Destination.Explore.route) {
ExploreScreen(
navController = navController,
viewModel = hiltViewModel()
)
}
}
fun NavGraphBuilder.addPreviewScreen(navController: NavController) {
composable(Destination.Preview.route) {
PreviewScreen(
navController = navController,
viewModel = hiltViewModel()
)
}
}
@@ -0,0 +1,42 @@
package com.ericampire.android.androidstudycase.app.di
import android.content.Context
import androidx.room.Room
import com.ericampire.android.androidstudycase.app.room.AppDatabase
import com.ericampire.android.androidstudycase.data.room.AnimatorDao
import com.ericampire.android.androidstudycase.data.room.BlogDao
import com.ericampire.android.androidstudycase.data.room.LottieFilesDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
object RoomModule {
@Provides
fun provideRoomDatabase(@ApplicationContext context: Context): AppDatabase {
val db = Room.databaseBuilder(
context,
AppDatabase::class.java, "database-name"
)
return db.build()
}
@Provides
fun provideBlogDao(appDatabase: AppDatabase): BlogDao {
return appDatabase.blogDao
}
@Provides
fun provideAnimatorDao(appDatabase: AppDatabase): AnimatorDao {
return appDatabase.animatorDao
}
@Provides
fun provideLottieFilesDao(appDatabase: AppDatabase): LottieFilesDao {
return appDatabase.lottieFileDao
}
}
@@ -1,6 +1,11 @@
package com.ericampire.android.androidstudycase.app.di package com.ericampire.android.androidstudycase.app.di
import com.ericampire.android.androidstudycase.domain.usecase.*
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.Module import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.components.ViewModelComponent
@@ -8,4 +13,35 @@ import dagger.hilt.android.components.ViewModelComponent
@InstallIn(ViewModelComponent::class) @InstallIn(ViewModelComponent::class)
object ViewModelModule { object ViewModelModule {
@Provides
fun provideHomeViewModel(
findFeaturedAnimatorUseCase: FindFeaturedAnimatorUseCase,
findFeaturedBlogUseCase: FindFeaturedBlogUseCase
): HomeViewModel {
return HomeViewModel(
findFeaturedAnimatorUseCase = findFeaturedAnimatorUseCase,
findFeaturedBlogUseCase = findFeaturedBlogUseCase
)
}
@Provides
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
)
}
} }
@@ -0,0 +1,23 @@
package com.ericampire.android.androidstudycase.app.room
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.ericampire.android.androidstudycase.data.room.AnimatorDao
import com.ericampire.android.androidstudycase.data.room.BlogDao
import com.ericampire.android.androidstudycase.data.room.LottieFilesDao
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.util.room.DateConverter
@Database(
entities = [Blog::class, Animator::class, Lottiefile::class],
version = 2,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract val blogDao: BlogDao
abstract val animatorDao: AnimatorDao
abstract val lottieFileDao: LottieFilesDao
}
@@ -0,0 +1,7 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.business
sealed interface ExploreAction {
object FindRecentFile : ExploreAction
object FindFeaturedFile : ExploreAction
object FindPopularFile : ExploreAction
}
@@ -0,0 +1,7 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.business
sealed interface ExploreEffect {
data class ShowErrorMessage(val message: String) : ExploreEffect
object Loading : ExploreEffect
object Success : ExploreEffect
}
@@ -0,0 +1,79 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.business
import androidx.lifecycle.viewModelScope
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.mvi.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
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(
private val findPopularLottieFileUseCase: FindPopularLottieFileUseCase,
private val findRecentLottieFileUseCase: FindRecentLottieFileUseCase,
private val findFeaturedLottieFileUseCase: FindFeaturedLottieFileUseCase
) : BaseViewModel<ExploreViewState, ExploreEffect, ExploreAction>() {
override val container: Container<ExploreViewState, ExploreEffect>
get() = container(initialState = ExploreViewState())
init {
viewModelScope.launch {
pendingAction.collectLatest { action ->
when(action) {
ExploreAction.FindFeaturedFile -> findFeaturedFile()
ExploreAction.FindPopularFile -> findPopularFile()
ExploreAction.FindRecentFile -> findRecentFile()
}
}
}
}
private fun Flow<Result<List<Lottiefile>>>.fetchData() = intent {
collect { result ->
when(result) {
is Result.Error -> {
val errorMessage = result.exception.localizedMessage ?: "Unknown Error"
postSideEffect(ExploreEffect.ShowErrorMessage(errorMessage))
}
Result.Loading -> {
postSideEffect(ExploreEffect.Loading)
}
is Result.Success -> {
postSideEffect(ExploreEffect.Success)
reduce { state.copy(files = result.data) }
}
}
}
}
private fun findRecentFile() {
viewModelScope.launch {
findRecentLottieFileUseCase(Unit).fetchData()
}
}
private fun findPopularFile() {
viewModelScope.launch {
findPopularLottieFileUseCase(Unit).fetchData()
}
}
private fun findFeaturedFile() {
viewModelScope.launch {
findFeaturedLottieFileUseCase(Unit).fetchData()
}
}
}
@@ -0,0 +1,7 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.business
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
data class ExploreViewState(
val files: List<Lottiefile> = emptyList()
)
@@ -0,0 +1,13 @@
package com.ericampire.android.androidstudycase.presentation.screen.explore.ui
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.ericampire.android.androidstudycase.presentation.screen.explore.business.ExploreViewModel
@Composable
fun ExploreScreen(
navController: NavController,
viewModel: ExploreViewModel
) {
}
@@ -0,0 +1,3 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
sealed interface HomeAction
@@ -0,0 +1,3 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
sealed interface HomeEffect
@@ -0,0 +1,30 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
import androidx.lifecycle.viewModelScope
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedAnimatorUseCase
import com.ericampire.android.androidstudycase.domain.usecase.FindFeaturedBlogUseCase
import com.ericampire.android.androidstudycase.util.mvi.BaseViewModel
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 HomeViewModel @Inject constructor(
private val findFeaturedBlogUseCase: FindFeaturedBlogUseCase,
private val findFeaturedAnimatorUseCase: FindFeaturedAnimatorUseCase
) : BaseViewModel<HomeViewState, HomeEffect, HomeAction>() {
override val container: Container<HomeViewState, HomeEffect>
get() = container(initialState = HomeViewState())
init {
viewModelScope.launch {
pendingAction.collectLatest { action ->
}
}
}
}
@@ -0,0 +1,7 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.business
import com.ericampire.android.androidstudycase.domain.entity.Blog
data class HomeViewState(
val blog: List<Blog> = emptyList()
)
@@ -0,0 +1,13 @@
package com.ericampire.android.androidstudycase.presentation.screen.home.ui
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.ericampire.android.androidstudycase.presentation.screen.home.business.HomeViewModel
@Composable
fun HomeScreen(
navController: NavController,
viewModel: HomeViewModel
) {
}
@@ -0,0 +1,92 @@
package com.ericampire.android.androidstudycase.presentation.screen.main.ui
import androidx.compose.runtime.Composable
import androidx.navigation.compose.rememberNavController
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Explore
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Scanner
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.compose.NavHost
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.currentBackStackEntryAsState
import com.ericampire.android.androidstudycase.app.addExploreScreen
import com.ericampire.android.androidstudycase.app.addHomeScreen
import com.ericampire.android.androidstudycase.app.addPreviewScreen
import com.ericampire.android.androidstudycase.util.Destination
@Composable
fun MainScreen() {
val navController = rememberNavController()
val items = listOf(
Destination.Home,
Destination.Preview,
Destination.Explore,
)
Scaffold(
bottomBar = {
BottomNavigation(
backgroundColor = MaterialTheme.colors.background,
content = {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEachIndexed { index, screen ->
BottomNavigationItem(
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = Color.Gray,
icon = {
Icon(
imageVector = getIconByIndex(index),
contentDescription = null
)
},
label = {
Text(text = stringResource(screen.resourceId))
},
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
launchSingleTop = true
restoreState = true
}
}
)
}
}
)
},
content = { innerPadding ->
NavHost(
navController = navController,
modifier = Modifier.padding(innerPadding),
startDestination = Destination.Home.route,
builder = {
addHomeScreen(navController = navController)
addPreviewScreen(navController = navController)
addExploreScreen(navController = navController)
}
)
},
)
}
fun getIconByIndex(index: Int): ImageVector {
return when (index) {
0 -> Icons.Rounded.Home
1 -> Icons.Rounded.Scanner
2 -> Icons.Rounded.Explore
else -> Icons.Rounded.Home
}
}
@@ -0,0 +1,3 @@
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
sealed interface PreviewAction
@@ -0,0 +1,3 @@
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
sealed interface PreviewEffect
@@ -0,0 +1,27 @@
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
import androidx.lifecycle.viewModelScope
import com.ericampire.android.androidstudycase.util.mvi.BaseViewModel
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<PreviewViewState, PreviewEffect, PreviewAction>() {
override val container: Container<PreviewViewState, PreviewEffect>
get() = container(initialState = PreviewViewState())
init {
viewModelScope.launch {
pendingAction.collectLatest { action ->
}
}
}
}
@@ -0,0 +1,7 @@
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
import com.ericampire.android.androidstudycase.domain.entity.Lottiefile
data class PreviewViewState(
val lottieFile: Lottiefile? = null
)
@@ -0,0 +1,13 @@
package com.ericampire.android.androidstudycase.presentation.screen.preview.ui
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.ericampire.android.androidstudycase.presentation.screen.preview.business.PreviewViewModel
@Composable
fun PreviewScreen(
navController: NavController,
viewModel: PreviewViewModel
) {
}
@@ -0,0 +1,12 @@
package com.ericampire.android.androidstudycase.presentation.theme
import androidx.compose.ui.graphics.Color
object AppColor {
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
}
@@ -1,4 +1,4 @@
package com.ericampire.android.androidstudycase.ui.theme package com.ericampire.android.androidstudycase.presentation.theme
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes import androidx.compose.material.Shapes
@@ -1,4 +1,4 @@
package com.ericampire.android.androidstudycase.ui.theme package com.ericampire.android.androidstudycase.presentation.theme
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
@@ -8,15 +8,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
private val DarkColorPalette = darkColors( private val DarkColorPalette = darkColors(
primary = Purple200, primary = AppColor.Purple200,
primaryVariant = Purple700, primaryVariant = AppColor.Purple700,
secondary = Teal200 secondary = AppColor.Teal200
) )
private val LightColorPalette = lightColors( private val LightColorPalette = lightColors(
primary = Purple500, primary = AppColor.Purple500,
primaryVariant = Purple700, primaryVariant = AppColor.Purple700,
secondary = Teal200, secondary = AppColor.Teal200,
background = Color.White, background = Color.White,
surface = Color.White, surface = Color.White,
onPrimary = Color.White, onPrimary = Color.White,
@@ -1,4 +1,4 @@
package com.ericampire.android.androidstudycase.ui.theme package com.ericampire.android.androidstudycase.presentation.theme
import androidx.compose.material.Typography import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -1,8 +0,0 @@
package com.ericampire.android.androidstudycase.ui.theme
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
@@ -0,0 +1,10 @@
package com.ericampire.android.androidstudycase.util
import androidx.annotation.StringRes
import com.ericampire.android.androidstudycase.R
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)
}
+4
View File
@@ -32,6 +32,7 @@ object Libs {
const val compose_ui_tooling = "androidx.compose.ui:ui-tooling:_" const val compose_ui_tooling = "androidx.compose.ui:ui-tooling:_"
const val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:_" const val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:_"
const val androidx_lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:_"
const val core_ktx = "androidx.core:core-ktx:_" const val core_ktx = "androidx.core:core-ktx:_"
const val lifecycle_runtime_ktx = "androidx.lifecycle:lifecycle-runtime-ktx:_" const val lifecycle_runtime_ktx = "androidx.lifecycle:lifecycle-runtime-ktx:_"
const val androidx_test_ext_junit = "androidx.test.ext:junit:_" const val androidx_test_ext_junit = "androidx.test.ext:junit:_"
@@ -131,6 +132,7 @@ object Libs {
const val room_compiler = "androidx.room:room-compiler:_" const val room_compiler = "androidx.room:room-compiler:_"
const val room_runtime = "androidx.room:room-runtime:_" const val room_runtime = "androidx.room:room-runtime:_"
const val room_ktx = "androidx.room:room-ktx:_"
const val room_testing = "androidx.room:room-testing:_" const val room_testing = "androidx.room:room-testing:_"
const val koin_core = "io.insert-koin:koin-core:_" const val koin_core = "io.insert-koin:koin-core:_"
@@ -143,4 +145,6 @@ object Libs {
const val orbit_mvi_viewmodel = "org.orbit-mvi:orbit-viewmodel:4.2.0" const val orbit_mvi_viewmodel = "org.orbit-mvi:orbit-viewmodel:4.2.0"
const val orbit_mvi_core = "org.orbit-mvi:orbit-core:4.2.0" const val orbit_mvi_core = "org.orbit-mvi:orbit-core:4.2.0"
const val orbit_mvi_test = "org.orbit-mvi:orbit-test:4.2.0" const val orbit_mvi_test = "org.orbit-mvi:orbit-test:4.2.0"
const val joda_time = "net.danlew:android.joda:2.10.9"
} }
@@ -1,6 +1,5 @@
package com.ericampire.android.androidstudycase.data.di package com.ericampire.android.androidstudycase.data.di
import com.ericampire.android.androidstudycase.data.datasource.animator.AnimatorDataSource
import com.ericampire.android.androidstudycase.data.datasource.animator.LocalAnimatorDataSource import com.ericampire.android.androidstudycase.data.datasource.animator.LocalAnimatorDataSource
import com.ericampire.android.androidstudycase.data.datasource.animator.RemoteAnimatorDataSource import com.ericampire.android.androidstudycase.data.datasource.animator.RemoteAnimatorDataSource
import com.ericampire.android.androidstudycase.data.datasource.blog.LocalBlogDataSource import com.ericampire.android.androidstudycase.data.datasource.blog.LocalBlogDataSource
+2 -3
View File
@@ -3,7 +3,7 @@ import de.fayard.refreshVersions.core.versionFor
plugins { plugins {
id("com.android.library") id("com.android.library")
id("kotlin-android") id("kotlin-android")
kotlin("plugin.serialization") version "1.5.21" kotlin("plugin.serialization") version "1.5.20"
kotlin("kapt") kotlin("kapt")
} }
@@ -41,8 +41,7 @@ dependencies {
api(Libs.ktor_client_core) api(Libs.ktor_client_core)
api(Libs.ktor_serialization) api(Libs.ktor_serialization)
api(Libs.joda_time)
api(Libs.room_runtime)
testImplementation(Libs.junit_jupiter_api) testImplementation(Libs.junit_jupiter_api)
testImplementation(Libs.junit_jupiter_engine) testImplementation(Libs.junit_jupiter_engine)
@@ -10,8 +10,8 @@ import kotlinx.serialization.Serializable
@Entity @Entity
data class Animator( data class Animator(
@PrimaryKey @PrimaryKey
val name: String = "", var name: String = "",
val avatarUrl: String = "", var avatarUrl: String = "",
) )
@Serializable @Serializable
@@ -2,6 +2,9 @@ package com.ericampire.android.androidstudycase.domain.entity
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey 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.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -9,29 +12,31 @@ import java.util.*
@Serializable @Serializable
data class BlogPage( data class BlogPage(
val currentPage: Int, val currentPage: Int,
val from: Int, val from: Int,
val perPage: Int, val perPage: Int,
val blogs: List<Blog>, val blogs: List<Blog>,
val to: Int, val to: Int,
val total: Int, val total: Int,
val totalPages: Int val totalPages: Int
) )
@Serializable @Serializable
data class BlogData( data class BlogData(
@SerialName("blogs") val blogPage: BlogPage @SerialName("blogs") val blogPage: BlogPage
) )
@Serializable @Serializable
data class BlogApiResponse( data class BlogApiResponse(
@SerialName("data") val blogBlogData: BlogData @SerialName("data") val blogBlogData: BlogData
) )
@Serializable @Serializable
@Entity @Entity
data class Blog( data class Blog(
@Contextual val postedAt: Date, // @TypeConverters(DateConverter::class)
@PrimaryKey val imageUrl: String, // @Serializable(with = DateSerializer::class)
val title: String var postedAt: String = "",
@PrimaryKey val imageUrl: String = "",
var title: String
) )
@@ -1,14 +1,14 @@
package com.ericampire.android.androidstudycase.domain.entity package com.ericampire.android.androidstudycase.domain.entity
import androidx.room.Entity import androidx.room.*
import androidx.room.PrimaryKey import com.ericampire.android.androidstudycase.domain.util.DateSerializer
import com.ericampire.android.androidstudycase.util.room.DateConverter
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import java.util.* import java.util.*
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames import kotlinx.serialization.json.JsonNames
import java.util.*
@Serializable @Serializable
data class LottieFilesApiResponse( data class LottieFilesApiResponse(
@@ -16,10 +16,9 @@ data class LottieFilesApiResponse(
val lottieFilesLottieFilesData: LottieFilesData val lottieFilesLottieFilesData: LottieFilesData
) )
@ExperimentalSerializationApi
@Serializable @Serializable
data class LottieFilesData( data class LottieFilesData(
@JsonNames("recent", "popular") @SerialName("recent")
val page: LottieFilesPage val page: LottieFilesPage
) )
@@ -39,15 +38,17 @@ data class LottieFilesPage(
@Serializable @Serializable
@Entity @Entity
class Lottiefile( class Lottiefile(
@PrimaryKey val id: Long, @PrimaryKey var id: Long? = null,
val bgColor: String, var bgColor: String = "",
val lottieUrl: String, var lottieUrl: String = "",
val gifUrl: String, var gifUrl: String = "",
val videoUrl: String, var videoUrl: String = "",
val imageUrl: String, var imageUrl: String = "",
val name: String, var name: String = "",
@Contextual val createdAt: Date, // @TypeConverters(DateConverter::class)
val createdBy: Animator // @Serializable(with = DateSerializer::class)
var createdAt: String = "",
@Ignore var createdBy: Animator? = null
) )
@@ -0,0 +1,23 @@
package com.ericampire.android.androidstudycase.domain.util
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import org.joda.time.DateTime
import java.util.*
object DateSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeString(DateTime(value).toString())
}
override fun deserialize(decoder: Decoder): Date {
return DateTime(decoder.decodeString()).toDate()
}
}
+6 -2
View File
@@ -2,6 +2,10 @@
<resources> <resources>
<string name="app_name">Lottiefile App</string> <string name="app_name">Lottiefile App</string>
<string name="txt_home">Home</string>
<string name="txt_explore">Explore</string>
<string name="txt_preview">Preview</string>
<string name="txt_featured">Featured</string>
<string name="txt_recent">Recent</string>
<string name="txt_popular">Popular</string>
</resources> </resources>
+6
View File
@@ -32,10 +32,16 @@ dependencies {
api(project(":i18n")) api(project(":i18n"))
implementation(Libs.core_ktx) implementation(Libs.core_ktx)
api(Libs.androidx_lifecycle_viewmodel_ktx)
api(Libs.room_runtime)
api(Libs.room_ktx)
api(platform(Libs.kotlin_coroutine_bom)) api(platform(Libs.kotlin_coroutine_bom))
api(Libs.kotlin_coroutine_core) api(Libs.kotlin_coroutine_core)
api(Libs.orbit_mvi_core)
testImplementation(Libs.junit_jupiter_api) testImplementation(Libs.junit_jupiter_api)
testImplementation(Libs.junit_jupiter_engine) testImplementation(Libs.junit_jupiter_engine)
@@ -0,0 +1,17 @@
package com.ericampire.android.androidstudycase.util.mvi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import org.orbitmvi.orbit.ContainerHost
abstract class BaseViewModel<S: Any, E: Any, A> : ContainerHost<S, E>, ViewModel() {
protected val pendingAction = MutableSharedFlow<A>()
protected fun submitAction(action: A) {
viewModelScope.launch {
pendingAction.emit(action)
}
}
}
@@ -0,0 +1,18 @@
package com.ericampire.android.androidstudycase.util.room
import androidx.room.TypeConverter
import java.util.*
object DateConverter {
@TypeConverter
fun fromTimestamp(value: Long): Date {
return Date(value)
}
@TypeConverter
fun dateToTimestamp(date: Date): Long {
return date.time
}
}
@@ -5,6 +5,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
/** /**
* Executes business logic in its execute method and keep posting updates to the result as * Executes business logic in its execute method and keep posting updates to the result as
@@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.flowOn
*/ */
abstract class FlowUseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) { abstract class FlowUseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
operator fun invoke(parameters: P): Flow<Result<R>> = execute(parameters) operator fun invoke(parameters: P): Flow<Result<R>> = execute(parameters)
.onStart { emit(Result.Loading) }
.catch { e -> emit(Result.Error(Exception(e))) } .catch { e -> emit(Result.Error(Exception(e))) }
.flowOn(coroutineDispatcher) .flowOn(coroutineDispatcher)