feat: move update to notification card (#1917)

This commit is contained in:
Robert 2024-05-27 21:50:02 +02:00 committed by GitHub
parent 4e7d96e91d
commit 6bfd9098d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 222 additions and 265 deletions

View file

@ -5,16 +5,8 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Update
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.destination.Destination
import app.revanced.manager.ui.destination.SettingsDestination
import app.revanced.manager.ui.screen.AppSelectorScreen
@ -46,7 +38,6 @@ class MainActivity : ComponentActivity() {
installSplashScreen()
val vm: MainViewModel = getAndroidViewModel()
vm.importLegacySettings(this)
setContent {
@ -59,37 +50,8 @@ class MainActivity : ComponentActivity() {
) {
val navController =
rememberNavController<Destination>(startDestination = Destination.Dashboard)
NavBackHandler(navController)
val firstLaunch by vm.prefs.firstLaunch.getAsState()
if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
vm.updatedManagerVersion?.let {
AlertDialog(
onDismissRequest = vm::dismissUpdateDialog,
confirmButton = {
TextButton(
onClick = {
vm.dismissUpdateDialog()
navController.navigate(Destination.Settings(SettingsDestination.Update(false)))
}
) {
Text(stringResource(R.string.update))
}
},
dismissButton = {
TextButton(onClick = vm::dismissUpdateDialog) {
Text(stringResource(R.string.dismiss_temporary))
}
},
icon = { Icon(Icons.Outlined.Update, null) },
title = { Text(stringResource(R.string.update_available_dialog_title)) },
text = { Text(stringResource(R.string.update_available_dialog_description, it)) }
)
}
AnimatedNavHost(
controller = navController
) { destination ->
@ -97,6 +59,9 @@ class MainActivity : ComponentActivity() {
is Destination.Dashboard -> DashboardScreen(
onSettingsClick = { navController.navigate(Destination.Settings()) },
onAppSelectorClick = { navController.navigate(Destination.AppSelector) },
onUpdateClick = { navController.navigate(
Destination.Settings(SettingsDestination.Update())
) },
onAppClick = { installedApp ->
navController.navigate(
Destination.InstalledApplicationInfo(

View file

@ -4,7 +4,6 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Source
import androidx.compose.material.icons.outlined.Update
@ -13,7 +12,6 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@ -21,11 +19,9 @@ 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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
@ -37,52 +33,35 @@ fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) {
AlertDialog(
onDismissRequest = {},
confirmButton = {
TextButton(
onClick = { onSubmit(managerEnabled, patchesEnabled) }
) {
TextButton(onClick = { onSubmit(managerEnabled, patchesEnabled) }) {
Text(stringResource(R.string.save))
}
},
icon = {
Icon(Icons.Outlined.Update, null)
},
title = {
Text(
text = stringResource(R.string.auto_updates_dialog_title),
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
color = MaterialTheme.colorScheme.onSurface,
)
},
icon = { Icon(Icons.Outlined.Update, null) },
title = { Text(text = stringResource(R.string.auto_updates_dialog_title)) },
text = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = stringResource(R.string.auto_updates_dialog_description),
style = MaterialTheme.typography.bodyMedium,
)
Text(text = stringResource(R.string.auto_updates_dialog_description))
AutoUpdatesItem(
headline = R.string.auto_updates_dialog_manager,
icon = Icons.Outlined.Update,
checked = managerEnabled,
onCheckedChange = { managerEnabled = it }
)
HorizontalDivider()
AutoUpdatesItem(
headline = R.string.auto_updates_dialog_patches,
icon = Icons.Outlined.Source,
checked = patchesEnabled,
onCheckedChange = { patchesEnabled = it }
)
Column {
AutoUpdatesItem(
headline = R.string.auto_updates_dialog_manager,
icon = Icons.Outlined.Update,
checked = managerEnabled,
onCheckedChange = { managerEnabled = it }
)
HorizontalDivider()
AutoUpdatesItem(
headline = R.string.auto_updates_dialog_patches,
icon = Icons.Outlined.Source,
checked = patchesEnabled,
onCheckedChange = { patchesEnabled = it }
)
}
Text(
text = stringResource(R.string.auto_updates_dialog_note),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.fillMaxWidth()
)
Text(text = stringResource(R.string.auto_updates_dialog_note))
}
}
)
@ -94,22 +73,9 @@ private fun AutoUpdatesItem(
icon: ImageVector,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
ListItem(
leadingContent = { Icon(icon, null, tint = MaterialTheme.colorScheme.onSurface) },
headlineContent = {
Text(
text = stringResource(headline),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
},
trailingContent = {
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange
)
},
modifier = Modifier.clickable { onCheckedChange(!checked) }
)
}
) = ListItem(
leadingContent = { Icon(icon, null) },
headlineContent = { Text(stringResource(headline)) },
trailingContent = { Checkbox(checked = checked, onCheckedChange = null) },
modifier = Modifier.clickable { onCheckedChange(!checked) }
)

View file

@ -1,8 +1,10 @@
package app.revanced.manager.ui.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -11,7 +13,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -27,74 +28,73 @@ import app.revanced.manager.R
@Composable
fun NotificationCard(
isWarning: Boolean = false,
title: String? = null,
text: String,
icon: ImageVector,
actions: (@Composable () -> Unit)?
modifier: Modifier = Modifier,
actions: (@Composable RowScope.() -> Unit)? = null,
title: String? = null,
isWarning: Boolean = false
) {
val color =
if (isWarning) MaterialTheme.colorScheme.onError else MaterialTheme.colorScheme.onPrimaryContainer
NotificationCardInstance(isWarning = isWarning) {
Column(
modifier = Modifier.padding(if (title != null) 20.dp else 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
NotificationCardInstance(modifier = modifier, isWarning = isWarning) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
if (title != null) {
Box(
modifier = Modifier.size(28.dp),
contentAlignment = Alignment.Center
) {
Icon(
modifier = Modifier.size(36.dp),
modifier = Modifier.size(24.dp),
imageVector = icon,
contentDescription = null,
tint = color,
)
Column(
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
title?.let {
Text(
text = title,
text = it,
style = MaterialTheme.typography.titleLarge,
color = color,
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
color = color,
)
}
} else {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Icon(
modifier = Modifier.size(24.dp),
imageVector = icon,
contentDescription = null,
tint = color,
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
color = color,
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
color = color,
)
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
actions?.invoke(this)
}
}
actions?.invoke()
}
}
}
@Composable
fun NotificationCard(
isWarning: Boolean = false,
title: String? = null,
text: String,
icon: ImageVector,
modifier: Modifier = Modifier,
title: String? = null,
isWarning: Boolean = false,
onDismiss: (() -> Unit)? = null,
primaryAction: (() -> Unit)? = null
onClick: (() -> Unit)? = null
) {
val color =
if (isWarning) MaterialTheme.colorScheme.onError else MaterialTheme.colorScheme.onPrimaryContainer
NotificationCardInstance(isWarning = isWarning, onClick = primaryAction) {
NotificationCardInstance(modifier = modifier, isWarning = isWarning, onClick = onClick) {
Row(
modifier = Modifier
.fillMaxWidth()
@ -102,12 +102,17 @@ fun NotificationCard(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Icon(
modifier = Modifier.size(if (title != null) 36.dp else 24.dp),
imageVector = icon,
contentDescription = null,
tint = color,
)
Box(
modifier = Modifier.size(28.dp),
contentAlignment = Alignment.Center
) {
Icon(
modifier = Modifier.size(24.dp),
imageVector = icon,
contentDescription = null,
tint = color,
)
}
if (title != null) {
Column(
modifier = Modifier.weight(1f),
@ -145,32 +150,31 @@ fun NotificationCard(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NotificationCardInstance(
modifier: Modifier = Modifier,
isWarning: Boolean = false,
onClick: (() -> Unit)? = null,
content: @Composable () -> Unit,
) {
val colors =
CardDefaults.cardColors(containerColor = if (isWarning) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primaryContainer)
val modifier = Modifier
val defaultModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(24.dp))
if (onClick != null) {
Card(
onClick = onClick,
colors = colors,
modifier = modifier
modifier = modifier.then(defaultModifier)
) {
content()
}
} else {
Card(
colors = colors,
modifier = modifier,
modifier = modifier.then(defaultModifier)
) {
content()
}

View file

@ -65,34 +65,22 @@ fun BundleItem(
.height(64.dp)
.fillMaxWidth()
.combinedClickable(
onClick = {
viewBundleDialogPage = true
},
onClick = { viewBundleDialogPage = true },
onLongClick = onSelect,
),
leadingContent = {
if(selectable) {
leadingContent = if (selectable) {
{
Checkbox(
checked = isBundleSelected,
onCheckedChange = toggleSelection,
)
}
},
} else null,
headlineContent = {
Text(
text = bundle.name,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
},
headlineContent = { Text(text = bundle.name) },
supportingContent = {
state.patchBundleOrNull()?.patches?.size?.let { patchCount ->
Text(
text = pluralStringResource(R.plurals.patch_count, patchCount, patchCount),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(text = pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
}
},
trailingContent = {
@ -114,13 +102,7 @@ fun BundleItem(
)
}
version?.let { txt ->
Text(
text = txt,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
version?.let { Text(text = it) }
}
},
)

View file

@ -27,7 +27,7 @@ sealed interface SettingsDestination : Parcelable {
object About : SettingsDestination
@Parcelize
data class Update(val downloadOnScreenEntry: Boolean) : SettingsDestination
data class Update(val downloadOnScreenEntry: Boolean = false) : SettingsDestination
@Parcelize
object Changelogs : SettingsDestination

View file

@ -2,6 +2,7 @@ package app.revanced.manager.ui.screen
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -15,6 +16,8 @@ import androidx.compose.material.icons.outlined.DeleteOutline
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Source
import androidx.compose.material.icons.outlined.Update
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
@ -24,6 +27,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -43,7 +47,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.isDefault
import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.bundle.BundleItem
import app.revanced.manager.ui.component.bundle.BundleTopBar
import app.revanced.manager.ui.component.bundle.ImportBundleDialog
@ -68,38 +75,26 @@ fun DashboardScreen(
vm: DashboardViewModel = koinViewModel(),
onAppSelectorClick: () -> Unit,
onSettingsClick: () -> Unit,
onUpdateClick: () -> Unit,
onAppClick: (InstalledApp) -> Unit
) {
var showBundleTypeSelectorDialog by rememberSaveable { mutableStateOf(false) }
var selectedBundleType: BundleType? by rememberSaveable { mutableStateOf(null) }
val bundlesSelectable by remember { derivedStateOf { vm.selectedSources.size > 0 } }
val pages: Array<DashboardPage> = DashboardPage.values()
val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0)
val androidContext = LocalContext.current
val composableScope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = DashboardPage.DASHBOARD.ordinal,
initialPageOffsetFraction = 0f
) {
DashboardPage.values().size
}
val composableScope = rememberCoroutineScope()
) { DashboardPage.entries.size }
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage != DashboardPage.BUNDLES.ordinal) vm.cancelSourceSelection()
}
if (showBundleTypeSelectorDialog) {
ImportBundleTypeSelectorDialog(
onDismiss = { showBundleTypeSelectorDialog = false },
onConfirm = {
selectedBundleType = it
showBundleTypeSelectorDialog = false
}
)
}
val firstLaunch by vm.prefs.firstLaunch.getAsState()
if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
var selectedBundleType: BundleType? by rememberSaveable { mutableStateOf(null) }
selectedBundleType?.let {
fun dismiss() {
selectedBundleType = null
@ -119,6 +114,17 @@ fun DashboardScreen(
)
}
var showBundleTypeSelectorDialog by rememberSaveable { mutableStateOf(false) }
if (showBundleTypeSelectorDialog) {
ImportBundleTypeSelectorDialog(
onDismiss = { showBundleTypeSelectorDialog = false },
onConfirm = {
selectedBundleType = it
showBundleTypeSelectorDialog = false
}
)
}
Scaffold(
topBar = {
if (bundlesSelectable) {
@ -192,9 +198,7 @@ fun DashboardScreen(
}
}
}
) {
Icon(Icons.Default.Add, stringResource(R.string.add))
}
) { Icon(Icons.Default.Add, stringResource(R.string.add)) }
}
) { paddingValues ->
Column(Modifier.padding(paddingValues)) {
@ -202,7 +206,7 @@ fun DashboardScreen(
selectedTabIndex = pagerState.currentPage,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
) {
pages.forEachIndexed { index, page ->
DashboardPage.entries.forEachIndexed { index, page ->
Tab(
selected = pagerState.currentPage == index,
onClick = { composableScope.launch { pagerState.animateScrollToPage(index) } },
@ -214,12 +218,41 @@ fun DashboardScreen(
}
}
Notifications(
if (!Aapt.supportsDevice()) {
{
NotificationCard(
isWarning = true,
icon = Icons.Outlined.WarningAmber,
text = stringResource(R.string.unsupported_architecture_warning),
onDismiss = null
)
}
} else null,
vm.updatedManagerVersion?.let {
{
NotificationCard(
text = stringResource(R.string.update_available_dialog_description, it),
icon = Icons.Outlined.Update,
actions = {
TextButton(onClick = vm::dismissUpdateDialog) {
Text(stringResource(R.string.dismiss))
}
TextButton(onClick = onUpdateClick) {
Text(stringResource(R.string.update))
}
}
)
}
}
)
HorizontalPager(
state = pagerState,
userScrollEnabled = true,
modifier = Modifier.fillMaxSize(),
pageContent = { index ->
when (pages[index]) {
when (DashboardPage.entries[index]) {
DashboardPage.DASHBOARD -> {
InstalledAppsScreen(
onAppClick = onAppClick
@ -238,11 +271,9 @@ fun DashboardScreen(
val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList())
Column(
modifier = Modifier
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
) {
sources.forEach {
BundleItem(
bundle = it,
onDelete = {
@ -273,3 +304,21 @@ fun DashboardScreen(
}
}
}
@Composable
fun Notifications(
vararg notifications: (@Composable () -> Unit)?,
) {
val activeNotifications = notifications.filterNotNull()
if (activeNotifications.isNotEmpty()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
activeNotifications.forEach { notification ->
notification()
}
}
}
}

View file

@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -20,12 +18,10 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.ui.component.AppIcon
import app.revanced.manager.ui.component.AppLabel
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.viewmodel.InstalledAppsViewModel
import org.koin.androidx.compose.koinViewModel
@ -37,16 +33,6 @@ fun InstalledAppsScreen(
val installedApps by viewModel.apps.collectAsStateWithLifecycle(initialValue = null)
Column {
if (!Aapt.supportsDevice()) {
NotificationCard(
isWarning = true,
icon = Icons.Outlined.WarningAmber,
text = stringResource(
R.string.unsupported_architecture_warning
),
)
}
LazyColumnWithScrollbar(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,

View file

@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar
@ -161,10 +162,11 @@ fun SettingsScreen(
) {
AnimatedVisibility(visible = showBatteryButton) {
NotificationCard(
modifier = Modifier.padding(16.dp),
isWarning = true,
icon = Icons.Default.BatteryAlert,
text = stringResource(R.string.battery_optimization_notification),
primaryAction = {
onClick = {
context.startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
data = Uri.parse("package:${context.packageName}")
})

View file

@ -3,21 +3,32 @@ package app.revanced.manager.ui.viewmodel
import android.app.Application
import android.content.ContentResolver
import android.net.Uri
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull
import app.revanced.manager.domain.bundles.RemotePatchBundle
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class DashboardViewModel(
private val app: Application,
private val patchBundleRepository: PatchBundleRepository
private val patchBundleRepository: PatchBundleRepository,
private val reVancedAPI: ReVancedAPI,
private val networkInfo: NetworkInfo,
val prefs: PreferencesManager
) : ViewModel() {
val availablePatches =
patchBundleRepository.bundles.map { it.values.sumOf { bundle -> bundle.patches.size } }
@ -25,6 +36,47 @@ class DashboardViewModel(
val sources = patchBundleRepository.sources
val selectedSources = mutableStateListOf<PatchBundleSource>()
var updatedManagerVersion: String? by mutableStateOf(null)
private set
init {
viewModelScope.launch { checkForManagerUpdates() }
}
fun dismissUpdateDialog() {
updatedManagerVersion = null
}
private suspend fun checkForManagerUpdates() {
if (!prefs.managerAutoUpdates.get() || !networkInfo.isConnected()) return
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
updatedManagerVersion = reVancedAPI.getAppUpdate()?.version
}
}
fun applyAutoUpdatePrefs(manager: Boolean, patches: Boolean) = viewModelScope.launch {
prefs.firstLaunch.update(false)
prefs.managerAutoUpdates.update(manager)
if (manager) checkForManagerUpdates()
if (patches) {
with(patchBundleRepository) {
sources
.first()
.find { it.uid == 0 }
?.asRemoteOrNull
?.setAutoUpdate(true)
updateCheck()
}
}
}
fun cancelSourceSelection() {
selectedSources.clear()
}

View file

@ -8,25 +8,18 @@ import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull
import app.revanced.manager.domain.manager.KeystoreManager
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.domain.repository.PatchSelectionRepository
import app.revanced.manager.domain.repository.SerializedSelection
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.util.isDebuggable
import app.revanced.manager.util.tag
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
@ -36,50 +29,9 @@ class MainViewModel(
private val patchBundleRepository: PatchBundleRepository,
private val patchSelectionRepository: PatchSelectionRepository,
private val keystoreManager: KeystoreManager,
private val reVancedAPI: ReVancedAPI,
private val app: Application,
private val networkInfo: NetworkInfo,
val prefs: PreferencesManager
) : ViewModel() {
var updatedManagerVersion: String? by mutableStateOf(null)
private set
init {
viewModelScope.launch { checkForManagerUpdates() }
}
fun dismissUpdateDialog() {
updatedManagerVersion = null
}
private suspend fun checkForManagerUpdates() {
if (app.isDebuggable || !prefs.managerAutoUpdates.get() || !networkInfo.isConnected()) return
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
updatedManagerVersion = reVancedAPI.getAppUpdate()?.version
}
}
fun applyAutoUpdatePrefs(manager: Boolean, patches: Boolean) = viewModelScope.launch {
prefs.firstLaunch.update(false)
prefs.managerAutoUpdates.update(manager)
if (manager) checkForManagerUpdates()
if (patches) {
with(patchBundleRepository) {
sources
.first()
.find { it.uid == 0 }
?.asRemoteOrNull
?.setAutoUpdate(true)
updateCheck()
}
}
}
fun importLegacySettings(componentActivity: ComponentActivity) {
if (!prefs.firstLaunch.getBlocking()) return

View file

@ -324,8 +324,7 @@
<string name="no_update_available">No update available</string>
<string name="update_check">Checking for updates…</string>
<string name="dismiss_temporary">Not now</string>
<string name="update_available_dialog_title">New update available</string>
<string name="update_available_dialog_description">A new version (%s) is available for download.</string>
<string name="update_available_dialog_description">A new version of ReVanced Manager (%s) is available.</string>
<string name="failed_to_download_update">Failed to download update: %s</string>
<string name="download">Download</string>
<string name="download_confirmation_metered">You are currently on a metered connection, and data charges from your service provider may apply.\n\nDo you still want to continue?</string>