Merge branch 'dev'

This commit is contained in:
Aayush Gupta
2025-02-23 15:36:39 +08:00
10 changed files with 90 additions and 183 deletions

View File

@@ -30,6 +30,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
/**
* A top app bar composable to be used with Scaffold in different Screen
@@ -94,3 +96,13 @@ fun SearchAppBarComposable(
}
)
}
@Preview(showBackground = true)
@Composable
private fun SearchAppBarComposablePreview() {
SearchAppBarComposable(
searchHint = R.string.search_hint,
onNavigateUp = {},
onSearch = {}
)
}

View File

@@ -16,6 +16,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
/**
* A top app bar composable to be used with Scaffold in different Screen
@@ -45,3 +47,12 @@ fun TopAppBarComposable(
actions = actions
)
}
@Preview(showBackground = true)
@Composable
private fun TopAppBarComposablePreview() {
TopAppBarComposable(
title = R.string.title_about,
onNavigateUp = {}
)
}

View File

@@ -37,7 +37,7 @@ import java.util.Calendar
@Composable
fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hiltViewModel()) {
val ctx = LocalContext.current
val context = LocalContext.current
val packages by viewModel.filteredPackages.collectAsStateWithLifecycle()
ScreenContent(
@@ -46,12 +46,12 @@ fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hi
isPackageBlacklisted = { pkgName -> pkgName in viewModel.blacklist },
isPackageFiltered = { pkgInfo -> viewModel.isFiltered(pkgInfo) },
onBlacklistImport = { uri ->
viewModel.importBlacklist(ctx, uri)
ctx.toast(R.string.toast_black_import_success)
viewModel.importBlacklist(context, uri)
context.toast(R.string.toast_black_import_success)
},
onBlacklistExport = { uri ->
viewModel.exportBlacklist(ctx, uri)
ctx.toast(R.string.toast_black_export_success)
viewModel.exportBlacklist(context, uri)
context.toast(R.string.toast_black_export_success)
},
onBlacklist = { packageName -> viewModel.blacklist(packageName) },
onBlacklistAll = { viewModel.blacklistAll() },
@@ -76,17 +76,25 @@ private fun ScreenContent(
onSearch: (query: String) -> Unit = {}
) {
val ctx = LocalContext.current
val context = LocalContext.current
val docImportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = {
if (it != null) onBlacklistImport(it) else ctx.toast(R.string.toast_black_import_failed)
if (it != null) {
onBlacklistImport(it)
} else {
context.toast(R.string.toast_black_import_failed)
}
}
)
val docExportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE),
onResult = {
if (it != null) onBlacklistExport(it) else ctx.toast(R.string.toast_black_export_failed)
if (it != null) {
onBlacklistExport(it)
} else {
context.toast(R.string.toast_black_export_failed)
}
}
)
@@ -129,8 +137,8 @@ private fun ScreenContent(
val isBlacklisted = isPackageBlacklisted(pkg.packageName)
val isFiltered = isPackageFiltered(pkg)
BlackListComposable(
icon = PackageUtil.getIconForPackage(ctx, pkg.packageName)!!,
displayName = pkg.applicationInfo!!.loadLabel(ctx.packageManager).toString(),
icon = PackageUtil.getIconForPackage(context, pkg.packageName)!!,
displayName = pkg.applicationInfo!!.loadLabel(context.packageManager).toString(),
packageName = pkg.packageName,
versionName = pkg.versionName!!,
versionCode = PackageInfoCompat.getLongVersionCode(pkg),

View File

@@ -12,6 +12,7 @@ import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import com.aurora.extensions.checkManifestPermission
import com.aurora.extensions.isDomainVerified
@@ -73,7 +74,22 @@ class PermissionProvider(private val fragment: Fragment) :
if (!isGranted(PermissionType.INSTALL_UNKNOWN_APPS)) {
context.toast(R.string.toast_permission_installer_required)
} else {
/**
* I don't know why, but for storage manager permission on Android 11 & 12,
* we need to request both permissions otherwise the permission is not granted,
* even though OS says it is granted.
*/
ActivityCompat.requestPermissions(
fragment.requireActivity(),
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
// TODO: Verify & remove this after testing
context.toast(R.string.toast_permission_esm_restart)
intentLauncher.launch(intent)
}
} else {

View File

@@ -174,18 +174,11 @@ object PackageUtil {
@RequiresApi(Build.VERSION_CODES.R)
fun getStorageManagerIntent(context: Context): Intent {
var intent = Intent(
val intent = Intent(
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
Uri.parse("package:${BuildConfig.APPLICATION_ID}")
)
if (isHuawei) {
intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:${BuildConfig.APPLICATION_ID}")
)
}
// Check if the intent can be resolved
val packageManager = context.packageManager
val isIntentAvailable = packageManager.queryIntentActivities(

View File

@@ -1,80 +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
import android.content.Context
import android.content.pm.PackageInfo
import android.util.AttributeSet
import android.widget.CompoundButton
import androidx.core.content.pm.PackageInfoCompat
import coil3.load
import coil3.request.placeholder
import coil3.request.transformations
import coil3.transform.RoundedCornersTransformation
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.store.R
import com.aurora.store.databinding.ViewBlackBinding
import com.aurora.store.util.PackageUtil
@ModelView(
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
baseModelClass = BaseModel::class
)
class BlackListView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseView<ViewBlackBinding>(context, attrs, defStyleAttr) {
@ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode])
fun packageInfo(packageInfo: PackageInfo) {
val appInfo = packageInfo.applicationInfo!!
binding.imgIcon.load(PackageUtil.getIconForPackage(context, appInfo.packageName)) {
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(25F))
}
binding.txtLine1.text = appInfo.loadLabel(context.packageManager)
binding.txtLine2.text = appInfo.packageName
binding.txtLine3.text = ("${packageInfo.versionName}.${PackageInfoCompat.getLongVersionCode(packageInfo)}")
}
@ModelProp
fun markChecked(isChecked: Boolean) {
binding.checkbox.isChecked = isChecked
}
@CallbackProp
fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) {
binding.checkbox.setOnCheckedChangeListener(onCheckedChangeListener)
}
@CallbackProp
fun click(onClickListener: OnClickListener?) {
binding.root.setOnClickListener(onClickListener)
}
@CallbackProp
fun longClick(onClickListener: OnLongClickListener?) {
binding.root.setOnLongClickListener(onClickListener)
}
}

