Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample for ark-core FileStorage JNI bindings usage #110

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build_sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ jobs:
- name: Validate Gradle wrapper
uses: gradle/[email protected]


- name: Decrypt the keystore for signing
run: |
echo "${{ secrets.KEYSTORE_ENCRYPTED }}" > keystore.asc
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,3 @@ git push --tags

- Create an action build file under `.github/workflows/` folder, following any existing build script.
- Create a release build file under `.github/workflows/` folder, following any existing release script.

2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ coil = "2.4.0"
androidxFragmentKtx = "1.6.1"
fastadapter = "5.7.0"
arkLib = "0.3.5"
core = "1.0.0"
orbitMvi = "6.1.0"
viewbindingPropertyDelegate = "1.5.9"
androidXCore = "1.9.0"
Expand All @@ -28,6 +29,7 @@ fastadapter = { group = "com.mikepenz", name = "fastadapter", version.ref = "fas
fastadapter-extensions-binding = { group = "com.mikepenz", name = "fastadapter-extensions-binding", version.ref = "fastadapter" }
fastadapter-extensions-diff = { group = "com.mikepenz", name = "fastadapter-extensions-diff", version.ref = "fastadapter" }
arklib = { group = "dev.arkbuilders", name = "arklib", version.ref = "arkLib" }
core = { group = "dev.arkbuilders", name = "core", version.ref = "core" }
orbit-mvi-viewmodel = { group = "org.orbit-mvi", name = "orbit-viewmodel", version.ref = "orbitMvi" }
viewbinding-property-delegate = { group = "com.github.kirich1409", name = "viewbindingpropertydelegate-noreflection", version.ref = "viewbindingPropertyDelegate" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidXCore" }
Expand Down
4 changes: 2 additions & 2 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ android {
dependencies {
implementation(project(":filepicker"))
implementation(project(":about"))

implementation(libraries.core)
implementation(libraries.arklib)
implementation("androidx.core:core-ktx:1.12.0")
implementation(libraries.androidx.appcompat)
implementation(libraries.android.material)
testImplementation(libraries.junit)
androidTestImplementation(libraries.androidx.test.junit)
androidTestImplementation(libraries.androidx.test.espresso)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class MapEntryDialog(private val isDelete: Boolean,
false
}
} else {
mBinding.tvTitle.text = getString(R.string.new_map_entry)
mBinding.tvTitle.text = getString(R.string.set_map_entry)
mBinding.edtValue.visibility = View.VISIBLE
mBinding.edtValue.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,48 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.DialogFragment
import dev.arkbuilders.components.filepicker.ArkFilePickerConfig
import dev.arkbuilders.components.filepicker.ArkFilePickerFragment
import dev.arkbuilders.components.filepicker.ArkFilePickerMode
import dev.arkbuilders.components.filepicker.onArkPathPicked
import dev.arkbuilders.core.FileStorage
import dev.arkbuilders.sample.R
import dev.arkbuilders.sample.databinding.FragmentStorageDemoBinding
import dev.arkbuilders.sample.extension.getAbsolutePath
import java.io.File
import java.util.UUID
import kotlin.io.path.Path
import kotlin.io.path.exists

class StorageDemoFragment: DialogFragment() {
class StorageDemoFragment : DialogFragment() {

private val TAG = StorageDemoFragment::class.java.name

private lateinit var binding: FragmentStorageDemoBinding
private val map by lazy { mutableMapOf<String, String>() }
private var workingDir: String = "/"

private val selectDirRequest = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
uri?.let {
// call this to persist permission across device reboots
context?.contentResolver?.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
workingDir = uri.getAbsolutePath()
refreshFilesTree()
private var storage: FileStorage? = null
oluiscabral marked this conversation as resolved.
Show resolved Hide resolved

private val selectDirRequest =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
uri?.let {
// call this to persist permission across device reboots
context?.contentResolver?.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
workingDir = uri.getAbsolutePath()
val storageName = UUID.randomUUID().toString().take(6) + ".txt"
binding.edtStorageName.setText(storageName)
newStorage(storageName)
updateDisplayMap()
}
}
}

private fun getCurrentAbsolutePath(): String {
return workingDir + "/" + binding.edtStoragePath.text.toString()
}

override fun onCreate(savedInstanceState: Bundle?) {
setStyle(STYLE_NORMAL, R.style.Theme_ArkComponents)
Expand All @@ -48,72 +58,163 @@ class StorageDemoFragment: DialogFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val layoutInflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
val layoutInflater =
context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
binding = FragmentStorageDemoBinding.inflate(layoutInflater ?: LayoutInflater.from(context))
initViews(binding)
return binding.root
}

private fun initViews(binding: FragmentStorageDemoBinding) {
binding.btnWorkingDir.setOnClickListener {
private fun initViews(binding: FragmentStorageDemoBinding) = binding.apply {
updateBtnsEnabled(storageFileExists = false)

btnWorkingDir.setOnClickListener {
selectDirRequest.launch(null)
}

binding.edtStoragePath.setOnEditorActionListener { v, actionId, event ->
edtStorageName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
refreshFilesTree()
val storageName = v.text.toString()
newStorage(storageName)
return@setOnEditorActionListener true
}
false
}

binding.btnNewMapEntry.setOnClickListener {
MapEntryDialog(isDelete = false, onDone = { key, value ->
map[key] = value ?: ""
refreshMap()
}).show(parentFragmentManager, MapEntryDialog::class.java.name)
btnSetEntry.setOnClickListener {
MapEntryDialog(
isDelete = false,
onDone = { key, value ->
storage?.set(key, value)
updateDisplayMap()
}
).show(parentFragmentManager, MapEntryDialog::class.java.name)
}

binding.btnDeleteEntry.setOnClickListener {
MapEntryDialog(isDelete = true, onDone = { key, value ->
map.remove(key)
refreshMap()
}).show(parentFragmentManager, MapEntryDialog::class.java.name)
btnDeleteEntry.setOnClickListener {
MapEntryDialog(
isDelete = true,
onDone = { key, _ ->
storage?.remove(key)
updateDisplayMap()
}
).show(parentFragmentManager, MapEntryDialog::class.java.name)
}

binding.btnClearMap.setOnClickListener {
map.clear()
refreshMap()
btnSyncStatus.setOnClickListener {
storage?.syncStatus()?.let {
Toast.makeText(requireContext(), it.name, Toast.LENGTH_SHORT).show()
}
}

btnSync.setOnClickListener {
storage?.sync()
updateDisplayMap()
}

btnReadFs.setOnClickListener {
storage?.let {
val data = storage!!.readFS()
Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_SHORT).show()
updateDisplayMap()
}
}

btnWriteFs.setOnClickListener {
storage?.let {
storage!!.writeFS()
updateBtnsEnabled(storageFileExists = true)
}
}
}

private fun refreshFilesTree() {
val currentAbsolutePath = getCurrentAbsolutePath()
binding.tvCurrentAbsolutePath.text = currentAbsolutePath

try {
val currentDir = File(currentAbsolutePath)
currentDir.mkdirs()
val listFiles = currentDir.listFiles() ?: return
val fileTreeBuilder = StringBuilder()
for (file in listFiles) {
fileTreeBuilder.append(file.name).append("\n")
btnErase.setOnClickListener {
storage?.erase()
storage = null
binding.tvCurrentAbsolutePath.text = workingDir
binding.edtStorageName.setText("")
updateDisplayMap()
updateBtnsEnabled(storageFileExists = false)
}

btnMerge.setOnClickListener {
ArkFilePickerFragment.newInstance(
ArkFilePickerConfig(
pathPickedRequestKey = mergeFileRequestKey,
initialPath = Path(workingDir),
mode = ArkFilePickerMode.FILE
)
).show(childFragmentManager, "merge")
}

childFragmentManager.onArkPathPicked(
lifecycleOwner = this@StorageDemoFragment,
customRequestKey = mergeFileRequestKey
) { pickedPath ->
storage?.merge(FileStorage("label", pickedPath.toString()))
updateDisplayMap()
}

btnGetId.setOnClickListener {
val id = binding.edtGetId.text.toString()
storage?.let {
val value = storage!!.get(id)
Toast.makeText(
requireContext(),
value ?: "NOT FOUND",
Toast.LENGTH_SHORT
).show()
}
binding.tvCurrentFileTree.text = fileTreeBuilder.toString()
} catch (e: Exception) {
Log.e(TAG, e.message.toString())
}
}

private fun refreshMap() {
if (map.isEmpty()) {
private fun newStorage(name: String) {
val absolutePath = "$workingDir/$name"
storage = FileStorage(name, absolutePath)
binding.tvCurrentAbsolutePath.text = absolutePath
updateDisplayMap()
updateBtnsEnabled(Path(absolutePath).exists())
}

private fun updateDisplayMap() {
storage ?: let {
binding.tvMapValues.text = getString(R.string.empty_map)
return
}
val mapEntries = StringBuilder()
for (entry in map) {
mapEntries.append(entry.key).append(" -> ").append(entry.value).append("\n")
storage!!.iterator().forEach { (key, value) ->
mapEntries.append(key).append(" -> ").append(value).append("\n")
}
binding.tvMapValues.text = mapEntries.toString()
binding.tvMapValues.text = mapEntries.toString().ifEmpty { getString(R.string.empty_map) }
}

private fun updateBtnsEnabled(storageFileExists: Boolean) {
val allBtns = with(binding) {
listOf(
btnSetEntry,
btnDeleteEntry,
btnSyncStatus,
btnSync,
btnReadFs,
btnWriteFs,
btnErase,
btnMerge,
btnGetId
)
}
val btnsRequireStorageFileExists =
with(binding) { listOf(btnSyncStatus, btnSync, btnReadFs, btnErase) }

storage?.let {
allBtns.forEach { it.isEnabled = true }
if (storageFileExists.not()) {
btnsRequireStorageFileExists.forEach { it.isEnabled = false }
}
} ?: let {
allBtns.forEach { it.isEnabled = false }
}
}

companion object {
private val mergeFileRequestKey = "merge"
}
}
Loading
Loading