Remove: Using AppDetailAdapter in AppSheet

This commit is contained in:
machiav3lli 2022-05-29 03:06:04 +02:00
parent 31d988ed2e
commit 1920820f1d

View File

@ -1,6 +1,9 @@
package com.looker.droidify.ui.fragments package com.looker.droidify.ui.fragments
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -8,32 +11,44 @@ import android.provider.Settings
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope 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.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.Release
import com.looker.droidify.database.entity.Repository import com.looker.droidify.entity.Cancelable
import com.looker.droidify.databinding.SheetAppXBinding import com.looker.droidify.entity.Connecting
import com.looker.droidify.entity.Action 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.ProductPreference
import com.looker.droidify.entity.Screenshot 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.installer.AppInstaller
import com.looker.droidify.screen.MessageDialog import com.looker.droidify.screen.MessageDialog
import com.looker.droidify.screen.ScreenshotsFragment import com.looker.droidify.screen.ScreenshotsFragment
import com.looker.droidify.service.Connection import com.looker.droidify.service.Connection
import com.looker.droidify.service.DownloadService import com.looker.droidify.service.DownloadService
import com.looker.droidify.ui.activities.MainActivityX 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.ui.viewmodels.AppViewModelX
import com.looker.droidify.utility.Utils.rootInstallerEnabled import com.looker.droidify.utility.Utils.rootInstallerEnabled
import com.looker.droidify.utility.Utils.startUpdate import com.looker.droidify.utility.Utils.startUpdate
import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.findSuggestedProduct import com.looker.droidify.utility.findSuggestedProduct
import com.looker.droidify.utility.isDarkTheme
import com.looker.droidify.utility.onLaunchClick import com.looker.droidify.utility.onLaunchClick
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@ -43,10 +58,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
// TODO clean up and replace dropped functions from AppDetailFragment // TODO clean up and replace dropped functions from AppDetailFragment
class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Callbacks { class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
companion object { companion object {
private const val EXTRA_PACKAGE_NAME = "packageName" private const val EXTRA_PACKAGE_NAME = "packageName"
private const val STATE_ADAPTER = "adapter"
} }
constructor(packageName: String) : this() { constructor(packageName: String) : this() {
@ -55,7 +69,6 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
} }
} }
private lateinit var binding: SheetAppXBinding
val viewModel: AppViewModelX by viewModels { val viewModel: AppViewModelX by viewModels {
AppViewModelX.Factory(mainActivityX.db, packageName) AppViewModelX.Factory(mainActivityX.db, packageName)
} }
@ -65,13 +78,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
val packageName: String val packageName: String
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!! get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
private var actions = Pair(emptySet<Action>(), null as Action?)
private var productRepos = emptyList<Pair<Product, Repository>>()
private var products = emptyList<Product>()
private var installed: Installed? = null
private var downloading = false private var downloading = false
private var productDisposable: Disposable? = null
private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder -> private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder ->
binder.stateSubject binder.stateSubject
.filter { it.packageName == packageName } .filter { it.packageName == packageName }
@ -86,83 +93,53 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = SheetAppXBinding.inflate(layoutInflater) super.onCreate(savedInstanceState)
binding.lifecycleOwner = this
setupAdapters() setupAdapters()
return binding.root return ComposeView(requireContext()).apply {
} setContent {
AppTheme(
private fun setupAdapters() { darkTheme = when (Preferences[Preferences.Key.Theme]) {
downloadConnection.bind(requireContext()) is Preferences.Theme.System -> isSystemInDarkTheme()
is Preferences.Theme.AmoledSystem -> isSystemInDarkTheme()
binding.recyclerView.apply { else -> isDarkTheme
id = android.R.id.list }
this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) ) {
this.adapter = AppDetailAdapter(this@AppSheetX) AppSheet()
} }
}
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()
} }
} }
} }
override fun updateSheet() { private fun setupAdapters() {
products.mapNotNull { product -> downloadConnection.bind(requireContext())
viewModel.repositories.value }
?.firstOrNull { it.id == product.repositoryId }
?.let { Pair(product, it) }
}.apply {
productRepos = this
val adapter = binding.recyclerView.adapter as AppDetailAdapter override fun setupLayout() {
adapter.setProducts( viewModel._productRepos.observe(this) {
binding.recyclerView.context,
packageName,
this,
installed
)
lifecycleScope.launch { lifecycleScope.launch {
updateButtons() updateButtons()
} }
} }
} }
override fun updateSheet() {
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
productDisposable?.dispose()
productDisposable = null
downloadConnection.unbind(requireContext()) 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() { private suspend fun updateButtons() {
updateButtons(ProductPreferences[packageName]) updateButtons(ProductPreferences[packageName])
} }
// TODO rename to updateActions
private suspend fun updateButtons(preference: ProductPreference) = private suspend fun updateButtons(preference: ProductPreference) =
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val installed = installed val installed = viewModel.installedItem.value
val productRepos = viewModel.productRepos
val product = findSuggestedProduct(productRepos, installed) { it.first }?.first val product = findSuggestedProduct(productRepos, installed) { it.first }?.first
val compatible = product != null && product.selectedReleases.firstOrNull() val compatible = product != null && product.selectedReleases.firstOrNull()
.let { it != null && it.incompatibilities.isEmpty() } .let { it != null && it.incompatibilities.isEmpty() }
@ -175,80 +152,76 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
product != null && installed != null && installed.launcherActivities.isNotEmpty() product != null && installed != null && installed.launcherActivities.isNotEmpty()
val canShare = product != null && productRepos[0].second.name == "F-Droid" val canShare = product != null && productRepos[0].second.name == "F-Droid"
val actions = mutableSetOf<Action>() val actions = mutableSetOf<PackageState>()
launch { launch {
if (canInstall) { if (canInstall) {
actions += Action.INSTALL actions += Install
} }
} }
launch { launch {
if (canUpdate) { if (canUpdate) {
actions += Action.UPDATE actions += Update
} }
} }
launch { launch {
if (canLaunch) { if (canLaunch) {
actions += Action.LAUNCH actions += Launch
} }
} }
launch { launch {
if (installed != null) { if (installed != null) {
actions += Action.DETAILS actions += Details
} }
} }
launch { launch {
if (canUninstall) { if (canUninstall) {
actions += Action.UNINSTALL actions += Uninstall
} }
} }
launch { launch {
if (canShare) { if (canShare) {
actions += Action.SHARE actions += Share
} }
} }
val primaryAction = when { val primaryAction = when {
canUpdate -> Action.UPDATE canUpdate -> Update
canLaunch -> Action.LAUNCH canLaunch -> Launch
canInstall -> Action.INSTALL canInstall -> Install
canShare -> Action.SHARE canShare -> Share
else -> null else -> null
} }
val secondaryAction = when { val secondaryAction = when {
primaryAction != Action.SHARE && canShare -> Action.SHARE primaryAction != Share && canShare -> Share
primaryAction != Action.LAUNCH && canLaunch -> Action.LAUNCH primaryAction != Launch && canLaunch -> Launch
installed != null && canUninstall -> Action.UNINSTALL installed != null && canUninstall -> Uninstall
else -> null else -> null
} }
launch(Dispatchers.Main) { withContext(Dispatchers.Main) {
val adapterAction = viewModel.actions.value = actions
if (downloading) Action.CANCEL else primaryAction if (!downloading) {
val adapterSecondaryAction = viewModel.state.value = primaryAction
if (downloading) null else secondaryAction viewModel.secondaryAction.value = secondaryAction
(binding.recyclerView.adapter as? AppDetailAdapter) } else {
?.setAction(adapterAction) viewModel.secondaryAction.value = null
(binding.recyclerView.adapter as? AppDetailAdapter) }
?.setSecondaryAction(adapterSecondaryAction)
} }
launch { this@AppSheetX.actions = Pair(actions, primaryAction) }
} }
private suspend fun updateDownloadState(state: DownloadService.State?) { private suspend fun updateDownloadState(state: DownloadService.State?) {
val status = when (state) { val status = when (state) {
is DownloadService.State.Pending -> AppDetailAdapter.Status.Pending is DownloadService.State.Pending -> Pending
is DownloadService.State.Connecting -> AppDetailAdapter.Status.Connecting is DownloadService.State.Connecting -> Connecting
is DownloadService.State.Downloading -> AppDetailAdapter.Status.Downloading( is DownloadService.State.Downloading -> com.looker.droidify.entity.Downloading(
state.read, state.read,
state.total state.total
) )
is DownloadService.State.Success, is DownloadService.State.Error, is DownloadService.State.Cancel, null -> null else -> null
} }
val downloading = status != null val downloading = status is Cancelable
if (this.downloading != downloading) { this.downloading = downloading
this.downloading = downloading updateButtons()
updateButtons() viewModel.state.value = status
}
(binding.recyclerView.adapter as? AppDetailAdapter)?.setStatus(status)
if (state is DownloadService.State.Success && isResumed && !rootInstallerEnabled) { if (state is DownloadService.State.Success && isResumed && !rootInstallerEnabled) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName) 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) { when (action) {
Action.INSTALL, Install,
Action.UPDATE, Update,
-> { -> {
val installedItem = installed val installedItem = viewModel.installedItem.value
lifecycleScope.launch { lifecycleScope.launch {
startUpdate( startUpdate(
packageName, packageName,
@ -272,31 +246,37 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
} }
Unit Unit
} }
Action.LAUNCH -> { Launch -> {
installed?.let { requireContext().onLaunchClick(it, childFragmentManager) } viewModel.installedItem.value?.let {
requireContext().onLaunchClick(
it,
childFragmentManager
)
}
Unit Unit
} }
Action.DETAILS -> { Details -> {
startActivity( startActivity(
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.parse("package:$packageName")) .setData(Uri.parse("package:$packageName"))
) )
} }
Action.UNINSTALL -> { Uninstall -> {
lifecycleScope.launch { lifecycleScope.launch {
AppInstaller.getInstance(context)?.defaultInstaller?.uninstall(packageName) AppInstaller.getInstance(context)?.defaultInstaller?.uninstall(packageName)
} }
Unit Unit
} }
Action.CANCEL -> { is Cancelable -> {
val binder = downloadConnection.binder val binder = downloadConnection.binder
if (downloading && binder != null) { if (downloading && binder != null) {
binder.cancel(packageName) binder.cancel(packageName)
} else Unit } else Unit
} }
Action.SHARE -> { Share -> {
shareIntent(packageName, productRepos[0].first.label) shareIntent(packageName, productRepos[0].first.label)
} }
else -> Unit
}::class }::class
} }
@ -325,8 +305,9 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
) )
} }
// TODO fix in compose implementation
override fun onScreenshotClick(screenshot: Screenshot) { override fun onScreenshotClick(screenshot: Screenshot) {
val pair = productRepos.asSequence() val pair = viewModel.productRepos.asSequence()
.map { it -> .map { it ->
Pair( Pair(
it.second, it.second,
@ -345,7 +326,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
} }
override fun onReleaseClick(release: Release) { override fun onReleaseClick(release: Release) {
val installedItem = installed val installedItem = viewModel.installedItem.value
when { when {
release.incompatibilities.isNotEmpty() -> { release.incompatibilities.isNotEmpty() -> {
MessageDialog( MessageDialog(
@ -365,7 +346,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Call
} }
else -> { else -> {
val productRepository = val productRepository =
productRepos.asSequence() viewModel.productRepos.asSequence()
.filter { it -> it.first.releases.any { it === release } } .filter { it -> it.first.releases.any { it === release } }
.firstOrNull() .firstOrNull()
if (productRepository != null) { 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() {
}
} }