mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
parent
7e2d4d8f3f
commit
f7a89594fa
@ -19,6 +19,7 @@
|
|||||||
package com.sadellie.unitto.core.ui.common
|
package com.sadellie.unitto.core.ui.common
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -39,6 +40,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||||||
fun UnittoScreenWithLargeTopBar(
|
fun UnittoScreenWithLargeTopBar(
|
||||||
title: String,
|
title: String,
|
||||||
navigationIcon: @Composable () -> Unit,
|
navigationIcon: @Composable () -> Unit,
|
||||||
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
content: @Composable (PaddingValues) -> Unit
|
content: @Composable (PaddingValues) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
@ -53,7 +55,8 @@ fun UnittoScreenWithLargeTopBar(
|
|||||||
Text(text = title)
|
Text(text = title)
|
||||||
},
|
},
|
||||||
navigationIcon = navigationIcon,
|
navigationIcon = navigationIcon,
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior,
|
||||||
|
actions = actions
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = content
|
content = content
|
||||||
|
1
data/backup/.gitignore
vendored
Normal file
1
data/backup/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
36
data/backup/build.gradle.kts
Normal file
36
data/backup/build.gradle.kts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("unitto.library")
|
||||||
|
id("unitto.android.library.jacoco")
|
||||||
|
id("unitto.android.hilt")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.sadellie.unitto.data.backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.androidx.datastore.datastore.preferences)
|
||||||
|
implementation(libs.com.squareup.moshi.moshi.kotlin)
|
||||||
|
|
||||||
|
implementation(project(":data:database"))
|
||||||
|
implementation(project(":data:model"))
|
||||||
|
implementation(project(":data:userprefs"))
|
||||||
|
}
|
0
data/backup/consumer-rules.pro
Normal file
0
data/backup/consumer-rules.pro
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.sadellie.unitto.data.userprefs.PrefsKeys
|
||||||
|
import com.sadellie.unitto.data.userprefs.getThemingMode
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class BackupManagerTest {
|
||||||
|
|
||||||
|
private lateinit var dataStore: DataStore<Preferences>
|
||||||
|
private lateinit var backupManager: BackupManager
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
|
dataStore = PreferenceDataStoreFactory.create(
|
||||||
|
corruptionHandler = null,
|
||||||
|
produceFile = { appContext.preferencesDataStoreFile("test") }
|
||||||
|
)
|
||||||
|
|
||||||
|
backupManager = BackupManager(
|
||||||
|
mContext = appContext,
|
||||||
|
dataStore = dataStore,
|
||||||
|
unitsDao = FakeUnitsDao,
|
||||||
|
timeZoneDao = FakeTimeZoneDao
|
||||||
|
)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
dataStore.edit {
|
||||||
|
it[PrefsKeys.THEMING_MODE] = FakeUsrPreferenceValues.themingMode
|
||||||
|
it[PrefsKeys.ENABLE_DYNAMIC_THEME] = FakeUsrPreferenceValues.enableDynamicTheme
|
||||||
|
it[PrefsKeys.ENABLE_AMOLED_THEME] = FakeUsrPreferenceValues.enableAmoledTheme
|
||||||
|
it[PrefsKeys.CUSTOM_COLOR] = FakeUsrPreferenceValues.customColor
|
||||||
|
it[PrefsKeys.MONET_MODE] = FakeUsrPreferenceValues.monetMode
|
||||||
|
it[PrefsKeys.STARTING_SCREEN] = FakeUsrPreferenceValues.startingScreen
|
||||||
|
it[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] = FakeUsrPreferenceValues.enableToolsExperiment
|
||||||
|
it[PrefsKeys.ENABLE_VIBRATIONS] = FakeUsrPreferenceValues.enableVibrations
|
||||||
|
it[PrefsKeys.MIDDLE_ZERO] = FakeUsrPreferenceValues.middleZero
|
||||||
|
it[PrefsKeys.AC_BUTTON] = FakeUsrPreferenceValues.acButton
|
||||||
|
it[PrefsKeys.RPN_MODE] = FakeUsrPreferenceValues.rpnMode
|
||||||
|
|
||||||
|
// FORMATTER
|
||||||
|
it[PrefsKeys.DIGITS_PRECISION] = FakeUsrPreferenceValues.precision
|
||||||
|
it[PrefsKeys.SEPARATOR] = FakeUsrPreferenceValues.separator
|
||||||
|
it[PrefsKeys.OUTPUT_FORMAT] = FakeUsrPreferenceValues.outputFormat
|
||||||
|
|
||||||
|
// CALCULATOR
|
||||||
|
it[PrefsKeys.RADIAN_MODE] = FakeUsrPreferenceValues.radianMode
|
||||||
|
it[PrefsKeys.PARTIAL_HISTORY_VIEW] = FakeUsrPreferenceValues.partialHistoryView
|
||||||
|
it[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] = FakeUsrPreferenceValues.clearInputAfterEquals
|
||||||
|
|
||||||
|
// UNIT CONVERTER
|
||||||
|
it[PrefsKeys.LATEST_LEFT_SIDE] = FakeUsrPreferenceValues.latestLeftSide
|
||||||
|
it[PrefsKeys.LATEST_RIGHT_SIDE] = FakeUsrPreferenceValues.latestRightSide
|
||||||
|
it[PrefsKeys.SHOWN_UNIT_GROUPS] = FakeUsrPreferenceValues.shownUnitGroups.joinToString(",")
|
||||||
|
it[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] = FakeUsrPreferenceValues.unitConverterFavoritesOnly
|
||||||
|
it[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] = FakeUsrPreferenceValues.unitConverterFormatTime
|
||||||
|
it[PrefsKeys.UNIT_CONVERTER_SORTING] = FakeUsrPreferenceValues.unitConverterSorting.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getUserDataTest() = runBlocking{
|
||||||
|
val actualUserData = backupManager.userDataFromApp()
|
||||||
|
val expectedUserData = UserData(
|
||||||
|
themingMode = FakeUsrPreferenceValues.themingMode,
|
||||||
|
enableDynamicTheme = FakeUsrPreferenceValues.enableDynamicTheme,
|
||||||
|
enableAmoledTheme = FakeUsrPreferenceValues.enableAmoledTheme,
|
||||||
|
customColor = FakeUsrPreferenceValues.customColor,
|
||||||
|
monetMode = FakeUsrPreferenceValues.monetMode,
|
||||||
|
startingScreen = FakeUsrPreferenceValues.startingScreen,
|
||||||
|
enableToolsExperiment = FakeUsrPreferenceValues.enableToolsExperiment,
|
||||||
|
enableVibrations = FakeUsrPreferenceValues.enableVibrations,
|
||||||
|
middleZero = FakeUsrPreferenceValues.middleZero,
|
||||||
|
acButton = FakeUsrPreferenceValues.acButton,
|
||||||
|
rpnMode = FakeUsrPreferenceValues.rpnMode,
|
||||||
|
precision = FakeUsrPreferenceValues.precision,
|
||||||
|
separator = FakeUsrPreferenceValues.separator,
|
||||||
|
outputFormat = FakeUsrPreferenceValues.outputFormat,
|
||||||
|
radianMode = FakeUsrPreferenceValues.radianMode,
|
||||||
|
partialHistoryView = FakeUsrPreferenceValues.partialHistoryView,
|
||||||
|
clearInputAfterEquals = FakeUsrPreferenceValues.clearInputAfterEquals,
|
||||||
|
latestLeftSide = FakeUsrPreferenceValues.latestLeftSide,
|
||||||
|
latestRightSide = FakeUsrPreferenceValues.latestRightSide,
|
||||||
|
shownUnitGroups = FakeUsrPreferenceValues.shownUnitGroups,
|
||||||
|
unitConverterFavoritesOnly = FakeUsrPreferenceValues.unitConverterFavoritesOnly,
|
||||||
|
unitConverterFormatTime = FakeUsrPreferenceValues.unitConverterFormatTime,
|
||||||
|
unitConverterSorting = FakeUsrPreferenceValues.unitConverterSorting,
|
||||||
|
unitsTable = units,
|
||||||
|
timeZoneTable = timeZones
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(expectedUserData, actualUserData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateDatastoreTest() = runBlocking{
|
||||||
|
backupManager.updateDatastore(
|
||||||
|
UserData(
|
||||||
|
themingMode = FakeUsrPreferenceValues.themingMode,
|
||||||
|
enableDynamicTheme = FakeUsrPreferenceValues.enableDynamicTheme,
|
||||||
|
enableAmoledTheme = FakeUsrPreferenceValues.enableAmoledTheme,
|
||||||
|
customColor = FakeUsrPreferenceValues.customColor,
|
||||||
|
monetMode = FakeUsrPreferenceValues.monetMode,
|
||||||
|
startingScreen = FakeUsrPreferenceValues.startingScreen,
|
||||||
|
enableToolsExperiment = FakeUsrPreferenceValues.enableToolsExperiment,
|
||||||
|
enableVibrations = FakeUsrPreferenceValues.enableVibrations,
|
||||||
|
middleZero = FakeUsrPreferenceValues.middleZero,
|
||||||
|
acButton = FakeUsrPreferenceValues.acButton,
|
||||||
|
rpnMode = FakeUsrPreferenceValues.rpnMode,
|
||||||
|
precision = FakeUsrPreferenceValues.precision,
|
||||||
|
separator = FakeUsrPreferenceValues.separator,
|
||||||
|
outputFormat = FakeUsrPreferenceValues.outputFormat,
|
||||||
|
radianMode = FakeUsrPreferenceValues.radianMode,
|
||||||
|
partialHistoryView = FakeUsrPreferenceValues.partialHistoryView,
|
||||||
|
clearInputAfterEquals = FakeUsrPreferenceValues.clearInputAfterEquals,
|
||||||
|
latestLeftSide = FakeUsrPreferenceValues.latestLeftSide,
|
||||||
|
latestRightSide = FakeUsrPreferenceValues.latestRightSide,
|
||||||
|
shownUnitGroups = FakeUsrPreferenceValues.shownUnitGroups,
|
||||||
|
unitConverterFavoritesOnly = FakeUsrPreferenceValues.unitConverterFavoritesOnly,
|
||||||
|
unitConverterFormatTime = FakeUsrPreferenceValues.unitConverterFormatTime,
|
||||||
|
unitConverterSorting = FakeUsrPreferenceValues.unitConverterSorting,
|
||||||
|
unitsTable = units,
|
||||||
|
timeZoneTable = timeZones
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val data = dataStore.data.first()
|
||||||
|
// TODO Wrong implementation, should test all
|
||||||
|
assertEquals(FakeUsrPreferenceValues.themingMode, data.getThemingMode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateUnitsTableTest() = runBlocking {
|
||||||
|
backupManager.updateUnitsTable(
|
||||||
|
UserData(
|
||||||
|
themingMode = FakeUsrPreferenceValues.themingMode,
|
||||||
|
enableDynamicTheme = FakeUsrPreferenceValues.enableDynamicTheme,
|
||||||
|
enableAmoledTheme = FakeUsrPreferenceValues.enableAmoledTheme,
|
||||||
|
customColor = FakeUsrPreferenceValues.customColor,
|
||||||
|
monetMode = FakeUsrPreferenceValues.monetMode,
|
||||||
|
startingScreen = FakeUsrPreferenceValues.startingScreen,
|
||||||
|
enableToolsExperiment = FakeUsrPreferenceValues.enableToolsExperiment,
|
||||||
|
enableVibrations = FakeUsrPreferenceValues.enableVibrations,
|
||||||
|
middleZero = FakeUsrPreferenceValues.middleZero,
|
||||||
|
acButton = FakeUsrPreferenceValues.acButton,
|
||||||
|
rpnMode = FakeUsrPreferenceValues.rpnMode,
|
||||||
|
precision = FakeUsrPreferenceValues.precision,
|
||||||
|
separator = FakeUsrPreferenceValues.separator,
|
||||||
|
outputFormat = FakeUsrPreferenceValues.outputFormat,
|
||||||
|
radianMode = FakeUsrPreferenceValues.radianMode,
|
||||||
|
partialHistoryView = FakeUsrPreferenceValues.partialHistoryView,
|
||||||
|
clearInputAfterEquals = FakeUsrPreferenceValues.clearInputAfterEquals,
|
||||||
|
latestLeftSide = FakeUsrPreferenceValues.latestLeftSide,
|
||||||
|
latestRightSide = FakeUsrPreferenceValues.latestRightSide,
|
||||||
|
shownUnitGroups = FakeUsrPreferenceValues.shownUnitGroups,
|
||||||
|
unitConverterFavoritesOnly = FakeUsrPreferenceValues.unitConverterFavoritesOnly,
|
||||||
|
unitConverterFormatTime = FakeUsrPreferenceValues.unitConverterFormatTime,
|
||||||
|
unitConverterSorting = FakeUsrPreferenceValues.unitConverterSorting,
|
||||||
|
unitsTable = emptyList(),
|
||||||
|
timeZoneTable = timeZones
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val data = FakeUnitsDao.getAllFlow().first()
|
||||||
|
// TODO Wrong implementation
|
||||||
|
assertEquals("$units | $data", units, data)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.TimeZoneDao
|
||||||
|
import com.sadellie.unitto.data.database.TimeZoneEntity
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
val timeZones = listOf(
|
||||||
|
TimeZoneEntity(
|
||||||
|
id = "id1",
|
||||||
|
position = 9,
|
||||||
|
label = "label"
|
||||||
|
),
|
||||||
|
TimeZoneEntity(
|
||||||
|
id = "id2",
|
||||||
|
position = 9,
|
||||||
|
label = "label"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
object FakeTimeZoneDao: TimeZoneDao {
|
||||||
|
override fun getFavorites(): Flow<List<TimeZoneEntity>> {
|
||||||
|
return flow {
|
||||||
|
emit(timeZones)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMaxPosition(): Int = 0
|
||||||
|
override suspend fun insert(vararg timeZoneEntity: TimeZoneEntity) {}
|
||||||
|
override suspend fun removeFromFavorites(id: String) {}
|
||||||
|
override suspend fun updateLabel(id: String, label: String) {}
|
||||||
|
override suspend fun updateDragged(id: String, oldPosition: Int, newPosition: Int) {}
|
||||||
|
override suspend fun moveDown(currentPosition: Int, targetPosition: Int) {}
|
||||||
|
override suspend fun moveUp(currentPosition: Int, targetPosition: Int) {}
|
||||||
|
override suspend fun clear() {}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.UnitsDao
|
||||||
|
import com.sadellie.unitto.data.database.UnitsEntity
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
val units = listOf(
|
||||||
|
UnitsEntity(
|
||||||
|
unitId = "UnitId1",
|
||||||
|
isFavorite = false,
|
||||||
|
pairedUnitId = "Pair",
|
||||||
|
frequency = 9
|
||||||
|
),
|
||||||
|
UnitsEntity(
|
||||||
|
unitId = "UnitId2",
|
||||||
|
isFavorite = false,
|
||||||
|
pairedUnitId = "Pair",
|
||||||
|
frequency = 9
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
object FakeUnitsDao : UnitsDao {
|
||||||
|
override fun getAllFlow(): Flow<List<UnitsEntity>> {
|
||||||
|
return flow {
|
||||||
|
emit(units)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUnit(unit: UnitsEntity) {}
|
||||||
|
override suspend fun getById(unitId: String): UnitsEntity? = null
|
||||||
|
override suspend fun clear() {}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.ALL_UNIT_GROUPS
|
||||||
|
import com.sadellie.unitto.data.model.UnitsListSorting
|
||||||
|
|
||||||
|
object FakeUsrPreferenceValues {
|
||||||
|
const val themingMode = "ThemingMode"
|
||||||
|
const val enableDynamicTheme = false
|
||||||
|
const val enableAmoledTheme = false
|
||||||
|
const val customColor = 777L
|
||||||
|
const val monetMode = "MonetMode"
|
||||||
|
const val startingScreen = "StartingScreen"
|
||||||
|
const val enableToolsExperiment = false
|
||||||
|
const val enableVibrations = false
|
||||||
|
const val middleZero = false
|
||||||
|
const val acButton = false
|
||||||
|
const val rpnMode = false
|
||||||
|
const val precision = 69
|
||||||
|
const val separator = 1
|
||||||
|
const val outputFormat = 1
|
||||||
|
const val radianMode = false
|
||||||
|
const val partialHistoryView = false
|
||||||
|
const val clearInputAfterEquals = false
|
||||||
|
const val latestLeftSide = "LeftSideUnit"
|
||||||
|
const val latestRightSide = "RightSideUnit"
|
||||||
|
val shownUnitGroups = ALL_UNIT_GROUPS
|
||||||
|
const val unitConverterFavoritesOnly = false
|
||||||
|
const val unitConverterFormatTime = false
|
||||||
|
val unitConverterSorting = UnitsListSorting.USAGE
|
||||||
|
}
|
32
data/backup/src/main/AndroidManifest.xml
Normal file
32
data/backup/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application>
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="com.sadellie.unitto.BackupManager"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_provider_path"/>
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import com.sadellie.unitto.data.database.TimeZoneDao
|
||||||
|
import com.sadellie.unitto.data.database.UnitsDao
|
||||||
|
import com.sadellie.unitto.data.userprefs.PrefsKeys
|
||||||
|
import com.sadellie.unitto.data.userprefs.getAcButton
|
||||||
|
import com.sadellie.unitto.data.userprefs.getClearInputAfterEquals
|
||||||
|
import com.sadellie.unitto.data.userprefs.getCustomColor
|
||||||
|
import com.sadellie.unitto.data.userprefs.getDigitsPrecision
|
||||||
|
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.getLatestLeftSide
|
||||||
|
import com.sadellie.unitto.data.userprefs.getLatestRightSide
|
||||||
|
import com.sadellie.unitto.data.userprefs.getMiddleZero
|
||||||
|
import com.sadellie.unitto.data.userprefs.getMonetMode
|
||||||
|
import com.sadellie.unitto.data.userprefs.getOutputFormat
|
||||||
|
import com.sadellie.unitto.data.userprefs.getPartialHistoryView
|
||||||
|
import com.sadellie.unitto.data.userprefs.getRadianMode
|
||||||
|
import com.sadellie.unitto.data.userprefs.getRpnMode
|
||||||
|
import com.sadellie.unitto.data.userprefs.getSeparator
|
||||||
|
import com.sadellie.unitto.data.userprefs.getShownUnitGroups
|
||||||
|
import com.sadellie.unitto.data.userprefs.getStartingScreen
|
||||||
|
import com.sadellie.unitto.data.userprefs.getThemingMode
|
||||||
|
import com.sadellie.unitto.data.userprefs.getUnitConverterFavoritesOnly
|
||||||
|
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.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BackupManager @Inject constructor(
|
||||||
|
@ApplicationContext private val mContext: Context,
|
||||||
|
private val dataStore: DataStore<Preferences>,
|
||||||
|
private val unitsDao: UnitsDao,
|
||||||
|
private val timeZoneDao: TimeZoneDao,
|
||||||
|
// Not planned at the moment
|
||||||
|
// private val calculatorHistoryDao: CalculatorHistoryDao,
|
||||||
|
) {
|
||||||
|
private val moshi: Moshi = Moshi.Builder()
|
||||||
|
.addLast(KotlinJsonAdapterFactory())
|
||||||
|
.build()
|
||||||
|
private val jsonAdapter: JsonAdapter<UserData> = moshi.adapter(UserData::class.java)
|
||||||
|
private val auth = "com.sadellie.unitto.BackupManager"
|
||||||
|
|
||||||
|
suspend fun backup(): Uri = withContext(Dispatchers.IO) {
|
||||||
|
val userData = userDataFromApp()
|
||||||
|
// save to disk
|
||||||
|
val tempFile = File.createTempFile("backup", ".unitto", mContext.cacheDir)
|
||||||
|
tempFile.writeText(jsonAdapter.toJson(userData))
|
||||||
|
|
||||||
|
return@withContext FileProvider.getUriForFile(mContext, auth, tempFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun restore(uri: Uri) = withContext(Dispatchers.IO) {
|
||||||
|
val jsonContent = StringBuilder()
|
||||||
|
mContext.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||||
|
var line: String? = reader.readLine()
|
||||||
|
while (line != null) {
|
||||||
|
jsonContent.append(line)
|
||||||
|
line = reader.readLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error
|
||||||
|
val userData: UserData = jsonAdapter.fromJson(jsonContent.toString()) ?: return@withContext IllegalArgumentException("Can't parse: $jsonContent")
|
||||||
|
|
||||||
|
// Apply tables
|
||||||
|
updateUnitsTable(userData)
|
||||||
|
updateTimeZonesTable(userData)
|
||||||
|
|
||||||
|
// Apply datastore prefs
|
||||||
|
// Datastore settings are restored at the end, because it will trigger recomposition of the
|
||||||
|
// entire app composable and all jobs in ViewModels will be canceled
|
||||||
|
updateDatastore(userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal suspend fun userDataFromApp(): UserData {
|
||||||
|
val data = dataStore.data.first()
|
||||||
|
val unitsTableData = unitsDao.getAllFlow().first()
|
||||||
|
val timeZoneTableData = timeZoneDao.getFavorites().first()
|
||||||
|
|
||||||
|
return UserData(
|
||||||
|
themingMode = data.getThemingMode(),
|
||||||
|
enableDynamicTheme = data.getEnableDynamicTheme(),
|
||||||
|
enableAmoledTheme = data.getEnableAmoledTheme(),
|
||||||
|
customColor = data.getCustomColor(),
|
||||||
|
monetMode = data.getMonetMode(),
|
||||||
|
startingScreen = data.getStartingScreen(),
|
||||||
|
enableToolsExperiment = data.getEnableToolsExperiment(),
|
||||||
|
enableVibrations = data.getEnableVibrations(),
|
||||||
|
middleZero = data.getMiddleZero(),
|
||||||
|
acButton = data.getAcButton(),
|
||||||
|
rpnMode = data.getRpnMode(),
|
||||||
|
precision = data.getDigitsPrecision(),
|
||||||
|
separator = data.getSeparator(),
|
||||||
|
outputFormat = data.getOutputFormat(),
|
||||||
|
radianMode = data.getRadianMode(),
|
||||||
|
partialHistoryView = data.getPartialHistoryView(),
|
||||||
|
clearInputAfterEquals = data.getClearInputAfterEquals(),
|
||||||
|
latestLeftSide = data.getLatestLeftSide(),
|
||||||
|
latestRightSide = data.getLatestRightSide(),
|
||||||
|
shownUnitGroups = data.getShownUnitGroups(),
|
||||||
|
unitConverterFavoritesOnly = data.getUnitConverterFavoritesOnly(),
|
||||||
|
unitConverterFormatTime = data.getUnitConverterFormatTime(),
|
||||||
|
unitConverterSorting = data.getUnitConverterSorting(),
|
||||||
|
unitsTable = unitsTableData,
|
||||||
|
timeZoneTable = timeZoneTableData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal suspend fun updateDatastore(userData: UserData) {
|
||||||
|
dataStore.edit { it.clear() }
|
||||||
|
dataStore.edit {
|
||||||
|
it[PrefsKeys.THEMING_MODE] = userData.themingMode
|
||||||
|
it[PrefsKeys.ENABLE_DYNAMIC_THEME] = userData.enableDynamicTheme
|
||||||
|
it[PrefsKeys.ENABLE_AMOLED_THEME] = userData.enableAmoledTheme
|
||||||
|
it[PrefsKeys.CUSTOM_COLOR] = userData.customColor
|
||||||
|
it[PrefsKeys.MONET_MODE] = userData.monetMode
|
||||||
|
it[PrefsKeys.STARTING_SCREEN] = userData.startingScreen
|
||||||
|
it[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] = userData.enableToolsExperiment
|
||||||
|
it[PrefsKeys.ENABLE_VIBRATIONS] = userData.enableVibrations
|
||||||
|
it[PrefsKeys.MIDDLE_ZERO] = userData.middleZero
|
||||||
|
it[PrefsKeys.AC_BUTTON] = userData.acButton
|
||||||
|
it[PrefsKeys.RPN_MODE] = userData.rpnMode
|
||||||
|
|
||||||
|
// FORMATTER
|
||||||
|
it[PrefsKeys.DIGITS_PRECISION] = userData.precision
|
||||||
|
it[PrefsKeys.SEPARATOR] = userData.separator
|
||||||
|
it[PrefsKeys.OUTPUT_FORMAT] = userData.outputFormat
|
||||||
|
|
||||||
|
// CALCULATOR
|
||||||
|
it[PrefsKeys.RADIAN_MODE] = userData.radianMode
|
||||||
|
it[PrefsKeys.PARTIAL_HISTORY_VIEW] = userData.partialHistoryView
|
||||||
|
it[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] = userData.clearInputAfterEquals
|
||||||
|
|
||||||
|
// UNIT CONVERTER
|
||||||
|
it[PrefsKeys.LATEST_LEFT_SIDE] = userData.latestLeftSide
|
||||||
|
it[PrefsKeys.LATEST_RIGHT_SIDE] = userData.latestRightSide
|
||||||
|
it[PrefsKeys.SHOWN_UNIT_GROUPS] = userData.shownUnitGroups.joinToString(",")
|
||||||
|
it[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] = userData.unitConverterFavoritesOnly
|
||||||
|
it[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] = userData.unitConverterFormatTime
|
||||||
|
it[PrefsKeys.UNIT_CONVERTER_SORTING] = userData.unitConverterSorting.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal suspend fun updateUnitsTable(userData: UserData) {
|
||||||
|
unitsDao.clear()
|
||||||
|
userData.unitsTable.forEach { unitsDao.insertUnit(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal suspend fun updateTimeZonesTable(userData: UserData) {
|
||||||
|
timeZoneDao.clear()
|
||||||
|
userData.timeZoneTable.forEach { timeZoneDao.insert(it) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.sadellie.unitto.data.model.UnitGroup
|
||||||
|
import com.sadellie.unitto.data.model.UnitsListSorting
|
||||||
|
|
||||||
|
// Don't move to model module. This uses entity classes from database module
|
||||||
|
data class UserData(
|
||||||
|
val themingMode: String,
|
||||||
|
val enableDynamicTheme: Boolean,
|
||||||
|
val enableAmoledTheme: Boolean,
|
||||||
|
val customColor: Long,
|
||||||
|
val monetMode: String,
|
||||||
|
val startingScreen: String,
|
||||||
|
val enableToolsExperiment: Boolean,
|
||||||
|
val enableVibrations: Boolean,
|
||||||
|
val middleZero: Boolean,
|
||||||
|
val acButton: Boolean,
|
||||||
|
val rpnMode: Boolean,
|
||||||
|
|
||||||
|
val precision: Int,
|
||||||
|
val separator: Int,
|
||||||
|
val outputFormat: Int,
|
||||||
|
|
||||||
|
val radianMode: Boolean,
|
||||||
|
val partialHistoryView: Boolean,
|
||||||
|
val clearInputAfterEquals: Boolean,
|
||||||
|
|
||||||
|
val latestLeftSide: String,
|
||||||
|
val latestRightSide: String,
|
||||||
|
val shownUnitGroups: List<UnitGroup>,
|
||||||
|
val unitConverterFavoritesOnly: Boolean,
|
||||||
|
val unitConverterFormatTime: Boolean,
|
||||||
|
val unitConverterSorting: UnitsListSorting,
|
||||||
|
|
||||||
|
val unitsTable: List<UnitsEntity>,
|
||||||
|
val timeZoneTable: List<TimeZoneEntity>,
|
||||||
|
)
|
21
data/backup/src/main/res/xml/file_provider_path.xml
Normal file
21
data/backup/src/main/res/xml/file_provider_path.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<paths>
|
||||||
|
<cache-path name="cache" path="."/>
|
||||||
|
</paths>
|
@ -74,4 +74,7 @@ interface TimeZoneDao {
|
|||||||
}
|
}
|
||||||
updateDragged(id, 0, targetPosition)
|
updateDragged(id, 0, targetPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM time_zones")
|
||||||
|
suspend fun clear()
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,7 @@ interface UnitsDao {
|
|||||||
|
|
||||||
@Query("SELECT * FROM units WHERE unitId == :unitId LIMIT 1")
|
@Query("SELECT * FROM units WHERE unitId == :unitId LIMIT 1")
|
||||||
suspend fun getById(unitId: String): UnitsEntity?
|
suspend fun getById(unitId: String): UnitsEntity?
|
||||||
|
|
||||||
|
@Query("DELETE FROM units")
|
||||||
|
suspend fun clear()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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.userprefs
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import com.sadellie.unitto.core.base.OutputFormat
|
||||||
|
import com.sadellie.unitto.core.base.Separator
|
||||||
|
import com.sadellie.unitto.core.base.TopLevelDestinations
|
||||||
|
import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS
|
||||||
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
|
import com.sadellie.unitto.data.model.UnitsListSorting
|
||||||
|
import com.sadellie.unitto.data.units.MyUnitIDS
|
||||||
|
|
||||||
|
fun Preferences.getEnableDynamicTheme(): Boolean {
|
||||||
|
return this[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getThemingMode(): String {
|
||||||
|
return this[PrefsKeys.THEMING_MODE] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getEnableAmoledTheme(): Boolean {
|
||||||
|
return this[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getCustomColor(): Long {
|
||||||
|
return this[PrefsKeys.CUSTOM_COLOR] ?: 16L // From Color.Unspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getMonetMode(): String {
|
||||||
|
return this[PrefsKeys.MONET_MODE] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getStartingScreen(): String {
|
||||||
|
return this[PrefsKeys.STARTING_SCREEN] ?: TopLevelDestinations.Calculator.graph
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getEnableToolsExperiment(): Boolean {
|
||||||
|
return this[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getEnableVibrations(): Boolean {
|
||||||
|
return this[PrefsKeys.ENABLE_VIBRATIONS] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getRadianMode(): Boolean {
|
||||||
|
return this[PrefsKeys.RADIAN_MODE] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getSeparator(): Int {
|
||||||
|
return this[PrefsKeys.SEPARATOR] ?: Separator.SPACE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getMiddleZero(): Boolean {
|
||||||
|
return this[PrefsKeys.MIDDLE_ZERO] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getPartialHistoryView(): Boolean {
|
||||||
|
return this[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getDigitsPrecision(): Int {
|
||||||
|
return this[PrefsKeys.DIGITS_PRECISION] ?: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getOutputFormat(): Int {
|
||||||
|
return this[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getUnitConverterFormatTime(): Boolean {
|
||||||
|
return this[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getUnitConverterSorting(): UnitsListSorting {
|
||||||
|
return this[PrefsKeys.UNIT_CONVERTER_SORTING]
|
||||||
|
?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getShownUnitGroups(): List<UnitGroup> {
|
||||||
|
return this[PrefsKeys.SHOWN_UNIT_GROUPS]
|
||||||
|
?.letTryOrNull { list ->
|
||||||
|
list
|
||||||
|
.ifEmpty { return@letTryOrNull listOf() }
|
||||||
|
.split(",")
|
||||||
|
.map { UnitGroup.valueOf(it) }
|
||||||
|
}
|
||||||
|
?: ALL_UNIT_GROUPS
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getUnitConverterFavoritesOnly(): Boolean {
|
||||||
|
return this[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY]
|
||||||
|
?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getLatestLeftSide(): String {
|
||||||
|
return this[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getLatestRightSide(): String {
|
||||||
|
return this[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getAcButton(): Boolean {
|
||||||
|
return this[PrefsKeys.AC_BUTTON] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getClearInputAfterEquals(): Boolean {
|
||||||
|
return this[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Preferences.getRpnMode(): Boolean {
|
||||||
|
return this[PrefsKeys.RPN_MODE] ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
|
||||||
|
this?.let(block)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
@ -23,7 +23,7 @@ import androidx.datastore.preferences.core.intPreferencesKey
|
|||||||
import androidx.datastore.preferences.core.longPreferencesKey
|
import androidx.datastore.preferences.core.longPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
|
||||||
internal object PrefsKeys {
|
object PrefsKeys {
|
||||||
// COMMON
|
// COMMON
|
||||||
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
|
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
|
||||||
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
|
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
|
||||||
|
@ -22,10 +22,6 @@ import androidx.datastore.core.DataStore
|
|||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.emptyPreferences
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
import com.sadellie.unitto.core.base.OutputFormat
|
|
||||||
import com.sadellie.unitto.core.base.Separator
|
|
||||||
import com.sadellie.unitto.core.base.TopLevelDestinations
|
|
||||||
import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS
|
|
||||||
import com.sadellie.unitto.data.model.UnitGroup
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
import com.sadellie.unitto.data.model.UnitsListSorting
|
import com.sadellie.unitto.data.model.UnitsListSorting
|
||||||
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||||
@ -40,7 +36,6 @@ import com.sadellie.unitto.data.model.userprefs.FormattingPreferences
|
|||||||
import com.sadellie.unitto.data.model.userprefs.GeneralPreferences
|
import com.sadellie.unitto.data.model.userprefs.GeneralPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.StartingScreenPreferences
|
import com.sadellie.unitto.data.model.userprefs.StartingScreenPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.UnitGroupsPreferences
|
import com.sadellie.unitto.data.model.userprefs.UnitGroupsPreferences
|
||||||
import com.sadellie.unitto.data.units.MyUnitIDS
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@ -287,107 +282,3 @@ class UserPreferencesRepositoryImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Preferences.getEnableDynamicTheme(): Boolean {
|
|
||||||
return this[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getThemingMode(): String {
|
|
||||||
return this[PrefsKeys.THEMING_MODE] ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getEnableAmoledTheme(): Boolean {
|
|
||||||
return this[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getCustomColor(): Long {
|
|
||||||
return this[PrefsKeys.CUSTOM_COLOR] ?: 16L // From Color.Unspecified
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getMonetMode(): String {
|
|
||||||
return this[PrefsKeys.MONET_MODE] ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getStartingScreen(): String {
|
|
||||||
return this[PrefsKeys.STARTING_SCREEN]
|
|
||||||
?: TopLevelDestinations.Calculator.graph
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getEnableToolsExperiment(): Boolean {
|
|
||||||
return this[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getEnableVibrations(): Boolean {
|
|
||||||
return this[PrefsKeys.ENABLE_VIBRATIONS] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getRadianMode(): Boolean {
|
|
||||||
return this[PrefsKeys.RADIAN_MODE] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getSeparator(): Int {
|
|
||||||
return this[PrefsKeys.SEPARATOR] ?: Separator.SPACE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getMiddleZero(): Boolean {
|
|
||||||
return this[PrefsKeys.MIDDLE_ZERO] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getPartialHistoryView(): Boolean {
|
|
||||||
return this[PrefsKeys.PARTIAL_HISTORY_VIEW] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getDigitsPrecision(): Int {
|
|
||||||
return this[PrefsKeys.DIGITS_PRECISION] ?: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getOutputFormat(): Int {
|
|
||||||
return this[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getUnitConverterFormatTime(): Boolean {
|
|
||||||
return this[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getUnitConverterSorting(): UnitsListSorting {
|
|
||||||
return this[PrefsKeys.UNIT_CONVERTER_SORTING]
|
|
||||||
?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getShownUnitGroups(): List<UnitGroup> {
|
|
||||||
return this[PrefsKeys.SHOWN_UNIT_GROUPS]?.letTryOrNull { list ->
|
|
||||||
list.ifEmpty { return@letTryOrNull listOf() }.split(",")
|
|
||||||
.map { UnitGroup.valueOf(it) }
|
|
||||||
} ?: ALL_UNIT_GROUPS
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getUnitConverterFavoritesOnly(): Boolean {
|
|
||||||
return this[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY]
|
|
||||||
?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getLatestLeftSide(): String {
|
|
||||||
return this[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getLatestRightSide(): String {
|
|
||||||
return this[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getAcButton(): Boolean {
|
|
||||||
return this[PrefsKeys.AC_BUTTON] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getClearInputAfterEquals(): Boolean {
|
|
||||||
return this[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preferences.getRpnMode(): Boolean {
|
|
||||||
return this[PrefsKeys.RPN_MODE] ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
|
|
||||||
this?.let(block)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
@ -33,6 +33,7 @@ dependencies {
|
|||||||
implementation(libs.org.burnoutcrew.composereorderable.reorderable)
|
implementation(libs.org.burnoutcrew.composereorderable.reorderable)
|
||||||
implementation(libs.androidx.appcompat.appcompat)
|
implementation(libs.androidx.appcompat.appcompat)
|
||||||
|
|
||||||
|
implementation(project(":data:backup"))
|
||||||
implementation(project(":data:common"))
|
implementation(project(":data:common"))
|
||||||
implementation(project(":data:database"))
|
implementation(project(":data:database"))
|
||||||
implementation(project(":data:model"))
|
implementation(project(":data:model"))
|
||||||
|
@ -18,12 +18,21 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.feature.settings
|
package com.sadellie.unitto.feature.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
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.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -34,16 +43,27 @@ import androidx.compose.material.icons.filled.Cached
|
|||||||
import androidx.compose.material.icons.filled.Calculate
|
import androidx.compose.material.icons.filled.Calculate
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Palette
|
import androidx.compose.material.icons.filled.Palette
|
||||||
import androidx.compose.material.icons.filled.RateReview
|
import androidx.compose.material.icons.filled.RateReview
|
||||||
import androidx.compose.material.icons.filled.SwapHoriz
|
import androidx.compose.material.icons.filled.SwapHoriz
|
||||||
import androidx.compose.material.icons.filled.Vibration
|
import androidx.compose.material.icons.filled.Vibration
|
||||||
import androidx.compose.material.icons.filled._123
|
import androidx.compose.material.icons.filled._123
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -59,8 +79,6 @@ import com.sadellie.unitto.core.ui.common.UnittoListItem
|
|||||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
|
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
|
||||||
import com.sadellie.unitto.core.ui.openLink
|
import com.sadellie.unitto.core.ui.openLink
|
||||||
import com.sadellie.unitto.core.ui.showToast
|
import com.sadellie.unitto.core.ui.showToast
|
||||||
import com.sadellie.unitto.data.model.userprefs.GeneralPreferences
|
|
||||||
import com.sadellie.unitto.data.userprefs.GeneralPreferencesImpl
|
|
||||||
import com.sadellie.unitto.feature.settings.navigation.aboutRoute
|
import com.sadellie.unitto.feature.settings.navigation.aboutRoute
|
||||||
import com.sadellie.unitto.feature.settings.navigation.calculatorSettingsRoute
|
import com.sadellie.unitto.feature.settings.navigation.calculatorSettingsRoute
|
||||||
import com.sadellie.unitto.feature.settings.navigation.converterSettingsRoute
|
import com.sadellie.unitto.feature.settings.navigation.converterSettingsRoute
|
||||||
@ -74,38 +92,99 @@ internal fun SettingsRoute(
|
|||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
navControllerAction: (String) -> Unit,
|
navControllerAction: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle().value
|
val mContext = LocalContext.current
|
||||||
val cachePercentage = viewModel.cachePercentage.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
when (userPrefs) {
|
val errorLabel = stringResource(R.string.error_label)
|
||||||
null -> UnittoEmptyScreen()
|
|
||||||
else -> {
|
val uiState: SettingsUIState = viewModel.uiState.collectAsStateWithLifecycle().value
|
||||||
SettingsScreen(
|
val backupFileUri: Uri? = viewModel.backupFileUri.collectAsStateWithLifecycle(initialValue = null).value
|
||||||
userPrefs = userPrefs,
|
val showErrorToast: Boolean = viewModel.showErrorToast.collectAsStateWithLifecycle(initialValue = false).value
|
||||||
|
|
||||||
|
// Share backup file when it's emitted
|
||||||
|
LaunchedEffect(backupFileUri) {
|
||||||
|
if (backupFileUri == null) return@LaunchedEffect
|
||||||
|
mContext.share(backupFileUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(showErrorToast) {
|
||||||
|
if (showErrorToast) Toast.makeText(mContext, errorLabel, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
Crossfade(targetState = uiState) { state ->
|
||||||
|
when (state) {
|
||||||
|
SettingsUIState.Loading -> UnittoEmptyScreen()
|
||||||
|
|
||||||
|
SettingsUIState.BackupInProgress -> BackingUpScreen()
|
||||||
|
|
||||||
|
is SettingsUIState.Ready -> SettingsScreen(
|
||||||
|
uiState = state,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
navControllerAction = navControllerAction,
|
navControllerAction = navControllerAction,
|
||||||
updateVibrations = viewModel::updateVibrations,
|
updateVibrations = viewModel::updateVibrations,
|
||||||
cachePercentage = cachePercentage.value,
|
|
||||||
clearCache = viewModel::clearCache,
|
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
|
@Composable
|
||||||
private fun SettingsScreen(
|
private fun SettingsScreen(
|
||||||
userPrefs: GeneralPreferences,
|
uiState: SettingsUIState.Ready,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
navControllerAction: (String) -> Unit,
|
navControllerAction: (String) -> Unit,
|
||||||
updateVibrations: (Boolean) -> Unit,
|
updateVibrations: (Boolean) -> Unit,
|
||||||
cachePercentage: Float,
|
|
||||||
clearCache: () -> Unit,
|
clearCache: () -> Unit,
|
||||||
|
backup: () -> Unit,
|
||||||
|
restore: (Uri) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val mContext = LocalContext.current
|
val mContext = LocalContext.current
|
||||||
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Pass picked file uri to BackupManager
|
||||||
|
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { pickedUri ->
|
||||||
|
if (pickedUri != null) restore(pickedUri)
|
||||||
|
}
|
||||||
|
|
||||||
UnittoScreenWithLargeTopBar(
|
UnittoScreenWithLargeTopBar(
|
||||||
title = stringResource(R.string.settings_title),
|
title = stringResource(R.string.settings_title),
|
||||||
navigationIcon = { NavigateUpButton(navigateUp) }
|
navigationIcon = { NavigateUpButton(navigateUp) },
|
||||||
|
actions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = { showMenu = !showMenu },
|
||||||
|
content = { Icon(Icons.Default.MoreVert, null) }
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMenu,
|
||||||
|
onDismissRequest = { showMenu = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = backup,
|
||||||
|
text = { Text("Backup") }
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = { launcher.launch(arrayOf(backupMimeType)) },
|
||||||
|
text = { Text("Restore") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -155,12 +234,12 @@ private fun SettingsScreen(
|
|||||||
headlineText = stringResource(R.string.settings_vibrations),
|
headlineText = stringResource(R.string.settings_vibrations),
|
||||||
supportingText = stringResource(R.string.settings_vibrations_support),
|
supportingText = stringResource(R.string.settings_vibrations_support),
|
||||||
modifier = Modifier.clickable { navControllerAction(converterSettingsRoute) },
|
modifier = Modifier.clickable { navControllerAction(converterSettingsRoute) },
|
||||||
switchState = userPrefs.enableVibrations,
|
switchState = uiState.enableVibrations,
|
||||||
onSwitchChange = updateVibrations
|
onSwitchChange = updateVibrations
|
||||||
)
|
)
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = cachePercentage > 0,
|
visible = uiState.cacheSize > 0,
|
||||||
enter = expandVertically() + fadeIn(),
|
enter = expandVertically() + fadeIn(),
|
||||||
exit = shrinkVertically() + fadeOut(),
|
exit = shrinkVertically() + fadeOut(),
|
||||||
) {
|
) {
|
||||||
@ -189,19 +268,38 @@ private fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(shareIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val backupMimeType = "application/octet-stream"
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewSettingsScreen() {
|
private fun PreviewSettingsScreen() {
|
||||||
var cacheSize by remember { mutableFloatStateOf(0.9f) }
|
var cacheSize by remember { mutableFloatStateOf(0.9f) }
|
||||||
|
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
userPrefs = GeneralPreferencesImpl(
|
uiState = SettingsUIState.Ready(
|
||||||
enableVibrations = true
|
enableVibrations = false,
|
||||||
|
cacheSize = 2,
|
||||||
),
|
),
|
||||||
navigateUp = {},
|
navigateUp = {},
|
||||||
navControllerAction = {},
|
navControllerAction = {},
|
||||||
updateVibrations = {},
|
updateVibrations = {},
|
||||||
cachePercentage = cacheSize,
|
clearCache = { cacheSize = 0f },
|
||||||
clearCache = { cacheSize = 0f }
|
backup = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PreviewBackingUpScreen() {
|
||||||
|
BackingUpScreen()
|
||||||
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.feature.settings
|
||||||
|
|
||||||
|
internal sealed class SettingsUIState {
|
||||||
|
data object Loading : SettingsUIState()
|
||||||
|
|
||||||
|
data object BackupInProgress : SettingsUIState()
|
||||||
|
|
||||||
|
data class Ready(
|
||||||
|
val enableVibrations: Boolean,
|
||||||
|
val cacheSize: Int,
|
||||||
|
) : SettingsUIState()
|
||||||
|
}
|
@ -18,14 +18,22 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.feature.settings
|
package com.sadellie.unitto.feature.settings
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.sadellie.unitto.data.backup.BackupManager
|
||||||
import com.sadellie.unitto.data.common.stateIn
|
import com.sadellie.unitto.data.common.stateIn
|
||||||
import com.sadellie.unitto.data.database.CurrencyRatesDao
|
import com.sadellie.unitto.data.database.CurrencyRatesDao
|
||||||
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -33,15 +41,61 @@ import javax.inject.Inject
|
|||||||
internal class SettingsViewModel @Inject constructor(
|
internal class SettingsViewModel @Inject constructor(
|
||||||
private val userPrefsRepository: UserPreferencesRepository,
|
private val userPrefsRepository: UserPreferencesRepository,
|
||||||
private val currencyRatesDao: CurrencyRatesDao,
|
private val currencyRatesDao: CurrencyRatesDao,
|
||||||
|
private val backupManager: BackupManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val userPrefs = userPrefsRepository.generalPrefs
|
private val _backupFileUri = MutableSharedFlow<Uri?>()
|
||||||
.stateIn(viewModelScope, null)
|
val backupFileUri = _backupFileUri.asSharedFlow()
|
||||||
|
|
||||||
val cachePercentage = currencyRatesDao.size()
|
private val _showErrorToast = MutableSharedFlow<Boolean>()
|
||||||
.map {
|
val showErrorToast = _showErrorToast.asSharedFlow()
|
||||||
(it / 100_000f).coerceIn(0f, 1f)
|
|
||||||
|
private val _operation = MutableStateFlow(false)
|
||||||
|
private var backupJob: Job? = null
|
||||||
|
|
||||||
|
val uiState = combine(
|
||||||
|
userPrefsRepository.generalPrefs,
|
||||||
|
currencyRatesDao.size(),
|
||||||
|
_operation,
|
||||||
|
) { prefs, cacheSize, operation ->
|
||||||
|
if (operation) return@combine SettingsUIState.BackupInProgress
|
||||||
|
|
||||||
|
SettingsUIState.Ready(
|
||||||
|
enableVibrations = prefs.enableVibrations,
|
||||||
|
cacheSize = cacheSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, SettingsUIState.Loading)
|
||||||
|
|
||||||
|
fun backup() {
|
||||||
|
backupJob?.cancel()
|
||||||
|
backupJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_operation.update { true }
|
||||||
|
try {
|
||||||
|
val backupFileUri = backupManager.backup()
|
||||||
|
_backupFileUri.emit(backupFileUri) // Emit to trigger file share intent
|
||||||
|
_showErrorToast.emit(false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_showErrorToast.emit(true)
|
||||||
|
Log.e(TAG, "$e")
|
||||||
|
}
|
||||||
|
_operation.update { false }
|
||||||
}
|
}
|
||||||
.stateIn(viewModelScope, 0f)
|
}
|
||||||
|
|
||||||
|
fun restore(uri: Uri) {
|
||||||
|
backupJob?.cancel()
|
||||||
|
backupJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_operation.update { true }
|
||||||
|
try {
|
||||||
|
backupManager.restore(uri)
|
||||||
|
_showErrorToast.emit(false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_showErrorToast.emit(true)
|
||||||
|
Log.e(TAG, "$e")
|
||||||
|
}
|
||||||
|
_operation.update { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see UserPreferencesRepository.updateVibrations
|
* @see UserPreferencesRepository.updateVibrations
|
||||||
@ -54,3 +108,5 @@ internal class SettingsViewModel @Inject constructor(
|
|||||||
currencyRatesDao.clear()
|
currencyRatesDao.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val TAG = "SettingsViewModel"
|
@ -34,3 +34,4 @@ include(":data:model")
|
|||||||
include(":data:common")
|
include(":data:common")
|
||||||
include(":data:evaluatto")
|
include(":data:evaluatto")
|
||||||
include(":data:timezone")
|
include(":data:timezone")
|
||||||
|
include(":data:backup")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user