Skip to content

Commit

Permalink
[implementation] export lists on sdcard
Browse files Browse the repository at this point in the history
  • Loading branch information
lolo-io committed Dec 2, 2019
1 parent 112787e commit 645f481
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 116 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ android {
applicationId "com.lolo.io.onelist"
minSdkVersion 16
targetSdkVersion 28
versionCode 6
versionName "1.1.0"
versionCode 7
versionName "1.2.0"
vectorDrawables.useSupportLibrary = true
}

Expand Down
31 changes: 24 additions & 7 deletions app/src/main/java/com/lolo/io/onelist/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.lolo.io.onelist

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Debug
import android.view.MotionEvent
import androidx.appcompat.app.AppCompatActivity
import com.lolo.io.onelist.updates.ReleaseNote
import com.lolo.io.onelist.updates.show
import com.lolo.io.onelist.util.REQUEST_CODE_OPEN_DOCUMENT
import com.lolo.io.onelist.util.REQUEST_CODE_OPEN_DOCUMENT_TREE
import io.github.tonnyl.whatsnew.WhatsNew

class MainActivity : AppCompatActivity() {
Expand All @@ -16,12 +19,14 @@ class MainActivity : AppCompatActivity() {
// 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
}
set(value) {
if (this.isResumed) value()
else field = value
}
private var isResumed = false

var onPathChosenActivityResult: (String) -> Any? = {}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Expand All @@ -38,7 +43,7 @@ class MainActivity : AppCompatActivity() {
.commit()

// WORKAROUND FOR WHATSNEW LIB NOT HANDLING WELL CONFIG CHANGES
if(savedInstanceState != null) {
if (savedInstanceState != null) {
supportFragmentManager.findFragmentByTag(WhatsNew.TAG)
?.let { supportFragmentManager.beginTransaction().remove(it).commit() }
?.let { ReleaseNote.releasesNotes.entries.last().value.show(this) }
Expand Down Expand Up @@ -68,9 +73,21 @@ class MainActivity : AppCompatActivity() {
fun onDispatchTouchEvent(ev: MotionEvent)
}


override fun onSupportNavigateUp(): Boolean {
onBackPressed()
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_WRITE_URI_PERMISSION)
onPathChosenActivityResult(uri.toString())
onPathChosenActivityResult = { }
}
}
}
}
13 changes: 3 additions & 10 deletions app/src/main/java/com/lolo/io/onelist/OneListFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,25 +145,18 @@ class OneListFragment : Fragment(), ListsCallbacks, ItemsCallbacks, MainActivity
else buttonClearComment.visibility = View.GONE
}

allLists.addAll(persistence.getAllLists())

setupListsRecyclerView()
setupItemsRecyclerView()
}

