compose: Replicate BlacklistFragment in compose
Introduce a new activity to host composables as View system behaves quite bad when composable are used inside it. Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -104,6 +104,11 @@
|
||||
<nav-graph android:value="@navigation/mobile_navigation" />
|
||||
</activity>
|
||||
|
||||
<!-- Activity to host composable screens -->
|
||||
<activity
|
||||
android:name=".ComposeActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Notification Action (Download Complete) -->
|
||||
<activity
|
||||
android:name=".data.activity.InstallActivity"
|
||||
|
||||
@@ -42,7 +42,9 @@ import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.ComposeActivity
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
|
||||
private const val TAG = "Context"
|
||||
|
||||
@@ -153,3 +155,10 @@ fun Context.isDomainVerified(domain: String): Boolean {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.navigate(screen: Screen) {
|
||||
val intent = Intent(this, ComposeActivity::class.java).apply {
|
||||
putExtra(Screen.PARCEL_KEY, screen)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
40
app/src/main/java/com/aurora/store/ComposeActivity.kt
Normal file
40
app/src/main/java/com/aurora/store/ComposeActivity.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.aurora.store.compose.navigation.NavGraph
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.compose.theme.AuroraTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ComposeActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// TODO: Change startDestination logic to mirror MainActivity
|
||||
val startDestination = IntentCompat.getParcelableExtra(
|
||||
intent,
|
||||
Screen.PARCEL_KEY,
|
||||
Screen::class.java
|
||||
) ?: Screen.Blacklist
|
||||
|
||||
setContent {
|
||||
AuroraTheme {
|
||||
val navController = rememberNavController()
|
||||
NavGraph(navHostController = navController, startDestination = startDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import com.aurora.store.R
|
||||
* @param versionName versionName of the package
|
||||
* @param versionCode versionCode of the package
|
||||
* @param isChecked Whether the app is blacklisted
|
||||
* @param isEnabled Whether this app is allowed to be blacklisted
|
||||
* @param onClick Callback when the composable is clicked
|
||||
*/
|
||||
@Composable
|
||||
@@ -51,12 +52,13 @@ fun BlackListComposable(
|
||||
versionName: String,
|
||||
versionCode: Long,
|
||||
isChecked: Boolean = false,
|
||||
isEnabled: Boolean = true,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
.clickable(enabled = isEnabled) { onClick() }
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
@@ -87,11 +89,13 @@ fun BlackListComposable(
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.version, versionName, versionCode),
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
Checkbox(checked = isChecked, onCheckedChange = { onClick() })
|
||||
Checkbox(checked = isChecked, enabled = isEnabled, onCheckedChange = { onClick() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +108,7 @@ private fun BlackListComposablePreview() {
|
||||
packageName = BuildConfig.APPLICATION_ID,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
versionCode = BuildConfig.VERSION_CODE.toLong(),
|
||||
isChecked = true
|
||||
isChecked = true,
|
||||
isEnabled = false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,9 @@ fun InstalledAppComposable(
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.version, versionName, versionCode),
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp)
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 10.sp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
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
|
||||
|
||||
/**
|
||||
* A top app bar composable to be used with Scaffold in different Screen
|
||||
* @param searchHint Hint to show to the user in search bar
|
||||
* @param onNavigateUp Action when user clicks the navigation icon
|
||||
* @param onSearch Callback for a search
|
||||
* @param actions Actions to display on the top app bar (for e.g. menu)
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun SearchAppBarComposable(
|
||||
@StringRes searchHint: Int? = null,
|
||||
onNavigateUp: () -> Unit,
|
||||
onSearch: (query: String) -> Unit,
|
||||
actions: @Composable (RowScope.() -> Unit) = {}
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var query by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
TopAppBar(
|
||||
title = {
|
||||
TextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = query,
|
||||
onValueChange = { newQuery ->
|
||||
query = newQuery.trim()
|
||||
onSearch(query)
|
||||
},
|
||||
maxLines = 1,
|
||||
placeholder = { if (searchHint != null) Text(text = stringResource(searchHint)) },
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions(onSearch = { focusManager.clearFocus() })
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (query.isNotBlank()) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
query = ""
|
||||
onSearch(query)
|
||||
focusManager.clearFocus(force = true)
|
||||
}
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Clear, contentDescription = null)
|
||||
}
|
||||
}
|
||||
actions()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* A top app bar composable to be used with Scaffold in different Screen
|
||||
* @param title Title of the screen
|
||||
* @param onNavigateUp Action when user clicks the navigation icon
|
||||
* @param actions Actions to display on the top app bar (for e.g. menu)
|
||||
*/
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun TopAppBarComposable(
|
||||
@StringRes title: Int? = null,
|
||||
onNavigateUp: () -> Unit,
|
||||
actions: @Composable (RowScope.() -> Unit) = {}
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
if (title != null) Text(text = stringResource(title))
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composables
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
|
||||
/**
|
||||
* A transparent icon for occupying space
|
||||
*
|
||||
* This is useful for occupying spaces in composable where alignment is not respected such as
|
||||
* DropDownMenu.
|
||||
*/
|
||||
@Composable
|
||||
fun TransparentIconComposable() {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_transparent),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TransparentIconComposablePreview() {
|
||||
TransparentIconComposable()
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.menu
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.TransparentIconComposable
|
||||
import com.aurora.store.compose.menu.items.BlacklistMenuItem
|
||||
|
||||
/**
|
||||
* Menu for the blacklist screen
|
||||
* @param onMenuItemClicked Callback when a menu item has been clicked
|
||||
* @see BlacklistMenuItem
|
||||
*/
|
||||
@Composable
|
||||
fun BlacklistMenu(onMenuItemClicked: (blacklistMenuItem: BlacklistMenuItem) -> Unit = {}) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
fun onClick(blacklistMenuItem: BlacklistMenuItem) {
|
||||
onMenuItemClicked(blacklistMenuItem)
|
||||
expanded = false
|
||||
}
|
||||
|
||||
Box {
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.menu))
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_select_all)) },
|
||||
onClick = { onClick(BlacklistMenuItem.SELECT_ALL) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_select_all),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_remove_all)) },
|
||||
onClick = { onClick(BlacklistMenuItem.REMOVE_ALL) },
|
||||
leadingIcon = { TransparentIconComposable() }
|
||||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_import)) },
|
||||
onClick = { onClick(BlacklistMenuItem.IMPORT) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_file_import),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_export)) },
|
||||
onClick = { onClick(BlacklistMenuItem.EXPORT) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_file_export),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BlacklistMenuPreview() {
|
||||
BlacklistMenu()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.menu.items
|
||||
|
||||
/**
|
||||
* Valid menu items for the purpose of handling clicks
|
||||
*/
|
||||
enum class BlacklistMenuItem {
|
||||
SELECT_ALL,
|
||||
REMOVE_ALL,
|
||||
IMPORT,
|
||||
EXPORT
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.navigation
|
||||
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.aurora.store.compose.ui.commons.BlacklistScreen
|
||||
|
||||
/**
|
||||
* Navigation graph for compose screens
|
||||
* @param navHostController [NavHostController] to navigate with compose
|
||||
* @param startDestination Starting destination for the activity/app
|
||||
*/
|
||||
@Composable
|
||||
fun NavGraph(navHostController: NavHostController, startDestination: Screen) {
|
||||
// TODO: Drop this logic once everything is in compose
|
||||
val activity = LocalActivity.current
|
||||
fun onNavigateUp() {
|
||||
if (navHostController.previousBackStackEntry != null) {
|
||||
navHostController.navigateUp()
|
||||
} else {
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
|
||||
NavHost(navController = navHostController, startDestination = startDestination) {
|
||||
composable<Screen.Blacklist> {
|
||||
BlacklistScreen(onNavigateUp = { onNavigateUp() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.navigation
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.aurora.store.R
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Destination (Screen) for navigation in compose
|
||||
* @param label Label of the screen
|
||||
* @param icon Optional icon for the screen; Must not be null if screen is a top-level destination
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
sealed class Screen(@StringRes val label: Int, @DrawableRes val icon: Int? = null): Parcelable {
|
||||
|
||||
companion object {
|
||||
const val PARCEL_KEY = "SCREEN"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Blacklist : Screen(label = R.string.title_blacklist_manager)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.commons
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composables.BlackListComposable
|
||||
import com.aurora.store.compose.composables.SearchAppBarComposable
|
||||
import com.aurora.store.compose.menu.BlacklistMenu
|
||||
import com.aurora.store.compose.menu.items.BlacklistMenuItem
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.viewmodel.all.BlacklistViewModel
|
||||
import java.util.Calendar
|
||||
|
||||
@Composable
|
||||
fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hiltViewModel()) {
|
||||
val ctx = LocalContext.current
|
||||
val packages by viewModel.filteredPackages.collectAsStateWithLifecycle()
|
||||
|
||||
ScreenContent(
|
||||
packages = packages,
|
||||
onNavigateUp = onNavigateUp,
|
||||
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)
|
||||
},
|
||||
onBlacklistExport = { uri ->
|
||||
viewModel.exportBlacklist(ctx, uri)
|
||||
ctx.toast(R.string.toast_black_export_success)
|
||||
},
|
||||
onBlacklist = { packageName -> viewModel.blacklist(packageName) },
|
||||
onBlacklistAll = { viewModel.blacklistAll() },
|
||||
onWhitelist = { packageName -> viewModel.whitelist(packageName) },
|
||||
onWhitelistAll = { viewModel.whitelistAll() },
|
||||
onSearch = { query -> viewModel.search(query) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(
|
||||
packages: List<PackageInfo>? = null,
|
||||
onNavigateUp: () -> Unit = {},
|
||||
isPackageBlacklisted: (packageName: String) -> Boolean = { false },
|
||||
isPackageFiltered: (packageInfo: PackageInfo) -> Boolean = { false },
|
||||
onBlacklistImport: (uri: Uri) -> Unit = {},
|
||||
onBlacklistExport: (uri: Uri) -> Unit = {},
|
||||
onBlacklist: (packageName: String) -> Unit = {},
|
||||
onBlacklistAll: () -> Unit = {},
|
||||
onWhitelist: (packageName: String) -> Unit = {},
|
||||
onWhitelistAll: () -> Unit = {},
|
||||
onSearch: (query: String) -> Unit = {}
|
||||
) {
|
||||
|
||||
val ctx = LocalContext.current
|
||||
val docImportLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument(),
|
||||
onResult = {
|
||||
if (it != null) onBlacklistImport(it) else ctx.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)
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SetupMenu() {
|
||||
BlacklistMenu { blacklistMenuItem ->
|
||||
when (blacklistMenuItem) {
|
||||
BlacklistMenuItem.SELECT_ALL -> onBlacklistAll()
|
||||
BlacklistMenuItem.REMOVE_ALL -> onWhitelistAll()
|
||||
BlacklistMenuItem.IMPORT -> {
|
||||
docImportLauncher.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||
}
|
||||
|
||||
BlacklistMenuItem.EXPORT -> {
|
||||
docExportLauncher.launch(
|
||||
"aurora_store_apps_${Calendar.getInstance().time.time}.json"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBarComposable(
|
||||
onNavigateUp = onNavigateUp,
|
||||
actions = { if (!packages.isNullOrEmpty()) SetupMenu() },
|
||||
searchHint = R.string.search_hint,
|
||||
onSearch = { query -> onSearch(query) }
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues),
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_xxsmall))
|
||||
) {
|
||||
items(items = packages ?: emptyList(), key = { p -> p.packageName.hashCode() }) { pkg ->
|
||||
val isBlacklisted = isPackageBlacklisted(pkg.packageName)
|
||||
val isFiltered = isPackageFiltered(pkg)
|
||||
BlackListComposable(
|
||||
icon = PackageUtil.getIconForPackage(ctx, pkg.packageName)!!,
|
||||
displayName = pkg.applicationInfo!!.loadLabel(ctx.packageManager).toString(),
|
||||
packageName = pkg.packageName,
|
||||
versionName = pkg.versionName!!,
|
||||
versionCode = PackageInfoCompat.getLongVersionCode(pkg),
|
||||
isChecked = isBlacklisted || isFiltered,
|
||||
isEnabled = !isFiltered,
|
||||
onClick = {
|
||||
if (isBlacklisted) {
|
||||
onWhitelist(pkg.packageName)
|
||||
} else {
|
||||
onBlacklist(pkg.packageName)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BlacklistScreenPreview() {
|
||||
ScreenContent()
|
||||
}
|
||||
@@ -113,6 +113,13 @@ class UpdateHelper @Inject constructor(
|
||||
updateDao.delete(packageName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all updates from the database
|
||||
*/
|
||||
suspend fun deleteAllUpdates() {
|
||||
updateDao.deleteAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the automated updates check
|
||||
* @see [UpdateWorker]
|
||||
|
||||
@@ -1,179 +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.commons
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.databinding.FragmentGenericWithSearchBinding
|
||||
import com.aurora.store.view.epoxy.views.BlackListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.viewmodel.all.BlacklistViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BlacklistFragment : BaseFragment<FragmentGenericWithSearchBinding>() {
|
||||
|
||||
private val viewModel: BlacklistViewModel by viewModels()
|
||||
|
||||
private val startForDocumentImport =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it != null) importBlacklist(it) else toast(R.string.toast_black_import_failed)
|
||||
}
|
||||
private val startForDocumentExport =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE)) {
|
||||
if (it != null) exportBlacklist(it) else toast(R.string.toast_black_export_failed)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.packages.collect {
|
||||
updateController(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
inflateMenu(R.menu.menu_blacklist)
|
||||
setNavigationOnClickListener {
|
||||
viewModel.blacklistProvider.blacklist = viewModel.selected
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_import -> {
|
||||
startForDocumentImport.launch(arrayOf(Constants.JSON_MIME_TYPE))
|
||||
}
|
||||
|
||||
R.id.action_export -> {
|
||||
startForDocumentExport.launch(
|
||||
"aurora_store_apps_${Calendar.getInstance().time.time}.json"
|
||||
)
|
||||
}
|
||||
|
||||
R.id.action_select_all -> {
|
||||
viewModel.selectAll()
|
||||
binding.recycler.requestModelBuild()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_remove_all -> {
|
||||
viewModel.removeAll()
|
||||
binding.recycler.requestModelBuild()
|
||||
true
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (s.isNullOrEmpty()) {
|
||||
updateController(viewModel.packages.value)
|
||||
} else {
|
||||
val filteredPackages = viewModel.packages.value?.filter {
|
||||
it.applicationInfo!!.loadLabel(requireContext().packageManager)
|
||||
.contains(s, true) || it.packageName.contains(s, true)
|
||||
}
|
||||
updateController(filteredPackages)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
viewModel.blacklistProvider.blacklist = viewModel.selected
|
||||
}
|
||||
|
||||
private fun updateController(packages: List<PackageInfo>?) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (packages == null) {
|
||||
for (i in 1..10) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
packages
|
||||
.sortedByDescending { app ->
|
||||
viewModel.blacklistProvider.isBlacklisted(app.packageName)
|
||||
}
|
||||
.forEach {
|
||||
add(
|
||||
BlackListViewModel_()
|
||||
.id(it.packageName.hashCode())
|
||||
.packageInfo(it)
|
||||
.markChecked(viewModel.selected.contains(it.packageName))
|
||||
.checked { _, isChecked ->
|
||||
if (isChecked) {
|
||||
viewModel.selected.add(it.packageName)
|
||||
AuroraApp.events.send(BusEvent.Blacklisted(it.packageName))
|
||||
} else {
|
||||
viewModel.selected.remove(it.packageName)
|
||||
}
|
||||
|
||||
requestModelBuild()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun importBlacklist(uri: Uri) {
|
||||
viewModel.importBlacklist(requireContext(), uri)
|
||||
binding.recycler.requestModelBuild()
|
||||
toast(R.string.toast_black_import_success)
|
||||
}
|
||||
|
||||
private fun exportBlacklist(uri: Uri) {
|
||||
viewModel.exportBlacklist(requireContext(), uri)
|
||||
toast(R.string.toast_black_export_success)
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,11 @@ import com.aurora.Constants
|
||||
import com.aurora.Constants.URL_TOS
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.getStyledAttributeColor
|
||||
import com.aurora.extensions.navigate
|
||||
import com.aurora.extensions.setAppTheme
|
||||
import com.aurora.store.MR
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.compose.theme.AuroraTheme
|
||||
import com.aurora.store.viewmodel.commons.MoreViewModel
|
||||
@@ -76,12 +78,23 @@ class MoreDialogFragment : DialogFragment() {
|
||||
private var secondaryColor: Color = Color.White
|
||||
private var onSecondaryColor: Color = Color.Black
|
||||
|
||||
private data class Option(
|
||||
@StringRes val title: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
@IdRes val destinationID: Int
|
||||
private abstract class Option(
|
||||
@StringRes open val title: Int,
|
||||
@DrawableRes open val icon: Int,
|
||||
)
|
||||
|
||||
private data class ViewOption(
|
||||
override val title: Int,
|
||||
override val icon: Int,
|
||||
@IdRes val destinationID: Int
|
||||
) : Option(title, icon)
|
||||
|
||||
private data class ComposeOption(
|
||||
override val title: Int,
|
||||
override val icon: Int,
|
||||
val screen: Screen
|
||||
) : Option(title, icon)
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(customDialogView(requireContext()))
|
||||
@@ -134,7 +147,16 @@ class MoreDialogFragment : DialogFragment() {
|
||||
OptionItem(
|
||||
option = option,
|
||||
tintColor = onPrimaryColor,
|
||||
textColor = onSecondaryColor
|
||||
textColor = onSecondaryColor,
|
||||
onClick = {
|
||||
when (option) {
|
||||
is ViewOption -> {
|
||||
findNavController().navigate(option.destinationID)
|
||||
}
|
||||
|
||||
is ComposeOption -> context.navigate(option.screen)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -142,7 +164,16 @@ class MoreDialogFragment : DialogFragment() {
|
||||
OptionItem(
|
||||
option = option,
|
||||
tintColor = onPrimaryColor,
|
||||
textColor = onPrimaryColor
|
||||
textColor = onPrimaryColor,
|
||||
onClick = {
|
||||
when (option) {
|
||||
is ViewOption -> {
|
||||
findNavController().navigate(option.destinationID)
|
||||
}
|
||||
|
||||
is ComposeOption -> context.navigate(option.screen)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Footer(onPrimaryColor)
|
||||
@@ -310,12 +341,13 @@ class MoreDialogFragment : DialogFragment() {
|
||||
private fun OptionItem(
|
||||
option: Option,
|
||||
tintColor: Color = Color.Blue,
|
||||
textColor: Color = Color.Black
|
||||
textColor: Color = Color.Black,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { findNavController().navigate(option.destinationID) }
|
||||
.clickable { onClick() }
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start)
|
||||
@@ -391,22 +423,22 @@ class MoreDialogFragment : DialogFragment() {
|
||||
|
||||
private fun getOptions(): List<Option> {
|
||||
return listOf(
|
||||
Option(
|
||||
ViewOption(
|
||||
title = R.string.title_apps_games,
|
||||
icon = R.drawable.ic_apps,
|
||||
destinationID = R.id.appsGamesFragment
|
||||
),
|
||||
Option(
|
||||
ComposeOption(
|
||||
title = R.string.title_blacklist_manager,
|
||||
icon = R.drawable.ic_blacklist,
|
||||
destinationID = R.id.blacklistFragment
|
||||
screen = Screen.Blacklist
|
||||
),
|
||||
Option(
|
||||
ViewOption(
|
||||
title = R.string.title_favourites_manager,
|
||||
icon = R.drawable.ic_favorite_unchecked,
|
||||
destinationID = R.id.favouriteFragment
|
||||
),
|
||||
Option(
|
||||
ViewOption(
|
||||
title = R.string.title_spoof_manager,
|
||||
icon = R.drawable.ic_spoof,
|
||||
destinationID = R.id.spoofFragment
|
||||
@@ -416,12 +448,12 @@ class MoreDialogFragment : DialogFragment() {
|
||||
|
||||
private fun getExtraOptions(): List<Option> {
|
||||
return listOf(
|
||||
Option(
|
||||
ViewOption(
|
||||
title = R.string.title_settings,
|
||||
icon = R.drawable.ic_menu_settings,
|
||||
destinationID = R.id.settingsFragment
|
||||
),
|
||||
Option(
|
||||
ViewOption(
|
||||
title = R.string.title_about,
|
||||
icon = R.drawable.ic_menu_about,
|
||||
destinationID = R.id.aboutFragment
|
||||
|
||||
@@ -37,9 +37,11 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.isMAndAbove
|
||||
import com.aurora.extensions.navigate
|
||||
import com.aurora.extensions.show
|
||||
import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.data.model.AuthState
|
||||
import com.aurora.store.databinding.FragmentSplashBinding
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
|
||||
@@ -93,7 +95,7 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_blacklist_manager -> {
|
||||
findNavController().navigate(R.id.blacklistFragment)
|
||||
requireContext().navigate(Screen.Blacklist)
|
||||
}
|
||||
|
||||
R.id.menu_spoof_manager -> {
|
||||
|
||||
@@ -23,10 +23,16 @@ import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.helper.UpdateHelper
|
||||
import com.aurora.store.data.providers.BlacklistProvider
|
||||
import com.aurora.store.util.CertUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -39,38 +45,87 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class BlacklistViewModel @Inject constructor(
|
||||
val blacklistProvider: BlacklistProvider,
|
||||
val gson: Gson,
|
||||
private val gson: Gson,
|
||||
private val updateHelper: UpdateHelper,
|
||||
private val blacklistProvider: BlacklistProvider,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = BlacklistViewModel::class.java.simpleName
|
||||
|
||||
private val _packages = MutableStateFlow<List<PackageInfo>?>(null)
|
||||
val packages = _packages.asStateFlow()
|
||||
private val isAuroraOnlyFilterEnabled =
|
||||
Preferences.getBoolean(context, Preferences.PREFERENCE_FILTER_AURORA_ONLY, false)
|
||||
private val isFDroidFilterEnabled =
|
||||
Preferences.getBoolean(context, Preferences.PREFERENCE_FILTER_FDROID, true)
|
||||
private val isExtendedUpdateEnabled =
|
||||
Preferences.getBoolean(context, Preferences.PREFERENCE_UPDATES_EXTENDED)
|
||||
|
||||
var selected: MutableSet<String> = blacklistProvider.blacklist
|
||||
private val _packages = MutableStateFlow<List<PackageInfo>?>(null)
|
||||
private val _filteredPackages = MutableStateFlow<List<PackageInfo>?>(null)
|
||||
val filteredPackages = _filteredPackages.asStateFlow()
|
||||
|
||||
val blacklist = mutableStateListOf<String>()
|
||||
|
||||
init {
|
||||
blacklist.addAll(blacklistProvider.blacklist)
|
||||
fetchApps()
|
||||
}
|
||||
|
||||
private fun fetchApps() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
_packages.value = PackageUtil.getAllValidPackages(context)
|
||||
_packages.value = PackageUtil.getAllValidPackages(context).also { pkgList ->
|
||||
_filteredPackages.value = pkgList
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch apps", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectAll() {
|
||||
selected.addAll(packages.value?.map { it.packageName } ?: emptyList())
|
||||
fun search(query: String) {
|
||||
if (query.isNotBlank()) {
|
||||
_filteredPackages.value = _packages.value!!
|
||||
.filter { it.applicationInfo!!.loadLabel(context.packageManager)
|
||||
.contains(query, true) || it.packageName.contains(query, true)
|
||||
}
|
||||
} else {
|
||||
_filteredPackages.value = _packages.value
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAll() {
|
||||
selected.clear()
|
||||
fun isFiltered(packageInfo: PackageInfo): Boolean {
|
||||
return when {
|
||||
!isExtendedUpdateEnabled && !packageInfo.applicationInfo!!.enabled -> true
|
||||
isAuroraOnlyFilterEnabled -> !CertUtil.isAuroraStoreApp(context, packageInfo.packageName)
|
||||
isFDroidFilterEnabled -> CertUtil.isFDroidApp(context, packageInfo.packageName)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun blacklist(packageName: String) {
|
||||
blacklist.add(packageName)
|
||||
blacklistProvider.blacklist(packageName)
|
||||
AuroraApp.events.send(BusEvent.Blacklisted(packageName))
|
||||
}
|
||||
|
||||
fun blacklistAll() {
|
||||
blacklistProvider.blacklist = _packages.value!!.map { it.packageName }.toMutableSet()
|
||||
blacklist.apply {
|
||||
clear()
|
||||
addAll(blacklistProvider.blacklist)
|
||||
}
|
||||
viewModelScope.launch { updateHelper.deleteAllUpdates() }
|
||||
}
|
||||
|
||||
fun whitelist(packageName: String) {
|
||||
blacklist.remove(packageName)
|
||||
blacklistProvider.whitelist(packageName)
|
||||
}
|
||||
|
||||
fun whitelistAll() {
|
||||
blacklist.clear()
|
||||
blacklistProvider.blacklist = mutableSetOf()
|
||||
}
|
||||
|
||||
fun importBlacklist(context: Context, uri: Uri) {
|
||||
@@ -82,10 +137,13 @@ class BlacklistViewModel @Inject constructor(
|
||||
object : TypeToken<MutableSet<String?>?>() {}.type
|
||||
)
|
||||
|
||||
val knownSet = blacklistProvider.blacklist
|
||||
knownSet.addAll(importedSet)
|
||||
|
||||
selected = knownSet
|
||||
val validImportedSet = importedSet
|
||||
.filter { pkgName -> _packages.value!!.any { it.packageName == pkgName } }
|
||||
blacklistProvider.blacklist.addAll(validImportedSet)
|
||||
blacklist.apply {
|
||||
clear()
|
||||
addAll(blacklistProvider.blacklist)
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to import blacklist", exception)
|
||||
|
||||
15
app/src/main/res/drawable/ic_file_export.xml
Normal file
15
app/src/main/res/drawable/ic_file_export.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M440,760L520,760L520,593L584,657L640,600L480,440L320,600L377,656L440,593L440,760ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360ZM240,160L240,160L240,360L240,360L240,160L240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_file_import.xml
Normal file
15
app/src/main/res/drawable/ic_file_import.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M720,840L880,680L824,624L760,688L760,521L680,521L680,688L616,624L560,680L720,840ZM560,960L560,880L880,880L880,960L560,960ZM240,800Q207,800 183.5,776.5Q160,753 160,720L160,160Q160,127 183.5,103.5Q207,80 240,80L520,80L760,320L760,441L680,441L680,360L480,360L480,160L240,160Q240,160 240,160Q240,160 240,160L240,720Q240,720 240,720Q240,720 240,720L480,720L480,800L240,800ZM240,720L240,441L240,441L240,360L240,160L240,160Q240,160 240,160Q240,160 240,160L240,720Q240,720 240,720Q240,720 240,720Z"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_select_all.xml
Normal file
15
app/src/main/res/drawable/ic_select_all.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M280,680L280,280L680,280L680,680L280,680ZM360,600L600,600L600,360L360,360L360,600ZM200,760L200,840Q167,840 143.5,816.5Q120,793 120,760L200,760ZM120,680L120,600L200,600L200,680L120,680ZM120,520L120,440L200,440L200,520L120,520ZM120,360L120,280L200,280L200,360L120,360ZM200,200L120,200Q120,167 143.5,143.5Q167,120 200,120L200,200ZM280,840L280,760L360,760L360,840L280,840ZM280,200L280,120L360,120L360,200L280,200ZM440,840L440,760L520,760L520,840L440,840ZM440,200L440,120L520,120L520,200L440,200ZM600,840L600,760L680,760L680,840L600,840ZM600,200L600,120L680,120L680,200L600,200ZM760,840L760,760L840,760Q840,793 816.5,816.5Q793,840 760,840ZM760,680L760,600L840,600L840,680L760,680ZM760,520L760,440L840,440L840,520L760,520ZM760,360L760,280L840,280L840,360L760,360ZM760,200L760,120Q793,120 816.5,143.5Q840,167 840,200L760,200Z"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_transparent.xml
Normal file
12
app/src/main/res/drawable/ic_transparent.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
~ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
</vector>
|
||||
@@ -1,33 +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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_select_all"
|
||||
android:title="@string/action_select_all" />
|
||||
<item
|
||||
android:id="@+id/action_remove_all"
|
||||
android:title="@string/action_remove_all" />
|
||||
<item
|
||||
android:id="@+id/action_import"
|
||||
android:title="@string/action_import" />
|
||||
<item
|
||||
android:id="@+id/action_export"
|
||||
android:title="@string/action_export" />
|
||||
</menu>
|
||||
@@ -82,11 +82,6 @@
|
||||
android:name="com.aurora.store.view.ui.commons.FavouriteFragment"
|
||||
android:label="@string/title_favourites_manager"
|
||||
tools:layout="@layout/fragment_generic_with_toolbar" />
|
||||
<fragment
|
||||
android:id="@+id/blacklistFragment"
|
||||
android:name="com.aurora.store.view.ui.commons.BlacklistFragment"
|
||||
android:label="@string/title_blacklist_manager"
|
||||
tools:layout="@layout/fragment_generic_with_toolbar" />
|
||||
<fragment
|
||||
android:id="@+id/accountFragment"
|
||||
android:name="com.aurora.store.view.ui.account.AccountFragment"
|
||||
|
||||
Reference in New Issue
Block a user