Navigate to changelogs for new versions

This commit is contained in:
Sad Ellie 2024-01-08 17:49:49 +03:00
parent a726c4c5bf
commit 99a8db2a50
16 changed files with 204 additions and 73 deletions

View File

@ -186,6 +186,12 @@ Maybe this can be labeled better? Let me know. It should be something that can d
<string name="settings_unit_groups_title">Unit groups</string>
<string name="settings_units_sorting">Units list sorting</string>
<string name="settings_units_sorting_support">Change units order</string>
<!-- Please keep "%1$s". It will be formatted in the app. Example (English): "Unitto has been updated" -->
<string name="settings_updated">%1$s has been updated</string>
<!-- Please keep "%1$s". It will be formatted in the app. Example (English): "See what's new in version Quick Silver" -->
<string name="settings_updated_support">See what\'s new in version %1$s</string>
<string name="settings_version_name">Version name</string>
<string name="settings_vibrations">Vibrations</string>
<string name="settings_vibrations_support">Haptic feedback when clicking keyboard buttons</string>

View File

@ -75,6 +75,7 @@ class BackupManagerTest {
it[PrefsKeys.STARTING_SCREEN] = fakeUserData.startingScreen
it[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] = fakeUserData.enableToolsExperiment
it[PrefsKeys.SYSTEM_FONT] = fakeUserData.systemFont
it[PrefsKeys.LAST_READ_CHANGELOG] = fakeUserData.lastReadChangelog
it[PrefsKeys.ENABLE_VIBRATIONS] = fakeUserData.enableVibrations
it[PrefsKeys.MIDDLE_ZERO] = fakeUserData.middleZero
it[PrefsKeys.AC_BUTTON] = fakeUserData.acButton

View File

@ -27,6 +27,7 @@ internal val fakeUserData = UserData(
startingScreen = "calculator_route",
enableToolsExperiment = false,
systemFont = false,
lastReadChangelog = "33",
enableVibrations = false,
middleZero = false,
acButton = false,

View File

@ -35,6 +35,7 @@ import com.sadellie.unitto.data.userprefs.getEnableAmoledTheme
import com.sadellie.unitto.data.userprefs.getEnableDynamicTheme
import com.sadellie.unitto.data.userprefs.getEnableToolsExperiment
import com.sadellie.unitto.data.userprefs.getEnableVibrations
import com.sadellie.unitto.data.userprefs.getLastReadChangelog
import com.sadellie.unitto.data.userprefs.getLatestLeftSide
import com.sadellie.unitto.data.userprefs.getLatestRightSide
import com.sadellie.unitto.data.userprefs.getMiddleZero
@ -128,6 +129,7 @@ class BackupManager @Inject constructor(
startingScreen = data.getStartingScreen(),
enableToolsExperiment = data.getEnableToolsExperiment(),
systemFont = data.getSystemFont(),
lastReadChangelog = data.getLastReadChangelog(),
enableVibrations = data.getEnableVibrations(),
middleZero = data.getMiddleZero(),
acButton = data.getAcButton(),
@ -160,6 +162,7 @@ class BackupManager @Inject constructor(
it[PrefsKeys.STARTING_SCREEN] = userData.startingScreen
it[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] = userData.enableToolsExperiment
it[PrefsKeys.SYSTEM_FONT] = userData.systemFont
it[PrefsKeys.LAST_READ_CHANGELOG] = userData.lastReadChangelog
it[PrefsKeys.ENABLE_VIBRATIONS] = userData.enableVibrations
it[PrefsKeys.MIDDLE_ZERO] = userData.middleZero
it[PrefsKeys.AC_BUTTON] = userData.acButton

View File

@ -35,6 +35,7 @@ internal data class UserData(
@Json(name = "startingScreen") val startingScreen: String,
@Json(name = "enableToolsExperiment") val enableToolsExperiment: Boolean,
@Json(name = "systemFont") val systemFont: Boolean,
@Json(name = "lastReadChangelog") val lastReadChangelog: String,
@Json(name = "enableVibrations") val enableVibrations: Boolean,
@Json(name = "middleZero") val middleZero: Boolean,
@Json(name = "acButton") val acButton: Boolean,

View File

@ -69,6 +69,8 @@ interface UserPreferencesRepository {
suspend fun updateShownUnitGroups(shownUnitGroups: List<UnitGroup>)
suspend fun updateLastReadChangelog(value: String)
suspend fun updateVibrations(enabled: Boolean)
suspend fun updateMiddleZero(enabled: Boolean)

View File

@ -19,5 +19,6 @@
package com.sadellie.unitto.data.model.userprefs
interface GeneralPreferences {
val lastReadChangelog: String
val enableVibrations: Boolean
}

View File

@ -59,6 +59,10 @@ fun Preferences.getSystemFont(): Boolean {
return this[PrefsKeys.SYSTEM_FONT] ?: false
}
fun Preferences.getLastReadChangelog(): String {
return this[PrefsKeys.LAST_READ_CHANGELOG] ?: ""
}
fun Preferences.getEnableVibrations(): Boolean {
return this[PrefsKeys.ENABLE_VIBRATIONS] ?: true
}

View File

@ -46,6 +46,7 @@ data class AppPreferencesImpl(
) : AppPreferences
data class GeneralPreferencesImpl(
override val lastReadChangelog: String,
override val enableVibrations: Boolean,
) : GeneralPreferences

View File

@ -33,6 +33,7 @@ object PrefsKeys {
val STARTING_SCREEN = stringPreferencesKey("STARTING_SCREEN_PREF_KEY")
val ENABLE_TOOLS_EXPERIMENT = booleanPreferencesKey("ENABLE_TOOLS_EXPERIMENT_PREF_KEY")
val SYSTEM_FONT = booleanPreferencesKey("SYSTEM_FONT_PREF_KEY")
val LAST_READ_CHANGELOG = stringPreferencesKey("LAST_READ_CHANGELOG_PREF_KEY")
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
val MIDDLE_ZERO = booleanPreferencesKey("MIDDLE_ZERO_PREF_KEY")
val AC_BUTTON = booleanPreferencesKey("AC_BUTTON_PREF_KEY")

View File

@ -67,6 +67,7 @@ class UserPreferencesRepositoryImpl @Inject constructor(
override val generalPrefs: Flow<GeneralPreferences> = data
.map { preferences ->
GeneralPreferencesImpl(
lastReadChangelog = preferences.getLastReadChangelog(),
enableVibrations = preferences.getEnableVibrations(),
)
}
@ -226,6 +227,12 @@ class UserPreferencesRepositoryImpl @Inject constructor(
}
}
override suspend fun updateLastReadChangelog(value: String) {
dataStore.edit { preferences ->
preferences[PrefsKeys.LAST_READ_CHANGELOG] = value
}
}
override suspend fun updateVibrations(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.ENABLE_VIBRATIONS] = enabled

View File

@ -34,6 +34,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -48,6 +49,7 @@ import androidx.compose.material.icons.filled.RateReview
import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material.icons.filled.Vibration
import androidx.compose.material.icons.filled._123
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@ -58,15 +60,16 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.BuildConfig
@ -78,12 +81,15 @@ import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.openLink
import com.sadellie.unitto.core.ui.showToast
import com.sadellie.unitto.feature.settings.components.AnnoyingBox
import com.sadellie.unitto.feature.settings.navigation.aboutRoute
import com.sadellie.unitto.feature.settings.navigation.calculatorSettingsRoute
import com.sadellie.unitto.feature.settings.navigation.converterSettingsRoute
import com.sadellie.unitto.feature.settings.navigation.displayRoute
import com.sadellie.unitto.feature.settings.navigation.formattingRoute
import com.sadellie.unitto.feature.settings.navigation.startingScreenRoute
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
internal fun SettingsRoute(
@ -116,9 +122,10 @@ internal fun SettingsRoute(
uiState = uiState,
navigateUp = navigateUp,
navControllerAction = navControllerAction,
updateLastReadChangelog = viewModel::updateLastReadChangelog,
updateVibrations = viewModel::updateVibrations,
clearCache = viewModel::clearCache,
backup = viewModel::backup,
backup = viewModel::backup,
restore = viewModel::restore
)
}
@ -129,6 +136,7 @@ private fun SettingsScreen(
uiState: SettingsUIState.Ready,
navigateUp: () -> Unit,
navControllerAction: (String) -> Unit,
updateLastReadChangelog: (String) -> Unit,
updateVibrations: (Boolean) -> Unit,
clearCache: () -> Unit,
backup: () -> Unit,
@ -173,6 +181,26 @@ private fun SettingsScreen(
.verticalScroll(rememberScrollState())
.padding(padding)
) {
AnimatedVisibility(
visible = uiState.showUpdateChangelog,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(),
) {
val title = stringResource(R.string.settings_updated, stringResource(R.string.app_name))
AnnoyingBox(
modifier = Modifier
.padding(16.dp, 8.dp)
.fillMaxWidth(),
imageVector = Icons.Outlined.NewReleases,
imageVectorContentDescription = title,
title = title,
support = stringResource(R.string.settings_updated_support, BuildConfig.VERSION_NAME),
) {
openLink(mContext, "https://github.com/sadellie/unitto/releases/latest")
updateLastReadChangelog(BuildConfig.VERSION_CODE)
}
}
UnittoListItem(
icon = Icons.Default.Palette,
headlineText = stringResource(R.string.settings_display),
@ -277,35 +305,40 @@ private const val backupMimeType = "application/octet-stream"
@Preview
@Composable
private fun PreviewSettingsScreen() {
var cacheSize by remember { mutableFloatStateOf(0.9f) }
val corScope = rememberCoroutineScope()
var uiState by remember {
mutableStateOf(
SettingsUIState.Ready(
enableVibrations = false,
cacheSize = 2,
backupInProgress = false,
showUpdateChangelog = true
)
)
}
SettingsScreen(
uiState = SettingsUIState.Ready(
enableVibrations = false,
cacheSize = 2,
backupInProgress = false
backupInProgress = false,
showUpdateChangelog = true
),
navigateUp = {},
navControllerAction = {},
updateLastReadChangelog = {
uiState = uiState.copy(showUpdateChangelog = false)
},
updateVibrations = {},
clearCache = { cacheSize = 0f },
backup = {}
)
}
@Preview
@Composable
private fun PreviewBackingUpScreen() {
SettingsScreen(
uiState = SettingsUIState.Ready(
enableVibrations = false,
cacheSize = 2,
backupInProgress = true
),
navigateUp = {},
navControllerAction = {},
updateVibrations = {},
clearCache = {},
backup = {}
clearCache = {
uiState = uiState.copy(cacheSize = 0)
},
backup = {
corScope.launch {
uiState = uiState.copy(backupInProgress = true)
delay(2000)
uiState = uiState.copy(backupInProgress = false)
}
}
)
}

View File

@ -25,5 +25,6 @@ internal sealed class SettingsUIState {
val enableVibrations: Boolean,
val cacheSize: Int,
val backupInProgress: Boolean,
val showUpdateChangelog: Boolean,
) : SettingsUIState()
}

View File

@ -22,6 +22,7 @@ import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.base.BuildConfig
import com.sadellie.unitto.data.backup.BackupManager
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.database.CurrencyRatesDao
@ -57,11 +58,11 @@ internal class SettingsViewModel @Inject constructor(
currencyRatesDao.size(),
_backupInProgress,
) { prefs, cacheSize, backupInProgress ->
SettingsUIState.Ready(
enableVibrations = prefs.enableVibrations,
cacheSize = cacheSize,
backupInProgress = backupInProgress
backupInProgress = backupInProgress,
showUpdateChangelog = prefs.lastReadChangelog != BuildConfig.VERSION_CODE
)
}
.stateIn(viewModelScope, SettingsUIState.Loading)
@ -97,6 +98,13 @@ internal class SettingsViewModel @Inject constructor(
}
}
/**
* @see UserPreferencesRepository.updateLastReadChangelog
*/
fun updateLastReadChangelog(value: String) = viewModelScope.launch {
userPrefsRepository.updateLastReadChangelog(value)
}
/**
* @see UserPreferencesRepository.updateVibrations
*/

View File

@ -0,0 +1,99 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings.components
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Accessibility
import androidx.compose.material3.Icon
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.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.common.squashable
@Composable
internal fun AnnoyingBox(
modifier: Modifier,
imageVector: ImageVector,
imageVectorContentDescription: String,
title: String,
support: String,
onClick: () -> Unit,
) {
Row(
modifier = modifier
.squashable(
onClick = onClick,
interactionSource = remember { MutableInteractionSource() },
cornerRadiusRange = 15..25
)
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(16.dp, 8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.size(24.dp),
imageVector = imageVector,
contentDescription = imageVectorContentDescription,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 8.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSecondaryContainer,
)
Text(
text = support,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer,
)
}
}
}
@Preview
@Composable
fun PreviewAnnoyingBox() {
AnnoyingBox(
modifier = Modifier.fillMaxWidth(),
imageVector = Icons.Default.Accessibility,
imageVectorContentDescription = "",
title = "Title text",
support = "Lorem ipsum or something"
) {}
}

View File

@ -19,25 +19,16 @@
package com.sadellie.unitto.feature.settings.language
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Translate
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
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.res.stringResource
@ -48,8 +39,8 @@ import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.common.squashable
import com.sadellie.unitto.core.ui.openLink
import com.sadellie.unitto.feature.settings.components.AnnoyingBox
@Composable
internal fun LanguageRoute(
@ -83,43 +74,14 @@ private fun LanguageScreen(
) { padding ->
LazyColumn(contentPadding = padding) {
item("translate") {
Box(Modifier.padding(16.dp, 8.dp)) {
Row(
modifier = Modifier
.squashable(
onClick = {
openLink(mContext, "https://poeditor.com/join/project/T4zjmoq8dx")
},
interactionSource = remember { MutableInteractionSource() },
cornerRadiusRange = 15..50
)
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(16.dp, 4.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Translate,
contentDescription = stringResource(R.string.settings_translate_app),
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
Column(
Modifier
.weight(1f)
.padding(vertical = 8.dp)) {
Text(
text = stringResource(R.string.settings_translate_app),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSecondaryContainer,
)
Text(
text = stringResource(R.string.settings_translate_app_support),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer,
)
}
}
AnnoyingBox(
modifier = Modifier.padding(16.dp, 8.dp).fillMaxWidth(),
imageVector = Icons.Default.Translate,
imageVectorContentDescription = stringResource(R.string.settings_translate_app),
title = stringResource(R.string.settings_translate_app),
support = stringResource(R.string.settings_translate_app_support)
) {
openLink(mContext, "https://poeditor.com/join/project/T4zjmoq8dx")
}
}