Consolidate autosave URI persistence management

This commit is contained in:
William Brawner 2021-02-21 18:32:53 -07:00
parent 1522806e62
commit 50d7622172
3 changed files with 50 additions and 61 deletions

View file

@ -1,7 +1,6 @@
package com.wbrawner.simplemarkdown.view.activity package com.wbrawner.simplemarkdown.view.activity
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -9,7 +8,6 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.viewmodel.PREF_KEY_AUTOSAVE_URI
import kotlinx.coroutines.* import kotlinx.coroutines.*
import timber.log.Timber import timber.log.Timber
@ -40,20 +38,12 @@ class SplashActivity : AppCompatActivity() {
} }
AppCompatDelegate.setDefaultNightMode(darkMode) AppCompatDelegate.setDefaultNightMode(darkMode)
val uri = withContext(Dispatchers.IO) { val uri = intent?.data?.let {
intent?.data?.let { Timber.d("Using uri from intent: $it")
Timber.d("Using uri from intent: $it") it
it } ?: run {
} ?: PreferenceManager.getDefaultSharedPreferences(this@SplashActivity)
.getString(PREF_KEY_AUTOSAVE_URI, null)
?.let {
Timber.d("Using uri from shared preferences: $it")
Uri.parse(it)
}
}
if (uri == null) {
Timber.d("No intent provided to load data from") Timber.d("No intent provided to load data from")
null
} }
val startIntent = Intent(this@SplashActivity, MainActivity::class.java) val startIntent = Intent(this@SplashActivity, MainActivity::class.java)

View file