View File

@@ -61,6 +61,7 @@ import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.data.models.datasafety.EntryType
import com.aurora.store.AppStreamStash
import com.aurora.store.AuroraApp
import com.aurora.store.MainActivity
import com.aurora.store.R
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.Event
@@ -87,6 +88,7 @@ import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.details.AppDetailsViewModel
import com.aurora.store.viewmodel.details.DetailsClusterViewModel
import com.jakewharton.processphoenix.ProcessPhoenix
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
@@ -538,8 +540,16 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
viewModel.download(app)
} else {
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
if (it) viewModel.download(app) else {
// TODO: Ask for permission again or redirect to Permission Manager
if (it) {
// Restart the app to ensure all permissions are granted
val intent = Intent(
requireContext(),
MainActivity::class.java
)
// Pass the packageName so we're back to same app
intent.putExtra("packageName", app.packageName)
ProcessPhoenix.triggerRebirth(requireContext(), intent)
}
}
}
@@ -604,9 +614,13 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
binding.layoutDetailsApp.btnSecondaryAction.isEnabled = true
if (app.isInstalled) {
val isUpdatable = PackageUtil.isUpdatable(requireContext(), app.packageName, app.versionCode.toLong())
val isUpdatable =
PackageUtil.isUpdatable(requireContext(), app.packageName, app.versionCode.toLong())
val hasValidCert = app.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes(requireContext(), app.packageName)
it.certificateSet in CertUtil.getEncodedCertificateHashes(
requireContext(),
app.packageName
)
}
if ((isUpdatable && hasValidCert) || (isUpdatable && isExtendedUpdateEnabled)) {

View File

@@ -45,25 +45,27 @@ class UpdatesRestrictionsDialog : DialogFragment() {
super.onDestroy()
}
private fun setupRestrictions(ctx: Context) {
private fun setupRestrictions(context: Context) {
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxMetered)?.apply {
isChecked = Preferences.getBoolean(ctx, PREFERENCES_UPDATES_RESTRICTIONS_METERED, true)
isChecked =
Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_METERED, true)
setOnCheckedChangeListener { _, isChecked ->
Preferences.putBoolean(ctx, PREFERENCES_UPDATES_RESTRICTIONS_METERED, isChecked)
Preferences.putBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_METERED, isChecked)
}
}
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxIdle)?.apply {
isChecked = Preferences.getBoolean(ctx, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, true)
isChecked = Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, true)
setOnCheckedChangeListener { _, isChecked ->
Preferences.putBoolean(ctx, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, isChecked)
Preferences.putBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, isChecked)
}
}
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxBattery)?.apply {
isChecked = Preferences.getBoolean(ctx, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, true)
isChecked =
Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, true)
setOnCheckedChangeListener { _, isChecked ->
Preferences.putBoolean(ctx, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, isChecked)
Preferences.putBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, isChecked)
}
}
}

View File

@@ -29,9 +29,9 @@ import android.os.Looper
import android.util.Base64
import android.util.Log
import android.view.View
import androidx.core.view.isVisible
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
@@ -240,7 +240,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
}
private fun navigateToDefaultTab() {
val defaultDestination = Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
val defaultDestination =
Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
val directions =
when (requireArguments().getInt("destinationId", defaultDestination)) {
R.id.updatesFragment -> {
@@ -263,6 +264,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
} else if (requireActivity().intent != null && requireActivity().intent.action == Intent.ACTION_SEND) {
val clipData = requireActivity().intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
UrlQuerySanitizer(clipData).getValue("id") ?: ""
} else if (requireActivity().intent != null && requireActivity().intent.extras != null) {
requireActivity().intent.extras?.getString("packageName") ?: ""
} else {
requireArguments().getString("packageName") ?: ""
}

View File

@@ -1,72 +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:orientation="horizontal"
android:paddingStart="@dimen/padding_normal"
android:paddingTop="@dimen/padding_xsmall"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_xsmall">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_icon"
android:layout_width="@dimen/icon_size_medium"
android:layout_height="@dimen/icon_size_medium"
android:layout_centerVertical="true" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line1"
style="@style/AuroraTextStyle.Line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_toStartOf="@id/checkbox"
android:layout_toEndOf="@id/img_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line2"
style="@style/AuroraTextStyle.Line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_line1"
android:layout_alignStart="@id/txt_line1"
android:layout_alignEnd="@id/txt_line1"
android:layout_marginTop="@dimen/margin_xxsmall" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line3"
style="@style/AuroraTextStyle.Line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_line2"
android:layout_alignStart="@id/txt_line1"
android:layout_alignEnd="@id/txt_line1"
android:layout_marginTop="@dimen/margin_xxsmall" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
android:minWidth="0dp" />
</RelativeLayout>