Use wrapper for table entities

This commit is contained in:
Sad Ellie 2023-12-04 00:35:24 +03:00
parent f7fe300706
commit 2d78ab05bd
8 changed files with 180 additions and 48 deletions

View File

@ -0,0 +1,4 @@
-keepclassmembers class ** {
@com.squareup.moshi.FromJson *;
@com.squareup.moshi.ToJson *;
}

View File

@ -52,6 +52,7 @@ import com.sadellie.unitto.data.userprefs.getUnitConverterFormatTime
import com.sadellie.unitto.data.userprefs.getUnitConverterSorting
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
@ -61,6 +62,7 @@ import java.io.File
import java.io.InputStreamReader
import javax.inject.Inject
@OptIn(ExperimentalStdlibApi::class)
class BackupManager @Inject constructor(
@ApplicationContext private val mContext: Context,
private val dataStore: DataStore<Preferences>,
@ -69,8 +71,10 @@ class BackupManager @Inject constructor(
// Not planned at the moment
// private val calculatorHistoryDao: CalculatorHistoryDao,
) {
private val moshi: Moshi = Moshi.Builder().build()
private val jsonAdapter: JsonAdapter<UserData> = moshi.adapter(UserData::class.java)
private val moshi: Moshi = Moshi.Builder()
.add(UserDataTableAdapter())
.build()
private val jsonAdapter: JsonAdapter<UserData> = moshi.adapter<UserData>()
private val auth = "com.sadellie.unitto.BackupManager"
suspend fun backup(): Uri = withContext(Dispatchers.IO) {

View File

@ -0,0 +1,62 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 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.data.backup
import com.sadellie.unitto.data.database.TimeZoneEntity
import com.sadellie.unitto.data.database.UnitsEntity
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
// Have to use this wrapper since entity classes are in database module
@Suppress("UNUSED")
internal class UserDataTableAdapter {
@ToJson
fun toJson(unitsEntity: UnitsEntity): UserDataUnit =
UserDataUnit(
unitId = unitsEntity.unitId,
isFavorite = unitsEntity.isFavorite,
pairedUnitId = unitsEntity.pairedUnitId,
frequency = unitsEntity.frequency
)
@FromJson
fun fromJson(userDataUnit: UserDataUnit): UnitsEntity =
UnitsEntity(
unitId = userDataUnit.unitId,
isFavorite = userDataUnit.isFavorite,
pairedUnitId = userDataUnit.pairedUnitId,
frequency = userDataUnit.frequency
)
@ToJson
fun toJson(timeZoneEntity: TimeZoneEntity): UserDataTimezone =
UserDataTimezone(
id = timeZoneEntity.id,
position = timeZoneEntity.position,
label = timeZoneEntity.label
)
@FromJson
fun fromJson(userDataTimezone: UserDataTimezone): TimeZoneEntity =
TimeZoneEntity(
id = userDataTimezone.id,
position = userDataTimezone.position,
label = userDataTimezone.label
)
}

View File

@ -0,0 +1,28 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 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.data.backup
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserDataTimezone(
val id: String,
val position: Int,
val label: String,
)

View File

@ -0,0 +1,29 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 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.data.backup
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserDataUnit(
val unitId: String,
val isFavorite: Boolean,
val pairedUnitId: String?,
val frequency: Int,
)

View File

@ -26,7 +26,6 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -110,41 +109,21 @@ internal fun SettingsRoute(
if (showErrorToast) Toast.makeText(mContext, errorLabel, Toast.LENGTH_SHORT).show()
}
Crossfade(targetState = uiState) { state ->
when (state) {
SettingsUIState.Loading -> UnittoEmptyScreen()
when (uiState) {
SettingsUIState.Loading -> UnittoEmptyScreen()
SettingsUIState.BackupInProgress -> BackingUpScreen()
is SettingsUIState.Ready -> SettingsScreen(
uiState = state,
navigateUp = navigateUp,
navControllerAction = navControllerAction,
updateVibrations = viewModel::updateVibrations,
clearCache = viewModel::clearCache,
backup = viewModel::backup,
restore = viewModel::restore
)
}
is SettingsUIState.Ready -> SettingsScreen(
uiState = uiState,
navigateUp = navigateUp,
navControllerAction = navControllerAction,
updateVibrations = viewModel::updateVibrations,
clearCache = viewModel::clearCache,
backup = viewModel::backup,
restore = viewModel::restore
)
}
}
@Composable
private fun BackingUpScreen() {
Scaffold { padding ->
Box(
modifier = Modifier
.padding(padding)
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}
BackHandler {}
}
@Composable
private fun SettingsScreen(
uiState: SettingsUIState.Ready,
@ -163,6 +142,8 @@ private fun SettingsScreen(
if (pickedUri != null) restore(pickedUri)
}
BackHandler(uiState.backupInProgress) {}
UnittoScreenWithLargeTopBar(
title = stringResource(R.string.settings_title),
navigationIcon = { NavigateUpButton(navigateUp) },
@ -176,11 +157,11 @@ private fun SettingsScreen(
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
onClick = backup,
onClick = { showMenu = false; backup() },
text = { Text("Backup") }
)
DropdownMenuItem(
onClick = { launcher.launch(arrayOf(backupMimeType)) },
onClick = { showMenu = false; launcher.launch(arrayOf(backupMimeType)) },
text = { Text("Restore") }
)
}
@ -266,13 +247,26 @@ private fun SettingsScreen(
)
}
}
AnimatedVisibility(visible = uiState.backupInProgress) {
Scaffold { padding ->
Box(
modifier = Modifier
.padding(padding)
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}
}
}
private fun Context.share(uri: Uri) {
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = backupMimeType // This is a fucking war crime, it should be text/json
type = backupMimeType
}
startActivity(shareIntent)
@ -289,6 +283,7 @@ private fun PreviewSettingsScreen() {
uiState = SettingsUIState.Ready(
enableVibrations = false,
cacheSize = 2,
backupInProgress = false
),
navigateUp = {},
navControllerAction = {},
@ -301,5 +296,16 @@ private fun PreviewSettingsScreen() {
@Preview
@Composable
private fun PreviewBackingUpScreen() {
BackingUpScreen()
SettingsScreen(
uiState = SettingsUIState.Ready(
enableVibrations = false,
cacheSize = 2,
backupInProgress = true
),
navigateUp = {},
navControllerAction = {},
updateVibrations = {},
clearCache = {},
backup = {}
)
}

View File

@ -21,10 +21,9 @@ package com.sadellie.unitto.feature.settings
internal sealed class SettingsUIState {
data object Loading : SettingsUIState()
data object BackupInProgress : SettingsUIState()
data class Ready(
val enableVibrations: Boolean,
val cacheSize: Int,
val backupInProgress: Boolean,
) : SettingsUIState()
}

View File

@ -49,19 +49,19 @@ internal class SettingsViewModel @Inject constructor(
private val _showErrorToast = MutableSharedFlow<Boolean>()
val showErrorToast = _showErrorToast.asSharedFlow()
private val _operation = MutableStateFlow(false)
private val _backupInProgress = MutableStateFlow(false)
private var backupJob: Job? = null
val uiState = combine(
userPrefsRepository.generalPrefs,
currencyRatesDao.size(),
_operation,
) { prefs, cacheSize, operation ->
if (operation) return@combine SettingsUIState.BackupInProgress
_backupInProgress,
) { prefs, cacheSize, backupInProgress ->
SettingsUIState.Ready(
enableVibrations = prefs.enableVibrations,
cacheSize = cacheSize,
backupInProgress = backupInProgress
)
}
.stateIn(viewModelScope, SettingsUIState.Loading)
@ -69,7 +69,7 @@ internal class SettingsViewModel @Inject constructor(
fun backup() {
backupJob?.cancel()
backupJob = viewModelScope.launch(Dispatchers.IO) {
_operation.update { true }
_backupInProgress.update { true }
try {
val backupFileUri = backupManager.backup()
_backupFileUri.emit(backupFileUri) // Emit to trigger file share intent
@ -78,14 +78,14 @@ internal class SettingsViewModel @Inject constructor(
_showErrorToast.emit(true)
Log.e(TAG, "$e")
}
_operation.update { false }
_backupInProgress.update { false }
}
}
fun restore(uri: Uri) {
backupJob?.cancel()
backupJob = viewModelScope.launch(Dispatchers.IO) {
_operation.update { true }
_backupInProgress.update { true }
try {
backupManager.restore(uri)
_showErrorToast.emit(false)
@ -93,7 +93,7 @@ internal class SettingsViewModel @Inject constructor(
_showErrorToast.emit(true)
Log.e(TAG, "$e")
}
_operation.update { false }
_backupInProgress.update { false }
}
}