diff --git a/.idea/gradle.xml b/.idea/gradle.xml index d7c9288..8250f55 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,10 +4,8 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 83ce945..a7a5484 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -99,6 +99,7 @@ dependencies { implementation(libs.core.ktx) implementation(libs.lifecycle.runtime.ktx) implementation(libs.activity.compose) + implementation(libs.androidx.navigation.compose) testImplementation(libs.junit) androidTestImplementation(libs.test.core.ktx) androidTestImplementation(libs.androidx.test.ext.junit) diff --git a/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/SettingsActivityTest.kt b/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/SettingsActivityTest.kt index dec7b6d..982a2fb 100644 --- a/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/SettingsActivityTest.kt +++ b/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/SettingsActivityTest.kt @@ -9,7 +9,6 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.test.rule.GrantPermissionRule import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.view.activity.MainActivity import com.lorenzovainigli.foodexpirationdates.view.composable.DateFormatDialog import com.lorenzovainigli.foodexpirationdates.view.composable.DateFormatRow import org.junit.Rule diff --git a/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivityTest.kt b/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivityTest.kt index cd2717c..7ee00c1 100644 --- a/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivityTest.kt +++ b/app/src/androidTest/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivityTest.kt @@ -14,6 +14,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.rule.GrantPermissionRule import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.view.MainActivity import com.lorenzovainigli.foodexpirationdates.view.composable.FOOD_CARD import org.junit.Before import org.junit.Rule diff --git a/app/src/androidTestFull/java/com/lorenzovainigli/foodexpirationdates/FirebaseTest.kt b/app/src/androidTestFull/java/com/lorenzovainigli/foodexpirationdates/FirebaseTest.kt index 5c24a64..d5b4cb0 100644 --- a/app/src/androidTestFull/java/com/lorenzovainigli/foodexpirationdates/FirebaseTest.kt +++ b/app/src/androidTestFull/java/com/lorenzovainigli/foodexpirationdates/FirebaseTest.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice -import com.lorenzovainigli.foodexpirationdates.view.activity.MainActivity +import com.lorenzovainigli.foodexpirationdates.view.MainActivity import junit.framework.TestCase.assertTrue import org.junit.Rule import org.junit.Test diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c5997f..570fba3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:theme="@style/Theme.FoodExpirationDates" tools:targetApi="31"> @@ -25,16 +25,6 @@ - - - - - - dueTime) + dueTime.add(Calendar.DAY_OF_MONTH, 1) + val initialDelay = dueTime.timeInMillis - currentTime.timeInMillis + val formattedTime = formatTimeDifference(initialDelay) + if (BuildConfig.DEBUG) { + Toast.makeText( + context, + "Notification in $formattedTime", + Toast.LENGTH_SHORT + ).show() + } + val workRequest = PeriodicWorkRequestBuilder( + 1, TimeUnit.DAYS + ) + .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) + .build() + WorkManager.getInstance(context) + .enqueueUniquePeriodicWork( + CheckExpirationsWorker.workerID, + ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + workRequest + ) + } + + private fun formatTimeDifference(timeDifference: Long): String { + val days = TimeUnit.MILLISECONDS.toDays(timeDifference) + val hours = TimeUnit.MILLISECONDS.toHours(timeDifference) % 24 + val minutes = TimeUnit.MILLISECONDS.toMinutes(timeDifference) % 60 + val seconds = TimeUnit.MILLISECONDS.toSeconds(timeDifference) % 60 + val formattedTime = StringBuilder() + if (days > 0) { + formattedTime.append("$days day${if (days > 1) "s" else ""} ") + } + if (hours > 0) { + formattedTime.append("$hours hour${if (hours > 1) "s" else ""} ") + } + if (minutes > 0) { + formattedTime.append("$minutes minute${if (minutes > 1) "s" else ""} ") + } + if (seconds > 0) { + formattedTime.append("$seconds second${if (seconds > 1) "s" else ""} ") + } + return formattedTime.toString().trim() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt index 1f7f3c1..29c95f5 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt @@ -12,7 +12,7 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.lorenzovainigli.foodexpirationdates.R import com.lorenzovainigli.foodexpirationdates.model.repository.ExpirationDateRepository -import com.lorenzovainigli.foodexpirationdates.view.activity.MainActivity +import com.lorenzovainigli.foodexpirationdates.view.MainActivity import kotlinx.coroutines.flow.first import java.util.Calendar import javax.inject.Inject diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/ui/theme/Theme.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/ui/theme/Theme.kt index 61aff0a..7ae0e85 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/ui/theme/Theme.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/ui/theme/Theme.kt @@ -5,8 +5,14 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository +import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel private val LightColors = lightColorScheme( primary = md_theme_light_primary, @@ -85,16 +91,27 @@ fun FoodExpirationDatesTheme( val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } + darkTheme -> DarkColors else -> LightColors } val systemUiController = rememberSystemUiController() - //val useDarkIcons = isSystemInDarkTheme() + var useDarkIcons: Boolean = !isSystemInDarkTheme() + if (LocalViewModelStoreOwner.current != null) { + val prefsViewModel: PreferencesViewModel = viewModel() + useDarkIcons = + when (prefsViewModel.getThemeMode(LocalContext.current).collectAsState().value) { + PreferencesRepository.Companion.ThemeMode.LIGHT.ordinal -> true + PreferencesRepository.Companion.ThemeMode.DARK.ordinal -> false + else -> !isSystemInDarkTheme() + } + } SideEffect { systemUiController.setSystemBarsColor( - color = colorScheme.surface + darkIcons = useDarkIcons, + color = Color.Transparent ) } diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt new file mode 100644 index 0000000..64c0f2a --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt @@ -0,0 +1,93 @@ +package com.lorenzovainigli.foodexpirationdates.view + +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.annotation.RequiresApi +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.core.view.WindowCompat +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.compose.rememberNavController +import com.lorenzovainigli.foodexpirationdates.di.AppModule +import com.lorenzovainigli.foodexpirationdates.di.DaggerAppComponent +import com.lorenzovainigli.foodexpirationdates.model.NotificationManager +import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository +import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.composable.MyScaffold +import com.lorenzovainigli.foodexpirationdates.viewmodel.ExpirationDatesViewModel +import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + val viewModel: ExpirationDatesViewModel by viewModels() + val preferencesViewModel: PreferencesViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + + val splashScreen = installSplashScreen() + splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenLoading.value } + + DaggerAppComponent.builder() + .appModule(AppModule()) + .build() + NotificationManager.setupNotificationChannel(this) + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onResume() { + super.onResume() + val context = this + setContent { + val viewModel: ExpirationDatesViewModel = viewModel() + val prefsViewModel: PreferencesViewModel = viewModel() + val darkThemeState = prefsViewModel.getThemeMode(context).collectAsState().value + val dynamicColorsState = prefsViewModel.getDynamicColors(context).collectAsState().value + val isInDarkTheme = when (darkThemeState) { + PreferencesRepository.Companion.ThemeMode.LIGHT.ordinal -> false + PreferencesRepository.Companion.ThemeMode.DARK.ordinal -> true + else -> isSystemInDarkTheme() + } + FoodExpirationDatesTheme( + darkTheme = isInDarkTheme, + dynamicColor = dynamicColorsState + ) { + Surface( + modifier = Modifier + .fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold( + activity = this, + navController = navController, + showSnackbar = showSnackbar + ) { + Navigation( + activity = this, + navController = navController, + showSnackbar = showSnackbar + ) + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/Navigation.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/Navigation.kt new file mode 100644 index 0000000..094ad2b --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/Navigation.kt @@ -0,0 +1,76 @@ +package com.lorenzovainigli.foodexpirationdates.view + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.InfoScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.InsertScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.MainScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.Screen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.SettingsScreen + +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun Navigation( + activity: MainActivity? = null, + navController: NavHostController, + showSnackbar: MutableState +) { + NavHost( + modifier = Modifier.fillMaxSize(), + navController = navController, + startDestination = Screen.MainScreen.route + ) { + composable(route = Screen.MainScreen.route) { + MainScreen( + activity = activity, + navController = navController, + showSnackbar = showSnackbar + ) + } + composable( + route = Screen.InsertScreen.route + "?itemId={itemId}", + arguments = listOf( + navArgument("itemId"){ + type = NavType.StringType + nullable = true + } + ) + ){ entry -> + InsertScreen( + activity = activity, + navController = navController, + itemId = entry.arguments?.getString("itemId") + ) + } + composable(route = Screen.AboutScreen.route){ + InfoScreen() + } + composable(route = Screen.SettingsScreen.route){ + SettingsScreen(activity = activity) + } + } +} + +//@RequiresApi(Build.VERSION_CODES.O) +//@Preview +//@Composable +//fun NavigationPreview(){ +// FoodExpirationDatesTheme { +// Surface(modifier = Modifier.fillMaxSize()) { +// Navigation( +// navController = rememberNavController(), +// coroutineScope = rememberCoroutineScope(), +// snackbarHostState = SnackbarHostState() +// ) +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/InfoActivity.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/InfoActivity.kt deleted file mode 100644 index 1b9489e..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/InfoActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.activity - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.InfoActivityLayout - -class InfoActivity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - InfoActivityLayout( - context = this - ) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/InsertActivity.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/InsertActivity.kt deleted file mode 100644 index f66534c..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/InsertActivity.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.activity - -import android.app.Activity -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import com.lorenzovainigli.foodexpirationdates.di.AppModule -import com.lorenzovainigli.foodexpirationdates.di.DaggerAppComponent -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.InsertActivityLayout -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class InsertActivity : ComponentActivity() { - - private lateinit var activity: Activity - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity = this - val itemId = intent.extras?.getInt("ITEM_ID") - DaggerAppComponent.builder() - .appModule(AppModule()) - .build() - setContent { - InsertActivityLayout( - context = this, - itemId = itemId - ) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivity.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivity.kt deleted file mode 100644 index 7b0d681..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/MainActivity.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.activity - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.lifecycle.viewmodel.compose.viewModel -import com.lorenzovainigli.foodexpirationdates.di.AppModule -import com.lorenzovainigli.foodexpirationdates.di.DaggerAppComponent -import com.lorenzovainigli.foodexpirationdates.model.NotificationManager -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.MainActivityLayout -import com.lorenzovainigli.foodexpirationdates.viewmodel.ExpirationDatesViewModel -import dagger.hilt.android.AndroidEntryPoint -import androidx.activity.viewModels -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen - -@AndroidEntryPoint -class MainActivity : ComponentActivity() { - - private val viewModel: ExpirationDatesViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - - val splashScreen = installSplashScreen() - splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenLoading.value } - - super.onCreate(savedInstanceState) - DaggerAppComponent.builder() - .appModule(AppModule()) - .build() - NotificationManager.setupNotificationChannel(this) - } - - override fun onResume() { - super.onResume() - setContent { - val items by viewModel.getDates().collectAsState(emptyList()) - MainActivityLayout( - context = this, - items = items, - viewModel = viewModel - ) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/SettingsActivity.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/SettingsActivity.kt deleted file mode 100644 index 1edec47..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/activity/SettingsActivity.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.activity - -import android.os.Build -import android.os.Bundle -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.annotation.RequiresApi -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import com.lorenzovainigli.foodexpirationdates.BuildConfig -import com.lorenzovainigli.foodexpirationdates.model.worker.CheckExpirationsWorker -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.SettingsActivityLayout -import dagger.hilt.android.AndroidEntryPoint -import java.util.Calendar -import java.util.concurrent.TimeUnit - -@AndroidEntryPoint -class SettingsActivity : ComponentActivity() { - - @RequiresApi(Build.VERSION_CODES.O) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - SettingsActivityLayout( - scheduleDailyNotification = ::scheduleDailyNotification - ) - } - } - - private fun scheduleDailyNotification(hour: Int, minute: Int) { - val currentTime = Calendar.getInstance() - val dueTime = Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, hour) - set(Calendar.MINUTE, minute) - set(Calendar.SECOND, 0) - } - if (currentTime > dueTime) - dueTime.add(Calendar.DAY_OF_MONTH, 1) - val initialDelay = dueTime.timeInMillis - currentTime.timeInMillis - val formattedTime = formatTimeDifference(initialDelay) - if (BuildConfig.DEBUG) { - Toast.makeText( - applicationContext, - "Notification in $formattedTime", - Toast.LENGTH_SHORT - ).show() - } - val workRequest = PeriodicWorkRequestBuilder( - 1, TimeUnit.DAYS - ) - .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) - .build() - WorkManager.getInstance(applicationContext) - .enqueueUniquePeriodicWork( - CheckExpirationsWorker.workerID, - ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - workRequest - ) - } - - private fun formatTimeDifference(timeDifference: Long): String { - val days = TimeUnit.MILLISECONDS.toDays(timeDifference) - val hours = TimeUnit.MILLISECONDS.toHours(timeDifference) % 24 - val minutes = TimeUnit.MILLISECONDS.toMinutes(timeDifference) % 60 - val seconds = TimeUnit.MILLISECONDS.toSeconds(timeDifference) % 60 - val formattedTime = StringBuilder() - if (days > 0) { - formattedTime.append("$days day${if (days > 1) "s" else ""} ") - } - if (hours > 0) { - formattedTime.append("$hours hour${if (hours > 1) "s" else ""} ") - } - if (minutes > 0) { - formattedTime.append("$minutes minute${if (minutes > 1) "s" else ""} ") - } - if (seconds > 0) { - formattedTime.append("$seconds second${if (seconds > 1) "s" else ""} ") - } - return formattedTime.toString().trim() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AppIcon.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AppIcon.kt index 653ce11..aa98dc9 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AppIcon.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AppIcon.kt @@ -18,7 +18,8 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asComposeColorFilter import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.res.imageResource -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times @@ -69,7 +70,8 @@ fun AppIcon( } } -@Preview +@PreviewLightDark +@PreviewDynamicColors @Composable fun AppIconPreview() { FoodExpirationDatesTheme { @@ -78,6 +80,8 @@ fun AppIconPreview() { ) { Column { AppIcon(size = 48.dp) + AppIcon(size = 48.dp, color = MaterialTheme.colorScheme.secondary) + AppIcon(size = 48.dp, color = MaterialTheme.colorScheme.tertiary) } } } diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AutoResizedText.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AutoResizedText.kt index 1819a1d..770c7af 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AutoResizedText.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/AutoResizedText.kt @@ -16,10 +16,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isUnspecified import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.view.preview.DefaultPreviews @Composable fun AutoResizedText( @@ -61,7 +61,7 @@ fun AutoResizedText( ) } -@DefaultPreviews +@Preview @Composable fun AutoResizedTextPreview(){ FoodExpirationDatesTheme { diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/DateFormatDialog.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/DateFormatDialog.kt index 86efc1c..c65a0c2 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/DateFormatDialog.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/DateFormatDialog.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.window.Dialog import com.lorenzovainigli.foodexpirationdates.R import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.view.preview.DefaultPreviews import com.lorenzovainigli.foodexpirationdates.view.preview.LanguagePreviews import java.text.SimpleDateFormat import java.util.Calendar @@ -105,7 +104,6 @@ fun DateFormatRow( ) } -@DefaultPreviews @LanguagePreviews @Composable fun DateFormatDialogPreview(){ diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt index 24a0002..21bca2b 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt @@ -35,8 +35,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository import com.lorenzovainigli.foodexpirationdates.model.entity.ExpirationDate +import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository import com.lorenzovainigli.foodexpirationdates.ui.theme.DarkOrange import com.lorenzovainigli.foodexpirationdates.ui.theme.DarkRed import com.lorenzovainigli.foodexpirationdates.ui.theme.DarkYellow @@ -44,8 +44,7 @@ import com.lorenzovainigli.foodexpirationdates.ui.theme.LightOrange import com.lorenzovainigli.foodexpirationdates.ui.theme.LightRed import com.lorenzovainigli.foodexpirationdates.ui.theme.LightYellow import com.lorenzovainigli.foodexpirationdates.ui.theme.TonalElevation -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.getItemsForPreview -import com.lorenzovainigli.foodexpirationdates.view.preview.DefaultPreviews +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.getItemsForPreview import com.lorenzovainigli.foodexpirationdates.view.preview.LanguagePreviews import java.text.SimpleDateFormat import java.util.Calendar @@ -104,7 +103,7 @@ fun FoodCard( .testTag(FOOD_CARD) .padding(4.dp) .clip(RoundedCornerShape(10.dp)), - tonalElevation = TonalElevation.level5() + tonalElevation = TonalElevation.level1() ) { Row( modifier = Modifier @@ -155,7 +154,6 @@ fun FoodCard( } } -@DefaultPreviews @LanguagePreviews @Composable fun FoodCardPreview() { diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MainMenu.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MainMenu.kt deleted file mode 100644 index 1e86aeb..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MainMenu.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.composable - -import android.content.Intent -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Info -import androidx.compose.material.icons.outlined.MoreVert -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.view.activity.InfoActivity -import com.lorenzovainigli.foodexpirationdates.view.activity.SettingsActivity - -data class MenuItem( - val label: String, - val icon: ImageVector, - val action: () -> Unit -) - -@Composable -fun getMenuItems(): List { - val context = LocalContext.current - return arrayListOf( - MenuItem( - label = stringResource(id = R.string.settings), - icon = Icons.Outlined.Settings, - action = { - context.startActivity(Intent(context, SettingsActivity::class.java)) - } - ), - MenuItem( - label = stringResource(id = R.string.about_this_app), - icon = Icons.Outlined.Info, - action = { - context.startActivity(Intent(context, InfoActivity::class.java)) - } - ) - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MainMenu() { - var isBottomSheetOpen by remember { - mutableStateOf(false) - } - IconButton(onClick = { - isBottomSheetOpen = true - }) { - Icon( - imageVector = Icons.Outlined.MoreVert, - contentDescription = stringResource(id = R.string.menu), - tint = MaterialTheme.colorScheme.onSurface - ) - } - if (isBottomSheetOpen) { - ModalBottomSheet(onDismissRequest = { - isBottomSheetOpen = false - }) { - val list = getMenuItems() - // adding each menu item - list.forEach { menuItem -> - DropdownMenuItem( - text = { - Row( - verticalAlignment = CenterVertically - ) { - Icon( - imageVector = menuItem.icon, - contentDescription = menuItem.label - ) - Spacer(modifier = Modifier.padding(4.dp)) - Text( - text = menuItem.label, - style = MaterialTheme.typography.titleMedium - ) - } - }, - onClick = { - menuItem.action() - isBottomSheetOpen = false - }, - enabled = true - ) - } - Spacer(modifier = Modifier.padding(18.dp)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyBottomAppBar.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyBottomAppBar.kt new file mode 100644 index 0000000..69a7e99 --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyBottomAppBar.kt @@ -0,0 +1,98 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.List +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material.icons.outlined.List +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.Screen +import com.lorenzovainigli.foodexpirationdates.view.preview.LanguagePreviews + +@Composable +fun MyBottomAppBar( + navController: NavHostController, + currentBackStackEntry: NavBackStackEntry? +){ + val navigationItems = listOf( + NavigationItem( + label = stringResource(id = R.string.about_this_app), + route = Screen.AboutScreen.route, + selectedIcon = Icons.Filled.Info, + unselectedIcon = Icons.Outlined.Info + ), + NavigationItem( + label = stringResource(id = R.string.list), + route = Screen.MainScreen.route, + selectedIcon = Icons.Filled.List, + unselectedIcon = Icons.Outlined.List + ), + NavigationItem( + label = stringResource(id = R.string.settings), + route = Screen.SettingsScreen.route, + selectedIcon = Icons.Filled.Settings, + unselectedIcon = Icons.Outlined.Settings + ) + ) + NavigationBar { + var selectedItem = when (currentBackStackEntry?.destination?.route) { + Screen.AboutScreen.route -> 0 + Screen.SettingsScreen.route -> 2 + else -> 1 + } + navigationItems.forEachIndexed { index, item -> + NavigationBarItem( + selected = selectedItem == index, + onClick = { + selectedItem = index + navController.navigate(item.route) + }, + icon = { + Icon( + imageVector = when (selectedItem == index) { + true -> item.selectedIcon + else -> item.unselectedIcon + }, + contentDescription = item.label + ) + }, + label = { + Text( + text = item.label, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + ) + } + } +} + +@PreviewLightDark +@LanguagePreviews +@Composable +fun MyBottomAppBarPreview(){ + FoodExpirationDatesTheme { + Surface { + MyBottomAppBar( + navController = rememberNavController(), + currentBackStackEntry = null + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyScaffold.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyScaffold.kt new file mode 100644 index 0000000..dfd354a --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyScaffold.kt @@ -0,0 +1,150 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable + +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.view.MainActivity +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.Screen +import kotlinx.coroutines.launch + +data class NavigationItem( + val label: String, + val route: String, + val selectedIcon: ImageVector, + val unselectedIcon: ImageVector +) + +@RequiresApi(Build.VERSION_CODES.O) +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MyScaffold( + activity: MainActivity? = null, + navController: NavHostController, + showSnackbar: MutableState, + content: @Composable () -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val currentBackStackEntry by navController.currentBackStackEntryAsState() + val context = LocalContext.current + if (showSnackbar.value){ + coroutineScope.launch { + try { + val deletedItem = activity?.viewModel?.deletedItem?.value + val snackbarResult = + snackbarHostState.showSnackbar( + message = context.resources.getString( + R.string.x_deleted, + deletedItem?.foodName ?: "" + ), + actionLabel = context.resources.getString(R.string.undo), + duration = SnackbarDuration.Short + ) + when (snackbarResult) { + SnackbarResult.ActionPerformed -> { + deletedItem?.let { + activity.viewModel.addExpirationDate(it) + } + } + + else -> { + Log.i("ERROR", "Error showing snackbar") + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + showSnackbar.value = false + } + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + snackbarHost = { SnackbarHost(snackbarHostState) }, + topBar = { + MyTopAppBar( + activity = activity, + title = when (currentBackStackEntry?.destination?.route) { + Screen.AboutScreen.route -> stringResource(id = R.string.about_this_app) + Screen.InsertScreen.route -> stringResource(id = R.string.insert) + Screen.SettingsScreen.route -> stringResource(id = R.string.settings) + else -> stringResource(id = R.string.app_name) + }, + actions = { + AppIcon(size = 48.dp) + }, + navigationIcon = { + if (currentBackStackEntry?.destination?.route == Screen.InsertScreen.route) { + Icon( + modifier = Modifier.clickable { + navController.popBackStack() + }, + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.back), + tint = MaterialTheme.colorScheme.primary + ) + } + }, + scrollBehavior = scrollBehavior + ) + }, + bottomBar = { + MyBottomAppBar( + navController = navController, + currentBackStackEntry = currentBackStackEntry + ) + } + ) { padding -> + Column( + modifier = Modifier + .padding(padding) + .fillMaxSize() + ) { + content() + } + } +} + +//@RequiresApi(Build.VERSION_CODES.O) +//@PreviewLightDark +//@PreviewScreenSizes +//@Composable +//fun MyScaffoldPreview() { +// val navController = rememberNavController() +// FoodExpirationDatesTheme { +// Surface(modifier = Modifier.fillMaxSize()) { +// MyScaffold( +// navController = navController +// ) +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyTopAppBar.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyTopAppBar.kt index 429e5fb..8f983c4 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyTopAppBar.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/MyTopAppBar.kt @@ -1,25 +1,33 @@ package com.lorenzovainigli.foodexpirationdates.view.composable import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding -import androidx.compose.material3.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp +import com.lorenzovainigli.foodexpirationdates.R import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.MainActivity import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun MyTopAppBar( + activity: MainActivity?, title: String, actions: @Composable RowScope.() -> Unit = {}, navigationIcon: @Composable () -> Unit = {}, @@ -27,16 +35,12 @@ fun MyTopAppBar( prefsViewModel: PreferencesViewModel? = null ) { val context = LocalContext.current - val topBarFontState = prefsViewModel?.getTopBarFont(context)?.collectAsState()?.value + val topBarFontState = activity?.preferencesViewModel?.getTopBarFont(context)?.collectAsState()?.value ?: PreferencesRepository.Companion.TopBarFont.NORMAL.ordinal LargeTopAppBar( modifier = Modifier - .padding(bottom = 4.dp) - .shadow( - elevation = 4.dp, - spotColor = MaterialTheme.colorScheme.primary - ), + .padding(bottom = 4.dp), title = { Text( text = title, @@ -51,24 +55,27 @@ fun MyTopAppBar( }, actions = actions, navigationIcon = navigationIcon, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - scrolledContainerColor = MaterialTheme.colorScheme.surface - ), scrollBehavior = scrollBehavior ) } @OptIn(ExperimentalMaterial3Api::class) -@Preview +@PreviewLightDark @Composable fun MyTopAppBarPreview(){ FoodExpirationDatesTheme( dynamicColor = false ) { MyTopAppBar( - title = "Title" + activity = null, + title = "Lorem Ipsum", + navigationIcon = { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.back), + tint = MaterialTheme.colorScheme.primary + ) + } ) - Spacer(modifier = Modifier.fillMaxHeight()) } } \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/TextIconButton.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/TextIconButton.kt index 2b93acf..c556c43 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/TextIconButton.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/TextIconButton.kt @@ -8,17 +8,21 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Email -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lorenzovainigli.foodexpirationdates.R import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.view.preview.DefaultPreviews @Composable fun TextIconButton( @@ -63,7 +67,7 @@ fun TextIconButton( } } -@DefaultPreviews +@Preview @Composable fun TextIconButtonPreview(){ FoodExpirationDatesTheme { diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/InfoActivityLayout.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/InfoActivityLayout.kt deleted file mode 100644 index 8b495c2..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/InfoActivityLayout.kt +++ /dev/null @@ -1,330 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.composable.activity - -import android.app.Activity -import android.content.Context -import android.content.Intent -import androidx.compose.foundation.Image -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.ClickableText -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material.icons.outlined.Edit -import androidx.compose.material.icons.outlined.Email -import androidx.compose.material.icons.outlined.Share -import androidx.compose.material.icons.outlined.Star -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringArrayResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import com.lorenzovainigli.foodexpirationdates.BuildConfig -import com.lorenzovainigli.foodexpirationdates.DEVELOPER_EMAIL -import com.lorenzovainigli.foodexpirationdates.GITHUB_URL -import com.lorenzovainigli.foodexpirationdates.PLAY_STORE_URL -import com.lorenzovainigli.foodexpirationdates.PRIVACY_POLICY_URL -import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.model.contributors -import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository -import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.view.composable.MyTopAppBar -import com.lorenzovainigli.foodexpirationdates.view.composable.TextIconButton -import com.lorenzovainigli.foodexpirationdates.view.composable.TextIconButtonData -import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun InfoActivityLayout( - context: Context = LocalContext.current, - prefsViewModel: PreferencesViewModel? = viewModel() -) { - val activity = (context as? Activity) - val uriHandler = LocalUriHandler.current - val darkThemeState = prefsViewModel?.getThemeMode(context)?.collectAsState()?.value - ?: PreferencesRepository.Companion.ThemeMode.SYSTEM - val dynamicColorsState = prefsViewModel?.getDynamicColors(context)?.collectAsState()?.value - ?: false - val isInDarkTheme = when (darkThemeState) { - PreferencesRepository.Companion.ThemeMode.LIGHT.ordinal -> false - PreferencesRepository.Companion.ThemeMode.DARK.ordinal -> true - else -> isSystemInDarkTheme() - } - val features = stringArrayResource(id = R.array.features) - .joinToString(separator = "\n") { it.asListItem() } - FoodExpirationDatesTheme( - darkTheme = isInDarkTheme, - dynamicColor = dynamicColorsState - ) { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - MyTopAppBar( - title = stringResource(id = R.string.about_this_app), - navigationIcon = { - IconButton(onClick = { activity?.finish() }) { - Icon( - imageVector = Icons.Outlined.ArrowBack, - contentDescription = stringResource(id = R.string.back), - tint = MaterialTheme.colorScheme.primary - ) - } - }, - scrollBehavior = scrollBehavior, - prefsViewModel = prefsViewModel - ) - } - ) { padding -> - Column( - modifier = Modifier - .padding(padding) - .verticalScroll(rememberScrollState()) - ) { - Column( - modifier = Modifier - .padding(10.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Image( - modifier = Modifier.fillMaxWidth() - .height(128.dp) - .padding(top = 16.dp), - painter = painterResource(id = R.drawable.fed_icon), - alignment = Alignment.Center, - contentDescription = null - ) - Text( - modifier = Modifier - .align(Alignment.CenterHorizontally), - text = stringResource(id = R.string.app_name), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier.align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.bodySmall, - text = stringResource( - id = R.string.version_x, - BuildConfig.VERSION_NAME - ), - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource( - id = R.string.app_description, - stringResource(id = R.string.app_name) - ) - ) - TextIconButton( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .width(256.dp), - onClick = { - uriHandler.openUri( - uri = "https://github.com/lorenzovngl/FoodExpirationDates" - ) - }, - imagePainter = painterResource(id = R.drawable.github), - text = stringResource(id = R.string.source_code) - ) - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(id = R.string.features), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier.fillMaxWidth(), - text = features - ) - /*TextIconButton( - modifier = Modifier.align(CenterHorizontally), - onClick = { - - }, - imagePainter = painterResource(id = R.drawable.bug_report), - contentDescription = "Star", - text = stringResource(id = R.string.report_a_bug) - )*/ - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(id = R.string.support_this_project), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center - ) - arrayOf( - TextIconButtonData( - iconImageVector = Icons.Outlined.Star, - text = stringResource(id = R.string.leave_a_star_on_github), - onClick = { - uriHandler.openUri( - uri = GITHUB_URL - ) - }, - ), - TextIconButtonData( - iconImageVector = Icons.Outlined.Edit, - text = stringResource(id = R.string.write_a_review), - onClick = { - uriHandler.openUri( - uri = PLAY_STORE_URL - ) - }, - ), - TextIconButtonData( - iconImageVector = Icons.Outlined.Share, - text = stringResource(id = R.string.share), - onClick = { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, PLAY_STORE_URL) - type = "text/plain" - } - val shareIntent = Intent.createChooser(sendIntent, null) - context.startActivity(shareIntent) - }, - ), - ).forEach { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ){ - TextIconButton( - modifier = Modifier.width(256.dp), - onClick = it.onClick, - iconImageVector = it.iconImageVector, - imagePainter = it.imagePainter, - text = it.text - ) - } - } - ContactSection() - ContributorsList() - ClickableText( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(top = 4.dp), - text = AnnotatedString(text = stringResource(id = R.string.privacy_policy)), - style = TextStyle.Default.copy(color = MaterialTheme.colorScheme.primary), - onClick = { - uriHandler.openUri( - uri = PRIVACY_POLICY_URL - ) - } - ) - } - } - } - } - } -} - -@Composable -fun ContactSection( - modifier: Modifier = Modifier -) { - Column(modifier = modifier.fillMaxWidth()) { - Text( - modifier = Modifier.padding(top = 16.dp), - text = "Contacts", - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp, bottom = 8.dp), - text = stringResource(id = R.string.contacts_text) - ) - val uriHandler = LocalUriHandler.current - TextIconButton( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .width(256.dp), - onClick = { - uriHandler.openUri( - uri = "mailto:$DEVELOPER_EMAIL" - ) - }, - iconImageVector = Icons.Outlined.Email, - text = stringResource(id = R.string.send_an_email) - ) - } -} - -@Composable -fun ContributorsList( - modifier: Modifier = Modifier -) { - val contributorsText = remember { - contributors.joinToString(separator = "\n") { - "${it.name} (@${it.username})".asListItem() - } - } - Column(modifier = modifier.fillMaxWidth()) { - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(id = R.string.contributors_list_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp, bottom = 8.dp), - text = stringResource(id = R.string.contributors_list_subtitle) - ) - Text( - modifier = Modifier.fillMaxWidth(), - text = contributorsText - ) - } -} - -private fun String.asListItem() = " ● $this" - -@Preview(locale = "fr") -@Composable -fun InfoActivityLayoutPreview() { - InfoActivityLayout() -} - -@Preview -@Composable -fun ContributorsListPreview() { - ContributorsList() -} diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/InsertActivityLayout.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/InsertActivityLayout.kt deleted file mode 100644 index 6b9b02f..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/InsertActivityLayout.kt +++ /dev/null @@ -1,315 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.composable.activity - -import android.app.Activity -import android.content.Context -import android.widget.Toast -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.clickable -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.DatePicker -import androidx.compose.material3.DatePickerDialog -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FabPosition -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberDatePickerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository -import com.lorenzovainigli.foodexpirationdates.model.entity.ExpirationDate -import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.view.composable.MyTopAppBar -import com.lorenzovainigli.foodexpirationdates.view.preview.DevicePreviews -import com.lorenzovainigli.foodexpirationdates.view.preview.LanguagePreviews -import com.lorenzovainigli.foodexpirationdates.viewmodel.ExpirationDatesViewModel -import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.Locale - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun InsertActivityLayout( - context: Context = LocalContext.current, - itemId: Int? = null, - viewModel: ExpirationDatesViewModel? = viewModel(), - prefsViewModel: PreferencesViewModel? = viewModel(), - addExpirationDate: ((ExpirationDate) -> Unit)? = viewModel!!::addExpirationDate -) { - val darkThemeState = prefsViewModel?.getThemeMode(context)?.collectAsState()?.value - ?: PreferencesRepository.Companion.ThemeMode.SYSTEM - val dynamicColorsState = prefsViewModel?.getDynamicColors(context)?.collectAsState()?.value - ?: false - val isInDarkTheme = when (darkThemeState) { - PreferencesRepository.Companion.ThemeMode.LIGHT.ordinal -> false - PreferencesRepository.Companion.ThemeMode.DARK.ordinal -> true - else -> isSystemInDarkTheme() - } - FoodExpirationDatesTheme( - darkTheme = isInDarkTheme, - dynamicColor = dynamicColorsState - ) { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - val activity = (LocalContext.current as? Activity) - val itemToEdit = itemId?.let { viewModel?.getDate(it) } - var foodNameToEdit = "" - var expDate: Long? = null - if (itemToEdit != null) { - foodNameToEdit = itemToEdit.foodName - expDate = itemToEdit.expirationDate - } - var foodName by remember { - mutableStateOf(foodNameToEdit) - } - val datePickerState = rememberDatePickerState(expDate) - var isDialogOpen by remember { - mutableStateOf(false) - } - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - MyTopAppBar( - title = stringResource( - id = if (itemToEdit != null) R.string.edit_item - else R.string.add_item - ), - navigationIcon = { - IconButton(onClick = { activity?.finish() }) { - Icon( - imageVector = Icons.Outlined.ArrowBack, - contentDescription = stringResource(id = R.string.back), - tint = MaterialTheme.colorScheme.primary - ) - } - }, - scrollBehavior = scrollBehavior, - prefsViewModel = prefsViewModel - ) - }, - floatingActionButtonPosition = FabPosition.End, - containerColor = MaterialTheme.colorScheme.background, - content = { padding -> - if (isDialogOpen) { - DatePickerDialog( - dismissButton = { - OutlinedButton( - onClick = { isDialogOpen = false }, - border = BorderStroke( - 1.dp, - MaterialTheme.colorScheme.tertiary - ), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.tertiary - ) - ) { - Text(text = stringResource(id = R.string.cancel)) - } - }, - confirmButton = { - Button( - modifier = Modifier.testTag("Insert date"), - onClick = { isDialogOpen = false }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiary, - contentColor = MaterialTheme.colorScheme.onTertiary - ) - ) { - Text(text = stringResource(id = R.string.insert)) - } - }, - content = { - DatePicker( - state = datePickerState - ) - }, - onDismissRequest = { - isDialogOpen = false - } - ) - } - Column(Modifier.padding(padding)) { - Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .verticalScroll(rememberScrollState()) - .padding(16.dp) - ) { - TextField( - label = { - Text( - text = stringResource(id = R.string.food_name), - modifier = Modifier.fillMaxWidth() - ) - }, - value = foodName, - onValueChange = { newText -> - foodName = newText - }, - singleLine = true, - keyboardOptions = KeyboardOptions( - capitalization = KeyboardCapitalization.Sentences - ) - ) - Spacer(modifier = Modifier.height(16.dp)) - TextField( - modifier = Modifier.clickable(onClick = { - isDialogOpen = true - }), - enabled = false, - label = { - Text( - text = stringResource(id = R.string.expiration_date), - modifier = Modifier.fillMaxWidth() - ) - }, - value = if (datePickerState.selectedDateMillis == null) "" else { - // TODO What kind of date format is the best here? - val dateFormat = (DateFormat.getDateInstance( - DateFormat.MEDIUM, Locale.getDefault() - ) as SimpleDateFormat).toLocalizedPattern() - val sdf = SimpleDateFormat(dateFormat, Locale.getDefault()) - sdf.format(datePickerState.selectedDateMillis) - }, - onValueChange = {}, - colors = TextFieldDefaults.colors( - disabledTextColor = MaterialTheme.colorScheme.onSurface, - disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant, - //For Icons - disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, - ) - ) - Row { - OutlinedButton( - onClick = { activity?.finish() }, - modifier = Modifier - .weight(0.5f) - .padding(top = 8.dp, end = 4.dp), - border = BorderStroke( - 1.dp, - MaterialTheme.colorScheme.tertiary - ), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.tertiary - ), - ) { - Text(text = stringResource(id = R.string.cancel)) - } - Button( - modifier = Modifier - .testTag("Insert item") - .weight(0.5f) - .padding(top = 8.dp, start = 4.dp), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiary, - contentColor = MaterialTheme.colorScheme.onTertiary - ), - onClick = { - try { - if(foodName.isNotEmpty()) { - if (datePickerState.selectedDateMillis != null) { - var id = 0 - if (itemToEdit != null) { - id = itemId - } - val entry = ExpirationDate( - id = id, - foodName = foodName, - expirationDate = datePickerState.selectedDateMillis!! - ) - if (addExpirationDate != null) { - addExpirationDate(entry) - } - activity?.finish() - } else { - Toast.makeText( - activity, - R.string.please_select_a_date, - Toast.LENGTH_SHORT - ).show() - } - } else { - Toast.makeText(activity, - R.string.please_enter_a_food_name, - Toast.LENGTH_SHORT).show() - } - } catch (e: Exception) { - Toast.makeText( - activity, - e.message, - Toast.LENGTH_SHORT - ).show() - } - } - ) { - if (itemToEdit != null) - Text(text = stringResource(id = R.string.update)) - else - Text(text = stringResource(id = R.string.insert)) - } - } - } - } - } - ) - } - } -} - -@Preview(showBackground = true) -@DevicePreviews -@LanguagePreviews -@Composable -fun InsertActivityLayoutPreview() { - InsertActivityLayout( - context = LocalContext.current, - itemId = null, - viewModel = null - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/MainActivityLayout.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/MainActivityLayout.kt deleted file mode 100644 index f2b7e53..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/MainActivityLayout.kt +++ /dev/null @@ -1,333 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.composable.activity - -import android.content.Context -import android.content.Intent -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Info -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material.icons.rounded.Add -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.Wallpapers -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.model.entity.ExpirationDate -import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository -import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.ui.theme.TonalElevation -import com.lorenzovainigli.foodexpirationdates.view.activity.InfoActivity -import com.lorenzovainigli.foodexpirationdates.view.activity.InsertActivity -import com.lorenzovainigli.foodexpirationdates.view.activity.SettingsActivity -import com.lorenzovainigli.foodexpirationdates.view.composable.AppIcon -import com.lorenzovainigli.foodexpirationdates.view.composable.FoodCard -import com.lorenzovainigli.foodexpirationdates.view.composable.MyTopAppBar -import com.lorenzovainigli.foodexpirationdates.view.preview.DevicePreviews -import com.lorenzovainigli.foodexpirationdates.viewmodel.ExpirationDatesViewModel -import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel -import kotlinx.coroutines.launch -import java.util.Calendar -import kotlin.math.min - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MainActivityLayout( - context: Context = LocalContext.current, - viewModel: ExpirationDatesViewModel? = viewModel(), - prefsViewModel: PreferencesViewModel? = viewModel(), - items: List? = null, - addExpirationDate: ((ExpirationDate) -> Unit)? = viewModel!!::addExpirationDate, - deleteExpirationDate: ((ExpirationDate) -> Unit)? = viewModel!!::deleteExpirationDate -) { - val darkThemeState = prefsViewModel?.getThemeMode(context)?.collectAsState()?.value - ?: PreferencesRepository.Companion.ThemeMode.SYSTEM - val dynamicColorsState = prefsViewModel?.getDynamicColors(context)?.collectAsState()?.value - ?: false - val isInDarkTheme = when (darkThemeState) { - PreferencesRepository.Companion.ThemeMode.LIGHT.ordinal -> false - PreferencesRepository.Companion.ThemeMode.DARK.ordinal -> true - else -> isSystemInDarkTheme() - } - FoodExpirationDatesTheme( - darkTheme = isInDarkTheme, - dynamicColor = dynamicColorsState - ) { - Surface( - modifier = Modifier - .fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - val snackbarHostState = remember { SnackbarHostState() } - val scope = rememberCoroutineScope() - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - MyTopAppBar( - title = stringResource(id = R.string.app_name), - navigationIcon = { - AppIcon(size = 48.dp) - }, - scrollBehavior = scrollBehavior, - prefsViewModel = prefsViewModel - ) - }, - bottomBar = { - NavigationBar( - modifier = Modifier.shadow( - elevation = 16.dp, - spotColor = MaterialTheme.colorScheme.primary - ), - tonalElevation = TonalElevation.level0() - ) { - NavigationBarItem( - selected = false, - onClick = { - context.startActivity(Intent(context, InfoActivity::class.java)) - }, - label = { - Text( - text = stringResource(id = R.string.about_this_app), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary - ) - }, - icon = { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = stringResource(id = R.string.about_this_app), - tint = MaterialTheme.colorScheme.primary - ) - } - ) - if (!items.isNullOrEmpty()) { - Row( - modifier = Modifier.fillMaxHeight(), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - modifier = Modifier - .size(56.dp), - contentPadding = PaddingValues(0.dp), - onClick = { - context.startActivity( - Intent( - context, - InsertActivity::class.java - ) - ) - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiary, - contentColor = MaterialTheme.colorScheme.onTertiary - ), - shape = CircleShape - ) { - Icon( - imageVector = Icons.Rounded.Add, - contentDescription = stringResource(id = R.string.insert) - ) - } - } - } - NavigationBarItem( - selected = false, - onClick = { - context.startActivity( - Intent( - context, - SettingsActivity::class.java - ) - ) - }, - label = { - Text( - text = stringResource(id = R.string.settings), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary - ) - }, - icon = { - Icon( - imageVector = Icons.Outlined.Settings, - contentDescription = stringResource(id = R.string.settings), - tint = MaterialTheme.colorScheme.primary - ) - } - ) - } - } - ) { padding -> - if (!items.isNullOrEmpty()) { - Column( - modifier = Modifier - .padding(padding) - .verticalScroll(rememberScrollState()) - ) { - Column( - modifier = Modifier - .padding(4.dp) - ) { - for (item in items) { - FoodCard( - item = item, - onClickEdit = { - val intent = - Intent(context, InsertActivity::class.java) - intent.putExtra("ITEM_ID", item.id) - context.startActivity(intent) - }, - onClickDelete = { - if (deleteExpirationDate != null) { - scope.launch { - val snackbarResult = - snackbarHostState.showSnackbar( - message = context.resources.getString( - R.string.x_deleted, - item.foodName - ), - actionLabel = context.resources.getString(R.string.undo), - duration = SnackbarDuration.Short - ) - when (snackbarResult) { - SnackbarResult.ActionPerformed -> { - if (addExpirationDate != null) { - addExpirationDate(item) - } - } - - else -> {} - } - } - deleteExpirationDate(item) - } - }, - isInDarkTheme = isInDarkTheme - ) - } - } - } - } else { - Column( - modifier = Modifier - .padding(padding) - .fillMaxSize(), - verticalArrangement = Arrangement.spacedBy( - space = 16.dp, - alignment = Alignment.CenterVertically - ), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = stringResource(id = R.string.no_items_found), - style = MaterialTheme.typography.displaySmall, - color = Color.Gray.copy(alpha = 0.5f), - textAlign = TextAlign.Center - ) - Text( - text = stringResource(id = R.string.please_insert_one), - textAlign = TextAlign.Center - ) - Button( - modifier = Modifier.size(50.dp), - contentPadding = PaddingValues(0.dp), - onClick = { - context.startActivity( - Intent( - context, - InsertActivity::class.java - ) - ) - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiary, - contentColor = MaterialTheme.colorScheme.onTertiary - ), - shape = CircleShape - ) { - Icon( - imageVector = Icons.Rounded.Add, - contentDescription = stringResource(id = R.string.insert) - ) - } - } - } - } - } - } -} - -fun getItemsForPreview(context: Context): List { - val items = ArrayList() - val foods = context.resources.getStringArray(R.array.example_foods) - val daysLeft = arrayOf(-1, 0, 1, 3, 7, 10, 30) - for (i in 0 until min(foods.size, daysLeft.size)) { - val cal = Calendar.getInstance() - cal.add(Calendar.DATE, daysLeft[i]) - items.add( - ExpirationDate( - id = 0, - foodName = foods[i], - expirationDate = cal.time.time - ) - ) - } - return items -} - -@Preview(showBackground = true, wallpaper = Wallpapers.GREEN_DOMINATED_EXAMPLE) -@DevicePreviews -@Composable -fun MainActivityLayoutPreview() { - val context = LocalContext.current - val items = getItemsForPreview(context) - MainActivityLayout( - items = items, - viewModel = null, - prefsViewModel = null, - addExpirationDate = null, - deleteExpirationDate = null - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/SettingsActivityLayout.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/SettingsActivityLayout.kt deleted file mode 100644 index e833c26..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/activity/SettingsActivityLayout.kt +++ /dev/null @@ -1,287 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.composable.activity - -import android.app.Activity -import android.content.Context -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.ClickableText -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material3.Button -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTimePickerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import com.lorenzovainigli.foodexpirationdates.R -import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository -import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme -import com.lorenzovainigli.foodexpirationdates.view.composable.AutoResizedText -import com.lorenzovainigli.foodexpirationdates.view.composable.DateFormatDialog -import com.lorenzovainigli.foodexpirationdates.view.composable.MyTopAppBar -import com.lorenzovainigli.foodexpirationdates.view.composable.NotificationTimeBottomSheet -import com.lorenzovainigli.foodexpirationdates.view.composable.SettingsItem -import com.lorenzovainigli.foodexpirationdates.viewmodel.PreferencesViewModel -import java.text.SimpleDateFormat -import java.util.Calendar - -@RequiresApi(Build.VERSION_CODES.O) -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SettingsActivityLayout( - context: Context = LocalContext.current, - prefsViewModel: PreferencesViewModel? = viewModel(), - scheduleDailyNotification: ((Int, Int) -> Unit)? = null -) { - val activity = (context as? Activity) - - val darkThemeState = prefsViewModel?.getThemeMode(context)?.collectAsState()?.value - ?: PreferencesRepository.Companion.ThemeMode.SYSTEM.ordinal - val dynamicColorsState = prefsViewModel?.getDynamicColors(context)?.collectAsState()?.value - ?: false - val topBarFontState = prefsViewModel?.getTopBarFont(context)?.collectAsState()?.value - ?: PreferencesRepository.Companion.TopBarFont.NORMAL.ordinal - - val dateFormat = prefsViewModel?.getDateFormat(context)?.collectAsState()?.value - ?: PreferencesRepository.getAvailOtherDateFormats()[0] - var sdf = SimpleDateFormat(dateFormat, context.resources.configuration.locales[0]) - var isDateFormatDialogOpened by remember { - mutableStateOf(false) - } - - val notificationTimeHour = prefsViewModel?.getNotificationTimeHour(context)?.collectAsState()?.value - ?: 11 - val notificationTimeMinute = prefsViewModel?.getNotificationTimeMinute(context)?.collectAsState()?.value - ?: 0 - val timePickerState = - rememberTimePickerState(notificationTimeHour, notificationTimeMinute, true) - var isNotificationTimeBottomSheetOpen by remember { - mutableStateOf(false) - } - FoodExpirationDatesTheme( - darkTheme = when (darkThemeState) { - PreferencesRepository.Companion.ThemeMode.LIGHT.ordinal -> false - PreferencesRepository.Companion.ThemeMode.DARK.ordinal -> true - else -> isSystemInDarkTheme() - }, - dynamicColor = dynamicColorsState - ) { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - MyTopAppBar( - title = stringResource(id = R.string.settings), - navigationIcon = { - IconButton(onClick = { activity?.finish() }) { - Icon( - imageVector = Icons.Outlined.ArrowBack, - contentDescription = stringResource(id = R.string.back), - tint = MaterialTheme.colorScheme.primary - ) - } - }, - scrollBehavior = scrollBehavior, - prefsViewModel = prefsViewModel - ) - } - ) { padding -> - prefsViewModel?.let { - DateFormatDialog( - isDialogOpen = isDateFormatDialogOpened, - onDismissRequest = { - sdf = SimpleDateFormat(dateFormat, context.resources.configuration.locales[0]) - isDateFormatDialogOpened = false - }, - onClickDate = it::setDateFormat - ) - } - if (isNotificationTimeBottomSheetOpen) { - NotificationTimeBottomSheet( - timePickerState = timePickerState, - onDismissRequest = { - prefsViewModel?.setNotificationTime( - context, - timePickerState.hour, timePickerState.minute - ) - if (scheduleDailyNotification != null) { - scheduleDailyNotification( - timePickerState.hour, - timePickerState.minute - ) - } - isNotificationTimeBottomSheetOpen = false - } - ) - } - Column( - modifier = Modifier - .padding(padding) - .padding(10.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = stringResource(R.string.behaviour), - style = MaterialTheme.typography.labelLarge - ) - SettingsItem( - label = stringResource(id = R.string.date_format) - ){ - ClickableText( - modifier = Modifier.testTag(stringResource(id = R.string.date_format)), - text = AnnotatedString(sdf.format(Calendar.getInstance().time)), - style = MaterialTheme.typography.headlineMedium.copy(color = MaterialTheme.colorScheme.onSurface), - onClick = { - isDateFormatDialogOpened = true - } - ) - } - SettingsItem( - label = stringResource(R.string.notification_time) - ){ - var text = "" - if (timePickerState.hour < 10) { - text += "0" - } - text = timePickerState.hour.toString() + ":" - if (timePickerState.minute < 10) { - text += "0" - } - text += timePickerState.minute.toString() - ClickableText( - modifier = Modifier.testTag("Notification time"), - text = AnnotatedString(text), - style = MaterialTheme.typography.headlineMedium.copy(color = MaterialTheme.colorScheme.onSurface), - onClick = { - isNotificationTimeBottomSheetOpen = true - } - ) - } - Text( - text = stringResource(R.string.appearance), - style = MaterialTheme.typography.labelLarge - ) - SettingsItem( - label = stringResource(R.string.theme) - ){ - PreferencesRepository.Companion.ThemeMode.values().forEach { - Spacer( - modifier = Modifier - .fillMaxHeight() - .weight(0.1f) - ) - if (it.ordinal == darkThemeState) { - Button(onClick = {}) { - AutoResizedText( - text = context.getString(it.label) - ) - } - } - if(it.ordinal != darkThemeState){ - OutlinedButton( - onClick = { - prefsViewModel?.setThemeMode(context, it) - }, - ) { - AutoResizedText( - text = context.getString(it.label) - ) - } - } - Spacer( - modifier = Modifier - .fillMaxHeight() - .weight(0.1f) - ) - } - } - SettingsItem( - label = stringResource(R.string.dynamic_colors) - ){ - Spacer( - Modifier - .weight(1f) - .fillMaxHeight()) - Switch( - checked = dynamicColorsState, - onCheckedChange = { - prefsViewModel?.setDynamicColors(context, it) - } - ) - } - SettingsItem( - label = stringResource(R.string.top_bar_font_style) - ) { - PreferencesRepository.Companion.TopBarFont.values().forEach { topBarFont-> - Spacer( - modifier = Modifier - .fillMaxHeight() - .weight(0.1f) - ) - if (topBarFont.ordinal != topBarFontState) { - OutlinedButton( - onClick = { - prefsViewModel?.setTopBarFont(context, topBarFont) - }, - ) { - AutoResizedText( - text = context.getString(topBarFont.label) - ) - } - } - if (topBarFont.ordinal == topBarFontState) { - Button(onClick = {}) { - AutoResizedText( - text = context.getString(topBarFont.label) - ) - } - } - } - } - } - } - } - } -} - -@RequiresApi(Build.VERSION_CODES.O) -@Preview -@Composable -fun SettingsActivityLayoutPreview() { - SettingsActivityLayout() -} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/InfoScreen.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/InfoScreen.kt new file mode 100644 index 0000000..36f3776 --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/InfoScreen.kt @@ -0,0 +1,270 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable.screen + +import android.content.Context +import android.content.Intent +import androidx.compose.foundation.Image +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.Email +import androidx.compose.material.icons.outlined.Share +import androidx.compose.material.icons.outlined.Star +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lorenzovainigli.foodexpirationdates.BuildConfig +import com.lorenzovainigli.foodexpirationdates.DEVELOPER_EMAIL +import com.lorenzovainigli.foodexpirationdates.GITHUB_URL +import com.lorenzovainigli.foodexpirationdates.PLAY_STORE_URL +import com.lorenzovainigli.foodexpirationdates.PRIVACY_POLICY_URL +import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.model.contributors +import com.lorenzovainigli.foodexpirationdates.view.composable.TextIconButton +import com.lorenzovainigli.foodexpirationdates.view.composable.TextIconButtonData + +@Composable +fun InfoScreen( + context: Context = LocalContext.current +) { + val uriHandler = LocalUriHandler.current + val features = stringArrayResource(id = R.array.features) + .joinToString(separator = "\n") { it.asListItem() } + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(10.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Image( + modifier = Modifier + .fillMaxWidth() + .height(128.dp) + .padding(top = 16.dp), + painter = painterResource(id = R.drawable.fed_icon), + alignment = Alignment.Center, + contentDescription = null + ) + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally), + text = stringResource(id = R.string.app_name), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + style = MaterialTheme.typography.bodySmall, + text = stringResource( + id = R.string.version_x, + BuildConfig.VERSION_NAME + ), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource( + id = R.string.app_description, + stringResource(id = R.string.app_name) + ) + ) + TextIconButton( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .width(256.dp), + onClick = { + uriHandler.openUri( + uri = "https://github.com/lorenzovngl/FoodExpirationDates" + ) + }, + imagePainter = painterResource(id = R.drawable.github), + text = stringResource(id = R.string.source_code) + ) + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = R.string.features), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = features + ) + /*TextIconButton( + modifier = Modifier.align(CenterHorizontally), + onClick = { + + }, + imagePainter = painterResource(id = R.drawable.bug_report), + contentDescription = "Star", + text = stringResource(id = R.string.report_a_bug) + )*/ + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = R.string.support_this_project), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + arrayOf( + TextIconButtonData( + iconImageVector = Icons.Outlined.Star, + text = stringResource(id = R.string.leave_a_star_on_github), + onClick = { + uriHandler.openUri( + uri = GITHUB_URL + ) + }, + ), + TextIconButtonData( + iconImageVector = Icons.Outlined.Edit, + text = stringResource(id = R.string.write_a_review), + onClick = { + uriHandler.openUri( + uri = PLAY_STORE_URL + ) + }, + ), + TextIconButtonData( + iconImageVector = Icons.Outlined.Share, + text = stringResource(id = R.string.share), + onClick = { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, PLAY_STORE_URL) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + context.startActivity(shareIntent) + }, + ), + ).forEach { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + TextIconButton( + modifier = Modifier.width(256.dp), + onClick = it.onClick, + iconImageVector = it.iconImageVector, + imagePainter = it.imagePainter, + text = it.text + ) + } + } + ContactSection() + ContributorsList() + ClickableText( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 4.dp), + text = AnnotatedString(text = stringResource(id = R.string.privacy_policy)), + style = TextStyle.Default.copy(color = MaterialTheme.colorScheme.primary), + onClick = { + uriHandler.openUri( + uri = PRIVACY_POLICY_URL + ) + } + ) + } +} + +@Composable +fun ContactSection( + modifier: Modifier = Modifier +) { + Column(modifier = modifier.fillMaxWidth()) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = "Contacts", + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, bottom = 8.dp), + text = stringResource(id = R.string.contacts_text) + ) + val uriHandler = LocalUriHandler.current + TextIconButton( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .width(256.dp), + onClick = { + uriHandler.openUri( + uri = "mailto:$DEVELOPER_EMAIL" + ) + }, + iconImageVector = Icons.Outlined.Email, + text = stringResource(id = R.string.send_an_email) + ) + } +} + +@Composable +fun ContributorsList( + modifier: Modifier = Modifier +) { + val contributorsText = remember { + contributors.joinToString(separator = "\n") { + "${it.name} (@${it.username})".asListItem() + } + } + Column(modifier = modifier.fillMaxWidth()) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = R.string.contributors_list_title), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, bottom = 8.dp), + text = stringResource(id = R.string.contributors_list_subtitle) + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = contributorsText + ) + } +} + +private fun String.asListItem() = " ● $this" + +//@PreviewLightDark +//@Composable +//fun InfoScreenPreview() { +// FoodExpirationDatesTheme { +// Surface (modifier = Modifier.fillMaxHeight()) { +// InfoScreen() +// } +// } +//} + +@Preview(showBackground = true) +@Composable +fun ContributorsListPreview() { + ContributorsList() +} diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/InsertScreen.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/InsertScreen.kt new file mode 100644 index 0000000..980a3e5 --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/InsertScreen.kt @@ -0,0 +1,242 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable.screen + +import android.os.Build +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.model.entity.ExpirationDate +import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.MainActivity +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Locale + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun InsertScreen( + activity: MainActivity? = null, + navController: NavController, + itemId: String? = null, +) { + val itemToEdit = itemId?.let { activity?.viewModel?.getDate(it.toInt()) } + var foodNameToEdit = "" + var expDate: Long? = null + if (itemToEdit != null) { + foodNameToEdit = itemToEdit.foodName + expDate = itemToEdit.expirationDate + } + var foodName by remember { + mutableStateOf(foodNameToEdit) + } + val datePickerState = rememberDatePickerState(expDate) + var isDialogOpen by remember { + mutableStateOf(false) + } + if (isDialogOpen) { + DatePickerDialog( + dismissButton = { + OutlinedButton( + onClick = { isDialogOpen = false }, + border = BorderStroke( + 1.dp, + MaterialTheme.colorScheme.tertiary + ), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.tertiary + ) + ) { + Text(text = stringResource(id = R.string.cancel)) + } + }, + confirmButton = { + Button( + modifier = Modifier.testTag("Insert date"), + onClick = { isDialogOpen = false }, +// colors = ButtonDefaults.buttonColors( +// containerColor = MaterialTheme.colorScheme.tertiary, +// contentColor = MaterialTheme.colorScheme.onTertiary +// ) + ) { + Text(text = stringResource(id = R.string.insert)) + } + }, + content = { + DatePicker( + state = datePickerState + ) + }, + onDismissRequest = { + isDialogOpen = false + } + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + TextField( + label = { + Text( + text = stringResource(id = R.string.food_name), + modifier = Modifier.fillMaxWidth() + ) + }, + value = foodName, + onValueChange = { newText -> + foodName = newText + }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Sentences + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + TextField( + modifier = Modifier.clickable(onClick = { + isDialogOpen = true + }), + enabled = false, + label = { + Text( + text = stringResource(id = R.string.expiration_date), + modifier = Modifier.fillMaxWidth() + ) + }, + value = if (datePickerState.selectedDateMillis == null) "" else { + // TODO What kind of date format is the best here? + val dateFormat = (DateFormat.getDateInstance( + DateFormat.MEDIUM, Locale.getDefault() + ) as SimpleDateFormat).toLocalizedPattern() + val sdf = SimpleDateFormat(dateFormat, Locale.getDefault()) + sdf.format(datePickerState.selectedDateMillis) + }, + onValueChange = {}, + colors = TextFieldDefaults.colors( + disabledTextColor = MaterialTheme.colorScheme.onSurface, + disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant, + //For Icons + disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, + ) + ) + Row { + OutlinedButton( + onClick = { navController.popBackStack() }, + modifier = Modifier + .weight(0.5f) + .padding(top = 8.dp, end = 4.dp), + border = BorderStroke( + 1.dp, + MaterialTheme.colorScheme.tertiary + ), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.tertiary + ), + ) { + Text(text = stringResource(id = R.string.cancel)) + } + Button( + modifier = Modifier + .testTag("Insert item") + .weight(0.5f) + .padding(top = 8.dp, start = 4.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiary, + contentColor = MaterialTheme.colorScheme.onTertiary + ), + onClick = { + try { + if (foodName.isNotEmpty()) { + if (datePickerState.selectedDateMillis != null) { + var id = 0 + if (itemToEdit != null) { + id = itemId.toInt() + } + val entry = ExpirationDate( + id = id, + foodName = foodName, + expirationDate = datePickerState.selectedDateMillis!! + ) + activity?.viewModel?.addExpirationDate(entry) + navController.popBackStack() + } else { + Toast.makeText( + activity, + R.string.please_select_a_date, + Toast.LENGTH_SHORT + ).show() + } + } else { + Toast.makeText( + activity, + R.string.please_enter_a_food_name, + Toast.LENGTH_SHORT + ).show() + } + } catch (e: Exception) { + Toast.makeText( + activity, + e.message, + Toast.LENGTH_SHORT + ).show() + } + } + ) { + if (itemToEdit != null) + Text(text = stringResource(id = R.string.update)) + else + Text(text = stringResource(id = R.string.insert)) + } + } + } +} + +@RequiresApi(Build.VERSION_CODES.O) +@PreviewLightDark +@Composable +fun InsertScreenPreview() { + FoodExpirationDatesTheme { + Surface { + InsertScreen(navController = rememberNavController()) + } + } +} diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/MainScreen.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/MainScreen.kt new file mode 100644 index 0000000..55d9078 --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/MainScreen.kt @@ -0,0 +1,186 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable.screen + +import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Add +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.model.entity.ExpirationDate +import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.MainActivity +import com.lorenzovainigli.foodexpirationdates.view.composable.FoodCard +import java.util.Calendar +import kotlin.math.min + +@Composable +fun MainScreen( + activity: MainActivity? = null, + navController: NavHostController, + showSnackbar: MutableState? = null +) { + Box( + modifier = Modifier + .padding(4.dp) + .fillMaxSize() + ) { + val itemsState = activity?.viewModel?.getDates()?.collectAsState(emptyList()) + val items = itemsState?.value ?: getItemsForPreview(LocalContext.current) + if (items.isNotEmpty()) { + ListOfItems( + activity = activity, + items = items, + showSnackbar = showSnackbar, + navController = navController + ) + } else { + EmptyList() + } + FloatingActionButton( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(12.dp), + onClick = { + navController.navigate(Screen.InsertScreen.route) + }, + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer + ) { + Icon( + imageVector = Icons.Rounded.Add, + contentDescription = stringResource(id = R.string.insert) + ) + } + } +} + +@RequiresApi(Build.VERSION_CODES.O) +@Preview +@Composable +fun MainScreenPreview() { + FoodExpirationDatesTheme { + Surface { + MainScreen( + navController = rememberNavController() + ) + } + } +} + +@Composable +fun ListOfItems( + activity: MainActivity? = null, + items: List, + navController: NavHostController, + showSnackbar: MutableState? +) { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + for (item in items) { + FoodCard( + item = item, + onClickEdit = { + navController.navigate(Screen.InsertScreen.route + "?itemId=${item.id}") + }, + onClickDelete = { + showSnackbar?.value = true + activity?.viewModel?.deleteExpirationDate(item) + } + ) + } + } +} + +@Composable +@Preview +fun ListOfItemsPreview() { + FoodExpirationDatesTheme { + Surface { + ListOfItems( + items = getItemsForPreview(LocalContext.current), + navController = rememberNavController(), + showSnackbar = null + ) + } + } +} + +@Composable +fun EmptyList() { + Surface(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy( + space = 16.dp, + alignment = Alignment.CenterVertically + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.no_items_found), + style = MaterialTheme.typography.displaySmall, + color = Color.Gray.copy(alpha = 0.5f), + textAlign = TextAlign.Center + ) + Text( + text = stringResource(id = R.string.please_insert_one), + textAlign = TextAlign.Center + ) + } + } +} + +@Composable +@Preview +fun EmptyListPreview() { + FoodExpirationDatesTheme { + Surface { + EmptyList() + } + } +} + +fun getItemsForPreview(context: Context): List { + val items = ArrayList() + val foods = context.resources.getStringArray(R.array.example_foods) + val daysLeft = arrayOf(-1, 0, 1, 3, 7, 10, 30) + for (i in 0 until min(foods.size, daysLeft.size)) { + val cal = Calendar.getInstance() + cal.add(Calendar.DATE, daysLeft[i]) + items.add( + ExpirationDate( + id = 0, + foodName = foods[i], + expirationDate = cal.time.time + ) + ) + } + return items +} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/Screen.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/Screen.kt new file mode 100644 index 0000000..37faa1e --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/Screen.kt @@ -0,0 +1,8 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable.screen + +sealed class Screen(val route: String) { + data object MainScreen : Screen("main_screen") + data object InsertScreen : Screen("insert_screen") + data object AboutScreen : Screen("about_screen") + data object SettingsScreen : Screen("setting_screen") +} diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt new file mode 100644 index 0000000..e150b82 --- /dev/null +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt @@ -0,0 +1,237 @@ +package com.lorenzovainigli.foodexpirationdates.view.composable.screen + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.lorenzovainigli.foodexpirationdates.R +import com.lorenzovainigli.foodexpirationdates.model.NotificationManager +import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository +import com.lorenzovainigli.foodexpirationdates.view.MainActivity +import com.lorenzovainigli.foodexpirationdates.view.composable.AutoResizedText +import com.lorenzovainigli.foodexpirationdates.view.composable.DateFormatDialog +import com.lorenzovainigli.foodexpirationdates.view.composable.NotificationTimeBottomSheet +import com.lorenzovainigli.foodexpirationdates.view.composable.SettingsItem +import java.text.SimpleDateFormat +import java.util.Calendar + +@RequiresApi(Build.VERSION_CODES.O) +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + activity: MainActivity? = null +) { + val context = LocalContext.current + val prefsViewModel = activity?.preferencesViewModel + val darkThemeState = prefsViewModel?.getThemeMode(context)?.collectAsState()?.value + ?: PreferencesRepository.Companion.ThemeMode.SYSTEM + val dynamicColorsState = prefsViewModel?.getDynamicColors(context)?.collectAsState()?.value + ?: false + val topBarFontState = prefsViewModel?.getTopBarFont(context)?.collectAsState()?.value + ?: PreferencesRepository.Companion.TopBarFont.NORMAL.ordinal + + val dateFormat = prefsViewModel?.getDateFormat(context)?.collectAsState()?.value + ?: PreferencesRepository.getAvailOtherDateFormats()[0] + var sdf = SimpleDateFormat(dateFormat, context.resources.configuration.locales[0]) + var isDateFormatDialogOpened by remember { + mutableStateOf(false) + } + + val notificationTimeHour = + prefsViewModel?.getNotificationTimeHour(context)?.collectAsState()?.value + ?: 11 + val notificationTimeMinute = + prefsViewModel?.getNotificationTimeMinute(context)?.collectAsState()?.value + ?: 0 + val timePickerState = + rememberTimePickerState(notificationTimeHour, notificationTimeMinute, true) + var isNotificationTimeBottomSheetOpen by remember { + mutableStateOf(false) + } + prefsViewModel?.let { + DateFormatDialog( + isDialogOpen = isDateFormatDialogOpened, + onDismissRequest = { + sdf = SimpleDateFormat(dateFormat, context.resources.configuration.locales[0]) + isDateFormatDialogOpened = false + }, + onClickDate = it::setDateFormat + ) + } + if (isNotificationTimeBottomSheetOpen) { + NotificationTimeBottomSheet( + timePickerState = timePickerState, + onDismissRequest = { + prefsViewModel?.setNotificationTime( + context, + timePickerState.hour, timePickerState.minute + ) + NotificationManager.scheduleDailyNotification( + context, + timePickerState.hour, + timePickerState.minute + ) + isNotificationTimeBottomSheetOpen = false + } + ) + } + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(10.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = stringResource(R.string.behaviour), + style = MaterialTheme.typography.labelLarge + ) + SettingsItem( + label = stringResource(id = R.string.date_format) + ) { + ClickableText( + modifier = Modifier.testTag(stringResource(id = R.string.date_format)), + text = AnnotatedString(sdf.format(Calendar.getInstance().time)), + style = MaterialTheme.typography.headlineMedium.copy(color = MaterialTheme.colorScheme.onSurface), + onClick = { + isDateFormatDialogOpened = true + } + ) + } + SettingsItem( + label = stringResource(R.string.notification_time) + ) { + var text = "" + if (timePickerState.hour < 10) { + text += "0" + } + text = timePickerState.hour.toString() + ":" + if (timePickerState.minute < 10) { + text += "0" + } + text += timePickerState.minute.toString() + ClickableText( + modifier = Modifier.testTag("Notification time"), + text = AnnotatedString(text), + style = MaterialTheme.typography.headlineMedium.copy(color = MaterialTheme.colorScheme.onSurface), + onClick = { + isNotificationTimeBottomSheetOpen = true + } + ) + } + Text( + text = stringResource(R.string.appearance), + style = MaterialTheme.typography.labelLarge + ) + SettingsItem( + label = stringResource(R.string.theme) + ) { + PreferencesRepository.Companion.ThemeMode.entries.forEach { + Spacer( + modifier = Modifier + .fillMaxHeight() + .weight(0.1f) + ) + if (it.ordinal == darkThemeState) { + Button(onClick = {}) { + AutoResizedText( + text = context.getString(it.label) + ) + } + } + if (it.ordinal != darkThemeState) { + OutlinedButton( + onClick = { + prefsViewModel?.setThemeMode(context, it) + }, + ) { + AutoResizedText( + text = context.getString(it.label) + ) + } + } + Spacer( + modifier = Modifier + .fillMaxHeight() + .weight(0.1f) + ) + } + } + SettingsItem( + label = stringResource(R.string.dynamic_colors) + ) { + Spacer( + Modifier + .weight(1f) + .fillMaxHeight() + ) + Switch( + checked = dynamicColorsState, + onCheckedChange = { + prefsViewModel?.setDynamicColors(context, it) + } + ) + } + SettingsItem( + label = stringResource(R.string.top_bar_font_style) + ) { + PreferencesRepository.Companion.TopBarFont.entries.forEach { topBarFont -> + Spacer( + modifier = Modifier + .fillMaxHeight() + .weight(0.1f) + ) + if (topBarFont.ordinal != topBarFontState) { + OutlinedButton( + onClick = { + prefsViewModel?.setTopBarFont(context, topBarFont) + }, + ) { + AutoResizedText( + text = context.getString(topBarFont.label) + ) + } + } + if (topBarFont.ordinal == topBarFontState) { + Button(onClick = {}) { + AutoResizedText( + text = context.getString(topBarFont.label) + ) + } + } + } + } + } +} + +@RequiresApi(Build.VERSION_CODES.O) +@Preview(showBackground = true) +@Composable +fun SettingsScreenPreview() { + SettingsScreen() +} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DefaultPreviews.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DefaultPreviews.kt index d178664..5a59812 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DefaultPreviews.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DefaultPreviews.kt @@ -1,8 +1,103 @@ package com.lorenzovainigli.foodexpirationdates.view.preview -import android.content.res.Configuration -import androidx.compose.ui.tooling.preview.Preview +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.navigation.compose.rememberNavController +import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.InfoScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.MainScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.MyScaffold +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.InsertScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.SettingsScreen -@Preview(name = "Light mode") -@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) -annotation class DefaultPreviews \ No newline at end of file +class DefaultPreviews { + @RequiresApi(Build.VERSION_CODES.O) + @PreviewLightDark + @Composable + fun MainScreenPreview() { + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + MainScreen(navController = navController) + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + @PreviewLightDark + @PreviewDynamicColors + @Composable + fun MainScreenDynamicColorsPreview() { + FoodExpirationDatesTheme { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + MainScreen(navController = navController) + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + @PreviewLightDark + @Composable + fun InsertScreenPreview() { + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + InsertScreen(navController = navController) + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + @PreviewLightDark + @Composable + fun SettingsScreenPreview() { + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + SettingsScreen() + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + @PreviewLightDark + @Composable + fun InfoScreenPreview() { + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + InfoScreen() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DevicePreviews.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DevicePreviews.kt deleted file mode 100644 index 1282f14..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DevicePreviews.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.preview - -import androidx.compose.ui.tooling.preview.Preview - - -@Preview(showSystemUi = true, device = "spec:width=411dp,height=891dp", name = "Phone") -@Preview(showSystemUi = true, device = "spec:width=673.5dp,height=841dp,dpi=480", name = "Foldable") -@Preview(showSystemUi = true, device = "spec:width=1280dp,height=800dp,dpi=480", name = "Tablet") -@Preview(showSystemUi = true, device = "spec:width=1920dp,height=1080dp,dpi=480", name = "Desktop") -annotation class DevicePreviews diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DynamicColorPreviews.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DynamicColorPreviews.kt deleted file mode 100644 index 26f4933..0000000 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/DynamicColorPreviews.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.lorenzovainigli.foodexpirationdates.view.preview - -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.Wallpapers - -@Preview(name = "Red dominated", wallpaper = Wallpapers.RED_DOMINATED_EXAMPLE) -@Preview(name = "Green dominated", wallpaper = Wallpapers.GREEN_DOMINATED_EXAMPLE) -@Preview(name = "Blue dominated", wallpaper = Wallpapers.BLUE_DOMINATED_EXAMPLE) -annotation class DynamicColorPreviews \ No newline at end of file diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/LanguagePreviews.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/LanguagePreviews.kt index 368a3bb..1bfeee1 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/LanguagePreviews.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/preview/LanguagePreviews.kt @@ -2,10 +2,12 @@ package com.lorenzovainigli.foodexpirationdates.view.preview import androidx.compose.ui.tooling.preview.Preview -@Preview(name = "Italian", locale = "it", showBackground = true) @Preview(name = "Arabic", locale = "ar", showBackground = true) +@Preview(name = "French", locale = "fr", showBackground = true) @Preview(name = "German", locale = "de", showBackground = true) @Preview(name = "Hindi", locale = "hi", showBackground = true) +@Preview(name = "Indonesian", locale = "in", showBackground = true) +@Preview(name = "Italian", locale = "it", showBackground = true) @Preview(name = "Japanese", locale = "ja", showBackground = true) @Preview(name = "Spanish", locale = "es", showBackground = true) annotation class LanguagePreviews diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/ExpirationDatesViewModel.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/ExpirationDatesViewModel.kt index dea7bec..93ce706 100644 --- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/ExpirationDatesViewModel.kt +++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/ExpirationDatesViewModel.kt @@ -25,6 +25,9 @@ class ExpirationDatesViewModel @Inject constructor( private val _isSplashScreenLoading: MutableState = mutableStateOf(value = true) val isSplashScreenLoading: State = _isSplashScreenLoading + private val _deletedItem: MutableState = mutableStateOf(value = null) + val deletedItem: State = _deletedItem + fun getDates(): Flow> { viewModelScope.launch { _isSplashScreenLoading.value = true @@ -57,5 +60,6 @@ class ExpirationDatesViewModel @Inject constructor( repository.deleteExpirationDate(expirationDate) expirationDates = repository.getAll() } + _deletedItem.value = expirationDate } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index da39db2..fdd2a67 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -54,6 +54,7 @@ Aussehen E-Mail senden Wenn Sie weitere Informationen zu dieser Anwendung erhalten, Feedback senden, einen Fehler melden, eine Funktion anfordern oder einfach den Entwickler kontaktieren möchten, senden Sie bitte eine E-Mail über die folgende Schaltfläche.\nWenn Sie Fehlerberichte und Funktionsanfragen haben, können Sie auch ein Issue im GitHub-Repository dieser Anwendung öffnen. + Liste Anzeige einer Liste mit Lebensmittelverfallsdaten in aufsteigender zeitlicher Reihenfolge. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b5df8f2..9e080be 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -55,6 +55,7 @@ Apariencia Enviar un e-mail Si desea obtener más información sobre esta aplicación, enviar comentarios, informar de un error, solicitar una función o simplemente ponerse en contacto con el desarrollador, envíe un correo electrónico utilizando el siguiente botón.\nEn caso de informes de errores y solicitudes de funciones, puede abrir una incidencia en el repositorio de GitHub de esta aplicación. + Lista Muestra una lista con las fechas de caducidad de los alimentos, ordenadas cronológicamente de forma ascendente. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d1e1d21..13d8402 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -52,6 +52,7 @@ Style de police de la barre supérieure Comportement Apparence + Liste Afficher une liste d\'aliments avec des dates d\'expiration dans l\'ordre croissant du temps. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ee6f278..5cc714a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -55,6 +55,7 @@ Aspetto Invia un\'e-mail Se desideri ottenere maggiori informazioni su questa applicazione, inviare un feedback, segnalare un bug, richiedere una funzionalità, o semplicemente contattare lo sviluppatore, invia un\'e-mail utilizzando il seguente pulsante.\nIn caso di segnalazione di bug e richieste di funzionalità, è possibile aprire una issue sul repository GitHub di questa applicazione. + Lista Visualizza un elenco con le date di scadenza degli alimenti in ordine crescente di tempo. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index fc1850c..1b4bcd7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -45,6 +45,7 @@ 暗い %1$sが削除されました 消去 + リスト 食品の賞味期限を時間の昇順に並べたリストを表示する。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88ef6c4..fc7f182 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,6 +54,7 @@ Appearance Send an email If you would like to get more information about this application, send a feedback, report a bug, request a feature, or simply contact the developer, please send an email using the following button.\nIf you have bug reports and feature requests, you can also open an issue on the GitHub repository for this application. + List Display a list with food expiration dates in ascending time order. diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 5ec40c9..bc5c55b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -6,6 +6,8 @@ \ No newline at end of file diff --git a/app/src/test/java/screenshot/Screenshot.kt b/app/src/test/java/screenshot/Screenshot.kt index 7598f26..0103448 100644 --- a/app/src/test/java/screenshot/Screenshot.kt +++ b/app/src/test/java/screenshot/Screenshot.kt @@ -1,51 +1,79 @@ package screenshot -import androidx.compose.ui.platform.LocalContext +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.navigation.compose.rememberNavController import app.cash.paparazzi.Paparazzi -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.InfoActivityLayout -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.InsertActivityLayout -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.MainActivityLayout -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.SettingsActivityLayout -import com.lorenzovainigli.foodexpirationdates.view.composable.activity.getItemsForPreview +import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme +import com.lorenzovainigli.foodexpirationdates.view.composable.MyScaffold +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.InfoScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.InsertScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.MainScreen +import com.lorenzovainigli.foodexpirationdates.view.composable.screen.SettingsScreen open class Screenshot { fun screen1MainActivity(paparazzi: Paparazzi) { paparazzi.snapshot { - MainActivityLayout( - items = getItemsForPreview(LocalContext.current), - viewModel = null, - prefsViewModel = null, - addExpirationDate = null, - deleteExpirationDate = null - ) + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + MainScreen(navController = navController) + } + } } } fun screen2InsertActivity(paparazzi: Paparazzi) { paparazzi.snapshot { - InsertActivityLayout( - itemId = null, - viewModel = null, - prefsViewModel = null, - addExpirationDate = null - ) + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + InsertScreen(navController = navController) + } + } } } fun screen3SettingsActivity(paparazzi: Paparazzi) { paparazzi.snapshot { - SettingsActivityLayout( - prefsViewModel = null - ) + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + SettingsScreen() + } + } } } fun screen4InfoActivity(paparazzi: Paparazzi) { paparazzi.snapshot { - InfoActivityLayout( - prefsViewModel = null - ) + FoodExpirationDatesTheme( + dynamicColor = false + ) { + val navController = rememberNavController() + val showSnackbar = remember { + mutableStateOf(false) + } + MyScaffold(navController = navController, showSnackbar = showSnackbar) { + InfoScreen() + } + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd1133a..8b352e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ hilt-common = "1.0.0" junit = "4.13.2" ksp = "1.9.10-1.0.13" lifecycle-runtime-ktx = "2.6.2" +navigation-compose = "2.7.5" org-jetbrains-kotlin-android = "1.9.10" paparazzi = "1.3.1" room = "2.5.2" @@ -35,6 +36,7 @@ androidx-hilt-common = { module = "androidx.hilt:hilt-common", version.ref = "hi androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hilt-common" } androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle-runtime-ktx" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle-runtime-ktx" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } @@ -66,7 +68,7 @@ ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "ui-test-junit4" } ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version="1.6.0-beta02"} splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splashscreen" } [plugins]