Compare commits

...

4 Commits

Author SHA1 Message Date
semantic-release-bot
bd69b45a69 chore: Release v1.26.0-dev.14 [skip ci]
# app [1.26.0-dev.14](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.13...v1.26.0-dev.14) (2025-12-28)

### Bug Fixes

* Update selected patch count when SelectionState changes ([#2896](https://github.com/ReVanced/revanced-manager/issues/2896)) ([0d26df0](0d26df03f4))
2025-12-28 18:20:22 +00:00
Robert
0d26df03f4 fix: Update selected patch count when SelectionState changes (#2896) 2025-12-28 19:13:00 +01:00
semantic-release-bot
c436a7a100 chore: Release v1.26.0-dev.13 [skip ci]
# app [1.26.0-dev.13](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.12...v1.26.0-dev.13) (2025-12-17)

### Features

* Make patcher screen design more consistent with inspiration ([#2805](https://github.com/ReVanced/revanced-manager/issues/2805)) ([dbb6c01](dbb6c01e89))
2025-12-17 20:05:47 +00:00
Ushie
dbb6c01e89 feat: Make patcher screen design more consistent with inspiration (#2805) 2025-12-17 22:58:02 +03:00
6 changed files with 126 additions and 102 deletions

View File

@@ -1,3 +1,17 @@
# app [1.26.0-dev.14](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.13...v1.26.0-dev.14) (2025-12-28)
### Bug Fixes
* Update selected patch count when SelectionState changes ([#2896](https://github.com/ReVanced/revanced-manager/issues/2896)) ([0d26df0](https://github.com/ReVanced/revanced-manager/commit/0d26df03f463195dae550240c7f652680763079c))
# app [1.26.0-dev.13](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.12...v1.26.0-dev.13) (2025-12-17)
### Features
* Make patcher screen design more consistent with inspiration ([#2805](https://github.com/ReVanced/revanced-manager/issues/2805)) ([dbb6c01](https://github.com/ReVanced/revanced-manager/commit/dbb6c01e89a5e710185ff4304de0ac9e19bed053))
# app [1.26.0-dev.12](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.11...v1.26.0-dev.12) (2025-12-17) # app [1.26.0-dev.12](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.11...v1.26.0-dev.12) (2025-12-17)

View File

@@ -1 +1 @@
version = 1.26.0-dev.12 version = 1.26.0-dev.14

View File

@@ -1,7 +1,7 @@
package app.revanced.manager.ui.component.patcher package app.revanced.manager.ui.component.patcher
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
@@ -21,6 +20,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -53,20 +53,11 @@ fun Steps(
category: StepCategory, category: StepCategory,
steps: List<Step>, steps: List<Step>,
stepCount: Pair<Int, Int>? = null, stepCount: Pair<Int, Int>? = null,
stepProgressProvider: StepProgressProvider stepProgressProvider: StepProgressProvider,
isExpanded: Boolean = false,
onExpand: () -> Unit,
onClick: () -> Unit
) { ) {
var expanded by rememberSaveable { mutableStateOf(true) }
val categoryColor by animateColorAsState(
if (expanded) MaterialTheme.colorScheme.surfaceContainerHigh else Color.Transparent,
label = "category"
)
val cardColor by animateColorAsState(
if (expanded) MaterialTheme.colorScheme.surfaceContainer else Color.Transparent,
label = "card"
)
val state = remember(steps) { val state = remember(steps) {
when { when {
steps.all { it.state == State.COMPLETED } -> State.COMPLETED steps.all { it.state == State.COMPLETED } -> State.COMPLETED
@@ -76,48 +67,52 @@ fun Steps(
} }
} }
LaunchedEffect(state) {
if (state == State.RUNNING)
onExpand()
}
Column( Column(
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(16.dp)) .clip(MaterialTheme.shapes.large)
.fillMaxWidth() .fillMaxWidth()
.background(cardColor) .background(MaterialTheme.colorScheme.surfaceContainerLow)
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(14.dp),
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(16.dp)) .clickable(true, onClick = onClick)
.clickable { expanded = !expanded } .fillMaxWidth()
.background(categoryColor) .padding(20.dp)
) { ) {
Row( StepIcon(state = state, size = 24.dp)
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(16.dp)
) {
StepIcon(state = state, size = 24.dp)
Text(stringResource(category.displayName)) Text(stringResource(category.displayName))
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
val stepProgress = remember(stepCount, steps) { val stepProgress = remember(stepCount, steps) {
stepCount?.let { (current, total) -> "$current/$total" } stepCount?.let { (current, total) -> "$current/$total" }
?: "${steps.count { it.state == State.COMPLETED }}/${steps.size}" ?: "${steps.count { it.state == State.COMPLETED }}/${steps.size}"
}
Text(
text = stepProgress,
style = MaterialTheme.typography.labelSmall
)
ArrowButton(modifier = Modifier.size(24.dp), expanded = expanded, onClick = null)
} }
Text(
text = stepProgress,
style = MaterialTheme.typography.labelSmall
)
ArrowButton(modifier = Modifier.size(24.dp), expanded = isExpanded, onClick = null)
} }
AnimatedVisibility(visible = expanded) { AnimatedVisibility(visible = isExpanded) {
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier
.background(MaterialTheme.colorScheme.background.copy(0.6f))
.fillMaxWidth()
.padding(top = 10.dp)
) { ) {
steps.forEach { step -> steps.forEachIndexed { index, step ->
val (progress, progressText) = when (step.progressKey) { val (progress, progressText) = when (step.progressKey) {
null -> null null -> null
ProgressKey.DOWNLOAD -> stepProgressProvider.downloadProgress?.let { (downloaded, total) -> ProgressKey.DOWNLOAD -> stepProgressProvider.downloadProgress?.let { (downloaded, total) ->
@@ -131,7 +126,9 @@ fun Steps(
state = step.state, state = step.state,
message = step.message, message = step.message,
progress = progress, progress = progress,
progressText = progressText progressText = progressText,
isFirst = index == 0,
isLast = index == steps.lastIndex,
) )
} }
} }
@@ -145,7 +142,9 @@ fun SubStep(
state: State, state: State,
message: String? = null, message: String? = null,
progress: Float? = null, progress: Float? = null,
progressText: String? = null progressText: String? = null,
isFirst: Boolean = false,
isLast: Boolean = false,
) { ) {
var messageExpanded by rememberSaveable { mutableStateOf(true) } var messageExpanded by rememberSaveable { mutableStateOf(true) }
@@ -156,22 +155,22 @@ fun SubStep(
clickable { messageExpanded = !messageExpanded } clickable { messageExpanded = !messageExpanded }
else this else this
} }
.padding(top = if (isFirst) 10.dp else 8.dp, bottom = if (isLast) 20.dp else 8.dp)
.padding(horizontal = 20.dp)
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
) { ) {
Box( StepIcon(
modifier = Modifier.size(24.dp), size = 18.dp,
contentAlignment = Alignment.Center state = state,
) { progress = progress,
StepIcon(state, progress, size = 20.dp) )
}
Text( Text(
text = name, text = name,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.labelLarge,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, true), modifier = Modifier.weight(1f, true),
@@ -201,7 +200,7 @@ fun SubStep(
text = message.orEmpty(), text = message.orEmpty(),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(horizontal = 52.dp, vertical = 8.dp) modifier = Modifier.padding(horizontal = 36.dp, vertical = 8.dp)
) )
} }
} }
@@ -211,40 +210,44 @@ fun SubStep(
fun StepIcon(state: State, progress: Float? = null, size: Dp) { fun StepIcon(state: State, progress: Float? = null, size: Dp) {
val strokeWidth = Dp(floor(size.value / 10) + 1) val strokeWidth = Dp(floor(size.value / 10) + 1)
when (state) { Crossfade(targetState = state, label = "State CrossFade") { state ->
State.COMPLETED -> Icon( when (state) {
Icons.Filled.CheckCircle, State.COMPLETED -> Icon(
contentDescription = stringResource(R.string.step_completed), Icons.Filled.CheckCircle,
tint = MaterialTheme.colorScheme.surfaceTint, contentDescription = stringResource(R.string.step_completed),
modifier = Modifier.size(size) tint = Color(0xFF59B463),
) modifier = Modifier.size(size)
State.FAILED -> Icon(
Icons.Filled.Cancel,
contentDescription = stringResource(R.string.step_failed),
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(size)
)
State.WAITING -> Icon(
Icons.Outlined.Circle,
contentDescription = stringResource(R.string.step_waiting),
tint = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.size(size)
)
State.RUNNING ->
LoadingIndicator(
modifier = stringResource(R.string.step_running).let { description ->
Modifier
.size(size)
.semantics {
contentDescription = description
}
},
progress = { progress },
strokeWidth = strokeWidth
) )
State.FAILED -> Icon(
Icons.Filled.Cancel,
contentDescription = stringResource(R.string.step_failed),
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(size)
)
State.WAITING -> Icon(
Icons.Outlined.Circle,
contentDescription = stringResource(R.string.step_waiting),
tint = MaterialTheme.colorScheme.onSurface.copy(.2f),
modifier = Modifier.size(size)
)
State.RUNNING -> {
LoadingIndicator(
modifier = stringResource(R.string.step_running).let { description ->
Modifier
.size(size)
.semantics {
contentDescription = description
}
},
progress = { progress },
strokeWidth = strokeWidth
)
}
}
} }
} }

View File

@@ -213,6 +213,12 @@ fun PatcherScreen(
.padding(paddingValues) .padding(paddingValues)
.fillMaxSize() .fillMaxSize()
) { ) {
var expandedCategory by rememberSaveable { mutableStateOf<StepCategory?>(null) }
val expandCategory: (StepCategory?) -> Unit = { category ->
expandedCategory = category
}
LinearProgressIndicator( LinearProgressIndicator(
progress = { viewModel.progress }, progress = { viewModel.progress },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@@ -231,7 +237,12 @@ fun PatcherScreen(
category = category, category = category,
steps = steps, steps = steps,
stepCount = if (category == StepCategory.PATCHING) viewModel.patchesProgress else null, stepCount = if (category == StepCategory.PATCHING) viewModel.patchesProgress else null,
stepProgressProvider = viewModel stepProgressProvider = viewModel,
isExpanded = expandedCategory == category,
onExpand = { expandCategory(category) },
onClick = {
expandCategory(if (expandedCategory == category) null else category)
}
) )
} }
} }

View File

@@ -25,6 +25,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -76,12 +77,12 @@ fun SelectedAppInfoScreen(
val bundles by vm.bundleInfoFlow.collectAsStateWithLifecycle(emptyList()) val bundles by vm.bundleInfoFlow.collectAsStateWithLifecycle(emptyList())
val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState() val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState()
val patches = remember(bundles, allowIncompatiblePatches) { val patches by remember {
vm.getPatches(bundles, allowIncompatiblePatches) derivedStateOf {
} vm.getPatches(bundles, allowIncompatiblePatches)
val selectedPatchCount = remember(patches) { }
patches.values.sumOf { it.size }
} }
val selectedPatchCount = patches.values.sumOf { it.size }
val launcher = rememberLauncherForActivityResult( val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(), contract = ActivityResultContracts.StartActivityForResult(),

View File

@@ -8,7 +8,6 @@ import android.os.Parcelable
import android.util.Log import android.util.Log
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -129,8 +128,6 @@ class SelectedAppInfoViewModel(
} }
var options: Options by savedStateHandle.saveable { var options: Options by savedStateHandle.saveable {
val state = mutableStateOf<Options>(emptyMap())
viewModelScope.launch { viewModelScope.launch {
if (!persistConfiguration) return@launch // TODO: save options for patched apps. if (!persistConfiguration) return@launch // TODO: save options for patched apps.
val bundlePatches = bundleInfoFlow.first() val bundlePatches = bundleInfoFlow.first()
@@ -141,7 +138,7 @@ class SelectedAppInfoViewModel(
} }
} }
state mutableStateOf(emptyMap())
} }
private set private set
@@ -149,8 +146,6 @@ class SelectedAppInfoViewModel(
if (input.patches != null) if (input.patches != null)
return@saveable mutableStateOf(SelectionState.Customized(input.patches)) return@saveable mutableStateOf(SelectionState.Customized(input.patches))
val selection: MutableState<SelectionState> = mutableStateOf(SelectionState.Default)
// Try to get the previous selection if customization is enabled. // Try to get the previous selection if customization is enabled.
viewModelScope.launch { viewModelScope.launch {
if (!prefs.disableSelectionWarning.get()) return@launch if (!prefs.disableSelectionWarning.get()) return@launch
@@ -160,7 +155,7 @@ class SelectedAppInfoViewModel(
selectionState = SelectionState.Customized(previous) selectionState = SelectionState.Customized(previous)
} }
selection mutableStateOf(SelectionState.Default)
} }
var showSourceSelector by mutableStateOf(false) var showSourceSelector by mutableStateOf(false)
@@ -311,7 +306,7 @@ class SelectedAppInfoViewModel(
} }
} }
enum class Error(@StringRes val resourceId: Int) { enum class Error(@param:StringRes val resourceId: Int) {
NoPlugins(R.string.downloader_no_plugins_available) NoPlugins(R.string.downloader_no_plugins_available)
} }