diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8338f47..1c3022d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,8 @@ on: jobs: build: + if: ${{ ! startsWith(github.actor, 'dependabot') }} + environment: Development runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -22,6 +24,21 @@ 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: Build components run: ./gradlew assembleRelease + + - name: Build release sample APK + run: ./gradlew sample:assembleRelease + + - name: Upload release APK + uses: actions/upload-artifact@v3 + with: + name: release-universal-apk + path: ./sample/build/outputs/apk/release/sample-release.apk diff --git a/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt b/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt index b716c15..7d86a8a 100644 --- a/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt +++ b/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt @@ -32,6 +32,7 @@ import coil.dispose import coil.load import coil.memory.MemoryCache import coil.request.CachePolicy +import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.GenericItem @@ -46,7 +47,6 @@ import dev.arkbuilders.components.databinding.ArkFilePickerHostFragmentBinding import dev.arkbuilders.components.databinding.ArkFilePickerItemFileBinding import dev.arkbuilders.components.databinding.ArkFilePickerItemFilesRootsPageBinding import dev.arkbuilders.components.filepicker.callback.OnFileItemLongClick -import dev.arkbuilders.components.filepicker.callback.PinFileCallback import dev.arkbuilders.components.folderstree.FolderTreeView import dev.arkbuilders.components.utils.args import dev.arkbuilders.components.utils.dpToPx @@ -92,11 +92,21 @@ open class ArkFilePickerFragment : } private val pagesAdapter = ItemAdapter() - private var mCurrentRootsWithFavorites: Map>? = null - private val mPinFileCallback = object : PinFileCallback { - override fun onPinFileClick(file: Path) { - pinFile(file) + private val mSharedPref by lazy { activity?.getPreferences(Context.MODE_PRIVATE) } + private val PREF_LAST_ACTIVE_TAB_INDEX = "pref_last_active_tab_index" + + private val mOnTabSelectListener by lazy { + object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab?) { + mSharedPref?.edit()?.putInt(PREF_LAST_ACTIVE_TAB_INDEX, tab?.position ?: 0)?.apply() + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { + } + + override fun onTabReselected(tab: TabLayout.Tab?) { + } } } @@ -160,6 +170,10 @@ open class ArkFilePickerFragment : TabLayoutMediator(tabs, vp) { tab, pos -> tab.text = tabsTitle[pos] }.attach() + mSharedPref?.getInt(PREF_LAST_ACTIVE_TAB_INDEX, 0)?.let { + tabs.getTabAt(it)?.select() + } + tabs.addOnTabSelectedListener(mOnTabSelectListener) } else { tabs.isVisible = false } @@ -197,7 +211,7 @@ open class ArkFilePickerFragment : if (newFolder.mkdirs()) { if (isARKMode()) { - pinFile(newFolder.toPath()) + viewModel.pinFile(newFolder.toPath()) } //Reload current files tree currentFolder?.let { viewModel.onItemClick(it) } @@ -208,7 +222,6 @@ open class ArkFilePickerFragment : } private fun render(state: FilePickerState) = binding.apply { - mCurrentRootsWithFavorites = state.rootsWithFavs displayPath(state) @@ -272,6 +285,23 @@ open class ArkFilePickerFragment : ) }) } + + FilePickerSideEffect.PinAsRoot -> + activity?.toast(R.string.ark_file_picker_pinned_as_root) + + FilePickerSideEffect.AlreadyRoot -> + activity?.toast(R.string.ark_file_picker_already_be_a_root) + + FilePickerSideEffect.PinAsFavorite -> + activity?.toast(R.string.ark_file_picker_pinned_as_favorite) + + FilePickerSideEffect.AlreadyFavorite -> + activity?.toast(R.string.ark_file_picker_already_a_favorite) + + FilePickerSideEffect.PinAsFirstRoot -> { + pagesAdapter.set(getPages()) + activity?.toast(R.string.ark_file_picker_pinned_as_root) + } } @@ -338,7 +368,7 @@ open class ArkFilePickerFragment : } } - private fun getPages() = if (showRoots!!) { + private fun getPages() = if (showRoots!! && viewModel.haveRoot()) { if (rootsFirstPage!!) { listOf( RootsPage(this, viewModel), @@ -346,8 +376,7 @@ open class ArkFilePickerFragment : ) } else { listOf( - FilesPage(this, viewModel, itemsPluralId!!, - pinFileCallback = mPinFileCallback), + FilesPage(this, viewModel, itemsPluralId!!), RootsPage(this, viewModel) ) } @@ -357,7 +386,6 @@ open class ArkFilePickerFragment : ) } - fun setConfig(config: ArkFilePickerConfig) { titleStringId = config.titleStringId pickButtonStringId = config.pickButtonStringId @@ -386,42 +414,6 @@ open class ArkFilePickerFragment : private const val PATH_PART_PADDING = 4f } - private fun pinFile(file: Path) { - val roots = mCurrentRootsWithFavorites?.keys - val root = roots?.find { root -> file.startsWith(root) } - val favorites = mCurrentRootsWithFavorites?.get(root)?.flatten() - - root?.let { - - //Make sure file isn't inside a root folder - if (root != file) { - var foundAsFavorite = false - favorites?.forEach { - if (file.endsWith(it)) { - foundAsFavorite = true - return@forEach - } - } - - if (!foundAsFavorite) { - viewModel.addFavorite(file) - activity?.toast(R.string.ark_file_picker_pinned_as_favorite) - } else { - activity?.toast(R.string.ark_file_picker_already_a_favorite) - } - } else { - activity?.toast(R.string.ark_file_picker_already_be_a_root) - } - } ?: let { - - if (mCurrentRootsWithFavorites?.keys?.contains(file) == true) { - activity?.toast(R.string.ark_file_picker_already_be_a_root) - } else { - viewModel.addRoot(file) - activity?.toast(R.string.ark_file_picker_pinned_as_root) - } - } - } } private fun pickerImageLoader(ctx: Context) = ImageLoader.Builder(ctx) @@ -445,8 +437,7 @@ private fun pickerImageLoader(ctx: Context) = ImageLoader.Builder(ctx) internal class FilesPage( private val fragment: Fragment, private val viewModel: ArkFilePickerViewModel, - private val itemsPluralId: Int, - private val pinFileCallback: PinFileCallback? = null + private val itemsPluralId: Int ) : AbstractBindingItem() { private val filesAdapter = ItemAdapter() private var currentFiles = emptyList() @@ -487,7 +478,7 @@ internal class FilesPage( val popupMenu = PopupMenu(fragment.activity, anchor, Gravity.END) popupMenu.menuInflater.inflate(R.menu.file_select_menu, popupMenu.menu) popupMenu.setOnMenuItemClickListener { - pinFileCallback?.onPinFileClick(file) + viewModel.pinFile(file) true } popupMenu.show() diff --git a/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt b/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt index 77a8cbc..27c2abb 100644 --- a/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt +++ b/components/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt @@ -3,6 +3,7 @@ package dev.arkbuilders.components.filepicker import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import dev.arkbuilders.arklib.arkGlobal import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost @@ -13,7 +14,10 @@ import org.orbitmvi.orbit.viewmodel.container import dev.arkbuilders.arklib.data.folders.FoldersRepo import dev.arkbuilders.arklib.utils.DeviceStorageUtils import dev.arkbuilders.arklib.utils.listChildren +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.nio.file.Path +import kotlin.io.path.exists import kotlin.io.path.isDirectory enum class ArkFilePickerMode { @@ -34,6 +38,12 @@ internal sealed class FilePickerSideEffect { object DismissDialog : FilePickerSideEffect() object ToastAccessDenied : FilePickerSideEffect() class NotifyPathPicked(val path: Path) : FilePickerSideEffect() + data object PinAsRoot : FilePickerSideEffect() + data object AlreadyRoot : FilePickerSideEffect() + data object PinAsFavorite : FilePickerSideEffect() + data object AlreadyFavorite : FilePickerSideEffect() + data object PinAsFirstRoot : FilePickerSideEffect() + } internal class ArkFilePickerViewModel( @@ -53,15 +63,13 @@ internal class ArkFilePickerViewModel( } } - fun addRoot(root:Path) { - viewModelScope.launch { - foldersRepo.addRoot(root) - refreshRootsWithFavs() - } + private suspend fun addRoot(root:Path) = withContext(Dispatchers.IO) { + foldersRepo.addRoot(root) + refreshRootsWithFavs() } - fun addFavorite(favorite: Path) { - viewModelScope.launch { + private fun addFavorite(favorite: Path) { + viewModelScope.launch(Dispatchers.IO) { val favoritePath = favorite.toRealPath() val folders = foldersRepo.provideFolders() val root = folders.keys.find { favoritePath.startsWith(it) } @@ -165,6 +173,49 @@ internal class ArkFilePickerViewModel( return children } + + fun haveRoot(): Boolean { + val arkGlobal = deviceStorageUtils.listStorages().firstOrNull()?.arkGlobal() + return arkGlobal?.exists() == true + } + + fun pinFile(file: Path) = intent { + val rootsWithFavorites = container.stateFlow.value.rootsWithFavs + val roots = rootsWithFavorites.keys + val root = roots.find { root -> file.startsWith(root) } + val favorites = rootsWithFavorites[root]?.flatten() + val haveRoot = haveRoot() + + root?.let { + + //Make sure file isn't inside a root folder + if (root != file) { + val foundAsFavorite = favorites?.any { file.endsWith(it) } ?: false + + if (!foundAsFavorite) { + addFavorite(file) + postSideEffect(FilePickerSideEffect.PinAsFavorite) + } else { + postSideEffect(FilePickerSideEffect.AlreadyFavorite) + } + } else { + postSideEffect(FilePickerSideEffect.AlreadyRoot) + } + } ?: let { + + if (rootsWithFavorites.keys.contains(file)) { + postSideEffect(FilePickerSideEffect.AlreadyRoot) + } else { + + addRoot(file) + if (!haveRoot) { + postSideEffect(FilePickerSideEffect.PinAsFirstRoot) + } else { + postSideEffect(FilePickerSideEffect.PinAsRoot) + } + } + } + } } internal class ArkFilePickerViewModelFactory( diff --git a/components/src/main/java/dev/arkbuilders/components/filepicker/callback/PinFileCallback.kt b/components/src/main/java/dev/arkbuilders/components/filepicker/callback/PinFileCallback.kt deleted file mode 100644 index 4b18fbe..0000000 --- a/components/src/main/java/dev/arkbuilders/components/filepicker/callback/PinFileCallback.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.arkbuilders.components.filepicker.callback - -import java.nio.file.Path - -interface PinFileCallback { - fun onPinFileClick(file: Path) -} \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 7242147..89bd415 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -17,9 +17,19 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + signingConfigs { + create("release") { + storeFile = project.rootProject.file("keystore.jks") + storePassword = "sw0rdf1sh" + keyAlias = "ark-builders-test" + keyPassword = "rybamech" + } + } + buildTypes { release { isMinifyEnabled = false + signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"