compose: preferences: Migrate installer logic to compose
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.preview.PreviewTemplate
|
||||||
|
import com.aurora.store.data.installer.SessionInstaller
|
||||||
|
import com.aurora.store.data.model.InstallerInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable to display installer details in a list
|
||||||
|
* @param modifier The modifier to be applied to the composable
|
||||||
|
* @param installerInfo A [InstallerInfo] object to display details
|
||||||
|
* @param isSelected Whether this installer is selected
|
||||||
|
* @param onClick Callback when this composable is clicked
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun InstallerListItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
installerInfo: InstallerInfo,
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
onClick: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(dimensionResource(R.dimen.padding_small)),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1F)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(installerInfo.title),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(installerInfo.subtitle),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(installerInfo.description),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RadioButton(selected = isSelected, onClick = onClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun InstallerListItemPreview() {
|
||||||
|
PreviewTemplate {
|
||||||
|
InstallerListItem(installerInfo = SessionInstaller.installerInfo, isSelected = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import com.aurora.store.compose.ui.dispenser.DispenserScreen
|
|||||||
import com.aurora.store.compose.ui.downloads.DownloadsScreen
|
import com.aurora.store.compose.ui.downloads.DownloadsScreen
|
||||||
import com.aurora.store.compose.ui.favourite.FavouriteScreen
|
import com.aurora.store.compose.ui.favourite.FavouriteScreen
|
||||||
import com.aurora.store.compose.ui.onboarding.OnboardingScreen
|
import com.aurora.store.compose.ui.onboarding.OnboardingScreen
|
||||||
|
import com.aurora.store.compose.ui.preferences.installation.InstallerScreen
|
||||||
import com.aurora.store.compose.ui.search.SearchScreen
|
import com.aurora.store.compose.ui.search.SearchScreen
|
||||||
import com.aurora.store.compose.ui.spoof.SpoofScreen
|
import com.aurora.store.compose.ui.spoof.SpoofScreen
|
||||||
|
|
||||||
@@ -140,6 +141,10 @@ fun NavDisplay(startDestination: NavKey) {
|
|||||||
entry<Screen.Dispenser> {
|
entry<Screen.Dispenser> {
|
||||||
DispenserScreen(onNavigateUp = ::onNavigateUp)
|
DispenserScreen(onNavigateUp = ::onNavigateUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry<Screen.Installer> {
|
||||||
|
InstallerScreen(onNavigateUp = ::onNavigateUp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,4 +57,7 @@ sealed class Screen : NavKey, Parcelable {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Dispenser : Screen()
|
data object Dispenser : Screen()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object Installer : Screen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.preferences.installation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composable.InstallerListItem
|
||||||
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
|
import com.aurora.store.compose.preview.PreviewTemplate
|
||||||
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
|
import com.aurora.store.data.installer.SessionInstaller
|
||||||
|
import com.aurora.store.data.model.Installer
|
||||||
|
import com.aurora.store.data.model.InstallerInfo
|
||||||
|
import com.aurora.store.viewmodel.preferences.InstallerViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InstallerScreen(onNavigateUp: () -> Unit, viewModel: InstallerViewModel = hiltViewModel()) {
|
||||||
|
val currentInstallerId by viewModel.currentInstaller.collectAsStateWithLifecycle()
|
||||||
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = Unit) {
|
||||||
|
viewModel.error.collect { error ->
|
||||||
|
snackBarHostState.showSnackbar(message = error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenContent(
|
||||||
|
onNavigateUp = onNavigateUp,
|
||||||
|
snackBarHostState = snackBarHostState,
|
||||||
|
currentInstaller = Installer.entries[currentInstallerId],
|
||||||
|
availableInstallers = AppInstaller.getAvailableInstallersInfo(LocalContext.current),
|
||||||
|
onInstallerSelected = { installer -> viewModel.save(installer) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(
|
||||||
|
onNavigateUp: () -> Unit = {},
|
||||||
|
snackBarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
|
currentInstaller: Installer = Installer.SESSION,
|
||||||
|
availableInstallers: List<InstallerInfo> = emptyList(),
|
||||||
|
onInstallerSelected: (installer: Installer) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val snackBarHostState = remember { snackBarHostState }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(hostState = snackBarHostState)
|
||||||
|
},
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.pref_install_mode_title),
|
||||||
|
onNavigateUp = onNavigateUp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
||||||
|
) {
|
||||||
|
items(items = availableInstallers, key = { i -> i.id }) { installerInfo ->
|
||||||
|
InstallerListItem(
|
||||||
|
installerInfo = installerInfo,
|
||||||
|
isSelected = installerInfo.installer == currentInstaller,
|
||||||
|
onClick = { onInstallerSelected(installerInfo.installer) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun InstallerScreenPreview() {
|
||||||
|
PreviewTemplate {
|
||||||
|
ScreenContent(
|
||||||
|
availableInstallers = listOf(SessionInstaller.installerInfo)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Aurora Store
|
|
||||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
*
|
|
||||||
* Aurora Store is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Aurora Store 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.view.epoxy.views.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.widget.CompoundButton
|
|
||||||
import com.airbnb.epoxy.CallbackProp
|
|
||||||
import com.airbnb.epoxy.ModelProp
|
|
||||||
import com.airbnb.epoxy.ModelView
|
|
||||||
import com.aurora.store.data.model.InstallerInfo
|
|
||||||
import com.aurora.store.databinding.ViewInstallerBinding
|
|
||||||
import com.aurora.store.view.epoxy.views.BaseModel
|
|
||||||
import com.aurora.store.view.epoxy.views.BaseView
|
|
||||||
|
|
||||||
@ModelView(
|
|
||||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
|
||||||
baseModelClass = BaseModel::class
|
|
||||||
)
|
|
||||||
class InstallerView @JvmOverloads constructor(
|
|
||||||
context: Context?,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0
|
|
||||||
) : BaseView<ViewInstallerBinding>(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
@ModelProp
|
|
||||||
fun installer(installer: InstallerInfo) {
|
|
||||||
binding.line1.text = context.getString(installer.title)
|
|
||||||
binding.line2.text = context.getString(installer.subtitle)
|
|
||||||
binding.line3.text = context.getString(installer.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ModelProp
|
|
||||||
fun markChecked(isChecked: Boolean) {
|
|
||||||
binding.radiobutton.isChecked = isChecked
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallbackProp
|
|
||||||
fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) {
|
|
||||||
binding.radiobutton.setOnCheckedChangeListener(onCheckedChangeListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallbackProp
|
|
||||||
fun click(onClickListener: OnClickListener?) {
|
|
||||||
binding.root.setOnClickListener(onClickListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,8 +28,10 @@ import androidx.core.content.getSystemService
|
|||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import com.aurora.extensions.navigate
|
||||||
import com.aurora.extensions.showDialog
|
import com.aurora.extensions.showDialog
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.navigation.Screen
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLATION_DEVICE_OWNER
|
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLATION_DEVICE_OWNER
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -42,7 +44,7 @@ class InstallationPreference : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
findPreference<Preference>(PREFERENCE_INSTALLER_ID)?.apply {
|
findPreference<Preference>(PREFERENCE_INSTALLER_ID)?.apply {
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
findNavController().navigate(R.id.installerFragment)
|
requireContext().navigate(Screen.Installer)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
/*
|
|
||||||
* Aurora Store
|
|
||||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
*
|
|
||||||
* Aurora Store is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Aurora Store 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.view.ui.preferences.installation
|
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.aurora.extensions.isMIUI
|
|
||||||
import com.aurora.extensions.isMiuiOptimizationDisabled
|
|
||||||
import com.aurora.extensions.isOAndAbove
|
|
||||||
import com.aurora.extensions.showDialog
|
|
||||||
import com.aurora.store.R
|
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
|
||||||
import com.aurora.store.databinding.FragmentInstallerBinding
|
|
||||||
import com.aurora.store.util.Preferences
|
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
|
||||||
import com.aurora.store.util.save
|
|
||||||
import com.aurora.store.view.epoxy.views.preference.InstallerViewModel_
|
|
||||||
import com.aurora.store.view.ui.commons.BaseFragment
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import rikka.shizuku.Shizuku
|
|
||||||
import rikka.sui.Sui
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class InstallerFragment : BaseFragment<FragmentInstallerBinding>() {
|
|
||||||
|
|
||||||
private val TAG = InstallerFragment::class.java.simpleName
|
|
||||||
|
|
||||||
private var installerId: Int = 0
|
|
||||||
|
|
||||||
private var shizukuAlive = Sui.isSui()
|
|
||||||
private val shizukuAliveListener = Shizuku.OnBinderReceivedListener {
|
|
||||||
Log.d(TAG, "ShizukuInstaller Alive!")
|
|
||||||
shizukuAlive = true
|
|
||||||
}
|
|
||||||
private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
|
|
||||||
Log.d(TAG, "ShizukuInstaller Dead!")
|
|
||||||
shizukuAlive = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private val shizukuResultListener =
|
|
||||||
Shizuku.OnRequestPermissionResultListener { _: Int, result: Int ->
|
|
||||||
if (result == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
this.installerId = 5
|
|
||||||
save(PREFERENCE_INSTALLER_ID, 5)
|
|
||||||
binding.epoxyRecycler.requestModelBuild()
|
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
R.string.action_installations,
|
|
||||||
R.string.installer_shizuku_unavailable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
|
||||||
|
|
||||||
installerId = Preferences.getInteger(requireContext(), PREFERENCE_INSTALLER_ID)
|
|
||||||
|
|
||||||
if (AppInstaller.hasShizukuOrSui(requireContext())) {
|
|
||||||
Shizuku.addBinderReceivedListenerSticky(shizukuAliveListener)
|
|
||||||
Shizuku.addBinderDeadListener(shizukuDeadListener)
|
|
||||||
Shizuku.addRequestPermissionResultListener(shizukuResultListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecyclerView
|
|
||||||
binding.epoxyRecycler.withModels {
|
|
||||||
setFilterDuplicates(true)
|
|
||||||
AppInstaller.getAvailableInstallersInfo(requireContext()).forEach {
|
|
||||||
add(
|
|
||||||
InstallerViewModel_()
|
|
||||||
.id(it.id)
|
|
||||||
.installer(it)
|
|
||||||
.markChecked(installerId == it.id)
|
|
||||||
.click { _ ->
|
|
||||||
save(it.id)
|
|
||||||
requestModelBuild()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMIUI && !isMiuiOptimizationDisabled) {
|
|
||||||
findNavController().navigate(R.id.deviceMiuiSheet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
if (AppInstaller.hasShizukuOrSui(requireContext())) {
|
|
||||||
Shizuku.removeBinderReceivedListener(shizukuAliveListener)
|
|
||||||
Shizuku.removeBinderDeadListener(shizukuDeadListener)
|
|
||||||
Shizuku.removeRequestPermissionResultListener(shizukuResultListener)
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun save(installerId: Int) {
|
|
||||||
when (installerId) {
|
|
||||||
0 -> {
|
|
||||||
if (isMIUI && !isMiuiOptimizationDisabled) {
|
|
||||||
findNavController().navigate(R.id.deviceMiuiSheet)
|
|
||||||
}
|
|
||||||
this.installerId = installerId
|
|
||||||
save(PREFERENCE_INSTALLER_ID, installerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
2 -> {
|
|
||||||
if (AppInstaller.hasRootAccess()) {
|
|
||||||
this.installerId = installerId
|
|
||||||
save(PREFERENCE_INSTALLER_ID, installerId)
|
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
R.string.action_installations,
|
|
||||||
R.string.installer_root_unavailable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
3 -> {
|
|
||||||
if (AppInstaller.hasAuroraService(requireContext())) {
|
|
||||||
this.installerId = installerId
|
|
||||||
save(PREFERENCE_INSTALLER_ID, installerId)
|
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
R.string.action_installations,
|
|
||||||
R.string.installer_service_unavailable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
4 -> {
|
|
||||||
if (AppInstaller.hasAppManager(requireContext())) {
|
|
||||||
this.installerId = installerId
|
|
||||||
save(PREFERENCE_INSTALLER_ID, installerId)
|
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
R.string.action_installations,
|
|
||||||
R.string.installer_am_unavailable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
5 -> {
|
|
||||||
if (isOAndAbove && AppInstaller.hasShizukuOrSui(requireContext())) {
|
|
||||||
if (shizukuAlive && AppInstaller.hasShizukuPerm()) {
|
|
||||||
this.installerId = installerId
|
|
||||||
save(PREFERENCE_INSTALLER_ID, installerId)
|
|
||||||
} else if (shizukuAlive && !Shizuku.shouldShowRequestPermissionRationale()) {
|
|
||||||
Shizuku.requestPermission(9000)
|
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
R.string.action_installations,
|
|
||||||
R.string.installer_shizuku_unavailable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
R.string.action_installations,
|
|
||||||
R.string.installer_shizuku_unavailable
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
this.installerId = installerId
|
|
||||||
save(PREFERENCE_INSTALLER_ID, installerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.viewmodel.preferences
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.aurora.extensions.isMIUI
|
||||||
|
import com.aurora.extensions.isMiuiOptimizationDisabled
|
||||||
|
import com.aurora.extensions.observeAsStateFlow
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
|
import com.aurora.store.data.model.Installer
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||||
|
import com.aurora.store.util.save
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import rikka.shizuku.Shizuku
|
||||||
|
import rikka.sui.Sui
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class InstallerViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) : ViewModel(), Shizuku.OnBinderReceivedListener, Shizuku.OnBinderDeadListener,
|
||||||
|
Shizuku.OnRequestPermissionResultListener {
|
||||||
|
|
||||||
|
private val TAG = InstallerViewModel::class.java.simpleName
|
||||||
|
private val sharedPreferences = Preferences.getPrefs(context)
|
||||||
|
private var isShizukuAlive = Sui.isSui()
|
||||||
|
|
||||||
|
private val _error = MutableSharedFlow<String>()
|
||||||
|
val error = _error.asSharedFlow()
|
||||||
|
|
||||||
|
private var _installerId: Int
|
||||||
|
get() = sharedPreferences.getInt(PREFERENCE_INSTALLER_ID, 0)
|
||||||
|
set(value) = sharedPreferences.edit { putInt(PREFERENCE_INSTALLER_ID, value) }
|
||||||
|
|
||||||
|
val currentInstaller = sharedPreferences.observeAsStateFlow(
|
||||||
|
key = PREFERENCE_INSTALLER_ID,
|
||||||
|
scope = viewModelScope,
|
||||||
|
initial = AppInstaller.getCurrentInstaller(context).ordinal,
|
||||||
|
valueProvider = { _installerId }
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
Shizuku.addBinderReceivedListenerSticky(this)
|
||||||
|
Shizuku.addBinderDeadListener(this)
|
||||||
|
Shizuku.addRequestPermissionResultListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBinderReceived() {
|
||||||
|
isShizukuAlive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBinderDead() {
|
||||||
|
isShizukuAlive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
|
||||||
|
if (grantResult == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
save(Installer.SHIZUKU)
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch {
|
||||||
|
Log.e(TAG, "Permission denied for shizuku")
|
||||||
|
_error.emit(context.getString(R.string.permissions_denied))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save(installer: Installer) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Error handling for different installers
|
||||||
|
when (installer) {
|
||||||
|
Installer.SESSION -> {
|
||||||
|
if (isMIUI && !isMiuiOptimizationDisabled) {
|
||||||
|
Log.e(TAG, "Trying to set session installer with MIUI optimizations")
|
||||||
|
_error.emit(context.getString(R.string.device_miui_description))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Installer.ROOT -> {
|
||||||
|
if (!AppInstaller.hasRootAccess()) {
|
||||||
|
Log.e(TAG, "Trying to set root installer without root access")
|
||||||
|
_error.emit(context.getString(R.string.installer_root_unavailable))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Installer.SERVICE -> {
|
||||||
|
if (!AppInstaller.hasAuroraService(context)) {
|
||||||
|
Log.e(TAG, "Trying to set service installer without companion app")
|
||||||
|
_error.emit(context.getString(R.string.installer_service_unavailable))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Installer.SHIZUKU -> {
|
||||||
|
if (AppInstaller.hasShizukuOrSui(context) && isShizukuAlive) {
|
||||||
|
if (!AppInstaller.hasShizukuPerm()) {
|
||||||
|
Log.i(TAG, "Requesting permission for shizuku")
|
||||||
|
Shizuku.requestPermission(9000)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Trying to set shizuku installer without appropriate setup")
|
||||||
|
_error.emit(context.getString(R.string.installer_shizuku_unavailable))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Installer.AM -> {
|
||||||
|
if (!AppInstaller.hasAppManager(context)) {
|
||||||
|
Log.e(TAG, "Trying to set AM installer without companion app")
|
||||||
|
_error.emit(context.getString(R.string.installer_am_unavailable))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Log.i(TAG, "Trying to set ${installer.name} installer without any checks")
|
||||||
|
}
|
||||||
|
|
||||||
|
context.save(PREFERENCE_INSTALLER_ID, installer.ordinal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store is free software: you can redistribute it and/or modify
|
|
||||||
~ it under the terms of the GNU General Public License as published by
|
|
||||||
~ the Free Software Foundation, either version 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 General Public License for more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU General Public License
|
|
||||||
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minHeight="?attr/actionBarSize"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_back"
|
|
||||||
app:title="@string/pref_install_mode_title" />
|
|
||||||
|
|
||||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
|
||||||
android:id="@+id/epoxy_recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:overScrollMode="never"
|
|
||||||
android:padding="@dimen/padding_normal"
|
|
||||||
android:scrollbars="none"
|
|
||||||
app:itemSpacing="@dimen/margin_small"
|
|
||||||
tools:listitem="@layout/view_installer" />
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store is free software: you can redistribute it and/or modify
|
|
||||||
~ it under the terms of the GNU General Public License as published by
|
|
||||||
~ the Free Software Foundation, either version 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 General Public License for more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU General Public License
|
|
||||||
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minHeight="?attr/actionBarSize"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_back"
|
|
||||||
app:title="@string/pref_install_mode_title" />
|
|
||||||
|
|
||||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
|
||||||
android:id="@+id/epoxy_recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:overScrollMode="never"
|
|
||||||
android:padding="@dimen/padding_normal"
|
|
||||||
android:scrollbars="none"
|
|
||||||
app:itemSpacing="@dimen/margin_small"
|
|
||||||
tools:listitem="@layout/view_installer" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Aurora Store
|
|
||||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
~
|
|
||||||
~ Aurora Store is free software: you can redistribute it and/or modify
|
|
||||||
~ it under the terms of the GNU General Public License as published by
|
|
||||||
~ the Free Software Foundation, either version 2 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ Aurora Store 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 General Public License for more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU General Public License
|
|
||||||
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
-->
|
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="@dimen/padding_small">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/line1"
|
|
||||||
style="@style/AuroraTextStyle.Subtitle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_toStartOf="@id/radiobutton" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/line2"
|
|
||||||
style="@style/AuroraTextStyle.Line1"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/line1"
|
|
||||||
android:layout_alignStart="@id/line1"
|
|
||||||
android:layout_alignEnd="@id/line1"
|
|
||||||
android:layout_marginTop="@dimen/margin_xxsmall" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/line3"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/line2"
|
|
||||||
android:layout_alignStart="@id/line1"
|
|
||||||
android:layout_alignEnd="@id/line1"
|
|
||||||
android:layout_marginTop="@dimen/margin_xxsmall"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
|
||||||
android:id="@+id/radiobutton"
|
|
||||||
android:layout_width="@dimen/icon_size_category"
|
|
||||||
android:layout_height="@dimen/icon_size_category"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:clickable="false"
|
|
||||||
android:focusable="false"
|
|
||||||
android:minWidth="0dp" />
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -184,11 +184,6 @@
|
|||||||
app:popUpTo="@id/splashFragment"
|
app:popUpTo="@id/splashFragment"
|
||||||
app:popUpToInclusive="true" />
|
app:popUpToInclusive="true" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
|
||||||
android:id="@+id/installerFragment"
|
|
||||||
android:name="com.aurora.store.view.ui.preferences.installation.InstallerFragment"
|
|
||||||
android:label="InstallerFragment"
|
|
||||||
tools:layout="@layout/fragment_installer" />
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/appMenuSheet"
|
android:id="@+id/appMenuSheet"
|
||||||
android:name="com.aurora.store.view.ui.sheets.AppMenuSheet"
|
android:name="com.aurora.store.view.ui.sheets.AppMenuSheet"
|
||||||
|
|||||||
Reference in New Issue
Block a user