diff --git a/README.md b/README.md index 0450631d..71ea4b99 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## Target platforms : -API 16 or later +API 23 or later ## Features : @@ -31,18 +31,6 @@ API 16 or later ## Donations : -[Beer Buy me a beer](https://www.paypal.me/loicteyssier/5) - -[Beer Buy me a pizza](https://www.paypal.me/loicteyssier/10) - -[Beer Buy me a meal](https://www.paypal.me/loicteyssier/20) - -[Beer Buy me whatever you want](https://www.paypal.me/loicteyssier) + height="20"> Buy me a beer](https://www.paypal.com/donate/?business=Z32JPDRAJV2ZQ&no_recurring=0&item_name=1List+App¤cy_code=EUR) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 9754d154..28c115e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { compileSdk 34 minSdkVersion 23 targetSdkVersion 34 - versionCode 14 - versionName "1.4.1" + versionCode 17 + versionName "1.4.0" vectorDrawables.useSupportLibrary = true } @@ -25,16 +25,16 @@ android { buildFeatures { viewBinding true + buildConfig = true } signingConfigs { release { - storeFile file('C:\\Users\\Loic\\Dropbox (Personal)\\.keys\\keystores\\android.jks') - storePassword '7c3c1779c148a6ff3d5ee87e7ef70f81' - keyAlias 'OneListKey' - keyPassword '97220be66f2b279723c09f770ab45089' + storeFile = file(System.getenv("ONELIST_KEYSTORE_PATH")) + storePassword = System.getenv("ONELIST_KEYSTORE_PASSWORD") + keyAlias = System.getenv("ONELIST_KEYSTORE_ALIAS") + keyPassword = System.getenv("ONELIST_KEYSTORE_ALIAS_PASSWORD") } - release } buildTypes { @@ -68,6 +68,8 @@ repositories { } dependencies { + def room_version = "2.6.1" + implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support.constraint:constraint-layout:2.0.4' implementation 'androidx.appcompat:appcompat:1.6.1' @@ -92,21 +94,7 @@ dependencies { implementation "io.insert-koin:koin-android:3.5.0" implementation "io.insert-koin:koin-androidx-navigation:3.5.0" - def room_version = "2.6.1" implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" ksp "androidx.room:room-compiler:$room_version" - -} - -def props = new Properties() -if (rootProject.file("release.properties").exists()) { - props.load(new FileInputStream(rootProject.file("release.properties"))) - android.signingConfigs.release.storeFile file(props.keyStore) - android.signingConfigs.release.storePassword props.keyStorePassword - android.signingConfigs.release.keyAlias props.keyAlias - android.signingConfigs.release.keyPassword props.keyAliasPassword -} else { - project.logger.info('INFO: Set the values storeFile, storePassword, keyAlias, and keyPassword in release.properties to sign the release.') - android.buildTypes.release.signingConfig = null } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..50746776 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,23 @@ +-dontwarn com.fasterxml.jackson.core.** +-dontwarn com.google.common.annotations.** +-dontwarn javax.ws.rs.** +-dontwarn org.immutables.value.** + +# R8 GSON special rules : +# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md => Troubleshooting +# For data classes used for serialization all fields that are used in the serialization must be kept by the configuration. R8 can decide to replace instances of types that are never instantiated with null. So if instances of a given class are only created through deserialization from JSON, R8 will not see that class as instantiated leaving it as always null. +# If the @SerializedName annotation is not used the following conservative rule can be used for each data class : + +-keepclassmembers class com.lolo.io.onelist.core.model.ItemList { + !transient ; +} +-keepclassmembers class com.lolo.io.onelist.core.model.Item { + !transient ; +} + +# GSON uses type tokens to serialize and deserialize generic types. +# The anonymous class will have a generic signature argument of List to the super type TypeToken that is reflective read for serialization. It is therefore necessary to keep both the Signature attribute, the com.google.gson.reflect.TypeToken class and all sub-types. + +-keep class com.google.gson.reflect.TypeToken +-keep class * extends com.google.gson.reflect.TypeToken +-keep public class * implements java.lang.reflect.Type diff --git a/app/src/main/java/com/lolo/io/onelist/App.kt b/app/src/main/java/com/lolo/io/onelist/App.kt index 7f152120..99aae152 100644 --- a/app/src/main/java/com/lolo/io/onelist/App.kt +++ b/app/src/main/java/com/lolo/io/onelist/App.kt @@ -9,6 +9,7 @@ import org.koin.androidx.fragment.koin.fragmentFactory import org.koin.core.context.startKoin class App : Application() { + override fun onCreate() { super.onCreate() startKoin { diff --git a/app/src/main/java/com/lolo/io/onelist/MainActivity.kt b/app/src/main/java/com/lolo/io/onelist/MainActivity.kt index 16e2c379..cb064f98 100644 --- a/app/src/main/java/com/lolo/io/onelist/MainActivity.kt +++ b/app/src/main/java/com/lolo/io/onelist/MainActivity.kt @@ -1,9 +1,7 @@ package com.lolo.io.onelist import android.content.Context -import android.content.Intent import android.content.res.Configuration -import android.os.Build import android.os.Bundle import android.view.MotionEvent import androidx.appcompat.app.AppCompatActivity @@ -11,8 +9,6 @@ import androidx.appcompat.app.AppCompatDelegate import com.anggrayudi.storage.SimpleStorageHelper import com.lolo.io.onelist.core.data.shared_preferences.SharedPreferencesHelper import com.lolo.io.onelist.core.ui.Config -import com.lolo.io.onelist.core.ui.REQUEST_CODE_OPEN_DOCUMENT -import com.lolo.io.onelist.core.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE import com.lolo.io.onelist.feature.lists.OneListFragment import com.lolo.io.onelist.feature.lists.utils.StorageHelperHolder import org.koin.android.ext.android.inject @@ -21,18 +17,7 @@ class MainActivity : AppCompatActivity(), StorageHelperHolder { override val storageHelper = SimpleStorageHelper(this) - val persistence by inject() - - // On some devices, displaying storage chooser fragment before activity is resumed leads to a crash. - // This is a workaround. - var whenResumed = {} - set(value) { - if (this.isResumed) value() - else field = value - } - private var isResumed = false - - var onPathChosenActivityResult: (String) -> Any? = {} + private val preferences by inject() override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme) @@ -63,18 +48,6 @@ class MainActivity : AppCompatActivity(), StorageHelperHolder { .replace(R.id.fragmentContainer, fragment, "OneListFragment") .commit() - - //supportFragmentManager.beginTransaction().add(R.id.fragmentContainer).commit() - - - /* todo migrate whatsnew to a normal fragment - // WORKAROUND FOR WHATSNEW LIB NOT HANDLING WELL CONFIG CHANGES - if (savedInstanceState != null) { - supportFragmentManager.findFragmentByTag(WhatsNew.TAG) - ?.let { supportFragmentManager.beginTransaction().remove(it).commit() } - ?.let { WhatsNew.releasesNotes.entries.last().value().show(this) } - } - */ } override fun onRequestPermissionsResult( @@ -93,19 +66,6 @@ class MainActivity : AppCompatActivity(), StorageHelperHolder { return super.dispatchTouchEvent(ev) } - override fun onResume() { - super.onResume() - - whenResumed() - whenResumed = {} - isResumed = true - } - - override fun onPause() { - super.onPause() - isResumed = false - } - interface OnDispatchTouchEvent { fun onDispatchTouchEvent(ev: MotionEvent) } @@ -115,22 +75,6 @@ class MainActivity : AppCompatActivity(), StorageHelperHolder { return true } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - - if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE || requestCode == REQUEST_CODE_OPEN_DOCUMENT) - data?.data?.let { uri -> - contentResolver.takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - onPathChosenActivityResult(uri.toString()) - onPathChosenActivityResult = { } - } - } - } - override fun attachBaseContext(newBase: Context) { val context: Context = updateThemeConfiguration(newBase) super.attachBaseContext(context) @@ -138,7 +82,7 @@ class MainActivity : AppCompatActivity(), StorageHelperHolder { private fun updateThemeConfiguration(context: Context): Context { var mode = context.resources.configuration.uiMode - when (persistence.theme) { + when (preferences.theme) { "light" -> { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) mode = Configuration.UI_MODE_NIGHT_NO; @@ -154,12 +98,6 @@ class MainActivity : AppCompatActivity(), StorageHelperHolder { val config = Configuration(context.resources.configuration) config.uiMode = mode - var ctx = context - if (Build.VERSION.SDK_INT >= 17) { - ctx = context.createConfigurationContext(config) - } else { - context.resources.updateConfiguration(config, context.resources.displayMetrics) - } - return ctx + return context.createConfigurationContext(config) } } \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/core/data/file_access/FileAccess.kt b/app/src/main/java/com/lolo/io/onelist/core/data/file_access/FileAccess.kt index 4043ed96..ec34d2e3 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/data/file_access/FileAccess.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/data/file_access/FileAccess.kt @@ -8,6 +8,7 @@ import android.widget.Toast import androidx.documentfile.provider.DocumentFile import com.anggrayudi.storage.file.DocumentFileCompat import com.anggrayudi.storage.file.getAbsolutePath +import com.anggrayudi.storage.file.isTreeDocumentFile import com.anggrayudi.storage.file.makeFile import com.google.gson.Gson import com.google.gson.JsonIOException @@ -19,6 +20,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import java.io.FileNotFoundException import java.io.IOException @@ -34,12 +36,8 @@ class FileAccess( get() = DocumentFileCompat.fromUri(app, this)?.canWrite() == true - private fun Uri.isIntoBackupFolder(backupUri: Uri): Boolean = - DocumentFileCompat.fromUri(app, this)?.getAbsolutePath(app) - ?.startsWith( - DocumentFileCompat.fromUri(app, backupUri)?.getAbsolutePath(app) - ?: throw IllegalArgumentException("Backup uri could not be parsed") - ) == true + private fun Uri.isIntoBackupFolder(): Boolean = + DocumentFileCompat.fromUri(app, this)?.isTreeDocumentFile == true private val Uri.fileExists get() = @@ -52,7 +50,6 @@ class FileAccess( SecurityException::class ) suspend fun getListFromLocalFile(list: ItemList): ItemList { - Log.d("1LogD", list.title) return coroutineIOScope.async(SupervisorJob()) { val listFromFile = list.uri?.let { uri -> app.contentResolver.openInputStream(uri).use { @@ -60,6 +57,7 @@ class FileAccess( } } ?: list listFromFile.apply { + id = list.id uri = list.uri } }.await() @@ -73,9 +71,10 @@ class FileAccess( ): ItemList { if (backupUri != null) { list.uri.let { - if (it == null + if ( + it == null || !it.fileExists - || !it.isIntoBackupFolder(Uri.parse(backupUri)) + || !it.isIntoBackupFolder() || !it.canWrite ) { val uri = createListFile(backupUri, list)?.uri @@ -91,16 +90,8 @@ class FileAccess( ) } } catch (e: Exception) { - coroutineIOScope.launch { - Toast.makeText( - app, - app.getString( - R.string.error_saving_to_path, - list.title - ), // todo change to path to just error while saving list - Toast.LENGTH_SHORT - ).show() - } + // Just don't save list in file. error has been toasted before normally. + // Should be handled better } } } @@ -118,28 +109,20 @@ class FileAccess( } - // TODO Toasts should not be here ! - fun deleteListBackupFile(list: ItemList) { - coroutineIOScope.launch { + @kotlin.jvm.Throws + fun deleteListBackupFile( + list: ItemList, + onFileDeleted: () -> Unit, + ) { + coroutineIOScope.run { list.uri?.let { uri -> - try { + if ( DocumentFile.fromSingleUri(app, uri)?.delete() - withContext(Dispatchers.Main) { - Toast.makeText( - app, - app.getString(R.string.file_deleted), - Toast.LENGTH_LONG - ).show() - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - Toast.makeText( - app, - app.getString(R.string.error_deleting_list_file), - Toast.LENGTH_SHORT - ).show() - } + != true + ) { + throw IOException("Could not delete file") } + onFileDeleted() } } } @@ -156,8 +139,10 @@ class FileAccess( .use { iss -> iss?.bufferedReader()?.use { it.readText() } } // return : gson.fromJson(content, ItemList::class.java).also { + it.uri = uri onListCreated(it) } + } catch (e: IllegalArgumentException) { throw e } catch (e: Exception) { diff --git a/app/src/main/java/com/lolo/io/onelist/core/data/migration/UpdateHelper.kt b/app/src/main/java/com/lolo/io/onelist/core/data/migration/UpdateHelper.kt index 56152744..c9c63e11 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/data/migration/UpdateHelper.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/data/migration/UpdateHelper.kt @@ -1,11 +1,103 @@ package com.lolo.io.onelist.core.data.migration +import android.content.Context +import android.content.SharedPreferences +import android.health.connect.datatypes.units.Length +import android.util.Log +import android.widget.Toast +import androidx.fragment.app.FragmentActivity +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.lolo.io.onelist.App +import com.lolo.io.onelist.core.data.reporitory.OneListRepository import com.lolo.io.onelist.core.data.shared_preferences.SharedPreferencesHelper +import com.lolo.io.onelist.core.database.dao.ItemListDao +import com.lolo.io.onelist.core.model.ItemList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.lang.ref.WeakReference class UpdateHelper( - val persistenceHelper: SharedPreferencesHelper + private val preferences: SharedPreferencesHelper, + private val repository: OneListRepository ) { - fun applyUpdatePatches() { + private var oldListsIds: Map = linkedMapOf() + + private var activityWR: WeakReference? = null + + private val oldVersionPref: String = "version" + private val oldSelectedListPref = "selectedList" + private val oldListIdsPref = "listsIds" + private val oldDefaultPathPref = "defaultPath" + private val oldThemePref: String = "theme" + + fun hasToMigratePrefs(activity: FragmentActivity): Boolean { + val activityPreferences = activity.getPreferences(Context.MODE_PRIVATE) + return activityPreferences.getString(oldVersionPref, null) != null } + + fun applyUpdatePatches(activity: FragmentActivity, then: () -> Unit) { + val activityPreferences = activity.getPreferences(Context.MODE_PRIVATE) + val editor = activityPreferences.edit() + + activityWR = WeakReference(activity) + + preferences.version = activityPreferences.getString(oldVersionPref, "0.0.0") ?: "0.0.0" + editor.putString(oldVersionPref, null) + + preferences.selectedListIndex = activityPreferences.getInt(oldSelectedListPref, 0) + editor.putString(oldSelectedListPref, null) + + preferences.theme = activityPreferences.getString(oldThemePref, "auto") ?: "auto" + editor.putString(oldThemePref, null) + + val oldLists = getAllLists(activityPreferences) + CoroutineScope(Dispatchers.IO).launch { + oldLists.forEach { + repository.createList(it.copy(id = 0)) + } + then() + } + + editor.putString(oldListIdsPref, null) + editor.putString(oldDefaultPathPref, null) + + preferences.firstLaunch = false + + editor.apply() + } + + + private fun getAllLists(sp: SharedPreferences): List { + val gson = Gson() + return runBlocking { + oldListsIds = getListIdsTable() + try { + val ret = oldListsIds.map { + gson.fromJson(sp.getString(it.key.toString(), ""), ItemList::class.java) + } + ret + } catch (e: Exception) { + listOf() + } + } + } + + private fun getListIdsTable(): Map { + return activityWR?.get()?.let { act -> + val sp = act.getPreferences(Context.MODE_PRIVATE) + val gson = Gson() + val json = + sp.getString(oldListIdsPref, null)?.replace("\\", "")?.removeSurrounding("\"") + return if (json != null) { + gson.fromJson(json, object : TypeToken>() { + }.type) + } else mapOf() + } ?: mapOf() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/core/data/reporitory/OneListRepository.kt b/app/src/main/java/com/lolo/io/onelist/core/data/reporitory/OneListRepository.kt index f69c1552..3525698f 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/data/reporitory/OneListRepository.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/data/reporitory/OneListRepository.kt @@ -57,6 +57,7 @@ class OneListRepository( list } catch (e: IllegalArgumentException) { errors += ErrorLoadingList.FileMissingError + print(e) list } catch (e: JsonSyntaxException) { errors += ErrorLoadingList.FileCorruptedError @@ -122,16 +123,17 @@ class OneListRepository( } } - suspend fun deleteList(itemList: ItemList, deleteBackupFile: Boolean) { + @Throws + suspend fun deleteList( + itemList: ItemList, + deleteBackupFile: Boolean, + onFileDeleted: () -> Unit + ) { withContext(Dispatchers.IO) { dao.delete(itemList.toItemListEntity()) } - if (deleteBackupFile) { - fileAccess.deleteListBackupFile(itemList) - } - _allListsWithErrors.value = AllListsWithErrors(_allListsWithErrors.value.lists .filter { it.id != itemList.id }) @@ -143,6 +145,10 @@ class OneListRepository( selectList(position - 1) } } + + if (deleteBackupFile) { + fileAccess.deleteListBackupFile(itemList, onFileDeleted) + } } suspend fun importList(uri: Uri): ItemList { diff --git a/app/src/main/java/com/lolo/io/onelist/core/data/shared_preferences/SharedPreferencesHelper.kt b/app/src/main/java/com/lolo/io/onelist/core/data/shared_preferences/SharedPreferencesHelper.kt index 1ebb4d1f..193402f4 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/data/shared_preferences/SharedPreferencesHelper.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/data/shared_preferences/SharedPreferencesHelper.kt @@ -59,7 +59,7 @@ class SharedPreferencesHelper( set(value) = editPref(backUpLocallyPref, value) var version: String - get() = getPref(versionPref) ?: "0.0.0" + get() = getPref(versionPref) ?: "" set(value) = editPref(versionPref, value) var theme: String @@ -71,7 +71,7 @@ class SharedPreferencesHelper( set(value) = firstLaunchPref.editPref(value) var preferUseFiles: Boolean - get() = preferUseFilesPref.getPref(true) + get() = preferUseFilesPref.getPref(false) set(value) = preferUseFilesPref.editPref(value) var selectedListIndex: Int diff --git a/app/src/main/java/com/lolo/io/onelist/core/data/utils/AndroidUtils.kt b/app/src/main/java/com/lolo/io/onelist/core/data/utils/AndroidUtils.kt deleted file mode 100644 index 7748a097..00000000 --- a/app/src/main/java/com/lolo/io/onelist/core/data/utils/AndroidUtils.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.lolo.io.onelist.core.data.utils - -import android.Manifest -import android.net.Uri -import android.widget.Toast -import com.karumi.dexter.Dexter -import com.karumi.dexter.PermissionToken -import com.karumi.dexter.listener.PermissionDeniedResponse -import com.karumi.dexter.listener.PermissionGrantedResponse -import com.karumi.dexter.listener.PermissionRequest -import com.karumi.dexter.listener.single.PermissionListener -import com.lolo.io.onelist.MainActivity - -val String?.toUri: Uri? // todo rm - get() = try { - if (this.isNullOrBlank() || !startsWith("content://")) throw Exception() - Uri.parse(this) - } catch (e: Exception) { - null - } - -fun withStoragePermission(activity: MainActivity, block: () -> Unit) { //todo rm - Dexter.withActivity(activity) - .withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) - .withListener(object : PermissionListener { - override fun onPermissionGranted(response: PermissionGrantedResponse) { - activity.whenResumed = block - } - - override fun onPermissionDenied(response: PermissionDeniedResponse) { - Toast.makeText( - activity, - "Permission is required to access external storage.", - Toast.LENGTH_LONG - ).show() - } - - override fun onPermissionRationaleShouldBeShown( - permission: PermissionRequest, - token: PermissionToken - ) { - token.continuePermissionRequest() - } - }).check() -} - diff --git a/app/src/main/java/com/lolo/io/onelist/core/data/utils/Converters.kt b/app/src/main/java/com/lolo/io/onelist/core/data/utils/Converters.kt index 12de041e..835ee3f0 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/data/utils/Converters.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/data/utils/Converters.kt @@ -9,7 +9,6 @@ fun ItemList.toItemListEntity() = ItemListEntity( id = this.id, items = this.items.toItemEntities().toMutableList(), uri = this.uri, - path = this.path, position = this.position, title = this.title ) diff --git a/app/src/main/java/com/lolo/io/onelist/core/database/dao/ItemDao.kt b/app/src/main/java/com/lolo/io/onelist/core/database/dao/ItemDao.kt deleted file mode 100644 index 7f9d0e7e..00000000 --- a/app/src/main/java/com/lolo/io/onelist/core/database/dao/ItemDao.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.lolo.io.onelist.core.database.dao - -import com.lolo.io.onelist.core.database.model.ItemEntity - -interface ItemDao { - fun upsert(itemList: ItemEntity): Long - fun delete(itemList: ItemEntity) - fun get(id: Long): ItemEntity - fun getAll(): List -} \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/core/database/model/ItemListEntity.kt b/app/src/main/java/com/lolo/io/onelist/core/database/model/ItemListEntity.kt index 492c3f9a..1b938841 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/database/model/ItemListEntity.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/database/model/ItemListEntity.kt @@ -12,7 +12,6 @@ data class ItemListEntity( val title: String = "", val position: Int = 0, val items: List = listOf(), - val path: Uri? = null, var uri: Uri? = null, @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/com/lolo/io/onelist/core/database/util/Converters.kt b/app/src/main/java/com/lolo/io/onelist/core/database/util/Converters.kt index 192fe198..591f647b 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/database/util/Converters.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/database/util/Converters.kt @@ -38,7 +38,6 @@ fun ItemListEntity.toItemListModel() = ItemList( id = this.id, items = this.items.toItemModels().toMutableList(), uri = this.uri, - path = this.path, position = this.position, title = this.title ) diff --git a/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/RemoveList.kt b/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/RemoveList.kt index c25599ce..7cfd8248 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/RemoveList.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/RemoveList.kt @@ -2,10 +2,14 @@ package com.lolo.io.onelist.core.domain.use_cases import com.lolo.io.onelist.core.data.reporitory.OneListRepository import com.lolo.io.onelist.core.model.ItemList +import kotlin.jvm.Throws class RemoveList(private val repository: OneListRepository) { - suspend operator fun invoke(itemList: ItemList, deleteBackupFile: Boolean) { - repository.deleteList(itemList, deleteBackupFile) + @Throws + suspend operator fun invoke(itemList: ItemList, + deleteBackupFile: Boolean, + onFieDeleted: () -> Unit) { + repository.deleteList(itemList, deleteBackupFile, onFieDeleted) } } \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/ShowWhatsNew.kt b/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/ShowWhatsNew.kt index fec366c4..17c2fc35 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/ShowWhatsNew.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/domain/use_cases/ShowWhatsNew.kt @@ -2,12 +2,12 @@ package com.lolo.io.onelist.core.domain.use_cases import com.lolo.io.onelist.BuildConfig import com.lolo.io.onelist.core.data.shared_preferences.SharedPreferencesHelper +import isNotNullOrEmpty class ShowWhatsNew(private val persistenceHelper: SharedPreferencesHelper) { operator fun invoke(): Boolean { - return persistenceHelper.version.substringBeforeLast(".") != + return persistenceHelper.version.isNotNullOrEmpty() && persistenceHelper.version.substringBeforeLast(".") != BuildConfig.VERSION_NAME.substringBeforeLast(".") - } } \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/core/model/ItemList.kt b/app/src/main/java/com/lolo/io/onelist/core/model/ItemList.kt index 53ea6f95..c23d3cf9 100644 --- a/app/src/main/java/com/lolo/io/onelist/core/model/ItemList.kt +++ b/app/src/main/java/com/lolo/io/onelist/core/model/ItemList.kt @@ -1,17 +1,14 @@ package com.lolo.io.onelist.core.model -import android.content.Context import android.net.Uri -import com.lolo.io.onelist.R +import com.google.gson.annotations.SerializedName import java.io.Serializable - data class ItemList( var title: String = "", @Transient var position: Int = 0, val items: MutableList = arrayListOf(), - @Transient var path: Uri? = null, @Transient var uri: Uri? = null, - var id: Long = 0L, + @Transient var id: Long = 0L, ) : Serializable diff --git a/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragment.kt b/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragment.kt index b17d1886..4686137c 100644 --- a/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragment.kt +++ b/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragment.kt @@ -28,6 +28,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView.ItemAnimator +import com.anggrayudi.storage.extension.launchOnUiThread import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayoutManager @@ -39,6 +40,7 @@ import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchAct import com.lolo.io.onelist.BuildConfig import com.lolo.io.onelist.MainActivity import com.lolo.io.onelist.R +import com.lolo.io.onelist.core.data.migration.UpdateHelper import com.lolo.io.onelist.core.model.Item import com.lolo.io.onelist.core.model.ItemList import com.lolo.io.onelist.core.ui.Config @@ -63,8 +65,13 @@ import com.lolo.io.onelist.feature.lists.lists_adapters.ListsCallbacks import com.lolo.io.onelist.feature.settings.SettingsFragment import com.lolo.io.onelist.feature.settings.showReleaseNote import ifNotEmpty +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.getViewModel +import javax.inject.Inject class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, @@ -85,18 +92,37 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, private var container: ViewGroup? = null - private val listsAdapter: ListsAdapter by lazy { ListsAdapter(requireActivity(), _fragmentAllListsFinalInstance, this) } + private val listsAdapter: ListsAdapter by lazy { + ListsAdapter( + requireActivity(), + _fragmentAllListsFinalInstance, + this + ) + } private val itemsAdapter by lazy { ItemsAdapter(requireActivity(), this) } private val isAddCommentShown get() = binding.addCommentEditText.height > 0 + private val updateHelper: UpdateHelper by inject() + override fun onAttach(context: Context) { super.onAttach(context) + + if (updateHelper.hasToMigratePrefs(requireActivity())) { + updateHelper.applyUpdatePatches(requireActivity()) { + launchOnUiThread { + listsAdapter.notifyDataSetChanged() + itemsAdapter.notifyDataSetChanged() + } + } + } + activity?.let { it.packageManager.getPackageInfo(it.packageName, 0).versionName } + } override fun onCreateView( @@ -146,7 +172,7 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.forceRefreshTrigger.collect { - if(it > 0) { + if (it > 0) { _fragmentAllListsFinalInstance.clear() _fragmentAllListsFinalInstance.addAll(viewModel.allLists.value) listsAdapter.notifyDataSetChanged() @@ -167,7 +193,7 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.errorMessage.collect { - if(it != null) { + if (it != null) { val message = StringBuilder(getString(it.resId)).apply { it.restResIds.ifNotEmpty { restResIds -> append(" : ") @@ -181,7 +207,10 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, Toast.LENGTH_SHORT ).show() - viewModel.resetError() + launch { + delay(500) + viewModel.resetError() + } } } } @@ -191,7 +220,7 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.showWhatsNew.collect { - if(it) { + if (it) { showReleaseNote(requireActivity()) viewModel.whatsNewShown() @@ -213,9 +242,11 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, binding.buttonAddList.setOnClickListener { editListDialog(requireContext()) { list -> - createList( - list - ) + lifecycleScope.launch { + createList( + list + ) + } }.show() } @@ -265,6 +296,11 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, binding.buttonClearComment.isVisible = uiState.showButtonClearComment } + override fun onStart() { + super.onStart() + viewModel.refreshAllLists() + } + override fun onResume() { super.onResume() @@ -289,10 +325,10 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, Toast.makeText(activity, e.message, Toast.LENGTH_LONG).show() } } + arguments?.clear() + viewModel.refreshAllLists() } - arguments?.clear() - viewModel.refreshAllLists() } private fun setupListsRecyclerView() { @@ -343,7 +379,7 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, // Lists handlers - private fun createList(itemList: ItemList) { + private suspend fun createList(itemList: ItemList) { listsAdapter.notifyItemInserted(viewModel.allLists.value.size) viewModel.createList(itemList) binding.addItemEditText.requestFocus() @@ -364,7 +400,29 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, itemsAdapter .notifyItemRangeRemoved(0, viewModel.selectedList.value.items.size) listsAdapter.notifyItemRemoved(viewModel.allLists.value.indexOf(itemList)) - viewModel.removeList(itemList, action and ACTION_RM_FILE != 0) + lifecycleScope.launch { + try { + viewModel.removeList(itemList, action and ACTION_RM_FILE != 0, + onFieDeleted = { + activity?.runOnUiThread { + Toast.makeText( + requireContext(), + getString(R.string.file_deleted), + Toast.LENGTH_LONG + ).show() + } + } + ) + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText( + requireContext(), + getString(R.string.error_deleting_list_file), + Toast.LENGTH_SHORT + ).show() + } + } + } hideEditionButtons() }.show() } @@ -383,7 +441,8 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, viewModel.allLists.value[position].items.size ) - (binding.itemsRecyclerView.itemAnimator as DraggableItemAnimator).supportsChangeAnimations = false + (binding.itemsRecyclerView.itemAnimator as DraggableItemAnimator).supportsChangeAnimations = + false } override fun onListAdapterStartDrag() = showEditionButtons() @@ -397,23 +456,25 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, // Items handlers : private fun addItem(item: Item) { - if (item.title.isNotEmpty()) { - if (viewModel.allLists.value.isEmpty()) { - createList(ItemList(title = getString(R.string.list_default_name))) - } - viewModel.addItem(item) - val position = viewModel.selectedList.value.items.indexOf(item) + lifecycleScope.launch { + if (item.title.isNotEmpty()) { + if (viewModel.allLists.value.isEmpty()) { + createList(ItemList(title = getString(R.string.list_default_name))) + } + viewModel.addItem(item) + val position = viewModel.selectedList.value.items.indexOf(item) - itemsAdapter.notifyItemInserted(position) - binding.itemsRecyclerView.smoothScrollToPosition(0) - binding.addItemEditText.setText(R.string.empty) - binding.addItemEditText.requestFocus() + itemsAdapter.notifyItemInserted(position) + binding.itemsRecyclerView.smoothScrollToPosition(0) + binding.addItemEditText.setText(R.string.empty) + binding.addItemEditText.requestFocus() - if (binding.addCommentEditText.text.isEmpty() && isAddCommentShown) { - switchCommentSection() - } + if (binding.addCommentEditText.text.isEmpty() && isAddCommentShown) { + switchCommentSection() + } - } else listOf(binding.addItemEditText, binding.validate).forEach { it.shake() } + } else listOf(binding.addItemEditText, binding.validate).forEach { it.shake() } + } } override fun onRemoveItem(item: Item) { diff --git a/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragmentViewModel.kt b/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragmentViewModel.kt index ff48fbe1..dc231cbc 100644 --- a/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragmentViewModel.kt +++ b/app/src/main/java/com/lolo/io/onelist/feature/lists/OneListFragmentViewModel.kt @@ -9,6 +9,7 @@ import com.lolo.io.onelist.BuildConfig import com.lolo.io.onelist.R import com.lolo.io.onelist.core.data.model.AllListsWithErrors import com.lolo.io.onelist.core.data.model.ErrorLoadingList +import com.lolo.io.onelist.core.data.model.Resource import com.lolo.io.onelist.core.data.shared_preferences.SharedPreferencesHelper import com.lolo.io.onelist.core.domain.use_cases.OneListUseCases import com.lolo.io.onelist.core.model.Item @@ -20,6 +21,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -74,10 +76,8 @@ class OneListFragmentViewModel( _uiState.value = block(_uiState.value).apply { } } - fun createList(itemList: ItemList) { - viewModelScope.launch { - useCases.createList(itemList) - } + suspend fun createList(itemList: ItemList) { + useCases.createList(itemList) } fun editList(itemList: ItemList) { @@ -87,18 +87,20 @@ class OneListFragmentViewModel( } fun refreshAllLists() { + updateUiState { copy(isRefreshing = true) } viewModelScope.launch { - updateUiState { copy(isRefreshing = true) } getAllLists() - updateUiState { copy(isRefreshing = false) } + _forceRefreshTrigger.value++ } } - fun removeList(itemList: ItemList, deleteBackupFile: Boolean) { - viewModelScope.launch { - useCases.removeList(itemList, deleteBackupFile) - } + suspend fun removeList( + itemList: ItemList, + deleteBackupFile: Boolean, + onFieDeleted: () -> Unit + ) { + useCases.removeList(itemList, deleteBackupFile, onFieDeleted) } fun selectList(position: Int) { @@ -205,8 +207,10 @@ class OneListFragmentViewModel( private fun getAllLists() { getAllListsJob?.cancel() viewModelScope.launch { + updateUiState { copy(isRefreshing = true) } useCases.getAllLists().onEach { allListsResources.value = it + updateUiState { copy(isRefreshing = false) } }.launchIn(this) } } diff --git a/app/src/main/java/com/lolo/io/onelist/feature/lists/di/ListsModule.kt b/app/src/main/java/com/lolo/io/onelist/feature/lists/di/ListsModule.kt index 519906fd..f8dde0d4 100644 --- a/app/src/main/java/com/lolo/io/onelist/feature/lists/di/ListsModule.kt +++ b/app/src/main/java/com/lolo/io/onelist/feature/lists/di/ListsModule.kt @@ -1,5 +1,6 @@ package com.lolo.io.onelist.feature.lists.di +import com.lolo.io.onelist.core.data.migration.UpdateHelper import com.lolo.io.onelist.feature.lists.OneListFragmentViewModel import com.lolo.io.onelist.feature.lists.tuto.FirstLaunchLists import org.koin.androidx.viewmodel.dsl.viewModel @@ -12,4 +13,8 @@ val listsModule = module { single { FirstLaunchLists(get()) } + + single { + UpdateHelper(get(), get()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/feature/lists/dialogs/StorageDialog.kt b/app/src/main/java/com/lolo/io/onelist/feature/lists/dialogs/StorageDialog.kt deleted file mode 100644 index 3184c190..00000000 --- a/app/src/main/java/com/lolo/io/onelist/feature/lists/dialogs/StorageDialog.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.lolo.io.onelist.feature.lists.dialogs - -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.view.LayoutInflater -import androidx.appcompat.app.AlertDialog -import com.codekidlabs.storagechooser.Content -import com.codekidlabs.storagechooser.StorageChooser -import com.lolo.io.onelist.App -import com.lolo.io.onelist.MainActivity -import com.lolo.io.onelist.R -import com.lolo.io.onelist.core.data.utils.withStoragePermission -import com.lolo.io.onelist.core.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE -import com.lolo.io.onelist.databinding.DialogListPathBinding - - -@SuppressLint("InflateParams") -fun defaultPathDialog(activity: MainActivity, onPathChosen: (String) -> Unit) { - val view = LayoutInflater.from(activity).inflate(R.layout.dialog_list_path, null) - val binding = DialogListPathBinding.bind(view) - binding.listPathTitle.text = activity.getString(R.string.default_storage_folder) - displayDialog(binding, activity, onPathChosen) -} - -@SuppressLint("InflateParams") -fun storagePathDialog(activity: MainActivity, onPathChosen: (String) -> Unit) { - val view = LayoutInflater.from(activity).inflate(R.layout.dialog_list_path, null) - val binding = DialogListPathBinding.bind(view) - displayDialog(binding, activity, onPathChosen) -} - -fun displayDialog( - binding: DialogListPathBinding, - activity: MainActivity, - onPathChosen: (String) -> Unit -) { - - val dialog = AlertDialog.Builder(activity).run { - setView(binding.root) - create() - }.apply { - setCanceledOnTouchOutside(true) - show() - } - - binding.apply { - appPrivateStorageButton.setOnClickListener { - onPathChosen("") - dialog.dismiss() - } - chooseFolderButton.setOnClickListener { - selectDirectory(activity, onPathChosen) - dialog.dismiss() - } - - helpChangePath.setOnClickListener { - AlertDialog.Builder(activity) - .setMessage(R.string.changeFolderHelp) - .setPositiveButton(activity.getString(R.string.ok_with_tick), null) - .create().apply { - setCanceledOnTouchOutside(true) - }.show() - } - cancelChangePath.setOnClickListener { dialog.dismiss() } - - } -} - -fun selectDirectory(activity: MainActivity, onPathChosen: (String) -> Any?) { - withStoragePermission(activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity.onPathChosenActivityResult = onPathChosen - activity.startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) - }, REQUEST_CODE_OPEN_DOCUMENT_TREE) - } else { - @Suppress("DEPRECATION") - StorageChooser.Builder() - .withActivity(activity) - .withContent(storageChooserLocales(activity)) - .withFragmentManager(activity.fragmentManager) // activity.fragmentManager deprecated, but lib StorageChooser hasn't fully migrated to androidx yet. - .withMemoryBar(true) - .allowCustomPath(true) - .allowAddFolder(true) - .setType(StorageChooser.DIRECTORY_CHOOSER) - .build() - .apply { - show() - setOnSelectListener { - onPathChosen(it) - } - } - } - } -} - -fun selectFile(activity: MainActivity, onUri: (Uri) -> Any?) { - activity.storageHelper.openFilePicker() - activity.storageHelper.onFileSelected = { _, files -> - onUri(files[0].uri) - } - -/* - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity.onPathChosenActivityResult = onPathChosen - activity.startActivityForResult( - Intent(Intent.ACTION_OPEN_DOCUMENT).apply { type = "* / *" }, - REQUEST_CODE_OPEN_DOCUMENT - ) - } else { - @Suppress("DEPRECATION") - StorageChooser.Builder() - .withActivity(activity) - .withFragmentManager(activity.fragmentManager) // activity.fragmentManager deprecated, but lib StorageChooser hasn't fully migrated to androidx yet. - .withMemoryBar(true) - .allowCustomPath(true) - .setType(StorageChooser.FILE_PICKER) - .build() - .apply { - show() - setOnSelectListener { - if (it.endsWith(".1list")) { - onPathChosen(it) - } else { - Toast.makeText( - activity, - activity.getString(R.string.not_a_1list_file), - Toast.LENGTH_LONG - ).show() - } - } - } - } -*/ -} - -fun storageChooserLocales(context: Context) = Content().apply { - selectLabel = context.getString(R.string.storage_chooser_select_label) - createLabel = context.getString(R.string.storage_chooser_create_label) - newFolderLabel = context.getString(R.string.storage_chooser_new_folder_label) - cancelLabel = context.getString(R.string.storage_chooser_cancel_label) - overviewHeading = context.getString(R.string.storage_chooser_overview_heading) - internalStorageText = context.getString(R.string.storage_chooser_internal_storage_text) - freeSpaceText = context.getString(R.string.storage_chooser_free_space_text) - folderCreatedToastText = - context.getString(R.string.storage_chooser_folder_created_toast_text) - folderErrorToastText = context.getString(R.string.storage_chooser_folder_error_toast_text) - textfieldHintText = context.getString(R.string.storage_chooser_text_field_hint_text) - textfieldErrorText = context.getString(R.string.storage_chooser_text_field_error_text) -} \ No newline at end of file diff --git a/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragment.kt b/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragment.kt index d2a51143..4a5830db 100644 --- a/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragment.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle @@ -16,7 +17,9 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import androidx.preference.get +import com.anggrayudi.storage.file.DocumentFileCompat import com.anggrayudi.storage.file.getAbsolutePath +import com.anggrayudi.storage.file.toTreeDocumentFile import com.lolo.io.onelist.R import com.lolo.io.onelist.databinding.FragmentSettingsBinding import com.lolo.io.onelist.feature.lists.utils.StorageHelperHolder @@ -24,7 +27,7 @@ import isNotNullOrEmpty import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.getViewModel -class SettingsFragment() : PreferenceFragmentCompat() { +class SettingsFragment : PreferenceFragmentCompat() { private val viewModel by lazy { getViewModel() } @@ -82,6 +85,7 @@ class SettingsFragment() : PreferenceFragmentCompat() { it != null displayDefaultPath() setBackupOptionsVisible(it.isNotNullOrEmpty()) + (this@SettingsFragment.preferenceScreen.get("preferUseFiles") as? SwitchPreference)?.isChecked = viewModel.preferUseFiles } } } @@ -124,21 +128,49 @@ class SettingsFragment() : PreferenceFragmentCompat() { storageHolder?.storageHelper?.openFilePicker() storageHolder?.storageHelper?.onFileSelected = { _, files -> lifecycleScope.launch { - viewModel.importList(files[0].uri) + try { + val treeUriIfPossible = DocumentFileCompat.fromUri(requireContext(), files[0].uri) + ?.toTreeDocumentFile(requireContext())?.uri + ?: files[0].uri + + viewModel.importList(treeUriIfPossible) + + Toast.makeText( + activity, + getString(R.string.list_imported), Toast.LENGTH_SHORT + ).show() + } catch (e: Exception) { + Toast.makeText( + activity, + getString(R.string.error_import_list), + Toast.LENGTH_SHORT + ).show() + } } } - } "backup_all" -> { - viewModel.backupAllListsOnDevice() + lifecycleScope.launch { + try { + viewModel.backupAllListsOnDevice() + Toast.makeText( + activity, + getString(R.string.success_all_backup), Toast.LENGTH_SHORT + ).show() + } catch (e: Exception) { + Toast.makeText( + activity, + getString(R.string.error_all_backup), Toast.LENGTH_SHORT + ).show() + } + } } "preferUseFiles" -> { viewModel.onPreferUseFiles() } - "releaseNote" -> showReleaseNote(requireActivity()) } return true diff --git a/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragmentViewModel.kt b/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragmentViewModel.kt index 5fc40d3f..0a2a492e 100644 --- a/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragmentViewModel.kt +++ b/app/src/main/java/com/lolo/io/onelist/feature/settings/SettingsFragmentViewModel.kt @@ -20,6 +20,9 @@ class SettingsFragmentViewModel( private var _backupDisplayPath = MutableStateFlow(null) val backupDisplayPath = _backupDisplayPath.asStateFlow() + val preferUseFiles + get() = preferences.preferUseFiles + val version: String get() = preferences.version @@ -28,6 +31,9 @@ class SettingsFragmentViewModel( } fun setBackupPath(uri: Uri?, displayPath: String? = null) { + if(uri == null) { + preferences.preferUseFiles = false + } viewModelScope.launch { useCases.setBackupUri(uri, displayPath) _backupDisplayPath.value = displayPath @@ -38,10 +44,8 @@ class SettingsFragmentViewModel( return useCases.importList(uri) } - fun backupAllListsOnDevice() { - viewModelScope.launch { - useCases.syncAllLists() - } + suspend fun backupAllListsOnDevice() { + useCases.syncAllLists() } fun onPreferUseFiles() { diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1c7a36e8..6290c8d6 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -43,17 +43,6 @@ Les modifications de fichier simultanées ne sont pas bien prises en charge par 1List. Le nouveau contenu écrasera l\'ancien contenu du fichier. Veillez à rafraîchir votre liste avant de l\'éditer si vous utilisez un fichier partagé. (Tirez la liste vers le bas pour la rafraîchir) Ce fichier n\'est pas un fichier .1list ✓ OK - Sélectionner - Créer - Nouveau dossier - Annuler - Périphériques de stockage - Stockage Interne - %s libre - Dossier Créé - Erreur lors de la création du dossier. Veuillez rééssayer. - Nom du nouveau dossier - Ce dossier doit être nommé Certaines listes n\'ont pas pu être chargées depuis vos fichiers Fichier inaccessible Permission refusée @@ -64,8 +53,12 @@ Si activée, vos listes seront récupérées à partir des fichiers de sauvegarde. Utile si vous synchronisez votre dossier de sauvegarde avec d\'autres appareils. Chaque liste est sauvegardée à chaque modification. Cliquez ici pour forcer une sauvegarde maintenant. Obtenir des listes à partir des fichiers de sauvegarde - Importer une liste depuis votre système de fichiers (fichier .1list) + Importer une liste depuis votre système de fichiers (fichier .1list).\nSi le fichier se trouve dans votre dossier de sauvegarde, le même fichier sera utilisé pour les sauvegardes ; sinon, un nouveau fichier sera créé dans votre dossier de sauvegarde. Importer une liste Sauvegarder les listes sur l\'appareil Sauvegarder toutes les listes + Votre liste a été importée. + Erreur lors de l\'importation de votre liste : Ce fichier ne peut pas être importé. + Toutes vos listes ont été sauvegardées. + Une erreur s\'est produite lors de la sauvegarde. diff --git a/app/src/main/res/values-fr/strings_whatsnew.xml b/app/src/main/res/values-fr/strings_whatsnew.xml index dd35a2ac..c22a87ca 100644 --- a/app/src/main/res/values-fr/strings_whatsnew.xml +++ b/app/src/main/res/values-fr/strings_whatsnew.xml @@ -2,9 +2,9 @@ Changement Majeur ! La façon dont vous enregistrez vos listes sur votre appareil a changé. - \n\nSi vous avez utilisiez la fonctionnalité de sauvegarde locale de vos listes, allez dans les paramètres, cochez "Sauvegarder les listes sur l'appareil", et sélectionnez ou créez un dossier où vos listes seront enregistrées. -\n\nSi vous avez utilisiez cette fonctionnalité, vos fichiers de listes précédents ne seront plus mis à jour. Assurez-vous de réaliser cette opération afin de sauvegarder à nouveau vos listes. -\n\nSi vous n\'avez pas utilisé cette fonctionnalité auparavant, vous pouvez ignorer ce message. + \n\nSi vous utilisiez la fonctionnalité de sauvegarde locale de vos listes, allez dans les paramètres, cochez "Sauvegarder les listes sur l'appareil", et sélectionnez ou créez un dossier où vos listes seront enregistrées. +\n\nSi vous utilisiez cette fonctionnalité, vos fichiers de listes précédents ne seront plus mis à jour. Assurez-vous de réaliser cette opération afin de sauvegarder à nouveau vos listes. Cela crééra de nouveaux fichiers de sauvegarde. +\n\nSi vous n\'utilisiez pas cette fonctionnalité, vous pouvez ignorer ce message. \n\nL\'importation des listes a également été déplacée dans les paramètres. Continuer \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 02893856..311ad56e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -45,17 +45,6 @@ Modificações simultâneas de arquivos não são tratadas adequadamente no 1List. O novo conteúdo substituirá o arquivo. Atualize sua lista antes de editá-la se estiver usando um arquivo compartilhado. (Puxe a lista para baixo para atualizá-la) O arquivo selecionado não é do tipo .1list ✓ OK - Selecionar - Criar - Nova Pasta - Cancelar - Dispositivos de armazenamento - Armazenamento interno - %s livre - Pasta Criada - Erro ao criar pasta. Tente novamente. - Nome Da Pasta - Nome Da Pasta Vazio Algumas listas não puderam ser carregadas do seu sistema de arquivos Arquivo inacessível Permissão negada @@ -66,8 +55,12 @@ Se ativada, suas listas serão recuperadas a partir dos arquivos de backup. Isso é útil se você sincronizar sua pasta de backup com outros dispositivos. Cada lista é salva toda vez que você a modificar. Clique aqui para forçar uma backup agora. Obter listas a partir de arquivos de backup - Importar uma lista do seu sistema de arquivos (arquivo .1list) + Importar uma lista do seu sistema de arquivos (arquivo .1list).\nSe o arquivo estiver na sua pasta de backup, o mesmo arquivo será usado para backups; caso contrário, um novo arquivo será criado na sua pasta de backup. Importar uma lista Fazer backup das listas no dispositivo Fazer backup de todas as listas + Sua lista foi importada. + Erro ao importar sua lista: Este arquivo não pode ser importado. + Todas as suas listas foram salvas. + Ocorreu um erro ao processar a cópia de segurança. diff --git a/app/src/main/res/values-pt-rBR/strings_whatsnew.xml b/app/src/main/res/values-pt-rBR/strings_whatsnew.xml index c30ccbf2..b428f86e 100644 --- a/app/src/main/res/values-pt-rBR/strings_whatsnew.xml +++ b/app/src/main/res/values-pt-rBR/strings_whatsnew.xml @@ -3,7 +3,7 @@ Mudança Importante! A maneira como você salva suas listas em seu dispositivo mudou. \n\nSe você utilizava a funcionalidade de backup local de suas listas, vá para as configurações, marque "Fazer backup de listas no dispositivo" e selecione ou crie uma pasta onde suas listas serão salvas. -\n\nSe você utilizava essa funcionalidade, seus arquivos de listas anteriores não serão mais atualizados. Certifique-se de realizar esta operação para ter suas listas novamente salvas. +\n\nSe você utilizava essa funcionalidade, seus arquivos de listas anteriores não serão mais atualizados. Certifique-se de realizar esta operação para ter suas listas novamente salvas. Isso criará novos arquivos de backup. \n\nSe você não utilizou essa funcionalidade anteriormente, pode ignorar esta mensagem. \n\nA importação de listas também foi movida para as configurações. Continuar diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27f7eda8..4f63fa83 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,17 +45,6 @@ Concurrent modifications of files are not properly handled in 1List. New content will overwrite the file. Be sure you refresh your list before editing it if you are using a shared file. (Pull the list down to refresh it) The selected file is not a .1list file. ✓ OK - Select - Create - New Folder - Cancel - Storage Devices - Internal Storage - %s free - Folder Created - Error occurred while creating folder. Try again. - Folder Name - Empty Folder Name Some lists could not be loaded from your filesystem File missing Permission denied @@ -66,8 +55,12 @@ If enabled, your lists will be fetched from the backup files. This is useful if you sync your backup folder with other devices. Each list is backed up every time you modify it. Click here to force a backup now. Get lists from backup files - Import a list from your filesystem (.1list file) + Import a list from your filesystem (.1list file).\nIf the file is in your backup folder, the same file will be used for backups; if not, a new file will be created in your backup folder. Import a list Backup lists on device Backup all lists + Your list has been imported. + Error while importing your list : This file can\'t be imported. + All your lists have been backed up. + An error occurred while processing backup. diff --git a/app/src/main/res/values/strings_whatsnew.xml b/app/src/main/res/values/strings_whatsnew.xml index 4d739156..7f1ea9b7 100644 --- a/app/src/main/res/values/strings_whatsnew.xml +++ b/app/src/main/res/values/strings_whatsnew.xml @@ -3,7 +3,7 @@ Breaking Change ! The way you save your lists on your device has changed. \n\nIf you used the feature to backup your lists locally before, go to settings, check \"Backup lists on device\", and select or create a folder where your lists will be saved. - \n\nIf you used this feature before, your previous list files won\'t be updated anymore, be sure you make this operation in order to have your lists backed up again. + \n\nIf you used this feature before, your previous list files won\'t be updated anymore, be sure you make this operation in order to have your lists backed up again. This will create new backup files. \n\nIf you didn\'t use this feature before, you can ignore this message. \n\n \n\nImport a list has also been moved into the settings. diff --git a/gradle.properties b/gradle.properties index f6720ae0..c03eac49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,6 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -android.defaults.buildfeatures.buildconfig=true android.enableJetifier=true android.nonFinalResIds=false android.nonTransitiveRClass=false