mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-24 11:52:13 +00:00
Add: AppSheetX
This commit is contained in:
parent
79865b49cc
commit
e93b972941
@ -25,6 +25,9 @@ open class Product {
|
|||||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
var data: com.looker.droidify.entity.Product? = null
|
var data: com.looker.droidify.entity.Product? = null
|
||||||
|
|
||||||
|
val trueData: com.looker.droidify.entity.Product?
|
||||||
|
get() = data?.copy(repositoryId = repository_id)
|
||||||
|
|
||||||
val selectedReleases: List<Release>
|
val selectedReleases: List<Release>
|
||||||
get() = releases.filter { it.selected }
|
get() = releases.filter { it.selected }
|
||||||
|
|
||||||
|
498
src/main/kotlin/com/looker/droidify/ui/fragments/AppSheetX.kt
Normal file
498
src/main/kotlin/com/looker/droidify/ui/fragments/AppSheetX.kt
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
package com.looker.droidify.ui.fragments
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.looker.droidify.R
|
||||||
|
import com.looker.droidify.content.ProductPreferences
|
||||||
|
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.Product
|
||||||
|
import com.looker.droidify.entity.ProductPreference
|
||||||
|
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.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 io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class AppSheetX() : FullscreenBottomSheetDialogFragment(), AppDetailAdapter.Callbacks {
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_PACKAGE_NAME = "packageName"
|
||||||
|
private const val STATE_ADAPTER = "adapter"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(packageName: String) : this() {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putString(EXTRA_PACKAGE_NAME, packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: SheetAppXBinding
|
||||||
|
val viewModel: AppViewModelX by viewModels {
|
||||||
|
AppViewModelX.Factory(mainActivityX.db, packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mainActivityX: MainActivityX
|
||||||
|
get() = requireActivity() as MainActivityX
|
||||||
|
val packageName: String
|
||||||
|
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 productDisposable: Disposable? = null
|
||||||
|
private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder ->
|
||||||
|
binder.stateSubject
|
||||||
|
.filter { it.packageName == packageName }
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
.onEach { updateDownloadState(it) }
|
||||||
|
.flowOn(Dispatchers.Main)
|
||||||
|
.launchIn(lifecycleScope)
|
||||||
|
})
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = SheetAppXBinding.inflate(layoutInflater)
|
||||||
|
binding.lifecycleOwner = this
|
||||||
|
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(requireActivity()) {
|
||||||
|
installed = it?.let {
|
||||||
|
val isSystem = try {
|
||||||
|
((requireContext().packageManager.getApplicationInfo(packageName, 0).flags)
|
||||||
|
and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
val launcherActivities =
|
||||||
|
if (packageName == requireContext().packageName) {
|
||||||
|
// Don't allow to launch self
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
val packageManager = requireContext().packageManager
|
||||||
|
packageManager
|
||||||
|
.queryIntentActivities(
|
||||||
|
Intent(Intent.ACTION_MAIN).addCategory(
|
||||||
|
Intent.CATEGORY_LAUNCHER
|
||||||
|
), 0
|
||||||
|
)
|
||||||
|
.asSequence()
|
||||||
|
.mapNotNull { resolveInfo -> resolveInfo.activityInfo }
|
||||||
|
.filter { activityInfo -> activityInfo.packageName == packageName }
|
||||||
|
.mapNotNull { activityInfo ->
|
||||||
|
val label = try {
|
||||||
|
activityInfo.loadLabel(packageManager).toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
label?.let { labelName ->
|
||||||
|
Pair(
|
||||||
|
activityInfo.name,
|
||||||
|
labelName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
Installed(it, isSystem, launcherActivities)
|
||||||
|
}
|
||||||
|
updateSheet()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.repositories.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isNotEmpty() && products.isNotEmpty()) updateSheet()
|
||||||
|
}
|
||||||
|
viewModel.products.observe(viewLifecycleOwner) {
|
||||||
|
products = it.mapNotNull { it?.trueData }
|
||||||
|
viewModel.repositories.value?.let { repos ->
|
||||||
|
if (repos.isNotEmpty() && products.isNotEmpty()) updateSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateSheet() {
|
||||||
|
products.mapNotNull { product ->
|
||||||
|
viewModel.repositories.value
|
||||||
|
?.firstOrNull { it.id == product.repositoryId }
|
||||||
|
?.let { Pair(product, it) }
|
||||||
|
}.apply {
|
||||||
|
productRepos = this
|
||||||
|
|
||||||
|
val adapter = binding.recyclerView.adapter as AppDetailAdapter
|
||||||
|
adapter.setProducts(
|
||||||
|
binding.recyclerView.context,
|
||||||
|
packageName,
|
||||||
|
this,
|
||||||
|
installed?.data
|
||||||
|
)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
updateButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateButtons(preference: ProductPreference) =
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
val installed = installed
|
||||||
|
val product =
|
||||||
|
Product.findSuggested(productRepos, installed?.data) { it.first }?.first
|
||||||
|
val compatible = product != null && product.selectedReleases.firstOrNull()
|
||||||
|
.let { it != null && it.incompatibilities.isEmpty() }
|
||||||
|
val canInstall = product != null && installed == null && compatible
|
||||||
|
val canUpdate =
|
||||||
|
product != null && compatible && product.canUpdate(installed?.data) &&
|
||||||
|
!preference.shouldIgnoreUpdate(product.versionCode)
|
||||||
|
val canUninstall = product != null && installed != null && !installed.isSystem
|
||||||
|
val canLaunch =
|
||||||
|
product != null && installed != null && installed.launcherActivities.isNotEmpty()
|
||||||
|
val canShare = product != null && productRepos[0].second.name == "F-Droid"
|
||||||
|
|
||||||
|
val actions = mutableSetOf<Action>()
|
||||||
|
launch {
|
||||||
|
if (canInstall) {
|
||||||
|
actions += Action.INSTALL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
if (canUpdate) {
|
||||||
|
actions += Action.UPDATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
if (canLaunch) {
|
||||||
|
actions += Action.LAUNCH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
if (installed != null) {
|
||||||
|
actions += Action.DETAILS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
if (canUninstall) {
|
||||||
|
actions += Action.UNINSTALL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
if (canShare) {
|
||||||
|
actions += Action.SHARE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val primaryAction = when {
|
||||||
|
canUpdate -> Action.UPDATE
|
||||||
|
canLaunch -> Action.LAUNCH
|
||||||
|
canInstall -> Action.INSTALL
|
||||||
|
installed != null -> Action.DETAILS
|
||||||
|
canShare -> Action.SHARE
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
val adapterAction =
|
||||||
|
if (downloading) AppDetailAdapter.Action.CANCEL else primaryAction?.adapterAction
|
||||||
|
(binding.recyclerView.adapter as? AppDetailAdapter)?.setAction(adapterAction)
|
||||||
|
for (action in sequenceOf(
|
||||||
|
Action.INSTALL,
|
||||||
|
Action.SHARE,
|
||||||
|
Action.UPDATE,
|
||||||
|
Action.UNINSTALL
|
||||||
|
)) {
|
||||||
|
//toolbar.menu.findItem(action.id).isEnabled = !downloading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
state.read,
|
||||||
|
state.total
|
||||||
|
)
|
||||||
|
is DownloadService.State.Success, is DownloadService.State.Error, is DownloadService.State.Cancel, null -> null
|
||||||
|
}
|
||||||
|
val downloading = status != null
|
||||||
|
if (this.downloading != downloading) {
|
||||||
|
this.downloading = downloading
|
||||||
|
updateButtons()
|
||||||
|
}
|
||||||
|
(binding.recyclerView.adapter as? AppDetailAdapter)?.setStatus(status)
|
||||||
|
if (state is DownloadService.State.Success && isResumed && !rootInstallerEnabled) {
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionClick(action: AppDetailAdapter.Action) {
|
||||||
|
when (action) {
|
||||||
|
AppDetailAdapter.Action.INSTALL,
|
||||||
|
AppDetailAdapter.Action.UPDATE,
|
||||||
|
-> {
|
||||||
|
val installedItem = installed?.data
|
||||||
|
lifecycleScope.launch {
|
||||||
|
startUpdate(
|
||||||
|
packageName,
|
||||||
|
installedItem,
|
||||||
|
productRepos,
|
||||||
|
downloadConnection
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
AppDetailAdapter.Action.LAUNCH -> {
|
||||||
|
val launcherActivities = installed?.launcherActivities.orEmpty()
|
||||||
|
if (launcherActivities.size >= 2) {
|
||||||
|
LaunchDialog(launcherActivities).show(
|
||||||
|
childFragmentManager,
|
||||||
|
LaunchDialog::class.java.name
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
launcherActivities.firstOrNull()?.let { startLauncherActivity(it.first) }
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
AppDetailAdapter.Action.DETAILS -> {
|
||||||
|
startActivity(
|
||||||
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
|
.setData(Uri.parse("package:$packageName"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AppDetailAdapter.Action.UNINSTALL -> {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
AppInstaller.getInstance(context)?.defaultInstaller?.uninstall(packageName)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
AppDetailAdapter.Action.CANCEL -> {
|
||||||
|
val binder = downloadConnection.binder
|
||||||
|
if (downloading && binder != null) {
|
||||||
|
binder.cancel(packageName)
|
||||||
|
} else Unit
|
||||||
|
}
|
||||||
|
AppDetailAdapter.Action.SHARE -> {
|
||||||
|
shareIntent(packageName, productRepos[0].first.name)
|
||||||
|
}
|
||||||
|
}::class
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startLauncherActivity(name: String) {
|
||||||
|
try {
|
||||||
|
startActivity(
|
||||||
|
Intent(Intent.ACTION_MAIN)
|
||||||
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
.setComponent(ComponentName(packageName, name))
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareIntent(packageName: String, appName: String) {
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||||
|
val extraText = if (Android.sdk(24)) {
|
||||||
|
"https://www.f-droid.org/${resources.configuration.locales[0].language}/packages/${packageName}/"
|
||||||
|
} else "https://www.f-droid.org/${resources.configuration.locale.language}/packages/${packageName}/"
|
||||||
|
|
||||||
|
|
||||||
|
shareIntent.type = "text/plain"
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TITLE, appName)
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT, appName)
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, extraText)
|
||||||
|
|
||||||
|
startActivity(Intent.createChooser(shareIntent, "Where to Send?"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceChanged(preference: ProductPreference) {
|
||||||
|
lifecycleScope.launch { updateButtons(preference) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionsClick(group: String?, permissions: List<String>) {
|
||||||
|
MessageDialog(MessageDialog.Message.Permissions(group, permissions)).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScreenshotClick(screenshot: Product.Screenshot) {
|
||||||
|
val pair = productRepos.asSequence()
|
||||||
|
.map { it ->
|
||||||
|
Pair(
|
||||||
|
it.second,
|
||||||
|
it.first.screenshots.find { it === screenshot }?.identifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.filter { it.second != null }.firstOrNull()
|
||||||
|
if (pair != null) {
|
||||||
|
val (repository, identifier) = pair
|
||||||
|
if (identifier != null) {
|
||||||
|
ScreenshotsFragment(packageName, repository.id, identifier).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReleaseClick(release: Release) {
|
||||||
|
val installedItem = installed?.data
|
||||||
|
when {
|
||||||
|
release.incompatibilities.isNotEmpty() -> {
|
||||||
|
MessageDialog(
|
||||||
|
MessageDialog.Message.ReleaseIncompatible(
|
||||||
|
release.incompatibilities,
|
||||||
|
release.platforms, release.minSdkVersion, release.maxSdkVersion
|
||||||
|
)
|
||||||
|
).show(childFragmentManager)
|
||||||
|
}
|
||||||
|
installedItem != null && installedItem.version_code > release.versionCode -> {
|
||||||
|
MessageDialog(MessageDialog.Message.ReleaseOlder).show(childFragmentManager)
|
||||||
|
}
|
||||||
|
installedItem != null && installedItem.signature != release.signature -> {
|
||||||
|
MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val productRepository =
|
||||||
|
productRepos.asSequence()
|
||||||
|
.filter { it -> it.first.releases.any { it === release } }
|
||||||
|
.firstOrNull()
|
||||||
|
if (productRepository != null) {
|
||||||
|
downloadConnection.binder?.enqueue(
|
||||||
|
packageName, productRepository.first.name,
|
||||||
|
productRepository.second, release
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUriClick(uri: Uri, shouldConfirm: Boolean): Boolean {
|
||||||
|
return if (shouldConfirm && (uri.scheme == "http" || uri.scheme == "https")) {
|
||||||
|
MessageDialog(MessageDialog.Message.Link(uri)).show(childFragmentManager)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, uri))
|
||||||
|
true
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LaunchDialog() : DialogFragment() {
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_NAMES = "names"
|
||||||
|
private const val EXTRA_LABELS = "labels"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(launcherActivities: List<Pair<String, String>>) : this() {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putStringArrayList(EXTRA_NAMES, ArrayList(launcherActivities.map { it.first }))
|
||||||
|
putStringArrayList(EXTRA_LABELS, ArrayList(launcherActivities.map { it.second }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
|
||||||
|
val names = requireArguments().getStringArrayList(EXTRA_NAMES)!!
|
||||||
|
val labels = requireArguments().getStringArrayList(EXTRA_LABELS)!!
|
||||||
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.launch)
|
||||||
|
.setItems(labels.toTypedArray()) { _, position ->
|
||||||
|
(parentFragment as AppSheetX)
|
||||||
|
.startLauncherActivity(names[position])
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Action(
|
||||||
|
val id: Int,
|
||||||
|
val adapterAction: AppDetailAdapter.Action,
|
||||||
|
) {
|
||||||
|
INSTALL(1, AppDetailAdapter.Action.INSTALL),
|
||||||
|
UPDATE(2, AppDetailAdapter.Action.UPDATE),
|
||||||
|
LAUNCH(3, AppDetailAdapter.Action.LAUNCH),
|
||||||
|
DETAILS(4, AppDetailAdapter.Action.DETAILS),
|
||||||
|
UNINSTALL(5, AppDetailAdapter.Action.UNINSTALL),
|
||||||
|
SHARE(6, AppDetailAdapter.Action.SHARE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Installed(
|
||||||
|
val data: com.looker.droidify.database.entity.Installed, val isSystem: Boolean,
|
||||||
|
val launcherActivities: List<Pair<String, String>>,
|
||||||
|
)
|
||||||
|
}
|
@ -56,6 +56,14 @@ class ExploreFragment : MainNavFragmentX() {
|
|||||||
}
|
}
|
||||||
appsFastAdapter = FastAdapter.with(appsItemAdapter)
|
appsFastAdapter = FastAdapter.with(appsItemAdapter)
|
||||||
appsFastAdapter?.setHasStableIds(true)
|
appsFastAdapter?.setHasStableIds(true)
|
||||||
|
appsFastAdapter?.onClickListener =
|
||||||
|
{ _: View?, _: IAdapter<VAppItem>?, item: VAppItem?, _: Int? ->
|
||||||
|
item?.item?.let {
|
||||||
|
AppSheetX(it.packageName)
|
||||||
|
.showNow(parentFragmentManager, "Product ${it.packageName}")
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
binding.recyclerView.apply {
|
binding.recyclerView.apply {
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.looker.droidify.ui.viewmodels
|
||||||
|
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.looker.droidify.database.DatabaseX
|
||||||
|
import com.looker.droidify.database.entity.Installed
|
||||||
|
import com.looker.droidify.database.entity.Product
|
||||||
|
import com.looker.droidify.database.entity.Repository
|
||||||
|
|
||||||
|
class AppViewModelX(val db: DatabaseX, val packageName: String) : ViewModel() {
|
||||||
|
|
||||||
|
val products = MediatorLiveData<List<Product?>>()
|
||||||
|
val repositories = MediatorLiveData<List<Repository>>()
|
||||||
|
val installedItem = MediatorLiveData<Installed?>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
products.addSource(db.productDao.getLive(packageName), products::setValue)
|
||||||
|
repositories.addSource(db.repositoryDao.allLive, repositories::setValue)
|
||||||
|
installedItem.addSource(db.installedDao.getLive(packageName), installedItem::setValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory(val db: DatabaseX, val packageName: String) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("unchecked_cast")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(AppViewModelX::class.java)) {
|
||||||
|
return AppViewModelX(db, packageName) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/main/res/layout/sheet_app_x.xml
Normal file
46
src/main/res/layout/sheet_app_x.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ OAndBackupX: open-source apps backup and restore app.
|
||||||
|
~ Copyright (C) 2020 Antonios Hazim
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU Affero General Public License as
|
||||||
|
~ published by the Free Software Foundation, either version 3 of the
|
||||||
|
~ License, or (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU Affero General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU Affero General Public License
|
||||||
|
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<com.google.android.material.circularreveal.CircularRevealFrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scrollLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:listitem="@layout/item_repository_x" />
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</com.google.android.material.circularreveal.CircularRevealFrameLayout>
|
||||||
|
</layout>
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user