Setup Presentation Module
This commit is contained in:
@@ -8,31 +8,20 @@ import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
AndroidStudyCaseTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
AndroidStudyCaseTheme(darkTheme = true) {
|
||||
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
|
||||
|
||||
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.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
|
||||
@@ -8,4 +13,35 @@ import dagger.hilt.android.components.ViewModelComponent
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
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
|
||||
}
|
||||
+7
@@ -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
|
||||
}
|
||||
+7
@@ -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
|
||||
}
|
||||
+79
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -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()
|
||||
)
|
||||
+13
@@ -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
|
||||
) {
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.ericampire.android.androidstudycase.presentation.screen.home.business
|
||||
|
||||
sealed interface HomeAction
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.ericampire.android.androidstudycase.presentation.screen.home.business
|
||||
|
||||
sealed interface HomeEffect
|
||||
+30
@@ -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 ->
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -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()
|
||||
)
|
||||
+13
@@ -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
|
||||
) {
|
||||
|
||||
}
|
||||
+92
@@ -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
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
|
||||
|
||||
sealed interface PreviewAction
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.ericampire.android.androidstudycase.presentation.screen.preview.business
|
||||
|
||||
sealed interface PreviewEffect
|
||||
+27
@@ -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 ->
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -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
|
||||
)
|
||||
+13
@@ -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
-1
@@ -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.material.Shapes
|
||||
+7
-7
@@ -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.material.MaterialTheme
|
||||
@@ -8,15 +8,15 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
private val DarkColorPalette = darkColors(
|
||||
primary = Purple200,
|
||||
primaryVariant = Purple700,
|
||||
secondary = Teal200
|
||||
primary = AppColor.Purple200,
|
||||
primaryVariant = AppColor.Purple700,
|
||||
secondary = AppColor.Teal200
|
||||
)
|
||||
|
||||
private val LightColorPalette = lightColors(
|
||||
primary = Purple500,
|
||||
primaryVariant = Purple700,
|
||||
secondary = Teal200,
|
||||
primary = AppColor.Purple500,
|
||||
primaryVariant = AppColor.Purple700,
|
||||
secondary = AppColor.Teal200,
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
onPrimary = Color.White,
|
||||
+1
-1
@@ -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.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)
|
||||
}
|
||||
@@ -32,6 +32,7 @@ object Libs {
|
||||
const val compose_ui_tooling = "androidx.compose.ui:ui-tooling:_"
|
||||
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 lifecycle_runtime_ktx = "androidx.lifecycle:lifecycle-runtime-ktx:_"
|
||||
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_runtime = "androidx.room:room-runtime:_"
|
||||
const val room_ktx = "androidx.room:room-ktx:_"
|
||||
const val room_testing = "androidx.room:room-testing:_"
|
||||
|
||||
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_core = "org.orbit-mvi:orbit-core: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
|
||||
|
||||
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.RemoteAnimatorDataSource
|
||||
import com.ericampire.android.androidstudycase.data.datasource.blog.LocalBlogDataSource
|
||||
|
||||
@@ -3,7 +3,7 @@ import de.fayard.refreshVersions.core.versionFor
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
kotlin("plugin.serialization") version "1.5.21"
|
||||
kotlin("plugin.serialization") version "1.5.20"
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ dependencies {
|
||||
|
||||
api(Libs.ktor_client_core)
|
||||
api(Libs.ktor_serialization)
|
||||
|
||||
api(Libs.room_runtime)
|
||||
api(Libs.joda_time)
|
||||
|
||||
testImplementation(Libs.junit_jupiter_api)
|
||||
testImplementation(Libs.junit_jupiter_engine)
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ import kotlinx.serialization.Serializable
|
||||
@Entity
|
||||
data class Animator(
|
||||
@PrimaryKey
|
||||
val name: String = "",
|
||||
val avatarUrl: String = "",
|
||||
var name: String = "",
|
||||
var avatarUrl: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
+17
-12
@@ -2,6 +2,9 @@ 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
|
||||
@@ -9,29 +12,31 @@ import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class BlogPage(
|
||||
val currentPage: Int,
|
||||
val from: Int,
|
||||
val perPage: Int,
|
||||
val blogs: List<Blog>,
|
||||
val to: Int,
|
||||
val total: Int,
|
||||
val totalPages: Int
|
||||
val currentPage: Int,
|
||||
val from: Int,
|
||||
val perPage: Int,
|
||||
val blogs: List<Blog>,
|
||||
val to: Int,
|
||||
val total: Int,
|
||||
val totalPages: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BlogData(
|
||||
@SerialName("blogs") val blogPage: BlogPage
|
||||
@SerialName("blogs") val blogPage: BlogPage
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BlogApiResponse(
|
||||
@SerialName("data") val blogBlogData: BlogData
|
||||
@SerialName("data") val blogBlogData: BlogData
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@Entity
|
||||
data class Blog(
|
||||
@Contextual val postedAt: Date,
|
||||
@PrimaryKey val imageUrl: String,
|
||||
val title: String
|
||||
// @TypeConverters(DateConverter::class)
|
||||
// @Serializable(with = DateSerializer::class)
|
||||
var postedAt: String = "",
|
||||
@PrimaryKey val imageUrl: String = "",
|
||||
var title: String
|
||||
)
|
||||
+15
-14
@@ -1,14 +1,14 @@
|
||||
package com.ericampire.android.androidstudycase.domain.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.*
|
||||
import com.ericampire.android.androidstudycase.domain.util.DateSerializer
|
||||
import com.ericampire.android.androidstudycase.util.room.DateConverter
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import java.util.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class LottieFilesApiResponse(
|
||||
@@ -16,10 +16,9 @@ data class LottieFilesApiResponse(
|
||||
val lottieFilesLottieFilesData: LottieFilesData
|
||||
)
|
||||
|
||||
@ExperimentalSerializationApi
|
||||
@Serializable
|
||||
data class LottieFilesData(
|
||||
@JsonNames("recent", "popular")
|
||||
@SerialName("recent")
|
||||
val page: LottieFilesPage
|
||||
)
|
||||
|
||||
@@ -39,15 +38,17 @@ data class LottieFilesPage(
|
||||
@Serializable
|
||||
@Entity
|
||||
class Lottiefile(
|
||||
@PrimaryKey val id: Long,
|
||||
val bgColor: String,
|
||||
val lottieUrl: String,
|
||||
val gifUrl: String,
|
||||
val videoUrl: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
@Contextual val createdAt: Date,
|
||||
val createdBy: Animator
|
||||
@PrimaryKey var id: Long? = null,
|
||||
var bgColor: String = "",
|
||||
var lottieUrl: String = "",
|
||||
var gifUrl: String = "",
|
||||
var videoUrl: String = "",
|
||||
var imageUrl: String = "",
|
||||
var name: String = "",
|
||||
// @TypeConverters(DateConverter::class)
|
||||
// @Serializable(with = DateSerializer::class)
|
||||
var createdAt: String = "",
|
||||
@Ignore var createdBy: Animator? = null
|
||||
)
|
||||
|
||||
|
||||
|
||||
+23
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
<resources>
|
||||
|
||||
<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>
|
||||
@@ -32,10 +32,16 @@ dependencies {
|
||||
|
||||
api(project(":i18n"))
|
||||
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(Libs.kotlin_coroutine_core)
|
||||
|
||||
api(Libs.orbit_mvi_core)
|
||||
|
||||
testImplementation(Libs.junit_jupiter_api)
|
||||
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.catch
|
||||
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
|
||||
@@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.flowOn
|
||||
*/
|
||||
abstract class FlowUseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
|
||||
operator fun invoke(parameters: P): Flow<Result<R>> = execute(parameters)
|
||||
.onStart { emit(Result.Loading) }
|
||||
.catch { e -> emit(Result.Error(Exception(e))) }
|
||||
.flowOn(coroutineDispatcher)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user