diff --git a/apps/mobile-app/build.gradle.kts b/apps/mobile-app/build.gradle.kts
index 3d4f1fe..d2d22c3 100644
--- a/apps/mobile-app/build.gradle.kts
+++ b/apps/mobile-app/build.gradle.kts
@@ -71,6 +71,9 @@ dependencies {
implementation(composeBom)
androidTestImplementation(composeBom)
+ val koinBom = platform(libs.koin.bom)
+ implementation(koinBom)
+
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.browser)
implementation(libs.androidx.compose.ui)
@@ -88,10 +91,13 @@ dependencies {
implementation(libs.androidx.navigation.runtime.ktx)
implementation(libs.androidx.window)
implementation(libs.coil.compose)
- implementation(libs.kotlinx.coroutines.android)
- implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.google.guava)
implementation(libs.google.oss.licenses)
implementation(libs.jakewharton.timber)
+ implementation(libs.koin.androidx.compose)
+ implementation(libs.koin.androidx.compose.navigation)
+ implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.kotlinx.coroutines.core)
implementation(libs.bundles.database.room)
testImplementation(libs.junit)
diff --git a/apps/mobile-app/src/main/AndroidManifest.xml b/apps/mobile-app/src/main/AndroidManifest.xml
index 4f15c38..c2106ba 100644
--- a/apps/mobile-app/src/main/AndroidManifest.xml
+++ b/apps/mobile-app/src/main/AndroidManifest.xml
@@ -11,7 +11,7 @@
+
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/CappajvApp.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/CappajvApp.kt
index fda553d..ee94f3d 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/CappajvApp.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/CappajvApp.kt
@@ -14,6 +14,11 @@ import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.request.CachePolicy
import coil.util.DebugLogger
+import dev.marlonlom.apps.cappajv.di.appModule
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidLogger
+import org.koin.core.context.startKoin
+import org.koin.core.logger.Level
import timber.log.Timber
/**
@@ -30,9 +35,11 @@ val Context.dataStore by preferencesDataStore("cappajv-preferences")
* @author marlonlom
*/
class CappajvApp : Application(), ImageLoaderFactory {
+
override fun onCreate() {
super.onCreate()
setupTimber()
+ initializeKoinConfig()
}
override fun newImageLoader(): ImageLoader = ImageLoader(this)
@@ -60,4 +67,12 @@ class CappajvApp : Application(), ImageLoaderFactory {
}
}
+ private fun initializeKoinConfig() {
+ startKoin {
+ androidContext(this@CappajvApp)
+ androidLogger(Level.DEBUG)
+ modules(appModule)
+ }
+ }
+
}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/app_module.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/app_module.kt
new file mode 100644
index 0000000..f995a0c
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/app_module.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.di
+
+import dev.marlonlom.apps.cappajv.ui.main.MainActivityViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.dsl.module
+
+val mainActivityModule = module {
+ viewModelOf(::MainActivityViewModel)
+}
+
+val appModule = module {
+ includes(viewModelsModule)
+ includes(mainActivityModule)
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/data_module.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/data_module.kt
new file mode 100644
index 0000000..5368b8b
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/data_module.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.di
+
+import dev.marlonlom.apps.cappajv.core.catalog_source.CatalogDataService
+import dev.marlonlom.apps.cappajv.core.database.CappaDatabase
+import dev.marlonlom.apps.cappajv.core.database.datasource.LocalDataSource
+import dev.marlonlom.apps.cappajv.core.database.datasource.LocalDataSourceImpl
+import dev.marlonlom.apps.cappajv.core.preferences.UserPreferencesRepository
+import dev.marlonlom.apps.cappajv.dataStore
+import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListRepository
+import org.koin.android.ext.koin.androidContext
+import org.koin.dsl.module
+import java.util.Locale
+
+val dataModule = module {
+ single {
+ CappaDatabase.getInstance(androidContext()).let { db ->
+ LocalDataSourceImpl(
+ catalogItemsDao = db.catalogProductsDao(),
+ catalogPunctuationsDao = db.catalogPunctuationsDao(),
+ catalogFavoriteItemsDao = db.catalogFavoriteItemsDao()
+ )
+ }
+ }
+ single {
+ CatalogDataService(Locale.getDefault().language)
+ }
+ single {
+ CatalogListRepository(
+ localDataSource = get(),
+ catalogDataService = get(),
+ )
+ }
+ single {
+ UserPreferencesRepository(androidContext().dataStore)
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/viewmodels_module.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/viewmodels_module.kt
new file mode 100644
index 0000000..fc03fdf
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/di/viewmodels_module.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.di
+
+import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailViewModel
+import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListViewModel
+import dev.marlonlom.apps.cappajv.features.settings.SettingsViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.dsl.module
+
+val viewModelsModule = module {
+ includes(dataModule)
+ viewModelOf(::CatalogListViewModel)
+ viewModelOf(::CatalogDetailViewModel)
+ viewModelOf(::SettingsViewModel)
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModel.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModel.kt
index 365c49b..b20dbb3 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModel.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModel.kt
@@ -8,7 +8,6 @@ package dev.marlonlom.apps.cappajv.features.catalog_detail
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
@@ -34,15 +33,4 @@ class CatalogDetailViewModel(
}
}
- companion object {
-
- fun factory(
- repository: CatalogDetailRepository
- ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun create(modelClass: Class): T {
- return CatalogDetailViewModel(repository) as T
- }
- }
- }
}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_favorites/FavoriteProductsRoute.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_favorites/FavoriteProductsRoute.kt
index e26c851..20da13e 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_favorites/FavoriteProductsRoute.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_favorites/FavoriteProductsRoute.kt
@@ -21,8 +21,8 @@ fun FavoriteProductsRoute(
appState: CappajvAppState,
) {
val contentHorizontalPadding = when {
- appState.isLandscapeOrientation.not().and(appState.is7InTabletWidth) -> 40.dp
- appState.isLandscapeOrientation.not().and(appState.is10InTabletWidth) -> 80.dp
+ appState.isLandscape.not().and(appState.isMediumWidth) -> 40.dp
+ appState.isLandscape.not().and(appState.isExpandedWidth) -> 80.dp
else -> 20.dp
}
Column(
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRoute.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRoute.kt
index faaa792..76bbf24 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRoute.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRoute.kt
@@ -27,6 +27,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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
@@ -38,11 +39,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import coil.request.ImageRequest
import dev.marlonlom.apps.cappajv.R
import dev.marlonlom.apps.cappajv.core.database.entities.CatalogItemTuple
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+import org.koin.androidx.compose.koinViewModel
import timber.log.Timber
@ExperimentalFoundationApi
@@ -51,10 +54,14 @@ import timber.log.Timber
@Composable
fun CatalogListRoute(
appState: CappajvAppState,
+ viewModel: CatalogListViewModel = koinViewModel(),
) {
+
+ val catalogListState: CatalogListState by viewModel.uiState.collectAsStateWithLifecycle()
+
val contentHorizontalPadding = when {
- appState.isLandscapeOrientation.not().and(appState.is7InTabletWidth) -> 40.dp
- appState.isLandscapeOrientation.not().and(appState.is10InTabletWidth) -> 80.dp
+ appState.isLandscape.not().and(appState.isMediumWidth) -> 40.dp
+ appState.isLandscape.not().and(appState.isExpandedWidth) -> 80.dp
else -> 20.dp
}
@@ -76,7 +83,7 @@ fun CatalogListRoute(
)
}
- when (val catalogListState = appState.catalogListState) {
+ when (catalogListState) {
CatalogListState.Empty -> {
item {
Text(" :( ")
@@ -88,8 +95,9 @@ fun CatalogListRoute(
}
is CatalogListState.Listing -> {
+ val listingsData = catalogListState as CatalogListState.Listing
- catalogListState.map.keys.sorted().forEach { category ->
+ listingsData.map.keys.sorted().forEach { category ->
item {
Row(
modifier = Modifier
@@ -110,7 +118,7 @@ fun CatalogListRoute(
}
item {
- val tuples: List = catalogListState.map[category]
+ val tuples: List = listingsData.map[category]
.orEmpty().shuffled().subList(0, 5)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(20.dp),
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_search/SearchProductsRoute.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_search/SearchProductsRoute.kt
index 6e7971d..b1f3ca7 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_search/SearchProductsRoute.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/catalog_search/SearchProductsRoute.kt
@@ -21,8 +21,8 @@ fun SearchProductsRoute(
appState: CappajvAppState,
) {
val contentHorizontalPadding = when {
- appState.isLandscapeOrientation.not().and(appState.is7InTabletWidth) -> 40.dp
- appState.isLandscapeOrientation.not().and(appState.is10InTabletWidth) -> 80.dp
+ appState.isLandscape.not().and(appState.isMediumWidth) -> 40.dp
+ appState.isLandscape.not().and(appState.isExpandedWidth) -> 80.dp
else -> 20.dp
}
Column(
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/viewmodel.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/SettingsViewModel.kt
similarity index 100%
rename from apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/viewmodel.kt
rename to apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/SettingsViewModel.kt
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog.kt
index 3c14004..e989f06 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog.kt
@@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -25,6 +25,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.marlonlom.apps.cappajv.R
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+import org.koin.androidx.compose.koinViewModel
/**
* Settings dialog route composable ui.
@@ -41,10 +42,10 @@ import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
@Composable
fun SettingsDialog(
appState: CappajvAppState,
- viewModel: SettingsViewModel,
onDialogDismissed: () -> Unit,
openOssLicencesInfo: () -> Unit,
- openExternalUrl: (String) -> Unit
+ openExternalUrl: (String) -> Unit,
+ viewModel: SettingsViewModel = koinViewModel()
) {
val settingsUiState by viewModel.uiState.collectAsStateWithLifecycle()
when (settingsUiState) {
@@ -104,7 +105,7 @@ internal fun SettingsDialogContent(
},
text = {
Column {
- Divider()
+ HorizontalDivider()
BooleanSettingsContent(
appState,
editableSettings,
@@ -112,7 +113,7 @@ internal fun SettingsDialogContent(
)
SectionDivider()
LinksPanelContent(openExternalUrl, openOssLicencesInfo)
- Divider()
+ HorizontalDivider()
}
},
confirmButton = {
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog_ui_parts.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog_ui_parts.kt
index e13b041..63d89c6 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog_ui_parts.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/settings/dialog_ui_parts.kt
@@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
@@ -46,7 +46,7 @@ internal fun LinksPanelContent(
FlowRow(
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
- alignment = Alignment.Start,
+ alignment = Alignment.CenterHorizontally,
),
modifier = Modifier.fillMaxWidth(),
) {
@@ -82,7 +82,7 @@ internal fun LinksPanelContent(
*/
@Composable
fun SectionDivider() {
- Divider(Modifier.padding(top = 8.dp))
+ HorizontalDivider(Modifier.padding(top = 8.dp))
}
/**
@@ -100,7 +100,7 @@ internal fun BooleanSettingsContent(
editableSettings: UserEditableSettings,
onBooleanSettingUpdated: (String, Boolean) -> Unit
) {
- if (appState.is7InTabletWidth) {
+ if (appState.isMediumWidth) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/WelcomeRoute.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/WelcomeRoute.kt
new file mode 100644
index 0000000..f786292
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/WelcomeRoute.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.features.welcome.screens.BookWelcomeScreen
+import dev.marlonlom.apps.cappajv.features.welcome.screens.LandscapeCompactWelcomeScreen
+import dev.marlonlom.apps.cappajv.features.welcome.screens.PortraitWelcomeScreen
+import dev.marlonlom.apps.cappajv.features.welcome.screens.TableTopWelcomeScreen
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+import dev.marlonlom.apps.cappajv.ui.main.scaffold.ScaffoldContentType
+
+/**
+ * Welcome route composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState Application ui state.
+ * @param onContinueHomeButtonClicked Action for continue button clicked.
+ */
+@Composable
+fun WelcomeRoute(
+ appState: CappajvAppState,
+ onContinueHomeButtonClicked: () -> Unit
+) {
+ val contentHorizontalPadding = when {
+ appState.devicePosture is DevicePosture.Separating.Book -> 0.dp
+ appState.isCompactWidth.not() -> 80.dp
+ else -> 20.dp
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = contentHorizontalPadding),
+ contentAlignment = Alignment.Center
+ ) {
+ when (appState.scaffoldContentType) {
+ ScaffoldContentType.SinglePane -> {
+ when {
+ appState.isCompactHeight -> {
+ LandscapeCompactWelcomeScreen(appState, onContinueHomeButtonClicked)
+ }
+
+ else -> {
+ PortraitWelcomeScreen(appState, onContinueHomeButtonClicked)
+ }
+ }
+ }
+
+ is ScaffoldContentType.TwoPane -> {
+ when (appState.devicePosture) {
+ is DevicePosture.Separating.TableTop -> {
+ TableTopWelcomeScreen(appState, onContinueHomeButtonClicked)
+ }
+
+ is DevicePosture.Separating.Book -> {
+ BookWelcomeScreen(appState, onContinueHomeButtonClicked)
+ }
+
+ else -> {
+ PortraitWelcomeScreen(appState, onContinueHomeButtonClicked)
+ }
+ }
+
+ }
+ }
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeButton.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeButton.kt
new file mode 100644
index 0000000..98afbba
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeButton.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.parts
+
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import dev.marlonlom.apps.cappajv.R
+
+@Composable
+internal fun WelcomeButton(
+ onContinueHomeButtonClicked: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Button(
+ modifier = modifier,
+ onClick = {
+ onContinueHomeButtonClicked()
+ },
+ shape = MaterialTheme.shapes.small,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ )
+ ) {
+ Text(
+ text = stringResource(R.string.text_welcome_button),
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeDetail.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeDetail.kt
new file mode 100644
index 0000000..aa13238
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeDetail.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.parts
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.R
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+
+/**
+ * Details text for welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState
+ */
+@Composable
+internal fun WelcomeDetail(appState: CappajvAppState) {
+ Text(
+ modifier = Modifier
+ .padding(horizontal = 20.dp),
+ text = stringResource(R.string.text_welcome_detail),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = getWelcomeTextAlign(appState),
+ fontWeight = FontWeight.Normal
+ )
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomePosterImage.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomePosterImage.kt
new file mode 100644
index 0000000..47b3b4a
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomePosterImage.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.parts
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.R
+
+/**
+ * Poster image for welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ */
+@Composable
+internal fun WelcomePosterImage() {
+ Image(
+ painter = painterResource(id = R.drawable.img_welcome_poster),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp),
+ contentScale = ContentScale.Crop
+ )
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeTitle.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeTitle.kt
new file mode 100644
index 0000000..09c5989
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/parts/WelcomeTitle.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.parts
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.R
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+
+
+/**
+ * Title text for welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState Application ui state.
+ */
+@Composable
+internal fun WelcomeTitle(appState: CappajvAppState) {
+ Text(
+ modifier = Modifier
+ .paddingFromBaseline(top = 40.dp, bottom = 20.dp)
+ .padding(horizontal = 20.dp),
+ text = stringResource(R.string.text_welcome_title),
+ style = MaterialTheme.typography.titleLarge,
+ textAlign = getWelcomeTextAlign(appState),
+ fontWeight = FontWeight.Bold
+ )
+}
+
+@Composable
+internal fun getWelcomeTextAlign(appState: CappajvAppState): TextAlign = when {
+ appState.isCompactHeight -> TextAlign.Start
+ else -> TextAlign.Center
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens.kt
deleted file mode 100644
index e2c5d6f..0000000
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens.kt
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright 2024 Marlonlom
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package dev.marlonlom.apps.cappajv.features.welcome
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
-
-/**
- * Welcome route composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-fun WelcomeRoute(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit
-) {
- val contentHorizontalPadding = when {
- appState.isDeviceBookPosture -> 0.dp
- appState.isCompactWidth.not() -> 80.dp
- else -> 20.dp
- }
-
- Box(
- modifier = Modifier
- .fillMaxSize()
- .padding(horizontal = contentHorizontalPadding),
- contentAlignment = Alignment.Center
- ) {
- when {
- appState.isDeviceBookPostureVertical.and(appState.is10InTabletWidth.not()) -> {
- FoldedPortraitWelcomeScreen(appState, onContinueHomeButtonClicked)
- }
-
- appState.isDeviceBookPostureHorizontal.and(appState.is10InTabletWidth.not()) -> {
- FoldedLandscapeWelcomeScreen(appState, onContinueHomeButtonClicked)
- }
-
- appState.isDeviceSeparatingHorizontal.and(appState.is10InTabletWidth.not()) -> {
- FoldingLandscapeWelcomeScreen(appState, onContinueHomeButtonClicked)
- }
-
- appState.isDeviceSeparatingVertical.and(appState.is10InTabletWidth.not()) -> {
- FoldingPortraitWelcomeScreen(appState, onContinueHomeButtonClicked)
- }
-
- appState.isCompactHeight -> {
- LandscapeCompactWelcomeScreen(appState, onContinueHomeButtonClicked)
- }
-
- else -> {
- PortraitWelcomeScreen(appState, onContinueHomeButtonClicked)
- }
- }
- }
-}
-
-/**
- * Folded Landscape welcome screen composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-fun FoldedLandscapeWelcomeScreen(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit
-) {
- Column(modifier = Modifier.fillMaxSize()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight(0.5f)
- .padding(horizontal = 20.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Column(
- modifier = Modifier
- .fillMaxWidth(0.5f)
- .fillMaxHeight(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
- WelcomePosterImage()
- }
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(top = 20.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- WelcomeTitle(appState)
- WelcomeDetail(appState)
- WelcomeButton(onContinueHomeButtonClicked)
- }
- }
- Row(
- modifier = Modifier
- .fillMaxSize(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
-
- }
- }
-}
-
-/**
- * Folded Portrait welcome screen composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-internal fun FoldedPortraitWelcomeScreen(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit,
-) {
- Row(modifier = Modifier.fillMaxSize()) {
- Column(
- modifier = Modifier
- .fillMaxWidth(0.5f)
- .fillMaxHeight(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
- WelcomePosterImage()
- WelcomeTitle(appState)
- WelcomeDetail(appState)
- WelcomeButton(onContinueHomeButtonClicked)
- }
- Column(
- modifier = Modifier
- .padding(top = 40.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
-
- }
- }
-}
-
-/**
- * Folding Portrait welcome screen composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-internal fun FoldingPortraitWelcomeScreen(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit,
-) {
- Row(modifier = Modifier.fillMaxSize()) {
- Column(
- modifier = Modifier
- .fillMaxWidth(0.5f)
- .fillMaxHeight(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
- WelcomePosterImage()
- }
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(top = 40.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
- WelcomeTitle(appState)
- WelcomeDetail(appState)
- WelcomeButton(onContinueHomeButtonClicked)
- }
- }
-}
-
-/**
- * Folding Landscape welcome screen composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-internal fun FoldingLandscapeWelcomeScreen(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit,
-) {
- Column(modifier = Modifier.fillMaxSize()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight(0.5f),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- WelcomePosterImage()
- }
- Row(
- modifier = Modifier
- .fillMaxSize(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(top = 40.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- WelcomeTitle(appState)
- WelcomeDetail(appState)
- WelcomeButton(onContinueHomeButtonClicked)
- }
- }
- }
-}
-
-/**
- * Landscape compact welcome screen composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-internal fun LandscapeCompactWelcomeScreen(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit,
-) {
- Column(
- modifier = Modifier
- .fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(0.75f),
- horizontalArrangement = Arrangement.SpaceAround,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Column(modifier = Modifier.fillMaxWidth(0.35f)) {
- WelcomePosterImage()
- }
- Column(modifier = Modifier.fillMaxWidth()) {
- WelcomeTitle(appState)
- WelcomeDetail(appState)
- }
- }
- Row(
- modifier = Modifier.fillMaxWidth(0.6f),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- WelcomeButton(onContinueHomeButtonClicked)
- }
- }
-}
-
-/**
- * Portrait welcome screen composable ui.
- *
- * @author marlonlom
- *
- * @param appState Application ui state.
- * @param onContinueHomeButtonClicked Action for continue button clicked.
- */
-@Composable
-internal fun PortraitWelcomeScreen(
- appState: CappajvAppState,
- onContinueHomeButtonClicked: () -> Unit,
-) {
- val contentModifier = when {
- appState.is10InTabletWidth -> Modifier.fillMaxWidth(0.5f)
- appState.is7InTabletWidth -> Modifier.fillMaxWidth(0.75f)
- else -> Modifier.fillMaxWidth()
- }
-
- Column(
- modifier = contentModifier,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- WelcomePosterImage()
- WelcomeTitle(appState)
- WelcomeDetail(appState)
- WelcomeButton(onContinueHomeButtonClicked)
- }
-}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/BookWelcomeScreen.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/BookWelcomeScreen.kt
new file mode 100644
index 0000000..12e23fe
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/BookWelcomeScreen.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.screens
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeButton
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeDetail
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomePosterImage
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeTitle
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+
+/**
+ * Book mode folding welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState Application ui state.
+ * @param onContinueHomeButtonClicked Action for continue button clicked.
+ */
+@Composable
+internal fun BookWelcomeScreen(
+ appState: CappajvAppState,
+ onContinueHomeButtonClicked: () -> Unit,
+) {
+ val columnWidthRatio = when (appState.devicePosture) {
+ is DevicePosture.Separating.Book -> appState.devicePosture.hingeRatio
+ else -> 0.5f
+ }
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(columnWidthRatio),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ WelcomePosterImage()
+ }
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ WelcomeTitle(appState)
+ WelcomeDetail(appState)
+ WelcomeButton(
+ modifier = Modifier.padding(20.dp),
+ onContinueHomeButtonClicked = onContinueHomeButtonClicked
+ )
+ }
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/LandscapeCompactWelcomeScreen.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/LandscapeCompactWelcomeScreen.kt
new file mode 100644
index 0000000..3a1f914
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/LandscapeCompactWelcomeScreen.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.screens
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeButton
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeDetail
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomePosterImage
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeTitle
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+
+/**
+ * Landscape compact welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState Application ui state.
+ * @param onContinueHomeButtonClicked Action for continue button clicked.
+ */
+@Composable
+internal fun LandscapeCompactWelcomeScreen(
+ appState: CappajvAppState,
+ onContinueHomeButtonClicked: () -> Unit,
+) {
+ val contentMaxWidthRatio = if (appState.isMediumWidth) 1f else 0.75f
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(contentMaxWidthRatio),
+ horizontalArrangement = Arrangement.spacedBy(20.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(
+ horizontalAlignment = Alignment.End,
+ ) {
+ WelcomePosterImage()
+ }
+ Column(modifier = Modifier.fillMaxWidth()) {
+ WelcomeTitle(appState)
+ WelcomeDetail(appState)
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ WelcomeButton(
+ modifier = Modifier.padding(20.dp),
+ onContinueHomeButtonClicked = onContinueHomeButtonClicked
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/PortraitWelcomeScreen.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/PortraitWelcomeScreen.kt
new file mode 100644
index 0000000..4084185
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/PortraitWelcomeScreen.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.screens
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeButton
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeDetail
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomePosterImage
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeTitle
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+
+/**
+ * Portrait welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState Application ui state.
+ * @param onContinueHomeButtonClicked Action for continue button clicked.
+ */
+@Composable
+internal fun PortraitWelcomeScreen(
+ appState: CappajvAppState,
+ onContinueHomeButtonClicked: () -> Unit,
+) {
+ val contentModifier = when {
+ appState.isExpandedWidth -> Modifier.fillMaxWidth(0.5f)
+ appState.isMediumWidth -> Modifier.fillMaxWidth(0.75f)
+ else -> Modifier.fillMaxWidth()
+ }
+
+ Column(
+ modifier = contentModifier,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ WelcomePosterImage()
+ WelcomeTitle(appState)
+ WelcomeDetail(appState)
+ WelcomeButton(
+ modifier = Modifier
+ .padding(horizontal = 20.dp)
+ .padding(bottom = 20.dp, top = 40.dp),
+ onContinueHomeButtonClicked = onContinueHomeButtonClicked
+ )
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/TableTopWelcomeScreen.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/TableTopWelcomeScreen.kt
new file mode 100644
index 0000000..e173d04
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/screens/TableTopWelcomeScreen.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.features.welcome.screens
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeButton
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeDetail
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomePosterImage
+import dev.marlonlom.apps.cappajv.features.welcome.parts.WelcomeTitle
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+
+/**
+ * TableTop mode folding welcome screen composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param appState Application ui state.
+ * @param onContinueHomeButtonClicked Action for continue button clicked.
+ */
+@Composable
+internal fun TableTopWelcomeScreen(
+ appState: CappajvAppState,
+ onContinueHomeButtonClicked: () -> Unit,
+) {
+ val columnHeightRatio = when (appState.devicePosture) {
+ is DevicePosture.Separating.TableTop -> appState.devicePosture.hingeRatio
+ else -> 0.5f
+ }
+
+ Column(modifier = Modifier.fillMaxSize()) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight(columnHeightRatio),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ WelcomePosterImage()
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 40.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.SpaceBetween,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ WelcomeTitle(appState)
+ WelcomeDetail(appState)
+ WelcomeButton(
+ modifier = Modifier.padding(20.dp),
+ onContinueHomeButtonClicked = onContinueHomeButtonClicked
+ )
+ }
+ }
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/ui_parts.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/ui_parts.kt
deleted file mode 100644
index 7753eb0..0000000
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/features/welcome/ui_parts.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2024 Marlonlom
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package dev.marlonlom.apps.cappajv.features.welcome
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.paddingFromBaseline
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import dev.marlonlom.apps.cappajv.R
-import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
-
-@Composable
-internal fun WelcomePosterImage() {
- Image(
- painter = painterResource(id = R.drawable.img_welcome_poster),
- contentDescription = null,
- modifier = Modifier
- .height(160.dp),
- contentScale = ContentScale.Crop
- )
-}
-
-@Composable
-internal fun WelcomeButton(onContinueHomeButtonClicked: () -> Unit) {
- Button(
- modifier = Modifier.padding(top = 40.dp, bottom = 20.dp),
- onClick = {
- onContinueHomeButtonClicked()
- },
- shape = MaterialTheme.shapes.small,
- colors = ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.primary,
- contentColor = MaterialTheme.colorScheme.onPrimary,
- )
- ) {
- Text(text = stringResource(R.string.text_welcome_button))
- }
-}
-
-@Composable
-internal fun WelcomeDetail(appState: CappajvAppState) {
- Text(
- modifier = Modifier
- .padding(horizontal = 20.dp),
- text = stringResource(R.string.text_welcome_detail),
- style = MaterialTheme.typography.bodyMedium,
- textAlign = getWelcomeTextAlign(appState),
- fontWeight = FontWeight.Normal
- )
-}
-
-@Composable
-internal fun WelcomeTitle(appState: CappajvAppState) {
- Text(
- modifier = Modifier
- .paddingFromBaseline(top = 40.dp, bottom = 20.dp)
- .padding(horizontal = 20.dp),
- text = stringResource(R.string.text_welcome_title),
- style = MaterialTheme.typography.titleLarge,
- textAlign = getWelcomeTextAlign(appState),
- fontWeight = FontWeight.Bold
- )
-}
-
-@Composable
-private fun getWelcomeTextAlign(appState: CappajvAppState): TextAlign = when {
- appState.isLandscapeOrientation.and(appState.isCompactHeight) -> TextAlign.Start
- else -> TextAlign.Center
-}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/layout/DevicePosture.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/layout/DevicePosture.kt
new file mode 100644
index 0000000..6266f84
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/layout/DevicePosture.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.ui.layout
+
+
+import android.graphics.Rect
+import androidx.window.layout.FoldingFeature
+
+/**
+ * Information about the posture of the device.
+ *
+ * @author marlonlom
+ */
+sealed interface DevicePosture {
+
+ /**
+ * Normal posture of the device.
+ *
+ * @author marlonlom
+ */
+ data object Normal : DevicePosture
+
+ /**
+ * Separating posture of the half opened device.
+ *
+ * @author marlonlom
+ *
+ */
+ sealed interface Separating : DevicePosture {
+ /** Folding feature rectangle bounds. */
+ val bounds: Rect
+
+ /** Hinge ratio for screen separation. */
+ val hingeRatio: Float
+
+ /** Folding feature axis orientation indication. */
+ val orientation: FoldingFeature.Orientation
+
+ /** Indicates if folding feature is separating. */
+ val isSeparating: Boolean
+
+ /**
+ * Book posture of the device.
+ *
+ * @author marlonlom
+ *
+ * @property bounds Folding feature rectangle bounds.
+ * @property hingeRatio Hinge ratio for screen separation.
+ * @property orientation Folding feature axis orientation indication.
+ * @property isSeparating True/False indicating if folding feature is separating.
+ */
+ data class Book(
+ override val bounds: Rect,
+ override val hingeRatio: Float,
+ override val orientation: FoldingFeature.Orientation,
+ override val isSeparating: Boolean,
+ ) : Separating
+
+ /**
+ * Table top posture of the device.
+ *
+ * @author marlonlom
+ *
+ * @property bounds Folding feature rectangle bounds.
+ * @property hingeRatio Hinge ratio for screen separation.
+ * @property orientation Folding feature axis orientation indication.
+ * @property isSeparating True/False indicating if folding feature is separating.
+ */
+ data class TableTop(
+ override val bounds: Rect,
+ override val hingeRatio: Float,
+ override val orientation: FoldingFeature.Orientation,
+ override val isSeparating: Boolean,
+ ) : Separating
+
+ }
+
+}
+
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/layout/DevicePostureDetector.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/layout/DevicePostureDetector.kt
new file mode 100644
index 0000000..6e0201b
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/layout/DevicePostureDetector.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.ui.layout
+
+import androidx.window.layout.FoldingFeature
+
+/**
+ * Device posture detector single object that uses [FoldingFeature] information.
+ *
+ * @author marlonlom
+ */
+object DevicePostureDetector {
+
+ /**
+ * Returns the device posture for selected layout information.
+ *
+ * @param foldingFeature Folding feature from application window.
+ */
+ @JvmStatic
+ fun fromLayoutInfo(foldingFeature: FoldingFeature?): DevicePosture = when {
+
+ foldingFeature != null && isBookMode(foldingFeature) -> {
+ DevicePosture.Separating.Book(
+ bounds = foldingFeature.bounds,
+ hingeRatio = getHingeRatio(foldingFeature),
+ orientation = foldingFeature.orientation,
+ isSeparating = foldingFeature.isSeparating,
+ )
+ }
+
+ foldingFeature != null && isTableTopMode(foldingFeature) -> {
+ DevicePosture.Separating.TableTop(
+ bounds = foldingFeature.bounds,
+ hingeRatio = getHingeRatio(foldingFeature),
+ orientation = foldingFeature.orientation,
+ isSeparating = foldingFeature.isSeparating,
+ )
+ }
+
+ else -> DevicePosture.Normal
+ }
+
+ private fun getHingeRatio(
+ foldFeature: FoldingFeature
+ ): Float = when (foldFeature.orientation) {
+ FoldingFeature.Orientation.VERTICAL -> {
+ val screenWidth = foldFeature.bounds.left + foldFeature.bounds.right
+ foldFeature.bounds.left.toFloat() / screenWidth.toFloat()
+ }
+
+ else -> {
+ val screenHeight = foldFeature.bounds.top + foldFeature.bounds.bottom
+ foldFeature.bounds.top.toFloat() / screenHeight.toFloat()
+ }
+ }
+
+
+ private fun isBookMode(foldingFeature: FoldingFeature) = foldingFeature.let {
+ return@let when (it.state) {
+ FoldingFeature.State.HALF_OPENED -> {
+ it.state == FoldingFeature.State.HALF_OPENED
+ && it.orientation == FoldingFeature.Orientation.VERTICAL
+ && it.occlusionType == FoldingFeature.OcclusionType.NONE
+ }
+
+ FoldingFeature.State.FLAT -> {
+ it.state == FoldingFeature.State.FLAT
+ && it.orientation == FoldingFeature.Orientation.VERTICAL
+ && it.occlusionType == FoldingFeature.OcclusionType.FULL
+ }
+
+ else -> false
+ }
+ }
+
+ private fun isTableTopMode(foldingFeature: FoldingFeature) = foldingFeature.let {
+ return@let when (it.state) {
+ FoldingFeature.State.HALF_OPENED -> {
+ it.state == FoldingFeature.State.HALF_OPENED
+ && it.orientation == FoldingFeature.Orientation.HORIZONTAL
+ && it.occlusionType == FoldingFeature.OcclusionType.NONE
+ }
+
+ FoldingFeature.State.FLAT -> {
+ it.state == FoldingFeature.State.FLAT
+ && it.orientation == FoldingFeature.Orientation.HORIZONTAL
+ && it.occlusionType == FoldingFeature.OcclusionType.FULL
+ }
+
+ else -> false
+ }
+ }
+
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContent.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContent.kt
index b58d22c..245e788 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContent.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContent.kt
@@ -9,17 +9,10 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.platform.LocalContext
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import dev.marlonlom.apps.cappajv.core.preferences.UserPreferencesRepository
-import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailViewModel
-import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListViewModel
+import dev.marlonlom.apps.cappajv.features.welcome.WelcomeRoute
import dev.marlonlom.apps.cappajv.ui.main.scaffold.MainScaffold
import dev.marlonlom.apps.cappajv.ui.theme.CappajvTheme
-import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
import kotlinx.coroutines.ExperimentalCoroutinesApi
/**
@@ -59,10 +52,9 @@ private fun shouldUseDarkTheme(
* @author marlonlom
*
* @param mainActivityUiState Main activity ui state.
- * @param windowSizeClass Window size class.
- * @param userPreferencesRepository User preferences repository.
- * @param catalogListViewModel Catalog list viewmodel.
- * @param catalogDetailViewModel Catalog detail viewmodel.
+ * @param appUiState Application ui state.
+ * @param appContentCallbacks Application content callbacks.
+ * @param onOnboardingComplete Action for onboarding complete.
*/
@ExperimentalFoundationApi
@ExperimentalLayoutApi
@@ -71,31 +63,29 @@ private fun shouldUseDarkTheme(
@Composable
fun AppContent(
mainActivityUiState: MainActivityUiState,
- windowSizeClass: WindowSizeClass,
- devicePosture: DevicePosture,
- userPreferencesRepository: UserPreferencesRepository,
- catalogListViewModel: CatalogListViewModel,
- catalogDetailViewModel: CatalogDetailViewModel,
+ appUiState: CappajvAppState,
+ appContentCallbacks: AppContentCallbacks,
onOnboardingComplete: () -> Unit,
) = CappajvTheme(
darkTheme = shouldUseDarkTheme(mainActivityUiState),
dynamicColor = shouldUseDynamicColor(mainActivityUiState)
) {
+ when (mainActivityUiState) {
+ MainActivityUiState.Loading -> Unit
- val catalogListState by catalogListViewModel.uiState.collectAsStateWithLifecycle()
-
- val appContentCallbacks = newAppContentCallbacks(
- activityContext = LocalContext.current,
- catalogDetailViewModel = catalogDetailViewModel
- )
-
- MainScaffold(
- mainActivityUiState = mainActivityUiState,
- windowSizeClass = windowSizeClass,
- devicePosture = devicePosture,
- appContentCallbacks = appContentCallbacks,
- userPreferencesRepository = userPreferencesRepository,
- onOnboardingComplete = onOnboardingComplete,
- catalogListState = catalogListState
- )
+ is MainActivityUiState.Success -> {
+ if (mainActivityUiState.userData.isOnboarding) {
+ WelcomeRoute(
+ appState = appUiState,
+ onContinueHomeButtonClicked = onOnboardingComplete
+ )
+ } else {
+ MainScaffold(
+ mainActivityUiState = mainActivityUiState,
+ appState = appUiState,
+ appContentCallbacks = appContentCallbacks,
+ )
+ }
+ }
+ }
}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContentCallbacks.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContentCallbacks.kt
index ace214a..6a6d66c 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContentCallbacks.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppContentCallbacks.kt
@@ -10,7 +10,6 @@ import android.content.Intent
import androidx.compose.runtime.Composable
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetail
-import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailViewModel
import dev.marlonlom.apps.cappajv.ui.util.CustomTabsOpener
import kotlinx.coroutines.ExperimentalCoroutinesApi
import timber.log.Timber
@@ -41,13 +40,11 @@ data class AppContentCallbacks(
* @author marlonlom
*
* @param activityContext Activity context.
- * @param catalogDetailViewModel Catalog details viewmodel.
*/
@ExperimentalCoroutinesApi
@Composable
internal fun newAppContentCallbacks(
activityContext: Context,
- catalogDetailViewModel: CatalogDetailViewModel
) = AppContentCallbacks(
onOnboardingCompleter = {
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppState.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppState.kt
index e487d6c..7db4534 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppState.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/AppState.kt
@@ -5,21 +5,18 @@
package dev.marlonlom.apps.cappajv.ui.main
-import android.content.res.Configuration
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
-import androidx.window.layout.FoldingFeature
-import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListState
-import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.main.scaffold.ScaffoldContentClassifier
+import dev.marlonlom.apps.cappajv.ui.navigation.NavigationTypeSelector
/**
* Remembers the application ui state value.
@@ -28,7 +25,6 @@ import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
*
* @param windowSizeClass Window size class.
* @param navController Navigation controller.
- * @param localConfiguration Local configuration object.
*
* @return Application ui state value.
*/
@@ -36,22 +32,19 @@ import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
fun rememberCappajvAppState(
windowSizeClass: WindowSizeClass,
devicePosture: DevicePosture,
+ screenWidthDp: Int,
navController: NavHostController = rememberNavController(),
- localConfiguration: Configuration = LocalConfiguration.current,
- catalogListState: CatalogListState,
): CappajvAppState = remember(
windowSizeClass,
devicePosture,
+ screenWidthDp,
navController,
- localConfiguration,
- catalogListState
) {
CappajvAppState(
navController = navController,
windowSizeClass = windowSizeClass,
- localConfiguration = localConfiguration,
devicePosture = devicePosture,
- catalogListState = catalogListState
+ screenWidthDp = screenWidthDp,
)
}
@@ -62,73 +55,31 @@ fun rememberCappajvAppState(
*
* @param navController Navigation controller.
* @param windowSizeClass Window size class.
- * @param localConfiguration Local configuration.
* @property devicePosture Device posture, used for detecting foldable features.
- * @property catalogListState Catalog list ui state value.
*/
@Stable
data class CappajvAppState(
internal val navController: NavHostController,
val windowSizeClass: WindowSizeClass,
- private val localConfiguration: Configuration,
val devicePosture: DevicePosture,
- val catalogListState: CatalogListState,
+ val screenWidthDp: Int,
) {
val isCompactWidth get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact
val isCompactHeight get() = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
- val isLandscapeOrientation get() = localConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val isMediumWidth get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Medium
- val is7InTabletWidth get() = localConfiguration.smallestScreenWidthDp.dp >= 600.dp
+ val isExpandedWidth get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded
- val is10InTabletWidth get() = localConfiguration.smallestScreenWidthDp.dp >= 720.dp
+ val isLandscape get() = isMediumWidth.and(isCompactHeight).or(isExpandedWidth.and(isCompactHeight.not()))
- val canShowBottomNavigation get() = isCompactWidth
+ val scaffoldContentType
+ get() = ScaffoldContentClassifier.classify(
+ devicePosture, isExpandedWidth, isMediumWidth, isCompactHeight
+ )
- val canShowNavigationRail get() = isCompactWidth.not().and(is10InTabletWidth.not())
-
- val canShowExpandedNavigationDrawer get() = isCompactWidth.not().and(is10InTabletWidth)
-
- val isDeviceBookPosture get() = devicePosture is DevicePosture.BookPosture
-
- val isDeviceBookPostureVertical
- get() = when (devicePosture) {
- is DevicePosture.BookPosture -> {
- devicePosture.orientation == FoldingFeature.Orientation.VERTICAL
- }
-
- else -> false
- }
-
- val isDeviceBookPostureHorizontal
- get() = when (devicePosture) {
- is DevicePosture.BookPosture -> {
- devicePosture.orientation == FoldingFeature.Orientation.HORIZONTAL
- }
-
- else -> false
- }
-
- val isDeviceSeparating get() = devicePosture is DevicePosture.Separating
-
- val isDeviceSeparatingVertical
- get() = when (devicePosture) {
- is DevicePosture.Separating -> {
- devicePosture.orientation == FoldingFeature.Orientation.VERTICAL
- }
-
- else -> false
- }
-
- val isDeviceSeparatingHorizontal
- get() = when (devicePosture) {
- is DevicePosture.Separating -> {
- devicePosture.orientation == FoldingFeature.Orientation.HORIZONTAL
- }
-
- else -> false
- }
+ val navigationType get() = NavigationTypeSelector.fromWindowSize(windowSizeClass, devicePosture, screenWidthDp)
/**
* Changes selected top destination.
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/MainActivity.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/MainActivity.kt
index 0250f1c..1e1402d 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/MainActivity.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/MainActivity.kt
@@ -9,7 +9,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -18,24 +17,17 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
-import dev.marlonlom.apps.cappajv.core.catalog_source.CatalogDataService
-import dev.marlonlom.apps.cappajv.core.database.CappaDatabase
-import dev.marlonlom.apps.cappajv.core.database.datasource.LocalDataSource
-import dev.marlonlom.apps.cappajv.core.database.datasource.LocalDataSourceImpl
-import dev.marlonlom.apps.cappajv.core.preferences.UserPreferencesRepository
-import dev.marlonlom.apps.cappajv.dataStore
-import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailRepository
-import dev.marlonlom.apps.cappajv.features.catalog_detail.CatalogDetailViewModel
-import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListRepository
-import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListViewModel
-import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePostureDetector
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collect
@@ -43,12 +35,15 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import org.koin.androidx.viewmodel.ext.android.viewModel
+import kotlin.contracts.ExperimentalContracts
/**
* Main activity class.
*
* @author marlonlom
*/
+@ExperimentalContracts
@ExperimentalFoundationApi
@ExperimentalLayoutApi
@ExperimentalCoroutinesApi
@@ -56,27 +51,7 @@ import kotlinx.coroutines.launch
@ExperimentalMaterial3WindowSizeClassApi
class MainActivity : ComponentActivity() {
- private val newUserPreferencesRepository: UserPreferencesRepository get() = UserPreferencesRepository(dataStore)
-
- private val mainActivityViewModel: MainActivityViewModel by viewModels(
- factoryProducer = {
- MainActivityViewModel.factory(newUserPreferencesRepository)
- })
-
- private val catalogListViewModel: CatalogListViewModel by viewModels(
- factoryProducer = {
- CatalogListViewModel.factory(
- CatalogListRepository(
- localDataSource = newLocalDataSource(),
- catalogDataService = CatalogDataService()
- )
- )
- })
-
- private val catalogDetailViewModel: CatalogDetailViewModel by viewModels(
- factoryProducer = {
- CatalogDetailViewModel.factory(CatalogDetailRepository(newLocalDataSource()))
- })
+ private val mainActivityViewModel: MainActivityViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -87,9 +62,7 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- mainActivityViewModel.uiState
- .onEach { mainActivityUiState = it }
- .collect()
+ mainActivityViewModel.uiState.onEach { mainActivityUiState = it }.collect()
}
}
@@ -102,41 +75,36 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
+ val devicePostureFlow = WindowInfoTracker
+ .getOrCreate(this@MainActivity)
+ .windowLayoutInfo(this@MainActivity)
+ .flowWithLifecycle(lifecycle)
+ .map { layoutInfo ->
+ val foldingFeature = layoutInfo.displayFeatures
+ .filterIsInstance(FoldingFeature::class.java)
+ .firstOrNull()
+ DevicePostureDetector.fromLayoutInfo(foldingFeature)
+ }.stateIn(
+ scope = lifecycleScope,
+ started = SharingStarted.Eagerly,
+ initialValue = DevicePosture.Normal
+ )
+
setContent {
- val windowSizeClass = calculateWindowSizeClass(this)
val devicePosture by devicePostureFlow.collectAsStateWithLifecycle()
+ val appState = rememberCappajvAppState(
+ windowSizeClass = calculateWindowSizeClass(this),
+ devicePosture = devicePosture,
+ screenWidthDp = LocalConfiguration.current.smallestScreenWidthDp
+ )
AppContent(
mainActivityUiState = mainActivityUiState,
- windowSizeClass = windowSizeClass,
- devicePosture = devicePosture,
- userPreferencesRepository = newUserPreferencesRepository,
- catalogListViewModel = catalogListViewModel,
- catalogDetailViewModel = catalogDetailViewModel,
- onOnboardingComplete = {
- mainActivityViewModel.setOnboardingComplete()
- },
+ appUiState = appState,
+ appContentCallbacks = newAppContentCallbacks(applicationContext),
+ onOnboardingComplete = mainActivityViewModel::setOnboardingComplete,
)
}
}
- private fun newLocalDataSource(): LocalDataSource {
- val cappaDatabase = CappaDatabase.getInstance(applicationContext)
- return LocalDataSourceImpl(
- catalogItemsDao = cappaDatabase.catalogProductsDao(),
- catalogFavoriteItemsDao = cappaDatabase.catalogFavoriteItemsDao(),
- catalogPunctuationsDao = cappaDatabase.catalogPunctuationsDao(),
- )
- }
-
- private val devicePostureFlow = WindowInfoTracker
- .getOrCreate(this@MainActivity)
- .windowLayoutInfo(this@MainActivity)
- .flowWithLifecycle(lifecycle)
- .map { layoutInfo -> DevicePosture.fromLayoutInfo(layoutInfo) }
- .stateIn(
- scope = lifecycleScope,
- started = SharingStarted.Eagerly,
- initialValue = DevicePosture.NormalPosture
- )
}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffold.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffold.kt
index 9f54012..12335a2 100644
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffold.kt
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffold.kt
@@ -6,37 +6,23 @@
package dev.marlonlom.apps.cappajv.ui.main.scaffold
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
import androidx.navigation.compose.currentBackStackEntryAsState
-import dev.marlonlom.apps.cappajv.core.preferences.UserPreferencesRepository
-import dev.marlonlom.apps.cappajv.features.catalog_list.CatalogListState
import dev.marlonlom.apps.cappajv.features.settings.SettingsDialog
-import dev.marlonlom.apps.cappajv.features.settings.SettingsViewModel
-import dev.marlonlom.apps.cappajv.features.welcome.WelcomeRoute
import dev.marlonlom.apps.cappajv.ui.main.AppContentCallbacks
import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
import dev.marlonlom.apps.cappajv.ui.main.MainActivityUiState
-import dev.marlonlom.apps.cappajv.ui.main.rememberCappajvAppState
import dev.marlonlom.apps.cappajv.ui.navigation.CatalogDestination
-import dev.marlonlom.apps.cappajv.ui.navigation.MainNavHost
-import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.navigation.NavigationType
/**
* Main scaffold composable ui.
@@ -44,11 +30,8 @@ import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
* @author marlonlom
*
* @param mainActivityUiState Main activity ui state.
- * @param windowSizeClass Window size class.
- * @param appContentCallbacks Application content callbacks.
- * @param userPreferencesRepository User preferences repository.
- * @param onOnboardingComplete Action for onboarding complete event.
* @param appState Main application ui state
+ * @param appContentCallbacks Application content callbacks.
*/
@ExperimentalFoundationApi
@ExperimentalMaterial3Api
@@ -56,21 +39,12 @@ import dev.marlonlom.apps.cappajv.ui.util.DevicePosture
@Composable
fun MainScaffold(
mainActivityUiState: MainActivityUiState,
- windowSizeClass: WindowSizeClass,
- devicePosture: DevicePosture,
+ appState: CappajvAppState,
appContentCallbacks: AppContentCallbacks,
- userPreferencesRepository: UserPreferencesRepository,
- onOnboardingComplete: () -> Unit,
- catalogListState: CatalogListState,
- appState: CappajvAppState = rememberCappajvAppState(
- windowSizeClass = windowSizeClass,
- devicePosture = devicePosture,
- catalogListState = catalogListState
- ),
) {
- val currentAppRoute = appState.navController
- .currentBackStackEntryAsState().value?.destination?.route ?: CatalogDestination.CatalogList.route
+ val currentAppRoute = appState.navController.currentBackStackEntryAsState().value?.destination?.route
+ ?: CatalogDestination.CatalogList.route
var bottomNavSelectedIndex by rememberSaveable {
mutableIntStateOf(
CatalogDestination.topCatalogDestinations.map { it.route }.indexOf(currentAppRoute)
@@ -83,7 +57,6 @@ fun MainScaffold(
if (showSettingsDialog) {
SettingsDialog(
appState = appState,
- viewModel = SettingsViewModel(repository = userPreferencesRepository),
onDialogDismissed = { showSettingsDialog = false },
openOssLicencesInfo = appContentCallbacks.openOssLicencesInfo,
openExternalUrl = appContentCallbacks.openExternalUrl
@@ -95,14 +68,13 @@ fun MainScaffold(
bottomBar = {
val isCurrentlyOnboarding: (MainActivityUiState) -> Boolean = { uiState ->
when (uiState) {
- is MainActivityUiState.Success ->
- uiState.userData.isOnboarding
+ is MainActivityUiState.Success -> uiState.userData.isOnboarding
else -> true
}
}
- val isBottomBarVisible = appState.canShowBottomNavigation.and(isTopDestination)
+ val isBottomBarVisible = (appState.navigationType == NavigationType.BOTTOM_NAV).and(isTopDestination)
.and(isCurrentlyOnboarding(mainActivityUiState).not())
if (isBottomBarVisible) {
@@ -122,91 +94,16 @@ fun MainScaffold(
) { paddingValues ->
MainScaffoldContent(
paddingValues = paddingValues,
- mainActivityUiState = mainActivityUiState,
appState = appState,
appContentCallbacks = appContentCallbacks,
- onOnboardingComplete = onOnboardingComplete,
selectedPosition = bottomNavSelectedIndex,
- onSelectedPositionChanged = { position, route ->
- if (route == CatalogDestination.Settings.route) {
- showSettingsDialog = true
- } else {
- bottomNavSelectedIndex = position
- appState.changeTopDestination(route)
- }
- },
- )
- }
-}
-
-/**
- * Main scaffold content composable ui.
- *
- * @author marlonlom
- *
- * @param paddingValues Padding values.
- * @param mainActivityUiState Main activity ui state.
- * @param appState Application ui state.
- * @param appContentCallbacks Application content callbacks.
- * @param onOnboardingComplete Action for onboarding complete event.
- */
-@ExperimentalFoundationApi
-@ExperimentalMaterial3Api
-@ExperimentalLayoutApi
-@Composable
-private fun MainScaffoldContent(
- paddingValues: PaddingValues,
- mainActivityUiState: MainActivityUiState,
- appState: CappajvAppState,
- appContentCallbacks: AppContentCallbacks,
- onOnboardingComplete: () -> Unit,
- selectedPosition: Int,
- onSelectedPositionChanged: (Int, String) -> Unit
-) {
- Box(
- modifier = Modifier
- .safeDrawingPadding()
- .padding(paddingValues),
- contentAlignment = Alignment.Center
- ) {
- when (mainActivityUiState) {
- is MainActivityUiState.Success -> {
- if (mainActivityUiState.userData.isOnboarding) {
- WelcomeRoute(
- appState = appState,
- onContinueHomeButtonClicked = onOnboardingComplete
- )
- } else {
- if (appState.canShowExpandedNavigationDrawer) {
- ExpandedNavigationDrawer(
- selectedPosition = selectedPosition,
- onSelectedPositionChanged = onSelectedPositionChanged,
- ) {
- MainNavHost(
- appState = appState,
- appContentCallbacks
- )
- }
- } else {
- Row {
- if (appState.canShowNavigationRail) {
- MainNavigationRail(
- selectedPosition = selectedPosition,
- onSelectedPositionChanged = onSelectedPositionChanged,
- )
- }
-
- MainNavHost(
- appState = appState,
- appContentCallbacks
- )
- }
- }
- }
+ ) { position, route ->
+ if (route == CatalogDestination.Settings.route) {
+ showSettingsDialog = true
+ } else {
+ bottomNavSelectedIndex = position
+ appState.changeTopDestination(route)
}
-
- else -> Unit
}
-
}
}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffoldContent.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffoldContent.kt
new file mode 100644
index 0000000..47620a1
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/MainScaffoldContent.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.ui.main.scaffold
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawingPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import dev.marlonlom.apps.cappajv.ui.main.AppContentCallbacks
+import dev.marlonlom.apps.cappajv.ui.main.CappajvAppState
+import dev.marlonlom.apps.cappajv.ui.navigation.MainNavHost
+import dev.marlonlom.apps.cappajv.ui.navigation.NavigationType
+import timber.log.Timber
+
+
+/**
+ * Main scaffold content composable ui.
+ *
+ * @author marlonlom
+ *
+ * @param paddingValues Padding values.
+ * @param appState Application ui state.
+ * @param appContentCallbacks Application content callbacks.
+ * @param selectedPosition
+ * @param onSelectedPositionChanged
+ */
+@ExperimentalFoundationApi
+@ExperimentalMaterial3Api
+@ExperimentalLayoutApi
+@Composable
+internal fun MainScaffoldContent(
+ paddingValues: PaddingValues,
+ appState: CappajvAppState,
+ appContentCallbacks: AppContentCallbacks,
+ selectedPosition: Int,
+ onSelectedPositionChanged: (Int, String) -> Unit
+) {
+ Timber.d(
+ """
+ [MainScaffoldContent]
+ scaffoldContentType=${appState.scaffoldContentType}
+ devicePosture=${appState.devicePosture}
+ """.trimIndent()
+ )
+ Box(
+ modifier = Modifier
+ .safeDrawingPadding()
+ .padding(paddingValues),
+ contentAlignment = Alignment.Center,
+ ) {
+ when (appState.navigationType) {
+ NavigationType.EXPANDED_NAV -> {
+ ExpandedNavigationDrawer(
+ selectedPosition = selectedPosition,
+ onSelectedPositionChanged = onSelectedPositionChanged,
+ ) {
+ MainNavHost(
+ appState = appState, appContentCallbacks
+ )
+ }
+ }
+
+ NavigationType.NAVIGATION_RAIL -> {
+ Row {
+ MainNavigationRail(
+ selectedPosition = selectedPosition,
+ onSelectedPositionChanged = onSelectedPositionChanged,
+ )
+
+ MainNavHost(
+ appState = appState, appContentCallbacks
+ )
+ }
+ }
+
+ NavigationType.BOTTOM_NAV -> {
+ MainNavHost(
+ appState = appState, appContentCallbacks
+ )
+ }
+ }
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/ScaffoldContentType.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/ScaffoldContentType.kt
new file mode 100644
index 0000000..3455f9d
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/main/scaffold/ScaffoldContentType.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.ui.main.scaffold
+
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+import dev.marlonlom.apps.cappajv.ui.main.scaffold.ScaffoldContentType.SinglePane
+import dev.marlonlom.apps.cappajv.ui.main.scaffold.ScaffoldContentType.TwoPane
+
+/**
+ * Scaffold inner content type sealed interface definition.
+ *
+ * @author marlonlom
+ *
+ */
+sealed interface ScaffoldContentType {
+
+ /**
+ * Single pane scaffold inner content type data object.
+ *
+ * @author marlonlom
+ *
+ */
+ data object SinglePane : ScaffoldContentType
+
+ /**
+ * Two pane scaffold inner content type data object.
+ *
+ * @author marlonlom
+ *
+ * @property hingeRatio Hinge ratio as percentage number.
+ */
+ data class TwoPane(
+ val hingeRatio: Float = 0.5f
+ ) : ScaffoldContentType
+}
+
+/**
+ * Scaffold inner content classifier single object.
+ *
+ * @author marlonlom
+ *
+ */
+object ScaffoldContentClassifier {
+
+ /**
+ * Indicated scaffold inner content type by window size information and device posture.
+ *
+ * @param devicePosture
+ *
+ * @return Scaffold inner content type.
+ */
+ @JvmStatic
+ fun classify(
+ devicePosture: DevicePosture,
+ isExpandedWidth: Boolean,
+ isMediumWidth: Boolean,
+ isCompactHeight: Boolean,
+ ): ScaffoldContentType = when {
+
+ isMediumWidth.not().and(!isExpandedWidth.not()) -> when (devicePosture) {
+ is DevicePosture.Separating.TableTop -> TwoPane(devicePosture.hingeRatio)
+ else -> SinglePane
+ }
+
+ isExpandedWidth.and(isCompactHeight.not()) -> when (devicePosture) {
+ DevicePosture.Normal -> TwoPane()
+ is DevicePosture.Separating.TableTop -> TwoPane(devicePosture.hingeRatio)
+ is DevicePosture.Separating.Book -> TwoPane(devicePosture.hingeRatio)
+ }
+
+ isMediumWidth.and(isCompactHeight.not()) -> when (devicePosture) {
+ DevicePosture.Normal -> SinglePane
+ is DevicePosture.Separating.TableTop -> TwoPane(devicePosture.hingeRatio)
+ is DevicePosture.Separating.Book -> TwoPane(devicePosture.hingeRatio)
+ }
+
+ isCompactHeight.and(devicePosture is DevicePosture.Normal) -> SinglePane
+
+ else -> SinglePane
+ }
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/navigation/NavigationType.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/navigation/NavigationType.kt
new file mode 100644
index 0000000..9e0a28b
--- /dev/null
+++ b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/navigation/NavigationType.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 Marlonlom
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package dev.marlonlom.apps.cappajv.ui.navigation
+
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
+import androidx.compose.material3.windowsizeclass.WindowSizeClass
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import dev.marlonlom.apps.cappajv.ui.layout.DevicePosture
+
+object NavigationTypeSelector {
+
+ @JvmStatic
+ fun fromWindowSize(
+ wsc: WindowSizeClass,
+ devicePosture: DevicePosture,
+ screenWidthDp: Int,
+ ): NavigationType = when {
+ screenWidthDp >= 720 -> NavigationType.EXPANDED_NAV
+ screenWidthDp >= 600 -> NavigationType.NAVIGATION_RAIL
+ isCompactHeight(devicePosture, wsc) -> NavigationType.NAVIGATION_RAIL
+ isFullyMediumWidth(devicePosture, wsc) -> NavigationType.NAVIGATION_RAIL
+ else -> NavigationType.BOTTOM_NAV
+ }
+
+ private fun isCompactHeight(devicePosture: DevicePosture, wsc: WindowSizeClass) =
+ devicePosture == DevicePosture.Normal && wsc.heightSizeClass == WindowHeightSizeClass.Compact
+
+ private fun isFullyMediumWidth(devicePosture: DevicePosture, wsc: WindowSizeClass) =
+ devicePosture == DevicePosture.Normal && wsc.widthSizeClass == WindowWidthSizeClass.Medium
+}
+
+enum class NavigationType {
+ BOTTOM_NAV, NAVIGATION_RAIL, EXPANDED_NAV
+}
diff --git a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/util/DevicePostureUtil.kt b/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/util/DevicePostureUtil.kt
deleted file mode 100644
index 3cc0746..0000000
--- a/apps/mobile-app/src/main/kotlin/dev/marlonlom/apps/cappajv/ui/util/DevicePostureUtil.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2024 Marlonlom
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package dev.marlonlom.apps.cappajv.ui.util
-
-import android.graphics.Rect
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowLayoutInfo
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
-
-/**
- * Information about the posture of the device.
- *
- * @author marlonlom
- */
-sealed interface DevicePosture {
-
- /**
- * Normal posture posture of the device.
- *
- * @author marlonlom
- */
- data object NormalPosture : DevicePosture
-
- /**
- * Book posture posture of the foldable device.
- *
- * @author marlonlom
- *
- * @property hingePosition Hinge position.
- * @property orientation Orientation (Horizontal, Vertical)
- */
- data class BookPosture(
- val hingePosition: Rect,
- val orientation: FoldingFeature.Orientation
- ) : DevicePosture
-
- /**
- * Separating posture posture of the foldable device.
- *
- * @author marlonlom
- *
- * @property hingePosition Hinge position.
- * @property orientation Orientation (Horizontal, Vertical)
- */
- data class Separating(
- val hingePosition: Rect,
- var orientation: FoldingFeature.Orientation,
- ) : DevicePosture
-
- companion object {
-
- /**
- * Returns the device posture for selected layout information.
- *
- * @param layoutInfo Window layout information.
- */
- @JvmStatic
- fun fromLayoutInfo(layoutInfo: WindowLayoutInfo): DevicePosture {
- val foldingFeature =
- layoutInfo.displayFeatures.filterIsInstance().firstOrNull()
-
- return when {
-
- isBookPosture(foldingFeature) -> BookPosture(
- hingePosition = foldingFeature.bounds,
- orientation = foldingFeature.orientation
- )
-
- isSeparating(foldingFeature) -> Separating(
- hingePosition = foldingFeature.bounds,
- orientation = foldingFeature.orientation
- )
-
- else -> NormalPosture
- }
- }
- }
-
-}
-
-@OptIn(ExperimentalContracts::class)
-fun isBookPosture(foldFeature: FoldingFeature?): Boolean {
- contract { returns(true) implies (foldFeature != null) }
- return foldFeature?.state == FoldingFeature.State.HALF_OPENED
-}
-
-@OptIn(ExperimentalContracts::class)
-fun isSeparating(foldFeature: FoldingFeature?): Boolean {
- contract { returns(true) implies (foldFeature != null) }
- return foldFeature?.state == FoldingFeature.State.FLAT && foldFeature.isSeparating
-}
diff --git a/apps/mobile-app/src/main/res/values-es/strings.xml b/apps/mobile-app/src/main/res/values-es/strings.xml
index e6fd969..8176b8b 100644
--- a/apps/mobile-app/src/main/res/values-es/strings.xml
+++ b/apps/mobile-app/src/main/res/values-es/strings.xml
@@ -20,7 +20,7 @@
configuraciones de la
Aplicación
Versión de la aplicación
- Favorites
+ Favoritos
Bienvenidos
A continuación podrás ver el catálogo de productos del programa Amigos Juan Valdez®, en el cual podrás vivir la experiencia única de visitar las tiendas Juan Valdez® Café utilizando tus puntos para canjearlos por alimentos, bebidas, premios y beneficios.
diff --git a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailRepositoryTest.kt b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailRepositoryTest.kt
index b5cf3b5..7de790a 100644
--- a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailRepositoryTest.kt
+++ b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailRepositoryTest.kt
@@ -17,6 +17,7 @@ import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import java.util.Locale
internal class CatalogDetailRepositoryTest {
@@ -26,7 +27,7 @@ internal class CatalogDetailRepositoryTest {
fun setUp() {
repository = CatalogDetailRepository(
FakeLocalDataSource(
- CatalogDataService()
+ CatalogDataService(Locale.getDefault().language)
)
)
}
diff --git a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModelTest.kt b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModelTest.kt
index 1fe9c9b..f07ea32 100644
--- a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModelTest.kt
+++ b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_detail/CatalogDetailViewModelTest.kt
@@ -13,6 +13,7 @@ import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import java.util.Locale
internal class CatalogDetailViewModelTest {
@@ -25,7 +26,7 @@ internal class CatalogDetailViewModelTest {
fun setUp() {
viewModel = CatalogDetailViewModel(
CatalogDetailRepository(
- FakeLocalDataSource(CatalogDataService())
+ FakeLocalDataSource(CatalogDataService(Locale.getDefault().language))
)
)
}
diff --git a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRepositoryTest.kt b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRepositoryTest.kt
index 593f6a3..edb37e8 100644
--- a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRepositoryTest.kt
+++ b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListRepositoryTest.kt
@@ -13,6 +13,7 @@ import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
+import java.util.Locale
internal class CatalogListRepositoryTest {
@@ -22,9 +23,9 @@ internal class CatalogListRepositoryTest {
fun setUp() {
repository = CatalogListRepository(
localDataSource = FakeLocalDataSource(
- CatalogDataService()
+ CatalogDataService(Locale.getDefault().language)
),
- catalogDataService = CatalogDataService()
+ catalogDataService = CatalogDataService(Locale.getDefault().language)
)
}
diff --git a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListViewModelTest.kt b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListViewModelTest.kt
index 3b0b5ee..9501c24 100644
--- a/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListViewModelTest.kt
+++ b/apps/mobile-app/src/test/kotlin/dev/marlonlom/apps/cappajv/features/catalog_list/CatalogListViewModelTest.kt
@@ -12,6 +12,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
+import java.util.Locale
internal class CatalogListViewModelTest {
@@ -25,9 +26,9 @@ internal class CatalogListViewModelTest {
viewModel = CatalogListViewModel(
CatalogListRepository(
localDataSource = FakeLocalDataSource(
- CatalogDataService()
+ CatalogDataService(Locale.getDefault().language)
),
- catalogDataService = CatalogDataService()
+ catalogDataService = CatalogDataService(Locale.getDefault().language)
)
)
val uiState = viewModel.uiState
diff --git a/features/core/catalog-source/src/main/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataService.kt b/features/core/catalog-source/src/main/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataService.kt
index 3e7fe5a..f83a2b5 100644
--- a/features/core/catalog-source/src/main/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataService.kt
+++ b/features/core/catalog-source/src/main/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataService.kt
@@ -7,16 +7,21 @@ package dev.marlonlom.apps.cappajv.core.catalog_source
import kotlinx.serialization.json.Json
import java.io.InputStream
+import java.util.Locale
/**
* Catalog data service class.
*
* @author marlonlom
*
+ * @property language Selected language for fetching catalog data, default to english (en).
*/
-class CatalogDataService {
+class CatalogDataService(
+ private val language: String = Locale.ENGLISH.language
+) {
+
+ private var catalogJsonPath = if (language == "es") CATALOG_JSON_FILENAME else ENG_CATALOG_JSON_FILENAME
- private var catalogJsonPath = CATALOG_JSON_FILENAME
internal fun changePath(jsonPath: String) {
catalogJsonPath = jsonPath
}
@@ -41,7 +46,8 @@ class CatalogDataService {
private fun getJsonResourceAsStream(): InputStream? = this.javaClass.classLoader.getResourceAsStream(catalogJsonPath)
companion object {
- private const val CATALOG_JSON_FILENAME = "catalog.json"
+ private const val CATALOG_JSON_FILENAME = "es/catalog.json"
+ private const val ENG_CATALOG_JSON_FILENAME = "en/catalog.json"
}
}
diff --git a/features/core/catalog-source/src/main/resources/en/catalog.json b/features/core/catalog-source/src/main/resources/en/catalog.json
new file mode 100644
index 0000000..e7abc52
--- /dev/null
+++ b/features/core/catalog-source/src/main/resources/en/catalog.json
@@ -0,0 +1,601 @@
+[
+ {
+ "id": "15398",
+ "title": "Affogato",
+ "category": "Cold drinks",
+ "detail": "It is a perfect Italian coffee-dessert for summer after-dinner meals.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Afogatto-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Affogato",
+ "pointsQty": 1750
+ }
+ ]
+ },
+ {
+ "id": "15389",
+ "title": "Almojábana",
+ "category": "Pastry",
+ "detail": "It is a Colombian dough based on corn flour and cheese.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Copia-de-Almojabana-Juan-Valdez-e1642542513402.png",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1400
+ }
+ ]
+ },
+ {
+ "id": "10411",
+ "title": "Aromática bosque infusión",
+ "category": "Hot drinks",
+ "detail": "It is a hot and very traditional drink in the Colombian Andes, forest infusion flavor.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/AROMATICA.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1475
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "10410",
+ "title": "Aromática fusión tropical",
+ "category": "Hot drinks",
+ "detail": "It is a hot and very traditional drink in the Colombian Andes, tropical fusion flavor.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/TE.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1475
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "970",
+ "title": "Aromática primavera",
+ "category": "Hot drinks",
+ "detail": "It is a hot and very traditional drink in the Colombian Andes, spring flavor.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/TE.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1475
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "971",
+ "title": "Aromática silvestre",
+ "category": "Hot drinks",
+ "detail": "It is a hot and very traditional drink in the Colombian Andes, wild flavor.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/AROMATICA.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1475
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "10419",
+ "title": "Café americano",
+ "category": "Hot drinks",
+ "detail": "Coffee made from espresso and hot water.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Cafe-Americano-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "10418",
+ "title": "Cappuccino",
+ "category": "Hot drinks",
+ "detail": "Espresso coffee with steamed milk with a creamy texture.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Cappuccino-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1625
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1875
+ },
+ {
+ "label": "Medium Decaf",
+ "pointsQty": 1875
+ },
+ {
+ "label": "Big",
+ "pointsQty": 2375
+ }
+ ]
+ },
+ {
+ "id": "10412",
+ "title": "Chai",
+ "category": "Hot drinks",
+ "detail": "Hot drink based on spiced tea.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Chai-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Traditional Medium",
+ "pointsQty": 2725
+ },
+ {
+ "label": "Apple Cinnamon Medium",
+ "pointsQty": 2875
+ },
+ {
+ "label": "Large Cinnamon Apple",
+ "pointsQty": 3125
+ },
+ {
+ "label": "Traditional Large 1.65",
+ "pointsQty": 2975
+ }
+ ]
+ },
+ {
+ "id": "10416",
+ "title": "Chocolate",
+ "category": "Hot drinks",
+ "detail": "Chocolate with steamed milk.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Chocolate-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 2125
+ }
+ ]
+ },
+ {
+ "id": "15390",
+ "title": "Cold brew nitro",
+ "category": "Cold drinks",
+ "detail": "Nitrogen foaming coffee after resting in cold water for 24 hours, with a mild flavor, delicious aroma, with the color of natural coffee and a beer-like appearance.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Cold-Brew-nitro-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1475
+ }
+ ]
+ },
+ {
+ "id": "15391",
+ "title": "Cold brew original",
+ "category": "Cold drinks",
+ "detail": "Coffee rested in cold water for 24 hours, with a mild flavor, delicious aroma and the color of natural coffee.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Cold-Brew-nitro-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1225
+ }
+ ]
+ },
+ {
+ "id": "10415",
+ "title": "Espresso",
+ "category": "Hot drinks",
+ "detail": "Coffee prepared in espresso machine.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Espresso-tradicional-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Normal",
+ "pointsQty": 1375
+ },
+ {
+ "label": "Double",
+ "pointsQty": 1725
+ },
+ {
+ "label": "chopped up",
+ "pointsQty": 1975
+ }
+ ]
+ },
+ {
+ "id": "979",
+ "title": "Flat white",
+ "category": "Hot drinks",
+ "detail": "Coffee prepared in an espresso machine with a thin layer of milk foam.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Flat-white-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 2075
+ }
+ ]
+ },
+ {
+ "id": "15379",
+ "title": "Galleta choco chip",
+ "category": "Pastry",
+ "detail": "A crunchy cookie with chocolate chips.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Galleta-chips-de-chocolate-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1375
+ }
+ ]
+ },
+ {
+ "id": "15396",
+ "title": "Granizado",
+ "category": "Cold drinks",
+ "detail": "It is a cold and refreshing iced coffee drink.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Granizado-juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 2225
+ }
+ ]
+ },
+ {
+ "id": "10417",
+ "title": "Latte",
+ "category": "Hot drinks",
+ "detail": "It is a coffee prepared in an espresso machine and steamed milk.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Latte-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1625
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1875
+ },
+ {
+ "label": "Medium Decaf",
+ "pointsQty": 1875
+ },
+ {
+ "label": "Big",
+ "pointsQty": 2375
+ }
+ ]
+ },
+ {
+ "id": "15397",
+ "title": "Latte frío",
+ "category": "Cold drinks",
+ "detail": "It is a coffee prepared in an espresso machine and steamed milk, served cold.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Latte-frio-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 2375
+ }
+ ]
+ },
+ {
+ "id": "10414",
+ "title": "Macchiatto",
+ "category": "Hot drinks",
+ "detail": "Espresso coffee with a layer of milk foam.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Machiatto-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Macchiatto",
+ "pointsQty": 1975
+ }
+ ]
+ },
+ {
+ "id": "983",
+ "title": "Mocca",
+ "category": "Hot drinks",
+ "detail": "Hot drink that combines espresso coffee, milk and chocolate.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Mocca-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 2225
+ }
+ ]
+ },
+ {
+ "id": "969",
+ "title": "Nevado arequipe",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold coffee drink with arequipe decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Nevado-Arequipe-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3225
+ }
+ ]
+ },
+ {
+ "id": "15392",
+ "title": "Nevado baileys",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold drink based on coffee with Baileys decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Nevado-Baileys-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3625
+ }
+ ]
+ },
+ {
+ "id": "15381",
+ "title": "Nevado brownie",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold coffee drink with brownie decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Nevado-Brownie-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3225
+ }
+ ]
+ },
+ {
+ "id": "12971",
+ "title": "Nevado cafe",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold coffee-based drink decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2023/02/nevado_de_cafe___300ml_700x700px.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 2975
+ }
+ ]
+ },
+ {
+ "id": "12981",
+ "title": "Nevado café reducido en azucar",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold coffee-based drink, reduced in sugar decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2023/02/nevado_rejado_en_azucar_300ml_700x700px.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3225
+ }
+ ]
+ },
+ {
+ "id": "15393",
+ "title": "Nevado chai",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold drink based on chai tea decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Nevado-chai-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3475
+ }
+ ]
+ },
+ {
+ "id": "12982",
+ "title": "Nevado chocolate",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold chocolate coffee drink decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2023/02/nevado_de_chocolate_300ml_700x700px.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3225
+ }
+ ]
+ },
+ {
+ "id": "968",
+ "title": "Nevado galleta",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold coffee drink with cookie pieces and condensed milk decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Nevado-galleta-oreo-y-leche-condensada-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3225
+ }
+ ]
+ },
+ {
+ "id": "12983",
+ "title": "Nevado mokachip",
+ "category": "Cold drinks",
+ "detail": "It is a creamy cold coffee drink with chocolate and chips decorated with Chantilly.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2023/02/nevado_mocachip_300ml_700x700px.jpg",
+ "punctuations": [
+ {
+ "label": "Medium",
+ "pointsQty": 3225
+ }
+ ]
+ },
+ {
+ "id": "15388",
+ "title": "Palito de queso",
+ "category": "Pastry",
+ "detail": "It is a butter-based puff pastry filled with a mixture of peasant cheese, mozarella and coastal cheese.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Palito-de-queso-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "15387",
+ "title": "Pandebono",
+ "category": "Pastry",
+ "detail": "It is a dough based on cassava starch and cheese.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Pandebono-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1400
+ }
+ ]
+ },
+ {
+ "id": "15386",
+ "title": "Pandebono de bocadillo",
+ "category": "Pastry",
+ "detail": "It is a dough based on cassava starch and cheese filled with a sandwich.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/PANDEBONO_DE_BOCADILLO.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1475
+ }
+ ]
+ },
+ {
+ "id": "15385",
+ "title": "Pastel gloria arequipe y guayaba",
+ "category": "Pastry",
+ "detail": "It is a puff pastry filled with arequipe and guava.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Pastel-gloria-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1375
+ }
+ ]
+ },
+ {
+ "id": "10413",
+ "title": "Pods",
+ "category": "Hot drinks",
+ "detail": "It is prepared in a special machine for Pods.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/pods.png",
+ "punctuations": [
+ {
+ "label": "Hill",
+ "pointsQty": 1500
+ },
+ {
+ "label": "Summit",
+ "pointsQty": 1500
+ },
+ {
+ "label": "Summit Decaf",
+ "pointsQty": 1500
+ },
+ {
+ "label": "Solid",
+ "pointsQty": 1500
+ },
+ {
+ "label": "Origin (Huila, Nariño, Sierra, Tolima)",
+ "pointsQty": 1625
+ }
+ ]
+ },
+ {
+ "id": "12984",
+ "title": "Rollito de canela",
+ "category": "Pastry",
+ "detail": "It is a sweet bread with a soft filling of cinnamon, butter and freshly baked sugar.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2023/02/rollito_de_canela.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1725
+ }
+ ]
+ },
+ {
+ "id": "987",
+ "title": "Tintos",
+ "category": "Hot drinks",
+ "detail": "Cup of filtered coffee flavored with panela, cloves, cinnamon and lemon.",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Tinto-Juan-Valdez-campesino.jpg",
+ "punctuations": [
+ {
+ "label": "Small",
+ "pointsQty": 1125
+ },
+ {
+ "label": "Medium",
+ "pointsQty": 1325
+ },
+ {
+ "label": "Big",
+ "pointsQty": 1575
+ },
+ {
+ "label": "Small Campesino",
+ "pointsQty": 1375
+ },
+ {
+ "label": "Medium Campesino",
+ "pointsQty": 1575
+ }
+ ]
+ },
+ {
+ "id": "15384",
+ "title": "Torta de banano",
+ "category": "Pastry",
+ "detail": "Delicious slice of banana cake",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Torta-de-banano-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 1875
+ }
+ ]
+ },
+ {
+ "id": "15383",
+ "title": "Torta de chocolate",
+ "category": "Pastry",
+ "detail": "Delicious slice of chocolate cake",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Torta-de-chocolate-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 2225
+ }
+ ]
+ },
+ {
+ "id": "15382",
+ "title": "Torta de zanahoria",
+ "category": "Pastry",
+ "detail": "Delicious slice of carrot cake",
+ "picture": "https://juanvaldez.com/wp-content/uploads/2022/10/Torta-de-zanahoria-Juan-Valdez.jpg",
+ "punctuations": [
+ {
+ "label": "Unit",
+ "pointsQty": 2225
+ }
+ ]
+ }
+]
diff --git a/features/core/catalog-source/src/main/resources/catalog.json b/features/core/catalog-source/src/main/resources/es/catalog.json
similarity index 100%
rename from features/core/catalog-source/src/main/resources/catalog.json
rename to features/core/catalog-source/src/main/resources/es/catalog.json
diff --git a/features/core/catalog-source/src/test/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataServiceTest.kt b/features/core/catalog-source/src/test/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataServiceTest.kt
index 0d1b791..824b9bf 100644
--- a/features/core/catalog-source/src/test/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataServiceTest.kt
+++ b/features/core/catalog-source/src/test/kotlin/dev/marlonlom/apps/cappajv/core/catalog_source/CatalogDataServiceTest.kt
@@ -19,7 +19,7 @@ internal class CatalogDataServiceTest {
@Before
fun init() {
- catalogResponse = CatalogDataService().fetchData()
+ catalogResponse = CatalogDataService("es").fetchData()
}
@Test
@@ -71,7 +71,7 @@ internal class CatalogDataServiceTest {
@Test
fun shouldValidateErrorFetchingWrongJsonData() {
- val service = CatalogDataService()
+ val service = CatalogDataService("es")
service.changePath("none.json")
catalogResponse = service.fetchData()
assertTrue(catalogResponse is Response.Failure)
@@ -81,7 +81,7 @@ internal class CatalogDataServiceTest {
@Test
fun shouldValidateErrorWhileSerializingJsonData() {
- val service = CatalogDataService()
+ val service = CatalogDataService("es")
service.changePath("catalog-single.json")
catalogResponse = service.fetchData()
assertTrue(catalogResponse is Response.Failure)
diff --git a/features/core/database/build.gradle.kts b/features/core/database/build.gradle.kts
index 9557518..7710f87 100644
--- a/features/core/database/build.gradle.kts
+++ b/features/core/database/build.gradle.kts
@@ -44,6 +44,7 @@ dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.bundles.database.room)
+ implementation(libs.google.guava)
ksp(libs.androidx.room.compiler)
diff --git a/features/core/preferences-datastore/build.gradle.kts b/features/core/preferences-datastore/build.gradle.kts
index 5685cc7..381411b 100644
--- a/features/core/preferences-datastore/build.gradle.kts
+++ b/features/core/preferences-datastore/build.gradle.kts
@@ -44,6 +44,7 @@ dependencies {
implementation(libs.androidx.datastore.preferences)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.google.guava)
testImplementation(libs.junit)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6e4c627..e5d5389 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -19,6 +19,7 @@ androidx-room-ktx = "androidx.room:room-ktx:2.6.1"
androidx-room-runtime = "androidx.room:room-runtime:2.6.1"
androidx-window = "androidx.window:window:1.2.0"
coil-compose = "io.coil-kt:coil-compose:2.6.0"
+google-guava = "com.google.guava:guava:27.0.1-android"
google-oss-licenses = "com.google.android.gms:play-services-oss-licenses:17.0.1"
google-oss-licenses-plugin = "com.google.android.gms:oss-licenses-plugin:0.10.6"
kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
@@ -28,6 +29,11 @@ kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0"
kotlinx-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
jakewharton-timber = "com.jakewharton.timber:timber:5.0.1"
+# koin-implementation-with-bom
+koin-bom = "io.insert-koin:koin-bom:3.5.0"
+koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose" }
+koin-androidx-compose-navigation = { module = "io.insert-koin:koin-androidx-compose-navigation" }
+
# test-implementation
junit = "junit:junit:4.13.2"
kotlinx-coroutines-test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0"
@@ -46,7 +52,6 @@ androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-material3-wsc = { module = "androidx.compose.material3:material3-window-size-class" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
-androidx-compose-ui-googlefonts = { module = "androidx.compose.ui:ui-text-google-fonts" }
androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }