From cc4e8965eb00c1362602c0c31954fad2f408c2d0 Mon Sep 17 00:00:00 2001 From: oluiscabral Date: Fri, 1 Nov 2024 16:07:25 -0300 Subject: [PATCH 01/15] Download and include `ark-core` JNI libs release during build --- .github/workflows/build_sample.yml | 6 +++- README.md | 36 +++++++++++++++++++ gradle/libs.versions.toml | 1 + sample/build.gradle.kts | 4 +-- .../sample/storage/StorageDemoFragment.kt | 35 +++++++++++------- settings.gradle.kts | 10 +++++- 6 files changed, 76 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build_sample.yml b/.github/workflows/build_sample.yml index d4551dc..e7ccf52 100644 --- a/.github/workflows/build_sample.yml +++ b/.github/workflows/build_sample.yml @@ -30,12 +30,16 @@ 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 gpg -d --passphrase "${{ secrets.KEYSTORE_PASSWORD }}" --batch keystore.asc > keystore.jks + - name: Download and extract `ark-core` JNI libs + run: | + wget https://github.com/ARK-Builders/ark-core/releases/download/v1.0.0/jniLibs.zip + unzip -d sample/src/main jniLibs.zip + - name: Build release sample APK run: ./gradlew sample:assembleRelease diff --git a/README.md b/README.md index 20f0cff..f38e941 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,39 @@ 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. +## Making the Storage Demo work + +This section guides you through setting up and running the Storage Demo subpage. + +**1. Download the `fs-storage` JNI Libraries:** + +The demo requires JNI libraries (libs). Download these from the following location: + +* **[ark-core repository](https://github.com/ARK-Builders/ark-core)** + + - If you can't find them in the "Releases" section, check the latest successful build actions for artifacts. + +**2. Place the Libraries:** + +After downloading, move the JNI library files into your project's `sample/src/main/jniLibs` directory. **If the path doesn't exist, create it** + +Your project structure should resemble this: + +``` +... +sample/ + ... + src/ + main/ + ... + jniLibs/ + arm64-v8a/ + armeabi-v7a/ + x86/ + x86_64/ + ... + ... + ... +``` + +With the `fs-storage` JNI libraries in place, you're ready to build, run the project and use the Storage Demo subpage. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19dcc50..b1696b9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,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.core", name = "lib", version = "1.0-SNAPSHOT"} 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/StorageDemoFragment.kt b/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt index 40f1e72..caf4331 100644 --- a/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt +++ b/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.DialogFragment +import dev.arkbuilders.core.FileStorage import dev.arkbuilders.sample.R import dev.arkbuilders.sample.databinding.FragmentStorageDemoBinding import dev.arkbuilders.sample.extension.getAbsolutePath @@ -21,8 +22,8 @@ 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 var storage: FileStorage? = null private val selectDirRequest = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> uri?.let { @@ -61,7 +62,10 @@ class StorageDemoFragment: DialogFragment() { binding.edtStoragePath.setOnEditorActionListener { v, actionId, event -> if (actionId == EditorInfo.IME_ACTION_DONE) { - refreshFilesTree() + val relativeStoragePath = v.text + storage = FileStorage(relativeStoragePath.toString(), + "$workingDir/$relativeStoragePath" + ) return@setOnEditorActionListener true } false @@ -69,21 +73,27 @@ class StorageDemoFragment: DialogFragment() { binding.btnNewMapEntry.setOnClickListener { MapEntryDialog(isDelete = false, onDone = { key, value -> - map[key] = value ?: "" - refreshMap() + if (storage != null) { + storage!!.set(key, value) + } + refreshStorage() }).show(parentFragmentManager, MapEntryDialog::class.java.name) } binding.btnDeleteEntry.setOnClickListener { - MapEntryDialog(isDelete = true, onDone = { key, value -> - map.remove(key) - refreshMap() + MapEntryDialog(isDelete = true, onDone = { key, _ -> + if (storage != null) { + storage!!.remove(key) + } + refreshStorage() }).show(parentFragmentManager, MapEntryDialog::class.java.name) } binding.btnClearMap.setOnClickListener { - map.clear() - refreshMap() + if (storage != null) { + storage!!.erase() + } + refreshStorage() } } @@ -105,13 +115,14 @@ class StorageDemoFragment: DialogFragment() { } } - private fun refreshMap() { - if (map.isEmpty()) { + private fun refreshStorage() { + if (storage == null) { binding.tvMapValues.text = getString(R.string.empty_map) return } + storage!!.writeFS() val mapEntries = StringBuilder() - for (entry in map) { + for (entry in storage!!) { mapEntries.append(entry.key).append(" -> ").append(entry.value).append("\n") } binding.tvMapValues.text = mapEntries.toString() diff --git a/settings.gradle.kts b/settings.gradle.kts index b7b6b93..d5e9efb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,13 +16,21 @@ dependencyResolutionManagement { url = URI("https://jitpack.io") } maven { - name = "GitHubPackages" + name = "arklib-android GitHub Packages" url = URI("https://maven.pkg.github.com/ARK-Builders/arklib-android") credentials { username = "token" password = "\u0037\u0066\u0066\u0036\u0030\u0039\u0033\u0066\u0032\u0037\u0033\u0036\u0033\u0037\u0064\u0036\u0037\u0066\u0038\u0030\u0034\u0039\u0062\u0030\u0039\u0038\u0039\u0038\u0066\u0034\u0066\u0034\u0031\u0064\u0062\u0033\u0064\u0033\u0038\u0065" } } + maven { + name = "ark-core GitHub Packages" + url = URI("https://maven.pkg.github.com/ARK-Builders/ark-core") + credentials { + username = "token" + password = "\u0037\u0066\u0066\u0036\u0030\u0039\u0033\u0066\u0032\u0037\u0033\u0036\u0033\u0037\u0064\u0036\u0037\u0066\u0038\u0030\u0034\u0039\u0062\u0030\u0039\u0038\u0039\u0038\u0066\u0034\u0066\u0034\u0031\u0064\u0062\u0033\u0064\u0033\u0038\u0065" + } + } } versionCatalogs { From 2ee8c1be4fe19d2c08eaf34c7384d05554fec35c Mon Sep 17 00:00:00 2001 From: oluiscabral Date: Sun, 1 Dec 2024 12:31:05 -0300 Subject: [PATCH 02/15] Avoid user adding entries without a storage set --- .../arkbuilders/sample/storage/StorageDemoFragment.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 caf4331..fe6744f 100644 --- a/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt +++ b/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt @@ -16,6 +16,7 @@ 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 class StorageDemoFragment: DialogFragment() { @@ -31,6 +32,11 @@ class StorageDemoFragment: DialogFragment() { context?.contentResolver?.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) workingDir = uri.getAbsolutePath() refreshFilesTree() + val initialStorageFile = UUID.randomUUID().toString() + binding.edtStoragePath.setText(initialStorageFile) + storage = FileStorage(initialStorageFile, + "$workingDir/$initialStorageFile" + ) } } @@ -62,10 +68,11 @@ class StorageDemoFragment: DialogFragment() { binding.edtStoragePath.setOnEditorActionListener { v, actionId, event -> if (actionId == EditorInfo.IME_ACTION_DONE) { - val relativeStoragePath = v.text - storage = FileStorage(relativeStoragePath.toString(), + val relativeStoragePath = v.text.toString() + storage = FileStorage(relativeStoragePath, "$workingDir/$relativeStoragePath" ) + binding.tvCurrentAbsolutePath.text = String.format("%s/%s", workingDir, relativeStoragePath) return@setOnEditorActionListener true } false From 73379047117de187c3fb51d8ac6538f093a0bfcb Mon Sep 17 00:00:00 2001 From: oluiscabral Date: Thu, 12 Dec 2024 16:26:52 -0300 Subject: [PATCH 03/15] Use newly published `ark-core` AAR --- gradle/libs.versions.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1696b9..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,7 +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.core", name = "lib", version = "1.0-SNAPSHOT"} +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" } From eb92dfe94a026c75fb129ac8bfa70fabe848f5eb Mon Sep 17 00:00:00 2001 From: oluiscabral Date: Thu, 12 Dec 2024 16:30:12 -0300 Subject: [PATCH 04/15] Remove residuals from old `sample` building approach --- .github/workflows/build_sample.yml | 5 ---- README.md | 37 ------------------------------ 2 files changed, 42 deletions(-) diff --git a/.github/workflows/build_sample.yml b/.github/workflows/build_sample.yml index e7ccf52..d5ac1cd 100644 --- a/.github/workflows/build_sample.yml +++ b/.github/workflows/build_sample.yml @@ -35,11 +35,6 @@ jobs: echo "${{ secrets.KEYSTORE_ENCRYPTED }}" > keystore.asc gpg -d --passphrase "${{ secrets.KEYSTORE_PASSWORD }}" --batch keystore.asc > keystore.jks - - name: Download and extract `ark-core` JNI libs - run: | - wget https://github.com/ARK-Builders/ark-core/releases/download/v1.0.0/jniLibs.zip - unzip -d sample/src/main jniLibs.zip - - name: Build release sample APK run: ./gradlew sample:assembleRelease diff --git a/README.md b/README.md index f38e941..e7a853e 100644 --- a/README.md +++ b/README.md @@ -28,40 +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. - -## Making the Storage Demo work - -This section guides you through setting up and running the Storage Demo subpage. - -**1. Download the `fs-storage` JNI Libraries:** - -The demo requires JNI libraries (libs). Download these from the following location: - -* **[ark-core repository](https://github.com/ARK-Builders/ark-core)** - - - If you can't find them in the "Releases" section, check the latest successful build actions for artifacts. - -**2. Place the Libraries:** - -After downloading, move the JNI library files into your project's `sample/src/main/jniLibs` directory. **If the path doesn't exist, create it** - -Your project structure should resemble this: - -``` -... -sample/ - ... - src/ - main/ - ... - jniLibs/ - arm64-v8a/ - armeabi-v7a/ - x86/ - x86_64/ - ... - ... - ... -``` - -With the `fs-storage` JNI libraries in place, you're ready to build, run the project and use the Storage Demo subpage. From 8d9d641753c132aa252f8a58068d11bae54a14a9 Mon Sep 17 00:00:00 2001 From: mdrlzy Date: Fri, 13 Dec 2024 08:35:18 +0600 Subject: [PATCH 05/15] minor refactor: remove files tree, write fs separate btn --- .../sample/storage/StorageDemoFragment.kt | 109 ++++++++---------- .../main/res/layout/fragment_storage_demo.xml | 31 ++--- 2 files changed, 66 insertions(+), 74 deletions(-) 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 fe6744f..f64970e 100644 --- a/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt +++ b/sample/src/main/java/dev/arkbuilders/sample/storage/StorageDemoFragment.kt @@ -4,7 +4,6 @@ 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 @@ -15,10 +14,9 @@ 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 -class StorageDemoFragment: DialogFragment() { +class StorageDemoFragment : DialogFragment() { private val TAG = StorageDemoFragment::class.java.name @@ -26,23 +24,21 @@ class StorageDemoFragment: DialogFragment() { private var workingDir: String = "/" 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() - refreshFilesTree() - val initialStorageFile = UUID.randomUUID().toString() - binding.edtStoragePath.setText(initialStorageFile) - storage = FileStorage(initialStorageFile, - "$workingDir/$initialStorageFile" - ) + 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) + 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) @@ -55,7 +51,8 @@ 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 @@ -66,72 +63,64 @@ class StorageDemoFragment: DialogFragment() { selectDirRequest.launch(null) } - binding.edtStoragePath.setOnEditorActionListener { v, actionId, event -> + binding.edtStorageName.setOnEditorActionListener { v, actionId, event -> if (actionId == EditorInfo.IME_ACTION_DONE) { - val relativeStoragePath = v.text.toString() - storage = FileStorage(relativeStoragePath, - "$workingDir/$relativeStoragePath" - ) - binding.tvCurrentAbsolutePath.text = String.format("%s/%s", workingDir, relativeStoragePath) + val storageName = v.text.toString() + newStorage(storageName) return@setOnEditorActionListener true } false } binding.btnNewMapEntry.setOnClickListener { - MapEntryDialog(isDelete = false, onDone = { key, value -> - if (storage != null) { - storage!!.set(key, value) + MapEntryDialog( + isDelete = false, + onDone = { key, value -> + storage?.set(key, value) + updateDisplayMap() } - refreshStorage() - }).show(parentFragmentManager, MapEntryDialog::class.java.name) + ).show(parentFragmentManager, MapEntryDialog::class.java.name) } binding.btnDeleteEntry.setOnClickListener { - MapEntryDialog(isDelete = true, onDone = { key, _ -> - if (storage != null) { - storage!!.remove(key) + MapEntryDialog( + isDelete = true, + onDone = { key, _ -> + storage?.remove(key) + updateDisplayMap() } - refreshStorage() - }).show(parentFragmentManager, MapEntryDialog::class.java.name) + ).show(parentFragmentManager, MapEntryDialog::class.java.name) } binding.btnClearMap.setOnClickListener { - if (storage != null) { - storage!!.erase() - } - refreshStorage() + storage?.erase() + storage = null + binding.tvCurrentAbsolutePath.text = workingDir + binding.edtStorageName.setText("") + updateDisplayMap() } - } - - 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") - } - binding.tvCurrentFileTree.text = fileTreeBuilder.toString() - } catch (e: Exception) { - Log.e(TAG, e.message.toString()) + binding.btnWriteFs.setOnClickListener { + storage?.writeFS() } } - private fun refreshStorage() { - if (storage == null) { + private fun newStorage(name: String) { + val absolutePath = "$workingDir/$name" + storage = FileStorage(name, absolutePath) + binding.tvCurrentAbsolutePath.text = absolutePath + updateDisplayMap() + } + + private fun updateDisplayMap() { + storage ?: let { binding.tvMapValues.text = getString(R.string.empty_map) return } - storage!!.writeFS() val mapEntries = StringBuilder() for (entry in storage!!) { mapEntries.append(entry.key).append(" -> ").append(entry.value).append("\n") } - binding.tvMapValues.text = mapEntries.toString() + binding.tvMapValues.text = mapEntries.toString().ifEmpty { getString(R.string.empty_map) } } } \ 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..c1eba3c 100644 --- a/sample/src/main/res/layout/fragment_storage_demo.xml +++ b/sample/src/main/res/layout/fragment_storage_demo.xml @@ -26,15 +26,15 @@ android:layout_marginTop="10dp" android:imeOptions="actionDone" android:inputType="text" - android:id="@+id/edt_storage_path" - android:hint="Relative storage path (without leading slash)"/> + android:id="@+id/edt_storage_name" + android:hint="Name"/> - - +