mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
Rewrite BackupManager
This commit is contained in:
parent
d9454d8d01
commit
8d46084b51
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2023 Elshan Agaev
|
* Copyright (c) 2023-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -17,19 +17,25 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.google.devtools.ksp")
|
|
||||||
id("unitto.library")
|
id("unitto.library")
|
||||||
id("unitto.android.library.jacoco")
|
|
||||||
id("unitto.android.hilt")
|
id("unitto.android.hilt")
|
||||||
|
id("unitto.room")
|
||||||
|
id("unitto.android.library.jacoco")
|
||||||
}
|
}
|
||||||
|
|
||||||
android.namespace = "com.sadellie.unitto.data.backup"
|
android.namespace = "com.sadellie.unitto.data.backup"
|
||||||
|
|
||||||
|
android {
|
||||||
|
room {
|
||||||
|
val schemaLocation = "$projectDir/schemas"
|
||||||
|
schemaDirectory(schemaLocation)
|
||||||
|
println("Exported Database schema to $schemaLocation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.datastore.datastore.preferences)
|
implementation(libs.androidx.datastore.datastore.preferences)
|
||||||
implementation(libs.com.squareup.moshi.moshi.kotlin)
|
|
||||||
implementation(libs.com.github.sadellie.themmo.core)
|
implementation(libs.com.github.sadellie.themmo.core)
|
||||||
ksp(libs.com.squareup.moshi.moshi.kotlin.codegen)
|
|
||||||
|
|
||||||
implementation(project(":data:database"))
|
implementation(project(":data:database"))
|
||||||
implementation(project(":data:model"))
|
implementation(project(":data:model"))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2023 Elshan Agaev
|
* Copyright (c) 2023-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,22 +18,8 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.data.backup
|
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.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import com.sadellie.unitto.data.database.CalculatorHistoryEntity
|
|
||||||
import com.sadellie.unitto.data.database.TimeZoneEntity
|
|
||||||
import com.sadellie.unitto.data.database.UnitsEntity
|
|
||||||
import com.sadellie.unitto.data.userprefs.PrefsKeys
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,130 +29,5 @@ import org.junit.runner.RunWith
|
|||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class BackupManagerTest {
|
class BackupManagerTest {
|
||||||
|
// TODO Write tests. Currently testing manually.
|
||||||
private lateinit var dataStore: DataStore<Preferences>
|
|
||||||
private lateinit var backupManager: BackupManager
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
// Inserting dummy data as app data (db and prefs)
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
|
|
||||||
dataStore = PreferenceDataStoreFactory.create(
|
|
||||||
corruptionHandler = null,
|
|
||||||
produceFile = { appContext.preferencesDataStoreFile("test") }
|
|
||||||
)
|
|
||||||
|
|
||||||
backupManager = BackupManager(
|
|
||||||
mContext = appContext,
|
|
||||||
dataStore = dataStore,
|
|
||||||
unitsDao = FakeUnitsDao,
|
|
||||||
timeZoneDao = FakeTimeZoneDao,
|
|
||||||
calculatorHistoryDao = FakeCalculatorHistoryDao
|
|
||||||
)
|
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
dataStore.edit {
|
|
||||||
it[PrefsKeys.THEMING_MODE] = fakeUserData.themingMode
|
|
||||||
it[PrefsKeys.ENABLE_DYNAMIC_THEME] = fakeUserData.enableDynamicTheme
|
|
||||||
it[PrefsKeys.ENABLE_AMOLED_THEME] = fakeUserData.enableAmoledTheme
|
|
||||||
it[PrefsKeys.CUSTOM_COLOR] = fakeUserData.customColor
|
|
||||||
it[PrefsKeys.MONET_MODE] = fakeUserData.monetMode
|
|
||||||
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
|
|
||||||
it[PrefsKeys.RPN_MODE] = fakeUserData.rpnMode
|
|
||||||
it[PrefsKeys.DIGITS_PRECISION] = fakeUserData.precision
|
|
||||||
it[PrefsKeys.SEPARATOR] = fakeUserData.separator
|
|
||||||
it[PrefsKeys.OUTPUT_FORMAT] = fakeUserData.outputFormat
|
|
||||||
it[PrefsKeys.RADIAN_MODE] = fakeUserData.radianMode
|
|
||||||
it[PrefsKeys.PARTIAL_HISTORY_VIEW] = fakeUserData.partialHistoryView
|
|
||||||
it[PrefsKeys.LATEST_LEFT_SIDE] = fakeUserData.latestLeftSide
|
|
||||||
it[PrefsKeys.LATEST_RIGHT_SIDE] = fakeUserData.latestRightSide
|
|
||||||
it[PrefsKeys.SHOWN_UNIT_GROUPS] = fakeUserData.shownUnitGroups
|
|
||||||
it[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] = fakeUserData.unitConverterFavoritesOnly
|
|
||||||
it[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] = fakeUserData.unitConverterFormatTime
|
|
||||||
it[PrefsKeys.UNIT_CONVERTER_SORTING] = fakeUserData.unitConverterSorting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBackup() = runBlocking{
|
|
||||||
// Backup manager also saves the file to disk, but it is not tested here.
|
|
||||||
// There is probably no need to test if the data in app is valid since moshi will throw an
|
|
||||||
// exceptions if the data in app is invalid. For example, if unitConverterSorting is set to
|
|
||||||
// "Qwerty" (this sorting doesn't exist) there will be an exception.
|
|
||||||
val actualUserData = backupManager.userDataFromApp()
|
|
||||||
val expectedUserData = fakeUserData
|
|
||||||
|
|
||||||
assertEquals(expectedUserData, actualUserData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRestoreDatastore() = runBlocking{
|
|
||||||
// Backup manager also saves the file to disk, but it is not tested here.
|
|
||||||
// There is probably no need to test if the data in app is valid since moshi will throw an
|
|
||||||
// exceptions if the data in app is invalid. For example, if unitConverterSorting is set to
|
|
||||||
// "Qwerty" (this sorting doesn't exist) there will be an exception.
|
|
||||||
backupManager.updateDatastore(fakeUserData)
|
|
||||||
|
|
||||||
assertEquals(backupManager.userDataFromApp(), fakeUserData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRestoreCalculatorHistoryTable() = runBlocking {
|
|
||||||
// Data for import
|
|
||||||
val fakeUserData2 = fakeUserData.copy(
|
|
||||||
calculatorHistoryTable = listOf(
|
|
||||||
CalculatorHistoryEntity(
|
|
||||||
timestamp = System.currentTimeMillis(),
|
|
||||||
expression = "69+420",
|
|
||||||
result = "444"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
backupManager.updateCalculatorHistoryTable(fakeUserData2)
|
|
||||||
|
|
||||||
assertEquals(FakeCalculatorHistoryDao.getAllDescending().first(), fakeUserData2.calculatorHistoryTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRestoreUnitsTable() = runBlocking {
|
|
||||||
// Data for import
|
|
||||||
val fakeUserData2 = fakeUserData.copy(
|
|
||||||
unitsTable = listOf(
|
|
||||||
UnitsEntity(
|
|
||||||
unitId = "UnitId3",
|
|
||||||
isFavorite = false,
|
|
||||||
pairedUnitId = "Pair4",
|
|
||||||
frequency = 123
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
backupManager.updateUnitsTable(fakeUserData2)
|
|
||||||
|
|
||||||
assertEquals(FakeUnitsDao.getAllFlow().first(), fakeUserData2.unitsTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRestoreTimeZonesTable() = runBlocking {
|
|
||||||
// Data for import
|
|
||||||
val fakeUserData2 = fakeUserData.copy(
|
|
||||||
timeZoneTable = listOf(
|
|
||||||
TimeZoneEntity(
|
|
||||||
id = "id3",
|
|
||||||
position = 123,
|
|
||||||
label = "label456"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
backupManager.updateTimeZonesTable(fakeUserData2)
|
|
||||||
|
|
||||||
assertEquals(FakeTimeZoneDao.getFavorites().first(), fakeUserData2.timeZoneTable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.CalculatorHistoryDao
|
|
||||||
import com.sadellie.unitto.data.database.CalculatorHistoryEntity
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
|
|
||||||
var calculatorHistory: List<CalculatorHistoryEntity> = listOf(
|
|
||||||
CalculatorHistoryEntity(
|
|
||||||
timestamp = System.currentTimeMillis(),
|
|
||||||
expression = "2+2",
|
|
||||||
result = "4"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
object FakeCalculatorHistoryDao: CalculatorHistoryDao {
|
|
||||||
override fun getAllDescending(): Flow<List<CalculatorHistoryEntity>> = flow {
|
|
||||||
emit(calculatorHistory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun insert(vararg historyEntity: CalculatorHistoryEntity) {
|
|
||||||
calculatorHistory += historyEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun clear() {
|
|
||||||
calculatorHistory = emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
var 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>> = flow {
|
|
||||||
emit(timeZones)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMaxPosition(): Int = 0
|
|
||||||
override suspend fun insert(vararg timeZoneEntity: TimeZoneEntity) {
|
|
||||||
timeZones += 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() {
|
|
||||||
timeZones = emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
var 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) {
|
|
||||||
units += unit
|
|
||||||
}
|
|
||||||
override suspend fun getById(unitId: String): UnitsEntity? = null
|
|
||||||
override suspend fun clear() {
|
|
||||||
units = emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
internal val fakeUserData = UserData(
|
|
||||||
themingMode = "AUTO",
|
|
||||||
enableDynamicTheme = false,
|
|
||||||
enableAmoledTheme = false,
|
|
||||||
customColor = 777L,
|
|
||||||
monetMode = "TonalSpot",
|
|
||||||
startingScreen = "calculator_route",
|
|
||||||
enableToolsExperiment = false,
|
|
||||||
systemFont = false,
|
|
||||||
lastReadChangelog = "33",
|
|
||||||
enableVibrations = false,
|
|
||||||
middleZero = false,
|
|
||||||
acButton = false,
|
|
||||||
rpnMode = false,
|
|
||||||
precision = 11,
|
|
||||||
separator = 1,
|
|
||||||
outputFormat = 1,
|
|
||||||
radianMode = false,
|
|
||||||
partialHistoryView = false,
|
|
||||||
latestLeftSide = "kilometer",
|
|
||||||
latestRightSide = "mile",
|
|
||||||
shownUnitGroups = "LENGTH",
|
|
||||||
unitConverterFavoritesOnly = false,
|
|
||||||
unitConverterFormatTime = false,
|
|
||||||
unitConverterSorting = "USAGE",
|
|
||||||
calculatorHistoryTable = calculatorHistory,
|
|
||||||
unitsTable = units,
|
|
||||||
timeZoneTable = timeZones
|
|
||||||
)
|
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
~ Unitto is a unit converter for Android
|
~ Unitto is a unit converter for Android
|
||||||
~ Copyright (c) 2023 Elshan Agaev
|
~ Copyright (c) 2023-2024 Elshan Agaev
|
||||||
~
|
~
|
||||||
~ This program is free software: you can redistribute it and/or modify
|
~ 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
|
~ it under the terms of the GNU General Public License as published by
|
||||||
@ -17,16 +17,5 @@
|
|||||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest>
|
||||||
<application>
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities=".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>
|
</manifest>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2023 Elshan Agaev
|
* Copyright (c) 2023-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,187 +18,137 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.data.backup
|
package com.sadellie.unitto.data.backup
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.content.FileProvider
|
import com.sadellie.unitto.data.database.DATABASE_NAME
|
||||||
import androidx.datastore.core.DataStore
|
import com.sadellie.unitto.data.database.UnittoDatabase
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import com.sadellie.unitto.data.database.checkpoint
|
||||||
import androidx.datastore.preferences.core.edit
|
import com.sadellie.unitto.data.userprefs.USER_PREFERENCES
|
||||||
import com.sadellie.unitto.data.database.CalculatorHistoryDao
|
|
||||||
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.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.getLastReadChangelog
|
|
||||||
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.getSystemFont
|
|
||||||
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.adapter
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.util.zip.ZipEntry
|
||||||
import javax.inject.Inject
|
import java.util.zip.ZipInputStream
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
class BackupManager {
|
||||||
class BackupManager @Inject constructor(
|
suspend fun backup(
|
||||||
@ApplicationContext private val mContext: Context,
|
context: Context,
|
||||||
private val dataStore: DataStore<Preferences>,
|
backupFileUri: Uri,
|
||||||
private val calculatorHistoryDao: CalculatorHistoryDao,
|
database: UnittoDatabase,
|
||||||
private val unitsDao: UnitsDao,
|
) = withContext(Dispatchers.IO) {
|
||||||
private val timeZoneDao: TimeZoneDao,
|
context
|
||||||
) {
|
.applicationContext
|
||||||
private val moshi: Moshi = Moshi.Builder()
|
.contentResolver
|
||||||
.add(UserDataTableAdapter())
|
.openOutputStream(backupFileUri)
|
||||||
.build()
|
?.use { backupFileOutputStream ->
|
||||||
private val jsonAdapter: JsonAdapter<UserData> = moshi.adapter<UserData>()
|
ZipOutputStream(backupFileOutputStream.buffered())
|
||||||
private val auth = "com.sadellie.unitto.BackupManager"
|
.use { zipOutputStream ->
|
||||||
|
// Datastore
|
||||||
|
context
|
||||||
|
.datastoreFile
|
||||||
|
.writeToZip(zipOutputStream, DATASTORE_FILE_NAME)
|
||||||
|
|
||||||
suspend fun backup(): Uri = withContext(Dispatchers.IO) {
|
// Database
|
||||||
val userData = userDataFromApp()
|
database.checkpoint()
|
||||||
// save to disk
|
database
|
||||||
val tempFile = File.createTempFile("backup", ".unitto", mContext.cacheDir)
|
.file
|
||||||
tempFile.writeText(jsonAdapter.toJson(userData))
|
.writeToZip(zipOutputStream, DATABASE_FILE_NAME)
|
||||||
|
}
|
||||||
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
|
|
||||||
updateCalculatorHistoryTable(userData)
|
|
||||||
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 {
|
suspend fun restore(
|
||||||
val data = dataStore.data.first()
|
context: Context,
|
||||||
val calculatorHistoryTable = calculatorHistoryDao.getAllDescending().first()
|
backupFileUri: Uri,
|
||||||
val unitsTableData = unitsDao.getAllFlow().first()
|
database: UnittoDatabase,
|
||||||
val timeZoneTableData = timeZoneDao.getFavorites().first()
|
) = withContext(Dispatchers.IO) {
|
||||||
|
context
|
||||||
|
.applicationContext
|
||||||
|
.contentResolver
|
||||||
|
.openInputStream(backupFileUri)
|
||||||
|
?.use { backupFileInputStream ->
|
||||||
|
ZipInputStream(backupFileInputStream).use { zipInputStream ->
|
||||||
|
var entry = zipInputStream.nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
when (entry.name) {
|
||||||
|
DATASTORE_FILE_NAME -> {
|
||||||
|
context
|
||||||
|
.datastoreFile
|
||||||
|
.writeFromZip(zipInputStream)
|
||||||
|
}
|
||||||
|
|
||||||
return UserData(
|
DATABASE_FILE_NAME -> {
|
||||||
themingMode = data.getThemingMode().name,
|
database.checkpoint()
|
||||||
enableDynamicTheme = data.getEnableDynamicTheme(),
|
database.close()
|
||||||
enableAmoledTheme = data.getEnableAmoledTheme(),
|
database
|
||||||
customColor = data.getCustomColor(),
|
.file
|
||||||
monetMode = data.getMonetMode().name,
|
.writeFromZip(zipInputStream)
|
||||||
startingScreen = data.getStartingScreen(),
|
}
|
||||||
enableToolsExperiment = data.getEnableToolsExperiment(),
|
}
|
||||||
systemFont = data.getSystemFont(),
|
entry = zipInputStream.nextEntry
|
||||||
lastReadChangelog = data.getLastReadChangelog(),
|
}
|
||||||
enableVibrations = data.getEnableVibrations(),
|
}
|
||||||
middleZero = data.getMiddleZero(),
|
} ?: return@withContext // Don't restart activity if the file is not found
|
||||||
acButton = data.getAcButton(),
|
|
||||||
rpnMode = data.getRpnMode(),
|
|
||||||
precision = data.getDigitsPrecision(),
|
|
||||||
separator = data.getSeparator(),
|
|
||||||
outputFormat = data.getOutputFormat(),
|
|
||||||
radianMode = data.getRadianMode(),
|
|
||||||
partialHistoryView = data.getPartialHistoryView(),
|
|
||||||
latestLeftSide = data.getLatestLeftSide(),
|
|
||||||
latestRightSide = data.getLatestRightSide(),
|
|
||||||
shownUnitGroups = data.getShownUnitGroups().joinToString(","),
|
|
||||||
unitConverterFavoritesOnly = data.getUnitConverterFavoritesOnly(),
|
|
||||||
unitConverterFormatTime = data.getUnitConverterFormatTime(),
|
|
||||||
unitConverterSorting = data.getUnitConverterSorting().name,
|
|
||||||
calculatorHistoryTable = calculatorHistoryTable,
|
|
||||||
unitsTable = unitsTableData,
|
|
||||||
timeZoneTable = timeZoneTableData,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun updateDatastore(userData: UserData) {
|
context.restartActivity()
|
||||||
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.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
|
|
||||||
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
|
|
||||||
|
|
||||||
// UNIT CONVERTER
|
|
||||||
it[PrefsKeys.LATEST_LEFT_SIDE] = userData.latestLeftSide
|
|
||||||
it[PrefsKeys.LATEST_RIGHT_SIDE] = userData.latestRightSide
|
|
||||||
it[PrefsKeys.SHOWN_UNIT_GROUPS] = userData.shownUnitGroups
|
|
||||||
it[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] = userData.unitConverterFavoritesOnly
|
|
||||||
it[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] = userData.unitConverterFormatTime
|
|
||||||
it[PrefsKeys.UNIT_CONVERTER_SORTING] = userData.unitConverterSorting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun updateCalculatorHistoryTable(userData: UserData) {
|
|
||||||
calculatorHistoryDao.clear()
|
|
||||||
userData.calculatorHistoryTable.forEach { calculatorHistoryDao.insert(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
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) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val Context.datastoreFile: File
|
||||||
|
get() = this
|
||||||
|
.filesDir
|
||||||
|
.addChild(DATASTORE_FOLDER_NAME)
|
||||||
|
.addChild(DATASTORE_FILE_NAME)
|
||||||
|
|
||||||
|
private fun Context.restartActivity() {
|
||||||
|
val componentName: ComponentName = this
|
||||||
|
.packageManager
|
||||||
|
.getLaunchIntentForPackage(this.packageName)
|
||||||
|
?.component
|
||||||
|
?: throw Exception("BackupManager was unable to find component for this context")
|
||||||
|
|
||||||
|
val restartIntent: Intent = Intent.makeRestartActivityTask(componentName)
|
||||||
|
this.startActivity(restartIntent)
|
||||||
|
Runtime.getRuntime().exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val UnittoDatabase.file: File
|
||||||
|
get() = File(this.openHelper.writableDatabase.path!!) // Will be caught on higher level if null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will write content of this [File] into given [ZipOutputStream].
|
||||||
|
*
|
||||||
|
* @receiver Source [File].
|
||||||
|
* @param zipOutputStream Target [ZipOutputStream].
|
||||||
|
* @param name Name of the file ([ZipEntry]) that will be created in the archive [ZipOutputStream].
|
||||||
|
*/
|
||||||
|
private fun File.writeToZip(zipOutputStream: ZipOutputStream, name: String) = this
|
||||||
|
.inputStream()
|
||||||
|
.buffered()
|
||||||
|
.use { inputStream ->
|
||||||
|
// Using explicit names only to reduce overhead. Don't use this.name,
|
||||||
|
zipOutputStream.putNextEntry(ZipEntry(name))
|
||||||
|
inputStream.copyTo(zipOutputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will write content from given [ZipInputStream] into [File].
|
||||||
|
*
|
||||||
|
* @receiver Target [File] that will be filled with content from [ZipInputStream].
|
||||||
|
* @param zipInputStream Source [ZipInputStream].
|
||||||
|
*/
|
||||||
|
private fun File.writeFromZip(zipInputStream: ZipInputStream) = this
|
||||||
|
.outputStream()
|
||||||
|
.buffered()
|
||||||
|
.use { outputStream ->
|
||||||
|
zipInputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.addChild(child: String): File = File(this, child)
|
||||||
|
|
||||||
|
private const val DATASTORE_FILE_NAME = "$USER_PREFERENCES.preferences_pb"
|
||||||
|
private const val DATASTORE_FOLDER_NAME = "datastore"
|
||||||
|
private const val DATABASE_FILE_NAME = "$DATABASE_NAME.db"
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.CalculatorHistoryEntity
|
|
||||||
import com.sadellie.unitto.data.database.TimeZoneEntity
|
|
||||||
import com.sadellie.unitto.data.database.UnitsEntity
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
|
|
||||||
// Don't move to model module. This uses entity classes from database module
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
internal data class UserData(
|
|
||||||
@Json(name = "themingMode") val themingMode: String,
|
|
||||||
@Json(name = "enableDynamicTheme") val enableDynamicTheme: Boolean,
|
|
||||||
@Json(name = "enableAmoledTheme") val enableAmoledTheme: Boolean,
|
|
||||||
@Json(name = "customColor") val customColor: Long,
|
|
||||||
@Json(name = "monetMode") val monetMode: String,
|
|
||||||
@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,
|
|
||||||
@Json(name = "rpnMode") val rpnMode: Boolean,
|
|
||||||
|
|
||||||
@Json(name = "precision") val precision: Int,
|
|
||||||
@Json(name = "separator") val separator: Int,
|
|
||||||
@Json(name = "outputFormat") val outputFormat: Int,
|
|
||||||
|
|
||||||
@Json(name = "radianMode") val radianMode: Boolean,
|
|
||||||
@Json(name = "partialHistoryView") val partialHistoryView: Boolean,
|
|
||||||
|
|
||||||
@Json(name = "latestLeftSide") val latestLeftSide: String,
|
|
||||||
@Json(name = "latestRightSide") val latestRightSide: String,
|
|
||||||
@Json(name = "shownUnitGroups") val shownUnitGroups: String,
|
|
||||||
@Json(name = "unitConverterFavoritesOnly") val unitConverterFavoritesOnly: Boolean,
|
|
||||||
@Json(name = "unitConverterFormatTime") val unitConverterFormatTime: Boolean,
|
|
||||||
@Json(name = "unitConverterSorting") val unitConverterSorting: String,
|
|
||||||
|
|
||||||
@Json(name = "calculatorHistoryTable") val calculatorHistoryTable: List<CalculatorHistoryEntity>,
|
|
||||||
@Json(name = "unitsTable") val unitsTable: List<UnitsEntity>,
|
|
||||||
@Json(name = "timeZoneTable") val timeZoneTable: List<TimeZoneEntity>,
|
|
||||||
)
|
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.CalculatorHistoryEntity
|
|
||||||
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(calculatorHistoryEntity: CalculatorHistoryEntity): UserDataCalculatorHistory =
|
|
||||||
UserDataCalculatorHistory(
|
|
||||||
entityId = calculatorHistoryEntity.entityId,
|
|
||||||
timestamp = calculatorHistoryEntity.timestamp,
|
|
||||||
expression = calculatorHistoryEntity.expression,
|
|
||||||
result = calculatorHistoryEntity.result
|
|
||||||
)
|
|
||||||
|
|
||||||
@FromJson
|
|
||||||
fun fromJson(userDataCalculatorHistory: UserDataCalculatorHistory): CalculatorHistoryEntity =
|
|
||||||
CalculatorHistoryEntity(
|
|
||||||
entityId = userDataCalculatorHistory.entityId,
|
|
||||||
timestamp = userDataCalculatorHistory.timestamp,
|
|
||||||
expression = userDataCalculatorHistory.expression,
|
|
||||||
result = userDataCalculatorHistory.result
|
|
||||||
)
|
|
||||||
|
|
||||||
@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
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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,
|
|
||||||
)
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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,
|
|
||||||
)
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2023 Elshan Agaev
|
* Copyright (c) 2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,14 +16,17 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.sadellie.unitto.data.backup
|
package com.sadellie.unitto.data.database
|
||||||
|
|
||||||
import com.squareup.moshi.JsonClass
|
import androidx.room.Dao
|
||||||
|
import androidx.room.RawQuery
|
||||||
|
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||||
|
import androidx.sqlite.db.SupportSQLiteQuery
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@Dao
|
||||||
internal data class UserDataCalculatorHistory(
|
interface RawDao {
|
||||||
val entityId: Int,
|
@RawQuery
|
||||||
val timestamp: Long,
|
suspend fun execute(query: SupportSQLiteQuery): Int
|
||||||
val expression: String,
|
|
||||||
val result: String,
|
suspend fun walCheckpoint() = execute(SimpleSQLiteQuery("PRAGMA wal_checkpoint(FULL)"))
|
||||||
)
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2022-2023 Elshan Agaev
|
* Copyright (c) 2022-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -46,4 +46,7 @@ abstract class UnittoDatabase : RoomDatabase() {
|
|||||||
abstract fun calculatorHistoryDao(): CalculatorHistoryDao
|
abstract fun calculatorHistoryDao(): CalculatorHistoryDao
|
||||||
abstract fun timeZoneDao(): TimeZoneDao
|
abstract fun timeZoneDao(): TimeZoneDao
|
||||||
abstract fun currencyRatesDao(): CurrencyRatesDao
|
abstract fun currencyRatesDao(): CurrencyRatesDao
|
||||||
|
internal abstract fun rawDao(): RawDao
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun UnittoDatabase.checkpoint() = this.rawDao().walCheckpoint()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2022-2023 Elshan Agaev
|
* Copyright (c) 2022-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -27,6 +27,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
const val DATABASE_NAME = "unitto_database"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module for database. Used to access same instance of database
|
* Module for database. Used to access same instance of database
|
||||||
*
|
*
|
||||||
@ -34,6 +36,10 @@ import javax.inject.Singleton
|
|||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@Module
|
@Module
|
||||||
class UnittoDatabaseModule {
|
class UnittoDatabaseModule {
|
||||||
|
@Provides
|
||||||
|
fun provideRawDao(unittoDatabase: UnittoDatabase): RawDao {
|
||||||
|
return unittoDatabase.rawDao()
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideUnitsDao(unittoDatabase: UnittoDatabase): UnitsDao {
|
fun provideUnitsDao(unittoDatabase: UnittoDatabase): UnitsDao {
|
||||||
@ -73,7 +79,7 @@ class UnittoDatabaseModule {
|
|||||||
return Room.databaseBuilder(
|
return Room.databaseBuilder(
|
||||||
appContext.applicationContext,
|
appContext.applicationContext,
|
||||||
UnittoDatabase::class.java,
|
UnittoDatabase::class.java,
|
||||||
"unitto_database"
|
DATABASE_NAME
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2022-2023 Elshan Agaev
|
* Copyright (c) 2022-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -34,7 +34,7 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
// DON'T TOUCH!!!
|
// DON'T TOUCH!!!
|
||||||
private const val USER_PREFERENCES = "settings"
|
const val USER_PREFERENCES = "settings"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module is for DataStore dependency injection
|
* This module is for DataStore dependency injection
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2022-2023 Elshan Agaev
|
* Copyright (c) 2022-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,13 +20,11 @@ package com.sadellie.unitto.feature.settings
|
|||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
@ -93,6 +91,8 @@ import com.sadellie.unitto.feature.settings.navigation.formattingRoute
|
|||||||
import com.sadellie.unitto.feature.settings.navigation.startingScreenRoute
|
import com.sadellie.unitto.feature.settings.navigation.startingScreenRoute
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SettingsRoute(
|
internal fun SettingsRoute(
|
||||||
@ -101,21 +101,13 @@ internal fun SettingsRoute(
|
|||||||
navControllerAction: (String) -> Unit,
|
navControllerAction: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val mContext = LocalContext.current
|
val mContext = LocalContext.current
|
||||||
|
val uiState: SettingsUIState = viewModel.uiState
|
||||||
val errorLabel = stringResource(R.string.error_label)
|
.collectAsStateWithLifecycle().value
|
||||||
|
val showErrorToast: Boolean = viewModel.showErrorToast
|
||||||
val uiState: SettingsUIState = viewModel.uiState.collectAsStateWithLifecycle().value
|
.collectAsStateWithLifecycle(initialValue = false).value
|
||||||
val backupFileUri: Uri? = viewModel.backupFileUri.collectAsStateWithLifecycle(initialValue = null).value
|
|
||||||
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) {
|
LaunchedEffect(showErrorToast) {
|
||||||
if (showErrorToast) Toast.makeText(mContext, errorLabel, Toast.LENGTH_SHORT).show()
|
if (showErrorToast) showToast(mContext, mContext.resources.getString(R.string.error_label))
|
||||||
}
|
}
|
||||||
|
|
||||||
when (uiState) {
|
when (uiState) {
|
||||||
@ -142,15 +134,24 @@ private fun SettingsScreen(
|
|||||||
updateLastReadChangelog: (String) -> Unit,
|
updateLastReadChangelog: (String) -> Unit,
|
||||||
updateVibrations: (Boolean) -> Unit,
|
updateVibrations: (Boolean) -> Unit,
|
||||||
clearCache: () -> Unit,
|
clearCache: () -> Unit,
|
||||||
backup: () -> Unit,
|
backup: (Context, Uri) -> Unit,
|
||||||
restore: (Uri) -> Unit = {},
|
restore: (Context, Uri) -> Unit,
|
||||||
) {
|
) {
|
||||||
val mContext = LocalContext.current
|
val mContext = LocalContext.current
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Pass picked file uri to BackupManager
|
// Pass picked file uri to BackupManager
|
||||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { pickedUri ->
|
val restoreLauncher = rememberLauncherForActivityResult(
|
||||||
if (pickedUri != null) restore(pickedUri)
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { pickedUri ->
|
||||||
|
if (pickedUri != null) restore(mContext, pickedUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass picked file uri to BackupManager
|
||||||
|
val backupLauncher = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument(backupMimeType)
|
||||||
|
) { pickedUri ->
|
||||||
|
if (pickedUri != null) backup(mContext, pickedUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(uiState.backupInProgress) {}
|
BackHandler(uiState.backupInProgress) {}
|
||||||
@ -168,11 +169,17 @@ private fun SettingsScreen(
|
|||||||
onDismissRequest = { showMenu = false }
|
onDismissRequest = { showMenu = false }
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = { showMenu = false; backup() },
|
onClick = {
|
||||||
|
showMenu = false
|
||||||
|
backupLauncher.launchSafely(backupFileName())
|
||||||
|
},
|
||||||
text = { Text(stringResource(R.string.settings_back_up)) }
|
text = { Text(stringResource(R.string.settings_back_up)) }
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = { showMenu = false; launcher.launchPicker() },
|
onClick = {
|
||||||
|
showMenu = false
|
||||||
|
restoreLauncher.launchSafely(arrayOf(backupMimeType))
|
||||||
|
},
|
||||||
text = { Text(stringResource(R.string.settings_restore)) }
|
text = { Text(stringResource(R.string.settings_restore)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -297,25 +304,20 @@ private fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.share(uri: Uri) {
|
private fun <T> ActivityResultLauncher<T>.launchSafely(input: T) {
|
||||||
val shareIntent = Intent().apply {
|
|
||||||
action = Intent.ACTION_SEND
|
|
||||||
putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
type = backupMimeType
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(shareIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ManagedActivityResultLauncher<Array<String>, Uri?>.launchPicker() {
|
|
||||||
try {
|
try {
|
||||||
launch(arrayOf(backupMimeType))
|
this.launch(input)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
Log.e("SettingsScreen", "launchPicker: ActivityNotFoundException")
|
Log.e("SettingsScreen", "launchSafely: ActivityNotFoundException")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val backupMimeType = "application/octet-stream"
|
private fun backupFileName(): String {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
|
||||||
|
return "${ZonedDateTime.now().format(formatter)}.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val backupMimeType = "application/zip"
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
@ -348,7 +350,14 @@ private fun PreviewSettingsScreen() {
|
|||||||
clearCache = {
|
clearCache = {
|
||||||
uiState = uiState.copy(cacheSize = 0)
|
uiState = uiState.copy(cacheSize = 0)
|
||||||
},
|
},
|
||||||
backup = {
|
backup = { _, _ ->
|
||||||
|
corScope.launch {
|
||||||
|
uiState = uiState.copy(backupInProgress = true)
|
||||||
|
delay(2000)
|
||||||
|
uiState = uiState.copy(backupInProgress = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restore = { _, _ ->
|
||||||
corScope.launch {
|
corScope.launch {
|
||||||
uiState = uiState.copy(backupInProgress = true)
|
uiState = uiState.copy(backupInProgress = true)
|
||||||
delay(2000)
|
delay(2000)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a unit converter for Android
|
* Unitto is a unit converter for Android
|
||||||
* Copyright (c) 2022-2023 Elshan Agaev
|
* Copyright (c) 2022-2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.feature.settings
|
package com.sadellie.unitto.feature.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@ -26,6 +27,7 @@ import com.sadellie.unitto.core.base.BuildConfig
|
|||||||
import com.sadellie.unitto.data.backup.BackupManager
|
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.database.UnittoDatabase
|
||||||
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
|
||||||
@ -42,11 +44,8 @@ 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
|
private val database: UnittoDatabase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _backupFileUri = MutableSharedFlow<Uri?>()
|
|
||||||
val backupFileUri = _backupFileUri.asSharedFlow()
|
|
||||||
|
|
||||||
private val _showErrorToast = MutableSharedFlow<Boolean>()
|
private val _showErrorToast = MutableSharedFlow<Boolean>()
|
||||||
val showErrorToast = _showErrorToast.asSharedFlow()
|
val showErrorToast = _showErrorToast.asSharedFlow()
|
||||||
|
|
||||||
@ -67,14 +66,15 @@ internal class SettingsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
.stateIn(viewModelScope, SettingsUIState.Loading)
|
.stateIn(viewModelScope, SettingsUIState.Loading)
|
||||||
|
|
||||||
fun backup() {
|
fun backup(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
) {
|
||||||
backupJob?.cancel()
|
backupJob?.cancel()
|
||||||
backupJob = viewModelScope.launch(Dispatchers.IO) {
|
backupJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
_backupInProgress.update { true }
|
_backupInProgress.update { true }
|
||||||
try {
|
try {
|
||||||
val backupFileUri = backupManager.backup()
|
BackupManager().backup(context, uri, database)
|
||||||
_backupFileUri.emit(backupFileUri) // Emit to trigger file share intent
|
|
||||||
_showErrorToast.emit(false)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_showErrorToast.emit(true)
|
_showErrorToast.emit(true)
|
||||||
Log.e(TAG, "$e")
|
Log.e(TAG, "$e")
|
||||||
@ -83,13 +83,15 @@ internal class SettingsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore(uri: Uri) {
|
fun restore(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
) {
|
||||||
backupJob?.cancel()
|
backupJob?.cancel()
|
||||||
backupJob = viewModelScope.launch(Dispatchers.IO) {
|
backupJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
_backupInProgress.update { true }
|
_backupInProgress.update { true }
|
||||||
try {
|
try {
|
||||||
backupManager.restore(uri)
|
BackupManager().restore(context, uri, database)
|
||||||
_showErrorToast.emit(false)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_showErrorToast.emit(true)
|
_showErrorToast.emit(true)
|
||||||
Log.e(TAG, "$e")
|
Log.e(TAG, "$e")
|
||||||
|
@ -76,7 +76,6 @@ com-google-dagger-android-hilt-android = { group = "com.google.dagger", name = "
|
|||||||
com-google-dagger-dagger-android-processor = { group = "com.google.dagger", name = "dagger-android-processor", version.ref = "comGoogleDagger" }
|
com-google-dagger-dagger-android-processor = { group = "com.google.dagger", name = "dagger-android-processor", version.ref = "comGoogleDagger" }
|
||||||
com-google-dagger-hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "comGoogleDagger" }
|
com-google-dagger-hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "comGoogleDagger" }
|
||||||
com-squareup-moshi-moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "comSquareupMoshiMoshiKotlin" }
|
com-squareup-moshi-moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "comSquareupMoshiMoshiKotlin" }
|
||||||
com-squareup-moshi-moshi-kotlin-codegen = { group = "com.squareup.moshi", name = "moshi-kotlin-codegen", version.ref = "comSquareupMoshiMoshiKotlin" }
|
|
||||||
com-squareup-retrofit2-converter-moshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "comSquareupRetrofit2ConverterMoshi" }
|
com-squareup-retrofit2-converter-moshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "comSquareupRetrofit2ConverterMoshi" }
|
||||||
junit-junit = { group = "junit", name = "junit", version.ref = "junitJunit" }
|
junit-junit = { group = "junit", name = "junit", version.ref = "junitJunit" }
|
||||||
org-burnoutcrew-composereorderable-reorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "orgBurnoutcrewComposereorderableReorderable" }
|
org-burnoutcrew-composereorderable-reorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "orgBurnoutcrewComposereorderableReorderable" }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user