override fun onResume() {
super.onResume()

allLists.apply {
clear()
addAll(persistence.getAllLists())
}

// in case files have changed during pause
listsAdapter.notifyDataSetChanged()
itemsAdapter.notifyDataSetChanged()

if (arguments?.containsKey("EXT_FILE_URI") == true) { // opened an external file
try {
val imported = persistence.createListFromUri(arguments?.getParcelable("EXT_FILE_URI"))
val imported = persistence.createListFromUri(arguments?.getParcelable("EXT_FILE_URI")?:throw IllegalArgumentException("uri must not be null"))
persistence.saveListAsync(imported)
allLists.add(imported)
persistence.updateListIdsTableAsync(allLists)
Expand Down
138 changes: 90 additions & 48 deletions app/src/main/java/com/lolo/io/onelist/PersistenceHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import android.widget.Toast
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.lolo.io.onelist.model.ItemList
import com.lolo.io.onelist.util.toUri
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.*

class PersistenceHelper(private val app: Activity) {

private val firstLaunchPrefCompat = "firstLaunch"
private val listsPrefsCompat = "lists"

var versionPref: String = "version"
private val selectedListPref = "selectedList"
private val listIdsPref = "listsIds"
Expand Down Expand Up @@ -49,32 +48,6 @@ class PersistenceHelper(private val app: Activity) {
editor.apply()
}

var firstLaunchCompat: Boolean
get() {
val sp = app.getPreferences(Context.MODE_PRIVATE)
return sp.getBoolean(firstLaunchPrefCompat, true)
}
set(value) {
val sp = app.getPreferences(Context.MODE_PRIVATE)
val editor = sp.edit()
editor.putBoolean(firstLaunchPrefCompat, value)
editor.apply()
}

val allListsCompat: List<ItemList>
get() {
val sp = app.getPreferences(Context.MODE_PRIVATE)
val gson = Gson()
val json = sp.getString(listsPrefsCompat, null)
var lists: List<ItemList> = ArrayList()
if (json != null) {
lists = gson.fromJson(json, object : TypeToken<List<ItemList>>() {
}.type)
}
return lists
}


fun getAllLists(): List<ItemList> {
return runBlocking {
listsIds = getListIdsTable()
Expand Down Expand Up @@ -102,7 +75,6 @@ class PersistenceHelper(private val app: Activity) {
}
}


fun updateListIdsTable(lists: List<ItemList>) {
listsIds = lists.map { it.stableId to it.path }.toMap()
val sp = app.getPreferences(Context.MODE_PRIVATE)
Expand All @@ -123,12 +95,13 @@ class PersistenceHelper(private val app: Activity) {
} else mapOf()
}

fun createListFromUri(uri: Uri?): ItemList {
fun createListFromUri(uri: Uri): ItemList {
try {
val gson = Gson()
val content = app.contentResolver.openInputStream(uri!!)?.bufferedReader()?.use { it.readText() }
val content = app.contentResolver.openInputStream(uri)?.bufferedReader()?.use { it.readText() }
val list = gson.fromJson(content, ItemList::class.java)
list.path = ""
if (uri.path?.startsWith("/tree/") == true) list.path = uri.toString()
require(!listsIds.containsKey(list.stableId)) { app.getString(R.string.list_already_in_your_lists) }
return list
} catch (e: IllegalArgumentException) {
Expand All @@ -138,40 +111,65 @@ class PersistenceHelper(private val app: Activity) {
}
}


fun importList(filePath: String): ItemList {
try {
val gson = Gson()
val json = File(filePath).readText()
val list = gson.fromJson(json, ItemList::class.java)
val fileUri = filePath.toUri
val list = fileUri?.let { uri ->
var ins: InputStream? = null
try {
ins = App.instance.contentResolver.openInputStream(uri)
gson.fromJson(ins!!.reader(), ItemList::class.java)
} catch (e: Exception) {
throw Exception()
} finally {
ins?.close()
}
} ?: filePath.takeIf { it.isNotBlank() }?.let {
val json = File(it).readText()
gson.fromJson(json, ItemList::class.java)
} ?: throw Exception()

require(!listsIds.containsKey(list.stableId)) { app.getString(R.string.list_already_in_your_lists) }
return list
} catch (e: IOException) {
throw IOException(app.getString(R.string.error_opening_file))
}
}


private fun getListAsync(listId: Long): Deferred<ItemList> {
return GlobalScope.async {
val filePath = listsIds[listId]
val path = listsIds[listId]
val gson = Gson()
val sp = app.getPreferences(Context.MODE_PRIVATE)
val list = (if (filePath?.isNotBlank() == true) {
val fileUri = path.toUri
val list = fileUri?.let { uri ->
var ins: InputStream? = null
try {
val json = File(filePath).readText()
ins = App.instance.contentResolver.openInputStream(uri)
gson.fromJson(ins!!.reader(), ItemList::class.java)
} catch (e: Exception) {
app.runOnUiThread { Toast.makeText(App.instance, app.getString(R.string.error_opening_filepath, uri), Toast.LENGTH_LONG).show() }
null
} finally {
ins?.close()
}
} ?: path.takeIf { it?.isNotBlank() == true }?.let {
try {
val json = File(path).readText()
gson.fromJson(json, ItemList::class.java)
} catch (e: Exception) {
app.runOnUiThread { Toast.makeText(App.instance, app.getString(R.string.error_opening_filepath, filePath), Toast.LENGTH_LONG).show() }
app.runOnUiThread { Toast.makeText(App.instance, app.getString(R.string.error_opening_filepath, path), Toast.LENGTH_LONG).show() }
null
}
} else null
) ?: gson.fromJson(sp.getString(listId.toString(), ""), ItemList::class.java)
list.apply { path = filePath ?: "" }
} ?: gson.fromJson(sp.getString(listId.toString(), ""), ItemList::class.java)

list.apply {
this.path = path ?: ""
}
}
}


fun saveListAsync(list: ItemList) {
GlobalScope.launch {
saveList(list)
Expand All @@ -183,12 +181,22 @@ class PersistenceHelper(private val app: Activity) {
val editor = sp.edit()
val gson = Gson()
val json = gson.toJson(list)
if (list.path.isNotBlank()) {
try {
try {
val fileUri = list.path.toUri
fileUri?.let { uri ->
val out = App.instance.contentResolver.openOutputStream(uri)
try {
out!!.write(json.toByteArray(Charsets.UTF_8)) // NPE is catched below
} catch (e: Exception) {
app.runOnUiThread { Toast.makeText(App.instance, app.getString(R.string.error_saving_to_path, list.path), Toast.LENGTH_LONG).show() }
} finally {
out?.close()
}
} ?: if (list.path.isNotBlank()) {
File(list.path).writeText(json)
} catch (e: Exception) {
app.runOnUiThread { Toast.makeText(App.instance, app.getString(R.string.error_saving_to_path, list.path), Toast.LENGTH_LONG).show() }
}
} catch (e: Exception) {
app.runOnUiThread { Toast.makeText(App.instance, app.getString(R.string.error_saving_to_path, list.path), Toast.LENGTH_LONG).show() }
}

// save in prefs anyway
Expand Down Expand Up @@ -232,4 +240,38 @@ class PersistenceHelper(private val app: Activity) {
editor.apply()
}
}

// Only to handle architecture updates between versions. do not use
val compat = Compat()

inner class Compat {

private val firstLaunchPrefCompat = "firstLaunch"
private val listsPrefsCompat = "lists"

val allListsCompat: List<ItemList>
get() {
val sp = app.getPreferences(Context.MODE_PRIVATE)
val gson = Gson()
val json = sp.getString(listsPrefsCompat, null)
var lists: List<ItemList> = ArrayList()
if (json != null) {
lists = gson.fromJson(json, object : TypeToken<List<ItemList>>() {
}.type)
}
return lists
}

var firstLaunchCompat: Boolean
get() {
val sp = app.getPreferences(Context.MODE_PRIVATE)
return sp.getBoolean(firstLaunchPrefCompat, true)
}
set(value) {
val sp = app.getPreferences(Context.MODE_PRIVATE)
val editor = sp.edit()
editor.putBoolean(firstLaunchPrefCompat, value)
editor.apply()
}
}
}
8 changes: 6 additions & 2 deletions app/src/main/java/com/lolo/io/onelist/PreferenceFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import androidx.preference.get
import com.lolo.io.onelist.dialogs.defaultPathDialog
import com.lolo.io.onelist.updates.ReleaseNote
import com.lolo.io.onelist.updates.show
import com.lolo.io.onelist.util.beautify
import com.lolo.io.onelist.util.toUri
import kotlinx.android.synthetic.main.fragment_settings.*

class PreferenceFragment : PreferenceFragmentCompat() {
Expand Down Expand Up @@ -61,8 +63,10 @@ class PreferenceFragment : PreferenceFragmentCompat() {
private fun displayDefaultPath() {
this.preferenceScreen.get<Preference>("storage")
?.summary =
if (mainActivity.persistence.defaultPath.isNotBlank())
mainActivity.persistence.defaultPath
if (mainActivity.persistence.defaultPath.isNotBlank()) {
val uri = mainActivity.persistence.defaultPath.toUri
uri?.path?.beautify() ?: mainActivity.persistence.defaultPath
}
else getString(R.string.app_private_storage)

}
Expand Down
Loading

0 comments on commit 645f481

Please sign in to comment.