@ -15,7 +15,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -28,7 +27,6 @@ import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.utility.errorHandlerImpl import com.wbrawner.simplemarkdown.utility.errorHandlerImpl
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
import com.wbrawner.simplemarkdown.viewmodel.PREF_KEY_AUTOSAVE_URI
import kotlinx.android.synthetic.main.fragment_main.* import kotlinx.android.synthetic.main.fragment_main.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -212,21 +210,9 @@ class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallba
} }
lifecycleScope.launch { lifecycleScope.launch {
val fileLoaded = context?.let { context?.let {
viewModel.load(it, data.data) if (!viewModel.load(it, data.data)) {
} Toast.makeText(it, R.string.file_load_error, Toast.LENGTH_SHORT).show()
if (fileLoaded == false) {
context?.let {
Toast.makeText(it, R.string.file_load_error, Toast.LENGTH_SHORT)
.show()
}
} else {
Timber.d(
"File load succeeded, updating autosave uri in shared prefs: %s",
data.data.toString()
)
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
putString(PREF_KEY_AUTOSAVE_URI, data.data.toString())
} }
} }
} }
@ -253,11 +239,10 @@ class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallba
private fun promptSaveOrDiscardChanges() { private fun promptSaveOrDiscardChanges() {
if (!viewModel.shouldPromptSave()) { if (!viewModel.shouldPromptSave()) {
viewModel.reset("Untitled.md") viewModel.reset(
Timber.i("Removing autosave uri from shared prefs") "Untitled.md",
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit { PreferenceManager.getDefaultSharedPreferences(requireContext())
remove(PREF_KEY_AUTOSAVE_URI) )
}
return return
} }
val context = context ?: run { val context = context ?: run {
@ -268,11 +253,11 @@ class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallba
.setTitle(R.string.save_changes) .setTitle(R.string.save_changes)
.setMessage(R.string.prompt_save_changes) .setMessage(R.string.prompt_save_changes)
.setNegativeButton(R.string.action_discard) { _, _ -> .setNegativeButton(R.string.action_discard) { _, _ ->
Timber.d("Discarding changes and deleting autosave uri from shared preferences") Timber.d("Discarding changes")
viewModel.reset("Untitled.md") viewModel.reset(
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit { "Untitled.md",
remove(PREF_KEY_AUTOSAVE_URI) PreferenceManager.getDefaultSharedPreferences(requireContext())
} )
} }
.setPositiveButton(R.string.action_save) { _, _ -> .setPositiveButton(R.string.action_save) { _, _ ->
Timber.d("Saving changes") Timber.d("Saving changes")

View file

@ -3,8 +3,10 @@ package com.wbrawner.simplemarkdown.viewmodel
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import androidx.core.content.edit
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.utility.getName import com.wbrawner.simplemarkdown.utility.getName
import com.wbrawner.simplemarkdown.view.fragment.MainFragment import com.wbrawner.simplemarkdown.view.fragment.MainFragment
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -32,10 +34,18 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
isDirty.set(true) isDirty.set(true)
} }
suspend fun load(context: Context, uri: Uri?): Boolean { suspend fun load(
context: Context,
uri: Uri?,
sharedPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
): Boolean {
if (uri == null) { if (uri == null) {
timber.i("Ignoring call to load null uri") timber.i("No URI provided to load, attempting to load last autosaved file")
return false sharedPrefs.getString(PREF_KEY_AUTOSAVE_URI, null)
?.let {
Timber.d("Using uri from shared preferences: $it")
return load(context, Uri.parse(it), sharedPrefs)
} ?: return false
} }
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
@ -55,6 +65,10 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
timber.i("Loaded file $fileName from $fileInput") timber.i("Loaded file $fileName from $fileInput")
timber.v("File contents:\n$content") timber.v("File contents:\n$content")
isDirty.set(false) isDirty.set(false)
timber.i("Persisting autosave uri in shared prefs: $uri")
sharedPrefs.edit()
.putString(PREF_KEY_AUTOSAVE_URI, uri.toString())
.apply()
true true
} ?: run { } ?: run {
timber.w("Open file descriptor returned null for uri: $uri") timber.w("Open file descriptor returned null for uri: $uri")
@ -67,7 +81,11 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
} }
} }
suspend fun save(context: Context, givenUri: Uri? = null): Boolean = saveMutex.withLock { suspend fun save(
context: Context,
givenUri: Uri? = null,
sharedPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
): Boolean = saveMutex.withLock {
val uri = givenUri?.let { val uri = givenUri?.let {
timber.i("Saving file with given uri: $it") timber.i("Saving file with given uri: $it")
it it
@ -94,6 +112,10 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
this@MarkdownViewModel.uri.postValue(uri) this@MarkdownViewModel.uri.postValue(uri)
isDirty.set(false) isDirty.set(false)
timber.i("Saved file $fileName to uri $uri") timber.i("Saved file $fileName to uri $uri")
timber.i("Persisting autosave uri in shared prefs: $uri")
sharedPrefs.edit()
.putString(PREF_KEY_AUTOSAVE_URI, uri.toString())
.apply()
true true
} catch (e: Exception) { } catch (e: Exception) {
timber.e(e, "Failed to save file at uri: $uri") timber.e(e, "Failed to save file at uri: $uri")
@ -114,37 +136,29 @@ class MarkdownViewModel(val timber: Timber.Tree = Timber.asTree()) : ViewModel()
return return
} }
val uri = if (save(context)) { if (save(context)) {
timber.i("Autosave with cached uri succeeded: ${uri.value}") timber.i("Autosave with cached uri succeeded: ${uri.value}")
uri.value
} else { } else {
// The user has left the app, with autosave enabled, and we don't already have a // The user has left the app, with autosave enabled, and we don't already have a
// Uri for them or for some reason we were unable to save to the original Uri. In // Uri for them or for some reason we were unable to save to the original Uri. In
// this case, we need to just save to internal file storage so that we can recover // this case, we need to just save to internal file storage so that we can recover
val fileUri = Uri.fromFile(File(context.filesDir, fileName.value ?: "Untitled.md")) val fileUri = Uri.fromFile(File(context.filesDir, fileName.value ?: "Untitled.md"))
timber.i("No cached uri for autosave, saving to $fileUri instead") timber.i("No cached uri for autosave, saving to $fileUri instead")
if (save(context, fileUri)) { save(context, fileUri)
fileUri
} else {
null
}
} ?: run {
timber.w("Unable to perform autosave, uri was null")
return@autosave
} }
timber.i("Persisting autosave uri in shared prefs: $uri")
sharedPrefs.edit()
.putString(PREF_KEY_AUTOSAVE_URI, uri.toString())
.apply()
} }
fun reset(untitledFileName: String) { fun reset(untitledFileName: String, sharedPrefs: SharedPreferences) {
timber.i("Resetting view model to default state") timber.i("Resetting view model to default state")
fileName.postValue(untitledFileName) fileName.postValue(untitledFileName)
uri.postValue(null) uri.postValue(null)
markdownUpdates.postValue("") markdownUpdates.postValue("")
editorActions.postValue(EditorAction.Load("")) editorActions.postValue(EditorAction.Load(""))
isDirty.set(false) isDirty.set(false)
timber.i("Removing autosave uri from shared prefs")
sharedPrefs.edit {
remove(PREF_KEY_AUTOSAVE_URI)
}
} }
fun shouldPromptSave() = isDirty.get() fun shouldPromptSave() = isDirty.get()