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.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction 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 * 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.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource 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 * A top app bar composable to be used with Scaffold in different Screen
@@ -45,3 +47,12 @@ fun TopAppBarComposable(
actions = actions 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 @Composable
fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hiltViewModel()) { fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hiltViewModel()) {
val ctx = LocalContext.current val context = LocalContext.current
val packages by viewModel.filteredPackages.collectAsStateWithLifecycle() val packages by viewModel.filteredPackages.collectAsStateWithLifecycle()
ScreenContent( ScreenContent(
@@ -46,12 +46,12 @@ fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hi
isPackageBlacklisted = { pkgName -> pkgName in viewModel.blacklist }, isPackageBlacklisted = { pkgName -> pkgName in viewModel.blacklist },
isPackageFiltered = { pkgInfo -> viewModel.isFiltered(pkgInfo) }, isPackageFiltered = { pkgInfo -> viewModel.isFiltered(pkgInfo) },
onBlacklistImport = { uri -> onBlacklistImport = { uri ->
viewModel.importBlacklist(ctx, uri) viewModel.importBlacklist(context, uri)
ctx.toast(R.string.toast_black_import_success) context.toast(R.string.toast_black_import_success)
}, },
onBlacklistExport = { uri -> onBlacklistExport = { uri ->
viewModel.exportBlacklist(ctx, uri) viewModel.exportBlacklist(context, uri)
ctx.toast(R.string.toast_black_export_success) context.toast(R.string.toast_black_export_success)
}, },
onBlacklist = { packageName -> viewModel.blacklist(packageName) }, onBlacklist = { packageName -> viewModel.blacklist(packageName) },
onBlacklistAll = { viewModel.blacklistAll() }, onBlacklistAll = { viewModel.blacklistAll() },
@@ -76,17 +76,25 @@ private fun ScreenContent(
onSearch: (query: String) -> Unit = {} onSearch: (query: String) -> Unit = {}
) { ) {
val ctx = LocalContext.current val context = LocalContext.current
val docImportLauncher = rememberLauncherForActivityResult( val docImportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(), contract = ActivityResultContracts.OpenDocument(),
onResult = { 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( val docExportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE), contract = ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE),
onResult = { 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 isBlacklisted = isPackageBlacklisted(pkg.packageName)
val isFiltered = isPackageFiltered(pkg) val isFiltered = isPackageFiltered(pkg)
BlackListComposable( BlackListComposable(
icon = PackageUtil.getIconForPackage(ctx, pkg.packageName)!!, icon = PackageUtil.getIconForPackage(context, pkg.packageName)!!,
displayName = pkg.applicationInfo!!.loadLabel(ctx.packageManager).toString(), displayName = pkg.applicationInfo!!.loadLabel(context.packageManager).toString(),
packageName = pkg.packageName, packageName = pkg.packageName,
versionName = pkg.versionName!!, versionName = pkg.versionName!!,
versionCode = PackageInfoCompat.getLongVersionCode(pkg), versionCode = PackageInfoCompat.getLongVersionCode(pkg),

View File

@@ -12,6 +12,7 @@ import android.util.Log
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultCallback import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.aurora.extensions.checkManifestPermission import com.aurora.extensions.checkManifestPermission
import com.aurora.extensions.isDomainVerified import com.aurora.extensions.isDomainVerified
@@ -73,7 +74,22 @@ class PermissionProvider(private val fragment: Fragment) :
if (!isGranted(PermissionType.INSTALL_UNKNOWN_APPS)) { if (!isGranted(PermissionType.INSTALL_UNKNOWN_APPS)) {
context.toast(R.string.toast_permission_installer_required) context.toast(R.string.toast_permission_installer_required)
} else { } 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) context.toast(R.string.toast_permission_esm_restart)
intentLauncher.launch(intent) intentLauncher.launch(intent)
} }
} else { } else {

View File

@@ -174,18 +174,11 @@ object PackageUtil {
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
fun getStorageManagerIntent(context: Context): Intent { fun getStorageManagerIntent(context: Context): Intent {
var intent = Intent( val intent = Intent(
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
Uri.parse("package:${BuildConfig.APPLICATION_ID}") 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 // Check if the intent can be resolved
val packageManager = context.packageManager val packageManager = context.packageManager
val isIntentAvailable = packageManager.queryIntentActivities( 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.gplayapi.data.models.datasafety.EntryType
import com.aurora.store.AppStreamStash import com.aurora.store.AppStreamStash
import com.aurora.store.AuroraApp import com.aurora.store.AuroraApp
import com.aurora.store.MainActivity
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.event.BusEvent import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.Event 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.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.details.AppDetailsViewModel import com.aurora.store.viewmodel.details.AppDetailsViewModel
import com.aurora.store.viewmodel.details.DetailsClusterViewModel import com.aurora.store.viewmodel.details.DetailsClusterViewModel
import com.jakewharton.processphoenix.ProcessPhoenix
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@@ -538,8 +540,16 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
viewModel.download(app) viewModel.download(app)
} else { } else {
permissionProvider.request(PermissionType.STORAGE_MANAGER) { permissionProvider.request(PermissionType.STORAGE_MANAGER) {
if (it) viewModel.download(app) else { if (it) {
// TODO: Ask for permission again or redirect to Permission Manager // 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 binding.layoutDetailsApp.btnSecondaryAction.isEnabled = true
if (app.isInstalled) { 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 { 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)) { if ((isUpdatable && hasValidCert) || (isUpdatable && isExtendedUpdateEnabled)) {

View File

@@ -45,25 +45,27 @@ class UpdatesRestrictionsDialog : DialogFragment() {
super.onDestroy() super.onDestroy()
} }
private fun setupRestrictions(ctx: Context) { private fun setupRestrictions(context: Context) {
dialog?.findViewById<MaterialCheckBox>(R.id.checkboxMetered)?.apply { 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 -> 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 { 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 -> 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 { 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 -> 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.Base64
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import androidx.core.view.isVisible
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@@ -240,7 +240,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
} }
private fun navigateToDefaultTab() { private fun navigateToDefaultTab() {
val defaultDestination = Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB) val defaultDestination =
Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
val directions = val directions =
when (requireArguments().getInt("destinationId", defaultDestination)) { when (requireArguments().getInt("destinationId", defaultDestination)) {
R.id.updatesFragment -> { R.id.updatesFragment -> {
@@ -263,6 +264,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
} else if (requireActivity().intent != null && requireActivity().intent.action == Intent.ACTION_SEND) { } else if (requireActivity().intent != null && requireActivity().intent.action == Intent.ACTION_SEND) {
val clipData = requireActivity().intent.getStringExtra(Intent.EXTRA_TEXT) ?: "" val clipData = requireActivity().intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
UrlQuerySanitizer(clipData).getValue("id") ?: "" UrlQuerySanitizer(clipData).getValue("id") ?: ""
} else if (requireActivity().intent != null && requireActivity().intent.extras != null) {
requireActivity().intent.extras?.getString("packageName") ?: ""
} else { } else {
requireArguments().getString("packageName") ?: "" 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>