diff --git a/.github/workflows/build_sample.yml b/.github/workflows/build_sample.yml index d4551dc..d5ac1cd 100644 --- a/.github/workflows/build_sample.yml +++ b/.github/workflows/build_sample.yml @@ -30,7 +30,6 @@ jobs: - name: Validate Gradle wrapper uses: gradle/wrapper-validation-action@v1.0.5 - - name: Decrypt the keystore for signing run: | echo "${{ secrets.KEYSTORE_ENCRYPTED }}" > keystore.asc diff --git a/README.md b/README.md index 20f0cff..e7a853e 100644 --- a/README.md +++ b/README.md @@ -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. - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19dcc50..98a413c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" @@ -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" } diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 5897047..65273ba 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -72,7 +72,7 @@ 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) @@ -80,4 +80,4 @@ dependencies { testImplementation(libraries.junit) androidTestImplementation(libraries.androidx.test.junit) androidTestImplementation(libraries.androidx.test.espresso) -} \ No newline at end of file +} diff --git a/sample/src/main/java/dev/arkbuilders/sample/storage/MapEntryDialog.kt b/sample/src/main/java/dev/arkbuilders/sample/storage/MapEntryDialog.kt index 4345d7a..d3d09b2 100644 --- a/sample/src/main/java/dev/arkbuilders/sample/storage/MapEntryDialog.kt +++ b/sample/src/main/java/dev/arkbuilders/sample/storage/MapEntryDialog.kt @@ -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) { diff --git a/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt b/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt index 40f1e72..fbf246f 100644 --- a/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt +++ b/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt @@ -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() } 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 + + 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) @@ -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" } } \ No newline at end of file diff --git a/sample/src/main/res/layout/fragment_storage_demo.xml b/sample/src/main/res/layout/fragment_storage_demo.xml index 8cb5333..736c819 100644 --- a/sample/src/main/res/layout/fragment_storage_demo.xml +++ b/sample/src/main/res/layout/fragment_storage_demo.xml @@ -1,118 +1,167 @@ - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> -