diff --git a/app/build.gradle b/app/build.gradle index fc167a8..d427160 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.thatsmanmeet.taskyapp" minSdk 26 targetSdk 34 - versionCode 19 - versionName "2.3.5" + versionCode 20 + versionName "2.3.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary true @@ -77,7 +77,7 @@ dependencies { annotationProcessor "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation 'androidx.core:core-splashscreen:1.0.1' - implementation "androidx.navigation:navigation-compose:2.7.0-beta01" + implementation "androidx.navigation:navigation-compose:2.7.0-beta02" implementation "androidx.datastore:datastore-preferences:1.0.0" def lottieVersion = "6.0.0" implementation "com.airbnb.android:lottie-compose:$lottieVersion" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 975b609..0c9dfbe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,7 @@ android:name=".MainActivity" android:exported="true" android:theme="@style/Theme.App.Starting" - android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"> + android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"> diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/components/DateHeader.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/components/DateHeader.kt index 7184cc4..0719ed4 100644 --- a/app/src/main/java/com/thatsmanmeet/taskyapp/components/DateHeader.kt +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/components/DateHeader.kt @@ -1,6 +1,5 @@ package com.thatsmanmeet.taskyapp.components -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DateRange @@ -21,8 +20,7 @@ fun DateHeader( Row( modifier = modifier .padding(bottom = 10.dp, top = 0.dp) - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface), + .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { Icon( diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/components/TaskCompleteAnimations.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/components/TaskCompleteAnimations.kt index d5c8b10..4ba232e 100644 --- a/app/src/main/java/com/thatsmanmeet/taskyapp/components/TaskCompleteAnimations.kt +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/components/TaskCompleteAnimations.kt @@ -26,7 +26,7 @@ fun TaskCompleteAnimations( progressAnimation }, modifier = modifier.fillMaxSize(), - contentScale = ContentScale.Fit + contentScale = ContentScale.Crop ) } \ No newline at end of file diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/components/ThemeDialog.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/components/ThemeDialog.kt new file mode 100644 index 0000000..ad7c034 --- /dev/null +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/components/ThemeDialog.kt @@ -0,0 +1,125 @@ +package com.thatsmanmeet.taskyapp.components + + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.thatsmanmeet.taskyapp.R +import com.thatsmanmeet.taskyapp.misc.AppTheme + + +@Composable +fun ThemeChangerDialog( + modifier: Modifier = Modifier, + selectedItem:MutableState, + isShowing:MutableState, + onClick : (String) -> Unit +) { + if(isShowing.value){ + AlertDialog( + onDismissRequest = { + isShowing.value = false + }, + title = { + Text(text = stringResource(R.string.select_app_theme_text)) + }, + text = { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center + ) { + val list = mutableListOf( + AppTheme("0","System Default",false, R.drawable.ic_phone), + AppTheme("1","Light",false,R.drawable.ic_light), + AppTheme("2","Dark",false,R.drawable.ic_dark) + ) + LazyColumn(modifier = modifier.padding(2.dp)){ + items(list){theme-> + ThemeItem(appTheme = theme, selectedThemeId = selectedItem) { selectedThemeId -> + selectedItem.value = selectedThemeId + } + } + } + } + }, + confirmButton = { + Button( + onClick = { + onClick(selectedItem.value) + isShowing.value = false + }, + colors = ButtonColors(containerColor = Color(0xFF229E28), contentColor = Color.White, disabledContentColor = Color.White, disabledContainerColor = Color.Gray) + ) { + Text(text = "OK") + } + }, + dismissButton = { + Button( + onClick = { + isShowing.value = false + }, + colors = ButtonColors(containerColor = Color(0xFFDB4C41), contentColor = Color.White, disabledContentColor = Color.White, disabledContainerColor = Color.Gray) + ) { + Text(text = "Cancel") + } + } + ) + } +} + +@Composable +fun ThemeItem( + modifier: Modifier = Modifier, + appTheme: AppTheme, + selectedThemeId: MutableState, + onThemeItemSelected: (String) -> Unit +) { + val isSelected = remember(appTheme.id == selectedThemeId.value) { + mutableStateOf(appTheme.id == selectedThemeId.value) + } + + Row( + modifier = modifier.padding(10.dp).fillMaxWidth().clickable { + onThemeItemSelected(appTheme.id) + }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(painter = painterResource(id = appTheme.icon), contentDescription = null) + Spacer(modifier = modifier.width(10.dp)) + Text(text = appTheme.mode) + } + if (isSelected.value) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = Color(0xFF009688) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/datastore/SettingsStore.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/datastore/SettingsStore.kt index 6acd899..66179ec 100644 --- a/app/src/main/java/com/thatsmanmeet/taskyapp/datastore/SettingsStore.kt +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/datastore/SettingsStore.kt @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -18,6 +19,7 @@ class SettingsStore( val ANIMATION_SHOW_KEY = booleanPreferencesKey("animation_list_preference") val SHOW_24_HOUR_CLOCK_KEY = booleanPreferencesKey("show_24_hour_clock_preference") val TASK_COMPLETION_SOUNDS = booleanPreferencesKey("sound_list_preference") + val THEME_MODE_KEY = stringPreferencesKey("theme_mode_preference") } val getTaskListKey : Flow = context.dataStore.data.map {preference-> @@ -35,6 +37,10 @@ class SettingsStore( preference[TASK_COMPLETION_SOUNDS] ?: true } + val getThemeModeKey : Flow = context.dataStore.data.map {preference -> + preference[THEME_MODE_KEY] ?: "" + } + suspend fun saveTaskListKey(isEnabled:Boolean) { context.dataStore.edit {preferences-> preferences[TASK_LIST_KEY] = isEnabled @@ -57,4 +63,10 @@ class SettingsStore( preference[SHOW_24_HOUR_CLOCK_KEY] = isEnabled } } + + suspend fun saveThemeModeKey(mode:String){ + context.dataStore.edit { preference-> + preference[THEME_MODE_KEY] = mode + } + } } diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/misc/AppTheme.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/misc/AppTheme.kt new file mode 100644 index 0000000..53f8b61 --- /dev/null +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/misc/AppTheme.kt @@ -0,0 +1,8 @@ +package com.thatsmanmeet.taskyapp.misc + +data class AppTheme( + val id:String, + val mode:String, + var isSelected:Boolean, + val icon:Int +) diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/screens/App.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/screens/App.kt index 47db26f..141e812 100644 --- a/app/src/main/java/com/thatsmanmeet/taskyapp/screens/App.kt +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/screens/App.kt @@ -7,6 +7,7 @@ import android.content.Context import android.content.Intent import android.media.AudioAttributes import android.net.Uri +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* import androidx.compose.material.icons.Icons @@ -92,7 +93,15 @@ fun MyApp( val settingsStore = SettingsStore(context) val savedTaskKey = settingsStore.getTaskListKey.collectAsState(initial = true) val savedAnimationKey = settingsStore.getAnimationKey.collectAsState(initial = true) - TaskyTheme { + val savedThemeKey = settingsStore.getThemeModeKey.collectAsState(initial = "") + TaskyTheme(darkTheme = when (savedThemeKey.value) { + "0" -> { + isSystemInDarkTheme() + } + "1" -> {false} + else -> {true} + } + ) { Scaffold( topBar = { TopAppBar( @@ -152,7 +161,9 @@ fun MyApp( color = MaterialTheme.colorScheme.background ) { if(todoListFromFlow.isEmpty()){ - Box(modifier = modifier.fillMaxSize(), + Box(modifier = modifier + .fillMaxSize() + .padding(paddingValues), contentAlignment = Alignment.Center) { Column( modifier = modifier, @@ -176,28 +187,27 @@ fun MyApp( } } }else { - if(savedTaskKey.value == null || savedTaskKey.value == true){ - TaskList( - state = listState, - list = todoListFromFlow, - todoViewModel = todoViewModel, - onClick = {index-> - selectedItem.value = index - openEditDialog.value = true - } - ) - }else{ - LegacyTaskList( - state = listState, - list = todoListFromFlow, - todoViewModel = todoViewModel, - onClick = {index-> - selectedItem.value = index - openEditDialog.value = true - } - ) - } - + if(savedTaskKey.value == null || savedTaskKey.value == true){ + TaskList( + state = listState, + list = todoListFromFlow, + todoViewModel = todoViewModel, + onClick = {index-> + selectedItem.value = index + openEditDialog.value = true + } + ) + }else{ + LegacyTaskList( + state = listState, + list = todoListFromFlow, + todoViewModel = todoViewModel, + onClick = {index-> + selectedItem.value = index + openEditDialog.value = true + } + ) + } } if (openEditDialog.value){ OpenEditTodoDialog( diff --git a/app/src/main/java/com/thatsmanmeet/taskyapp/screens/SettingsScreen.kt b/app/src/main/java/com/thatsmanmeet/taskyapp/screens/SettingsScreen.kt index 1733fc1..2e534b8 100644 --- a/app/src/main/java/com/thatsmanmeet/taskyapp/screens/SettingsScreen.kt +++ b/app/src/main/java/com/thatsmanmeet/taskyapp/screens/SettingsScreen.kt @@ -2,6 +2,7 @@ package com.thatsmanmeet.taskyapp.screens import android.app.Activity import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -23,6 +24,7 @@ import androidx.navigation.NavHostController import com.thatsmanmeet.taskyapp.MainActivity import com.thatsmanmeet.taskyapp.R import com.thatsmanmeet.taskyapp.components.SettingsComponent +import com.thatsmanmeet.taskyapp.components.ThemeChangerDialog import com.thatsmanmeet.taskyapp.datastore.SettingsStore import com.thatsmanmeet.taskyapp.ui.theme.TaskyTheme import com.thatsmanmeet.taskyapp.viewmodels.MainViewModel @@ -43,6 +45,7 @@ fun SettingsScreen( val context = LocalContext.current val scope = rememberCoroutineScope() val settingStore = SettingsStore(context) + val savedThemeKey = settingStore.getThemeModeKey.collectAsState(initial = "") val isCheckedState = remember { mutableStateOf(isChecked.value) } @@ -58,10 +61,20 @@ fun SettingsScreen( var isDialogShowingState by rememberSaveable { mutableStateOf(false) } + val isThemeChangerShowing = rememberSaveable { + mutableStateOf(false) + } val mainViewModel = MainViewModel() val activity = LocalContext.current as Activity val dbPath = activity.getDatabasePath("todo_database").absolutePath - TaskyTheme{ + TaskyTheme(darkTheme = when (savedThemeKey.value) { + "0" -> { + isSystemInDarkTheme() + } + "1" -> {false} + else -> {true} + } + ){ Scaffold( modifier = modifier.fillMaxSize(), topBar = { @@ -110,6 +123,14 @@ fun SettingsScreen( ) .verticalScroll(rememberScrollState()), ) { + SettingsComponent( + settingHeaderText = stringResource(R.string.set_app_theme_settings_text), + settingText = stringResource(R.string.set_app_theme_info_text), + painterResourceID = R.drawable.ic_phone + ) { + isThemeChangerShowing.value = true + } + Spacer(modifier = modifier.height(12.dp)) Card( modifier = modifier .clip(RoundedCornerShape(15.dp)) @@ -298,5 +319,16 @@ fun SettingsScreen( } ) } + // Backup dialog ends here + ThemeChangerDialog( + selectedItem = remember { + mutableStateOf(savedThemeKey.value.toString()) + }, + isShowing = isThemeChangerShowing + ){mode-> + scope.launch { + settingStore.saveThemeModeKey(mode) + } + } } } diff --git a/app/src/main/res/drawable/ic_dark.xml b/app/src/main/res/drawable/ic_dark.xml new file mode 100644 index 0000000..c384362 --- /dev/null +++ b/app/src/main/res/drawable/ic_dark.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_light.xml b/app/src/main/res/drawable/ic_light.xml new file mode 100644 index 0000000..ab9995a --- /dev/null +++ b/app/src/main/res/drawable/ic_light.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..e948564 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5ccfb3a..35dec03 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -30,4 +30,7 @@ Sicherung/Wiederherstellung Sicherung oder zu Wiederherstellung ihrer Aufgaben durch eine lokale Zip-Datei Verwende Töne bei erfüllter Aufgabe + Wählen Sie App-Theme + Wählen Sie App-Theme + Ändern Sie das aktuelle Design der App in hell, dunkel oder Systemstandard. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fd86e6f..4e67015 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,5 +29,7 @@ Backup/Restore Backup or Restore your tasks from a local zip file. Use task complete sounds - + Select App Theme + Set app theme + Change current theme of the app to light, dark or system default. \ No newline at end of file