From 1920820f1d143418bfd0f778f34d5013d0bc36cf Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Sun, 29 May 2022 03:06:04 +0200 Subject: [PATCH] Remove: Using AppDetailAdapter in AppSheet --- .../looker/droidify/ui/fragments/AppSheetX.kt | 228 +++++++++--------- 1 file changed, 111 insertions(+), 117 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/AppSheetX.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/AppSheetX.kt index 5515b5da..53489946 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/AppSheetX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/AppSheetX.kt @@ -1,6 +1,9 @@ package com.looker.droidify.ui.fragments import android.content.ActivityNotFoundException +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle @@ -8,32 +11,44 @@ import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar +import com.looker.droidify.R +import com.looker.droidify.content.Preferences import com.looker.droidify.content.ProductPreferences -import com.looker.droidify.database.entity.Installed -import com.looker.droidify.database.entity.Product import com.looker.droidify.database.entity.Release -import com.looker.droidify.database.entity.Repository -import com.looker.droidify.databinding.SheetAppXBinding -import com.looker.droidify.entity.Action +import com.looker.droidify.entity.Cancelable +import com.looker.droidify.entity.Connecting +import com.looker.droidify.entity.Details +import com.looker.droidify.entity.Install +import com.looker.droidify.entity.Launch +import com.looker.droidify.entity.PackageState +import com.looker.droidify.entity.Pending import com.looker.droidify.entity.ProductPreference import com.looker.droidify.entity.Screenshot +import com.looker.droidify.entity.Share +import com.looker.droidify.entity.Uninstall +import com.looker.droidify.entity.Update import com.looker.droidify.installer.AppInstaller import com.looker.droidify.screen.MessageDialog import com.looker.droidify.screen.ScreenshotsFragment import com.looker.droidify.service.Connection import com.looker.droidify.service.DownloadService import com.looker.droidify.ui.activities.MainActivityX -import com.looker.droidify.ui.adapters.AppDetailAdapter +import com.looker.droidify.ui.compose.theme.AppTheme +import com.looker.droidify.ui.compose.utils.Callbacks import com.looker.droidify.ui.viewmodels.AppViewModelX import com.looker.droidify.utility.Utils.rootInstallerEnabled import com.looker.droidify.utility.Utils.startUpdate import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.findSuggestedProduct +import com.looker.droidify.utility.isDarkTheme import com.looker.droidify.utility.onLaunchClick -import io.reactivex.rxjava3.disposables.Disposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn @@ -43,10 +58,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext // TODO clean up and replace dropped functions from AppDetailFragment -class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Callbacks { +class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks { companion object { private const val EXTRA_PACKAGE_NAME = "packageName" - private const val STATE_ADAPTER = "adapter" } constructor(packageName: String) : this() { @@ -55,7 +69,6 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call } } - private lateinit var binding: SheetAppXBinding val viewModel: AppViewModelX by viewModels { AppViewModelX.Factory(mainActivityX.db, packageName) } @@ -65,13 +78,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call val packageName: String get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!! - private var actions = Pair(emptySet(), null as Action?) - private var productRepos = emptyList>() - private var products = emptyList() - private var installed: Installed? = null private var downloading = false - - private var productDisposable: Disposable? = null private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder -> binder.stateSubject .filter { it.packageName == packageName } @@ -86,83 +93,53 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = SheetAppXBinding.inflate(layoutInflater) - binding.lifecycleOwner = this + super.onCreate(savedInstanceState) setupAdapters() - return binding.root - } - - private fun setupAdapters() { - downloadConnection.bind(requireContext()) - - binding.recyclerView.apply { - id = android.R.id.list - this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - this.adapter = AppDetailAdapter(this@AppSheetX) - } - } - - override fun setupLayout() { - // TODO simplify observing and updating - viewModel.installedItem.observe(viewLifecycleOwner) { - installed = it - updateSheet() - } - - viewModel.repositories.observe(viewLifecycleOwner) { - if (it.isNotEmpty() && products.isNotEmpty()) updateSheet() - } - viewModel.products.observe(viewLifecycleOwner) { - products = it.filterNotNull() - viewModel.repositories.value?.let { repos -> - if (repos.isNotEmpty() && products.isNotEmpty()) updateSheet() + return ComposeView(requireContext()).apply { + setContent { + AppTheme( + darkTheme = when (Preferences[Preferences.Key.Theme]) { + is Preferences.Theme.System -> isSystemInDarkTheme() + is Preferences.Theme.AmoledSystem -> isSystemInDarkTheme() + else -> isDarkTheme + } + ) { + AppSheet() + } } } } - override fun updateSheet() { - products.mapNotNull { product -> - viewModel.repositories.value - ?.firstOrNull { it.id == product.repositoryId } - ?.let { Pair(product, it) } - }.apply { - productRepos = this + private fun setupAdapters() { + downloadConnection.bind(requireContext()) + } - val adapter = binding.recyclerView.adapter as AppDetailAdapter - adapter.setProducts( - binding.recyclerView.context, - packageName, - this, - installed - ) + override fun setupLayout() { + viewModel._productRepos.observe(this) { lifecycleScope.launch { updateButtons() } } } + override fun updateSheet() { + } + override fun onDestroyView() { super.onDestroyView() - productDisposable?.dispose() - productDisposable = null downloadConnection.unbind(requireContext()) } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - - val adapterState = (binding.recyclerView.adapter as? AppDetailAdapter)?.saveState() - adapterState?.let { outState.putParcelable(STATE_ADAPTER, it) } - } - private suspend fun updateButtons() { updateButtons(ProductPreferences[packageName]) } + // TODO rename to updateActions private suspend fun updateButtons(preference: ProductPreference) = withContext(Dispatchers.Default) { - val installed = installed + val installed = viewModel.installedItem.value + val productRepos = viewModel.productRepos val product = findSuggestedProduct(productRepos, installed) { it.first }?.first val compatible = product != null && product.selectedReleases.firstOrNull() .let { it != null && it.incompatibilities.isEmpty() } @@ -175,80 +152,76 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call product != null && installed != null && installed.launcherActivities.isNotEmpty() val canShare = product != null && productRepos[0].second.name == "F-Droid" - val actions = mutableSetOf() + val actions = mutableSetOf() launch { if (canInstall) { - actions += Action.INSTALL + actions += Install } } launch { if (canUpdate) { - actions += Action.UPDATE + actions += Update } } launch { if (canLaunch) { - actions += Action.LAUNCH + actions += Launch } } launch { if (installed != null) { - actions += Action.DETAILS + actions += Details } } launch { if (canUninstall) { - actions += Action.UNINSTALL + actions += Uninstall } } launch { if (canShare) { - actions += Action.SHARE + actions += Share } } val primaryAction = when { - canUpdate -> Action.UPDATE - canLaunch -> Action.LAUNCH - canInstall -> Action.INSTALL - canShare -> Action.SHARE + canUpdate -> Update + canLaunch -> Launch + canInstall -> Install + canShare -> Share else -> null } val secondaryAction = when { - primaryAction != Action.SHARE && canShare -> Action.SHARE - primaryAction != Action.LAUNCH && canLaunch -> Action.LAUNCH - installed != null && canUninstall -> Action.UNINSTALL + primaryAction != Share && canShare -> Share + primaryAction != Launch && canLaunch -> Launch + installed != null && canUninstall -> Uninstall else -> null } - launch(Dispatchers.Main) { - val adapterAction = - if (downloading) Action.CANCEL else primaryAction - val adapterSecondaryAction = - if (downloading) null else secondaryAction - (binding.recyclerView.adapter as? AppDetailAdapter) - ?.setAction(adapterAction) - (binding.recyclerView.adapter as? AppDetailAdapter) - ?.setSecondaryAction(adapterSecondaryAction) + withContext(Dispatchers.Main) { + viewModel.actions.value = actions + if (!downloading) { + viewModel.state.value = primaryAction + viewModel.secondaryAction.value = secondaryAction + } else { + viewModel.secondaryAction.value = null + } } - launch { this@AppSheetX.actions = Pair(actions, primaryAction) } } private suspend fun updateDownloadState(state: DownloadService.State?) { val status = when (state) { - is DownloadService.State.Pending -> AppDetailAdapter.Status.Pending - is DownloadService.State.Connecting -> AppDetailAdapter.Status.Connecting - is DownloadService.State.Downloading -> AppDetailAdapter.Status.Downloading( + is DownloadService.State.Pending -> Pending + is DownloadService.State.Connecting -> Connecting + is DownloadService.State.Downloading -> com.looker.droidify.entity.Downloading( state.read, state.total ) - is DownloadService.State.Success, is DownloadService.State.Error, is DownloadService.State.Cancel, null -> null + else -> null } - val downloading = status != null - if (this.downloading != downloading) { - this.downloading = downloading - updateButtons() - } - (binding.recyclerView.adapter as? AppDetailAdapter)?.setStatus(status) + val downloading = status is Cancelable + this.downloading = downloading + updateButtons() + viewModel.state.value = status if (state is DownloadService.State.Success && isResumed && !rootInstallerEnabled) { withContext(Dispatchers.Default) { AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName) @@ -256,12 +229,13 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call } } - override fun onActionClick(action: Action) { + override fun onActionClick(action: PackageState?) { + val productRepos = viewModel.productRepos when (action) { - Action.INSTALL, - Action.UPDATE, + Install, + Update, -> { - val installedItem = installed + val installedItem = viewModel.installedItem.value lifecycleScope.launch { startUpdate( packageName, @@ -272,31 +246,37 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call } Unit } - Action.LAUNCH -> { - installed?.let { requireContext().onLaunchClick(it, childFragmentManager) } + Launch -> { + viewModel.installedItem.value?.let { + requireContext().onLaunchClick( + it, + childFragmentManager + ) + } Unit } - Action.DETAILS -> { + Details -> { startActivity( Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData(Uri.parse("package:$packageName")) ) } - Action.UNINSTALL -> { + Uninstall -> { lifecycleScope.launch { AppInstaller.getInstance(context)?.defaultInstaller?.uninstall(packageName) } Unit } - Action.CANCEL -> { + is Cancelable -> { val binder = downloadConnection.binder if (downloading && binder != null) { binder.cancel(packageName) } else Unit } - Action.SHARE -> { + Share -> { shareIntent(packageName, productRepos[0].first.label) } + else -> Unit }::class } @@ -325,8 +305,9 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call ) } + // TODO fix in compose implementation override fun onScreenshotClick(screenshot: Screenshot) { - val pair = productRepos.asSequence() + val pair = viewModel.productRepos.asSequence() .map { it -> Pair( it.second, @@ -345,7 +326,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call } override fun onReleaseClick(release: Release) { - val installedItem = installed + val installedItem = viewModel.installedItem.value when { release.incompatibilities.isNotEmpty() -> { MessageDialog( @@ -365,7 +346,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call } else -> { val productRepository = - productRepos.asSequence() + viewModel.productRepos.asSequence() .filter { it -> it.first.releases.any { it === release } } .firstOrNull() if (productRepository != null) { @@ -392,4 +373,17 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call } } } + + private fun copyLinkToClipboard(view: View, link: String) { + val clipboardManager = + requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboardManager.setPrimaryClip(ClipData.newPlainText(null, link)) + Snackbar.make(view, R.string.link_copied_to_clipboard, Snackbar.LENGTH_SHORT).show() + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun AppSheet() { + + } }