Compare commits

..

11 Commits

19 changed files with 132 additions and 92 deletions

View File

@ -5,6 +5,7 @@ on:
pull_request:
branches:
- dev
- compose-dev
jobs:
release:
@ -13,11 +14,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
uses: burrunan/gradle-cache-action@v3
- name: Build
env:

View File

@ -18,8 +18,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
@ -28,7 +26,7 @@ jobs:
java-version: '17'
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
uses: burrunan/gradle-cache-action@v3
- name: Build
env:

View File

@ -1,3 +1,4 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.IOException
plugins {
@ -81,8 +82,10 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
}
}
buildFeatures {

View File

@ -1,3 +1,4 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import kotlin.random.Random
plugins {
@ -40,10 +41,6 @@ dependencies {
// Placeholder
implementation(libs.placeholder.material3)
// HTML Scraper
implementation(libs.skrapeit.dsl)
implementation(libs.skrapeit.parser)
// Coil (async image loading, network image)
implementation(libs.coil.compose)
implementation(libs.coil.appiconloader)
@ -56,7 +53,6 @@ dependencies {
// Room
implementation(libs.room.runtime)
implementation(libs.room.ktx)
annotationProcessor(libs.room.compiler)
ksp(libs.room.compiler)
// ReVanced
@ -128,6 +124,8 @@ android {
}
buildTypes {
configureEach {
}
debug {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "ReVanced Manager (Debug)")
@ -203,8 +201,11 @@ android {
arg("room.schemaLocation", "$projectDir/schemas")
}
kotlinOptions {
jvmTarget = "17"
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
jvmToolchain(17)
}
}
buildFeatures {
@ -227,10 +228,6 @@ android {
}
}
kotlin {
jvmToolchain(17)
}
tasks {
// Needed by gradle-semantic-release-plugin.
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.

View File

@ -10,7 +10,12 @@
# Required for serialization to work properly
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
static <1>$* Companion;
}
-keepnames @kotlinx.serialization.internal.NamedCompanion class *
-if @kotlinx.serialization.internal.NamedCompanion class *
-keepclassmembernames class * {
static <1> *;
}
-if @kotlinx.serialization.Serializable class ** {
static **$* *;

View File

@ -8,7 +8,6 @@ import app.revanced.manager.util.tag
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.client.request.prepareGet
import io.ktor.client.request.request
import io.ktor.client.statement.bodyAsText
@ -17,7 +16,6 @@ import io.ktor.http.isSuccess
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.isNotEmpty
import io.ktor.utils.io.core.readBytes
import it.skrape.core.htmlDocument
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
@ -93,9 +91,5 @@ class HttpService(
builder: HttpRequestBuilder.() -> Unit
) = saveLocation.outputStream().use { streamTo(it, builder) }
suspend fun getHtml(builder: HttpRequestBuilder.() -> Unit) = htmlDocument(
html = http.get(builder).bodyAsText()
)
class HttpException(status: HttpStatusCode) : Exception("Failed to fetch: http status: $status")
}

View File

@ -1,6 +1,7 @@
package app.revanced.manager.ui.component
import android.view.WindowManager
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.graphics.Color
@ -9,6 +10,7 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import androidx.core.view.WindowCompat
private val properties = DialogProperties(
usePlatformDefaultWidth = false,
@ -22,11 +24,17 @@ fun FullscreenDialog(onDismissRequest: () -> Unit, content: @Composable () -> Un
onDismissRequest = onDismissRequest,
properties = properties
) {
val window = (LocalView.current.parent as DialogWindowProvider).window
LaunchedEffect(Unit) {
val view = LocalView.current
val isDarkTheme = isSystemInDarkTheme()
LaunchedEffect(isDarkTheme) {
val window = (view.parent as DialogWindowProvider).window
window.statusBarColor = Color.Transparent.toArgb()
window.navigationBarColor = Color.Transparent.toArgb()
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
val insetsController = WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
}
content()

View File

@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@ -74,13 +73,11 @@ import app.revanced.manager.util.saver.snapshotStateListSaver
import app.revanced.manager.util.saver.snapshotStateSetSaver
import app.revanced.manager.util.toast
import app.revanced.manager.util.transparentListItemColors
import kotlinx.coroutines.CoroutineScope
import kotlinx.parcelize.Parcelize
import org.koin.compose.koinInject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyColumnState
import sh.calvin.reorderable.rememberReorderableLazyListState
import java.io.Serializable
import kotlin.random.Random
@ -91,15 +88,28 @@ private class OptionEditorScope<T : Any>(
val option: Option<T>,
val openDialog: () -> Unit,
val dismissDialog: () -> Unit,
val selectionWarningEnabled: Boolean,
val showSelectionWarning: () -> Unit,
val value: T?,
val setValue: (T?) -> Unit,
val setValue: (T?) -> Unit
) {
fun submitDialog(value: T?) {
setValue(value)
dismissDialog()
}
fun clickAction() = editor.clickAction(this)
fun checkSafeguard(block: () -> Unit) {
if (!option.required && selectionWarningEnabled)
showSelectionWarning()
else
block()
}
fun clickAction() {
checkSafeguard {
editor.clickAction(this)
}
}
@Composable
fun ListItemTrailingContent() = editor.ListItemTrailingContent(this)
@ -113,7 +123,7 @@ private interface OptionEditor<T : Any> {
@Composable
fun ListItemTrailingContent(scope: OptionEditorScope<T>) {
IconButton(onClick = { clickAction(scope) }) {
IconButton(onClick = { scope.checkSafeguard { clickAction(scope) } }) {
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
}
}
@ -141,11 +151,14 @@ private inline fun <T : Any> WithOptionEditor(
option: Option<T>,
value: T?,
noinline setValue: (T?) -> Unit,
selectionWarningEnabled: Boolean,
crossinline onDismissDialog: @DisallowComposableCalls () -> Unit = {},
block: OptionEditorScope<T>.() -> Unit
) {
var showDialog by rememberSaveable { mutableStateOf(false) }
val scope = remember(editor, option, value, setValue) {
var showSelectionWarningDialog by rememberSaveable { mutableStateOf(false) }
val scope = remember(editor, option, value, setValue, selectionWarningEnabled) {
OptionEditorScope(
editor,
option,
@ -154,11 +167,18 @@ private inline fun <T : Any> WithOptionEditor(
showDialog = false
onDismissDialog()
},
selectionWarningEnabled,
showSelectionWarning = { showSelectionWarningDialog = true },
value,
setValue
)
}
if (showSelectionWarningDialog)
SelectionWarningDialog(
onDismiss = { showSelectionWarningDialog = false }
)
if (showDialog) scope.Dialog()
scope.block()
@ -169,6 +189,7 @@ fun <T : Any> OptionItem(
option: Option<T>,
value: T?,
setValue: (T?) -> Unit,
selectionWarningEnabled: Boolean
) {
val editor = remember(option.type, option.presets) {
@Suppress("UNCHECKED_CAST")
@ -181,7 +202,7 @@ fun <T : Any> OptionItem(
else baseOptionEditor
}
WithOptionEditor(editor, option, value, setValue) {
WithOptionEditor(editor, option, value, setValue, selectionWarningEnabled) {
ListItem(
modifier = Modifier.clickable(onClick = ::clickAction),
headlineContent = { Text(option.title) },
@ -300,7 +321,7 @@ private object StringOptionEditor : OptionEditor<String> {
private abstract class NumberOptionEditor<T : Number> : OptionEditor<T> {
@Composable
protected abstract fun NumberDialog(
abstract fun NumberDialog(
title: String,
current: T?,
validator: (T?) -> Boolean,
@ -354,7 +375,14 @@ private object BooleanOptionEditor : OptionEditor<Boolean> {
@Composable
override fun ListItemTrailingContent(scope: OptionEditorScope<Boolean>) {
HapticSwitch(checked = scope.current, onCheckedChange = scope.setValue)
HapticSwitch(
checked = scope.current,
onCheckedChange = { value ->
scope.checkSafeguard {
scope.setValue(value)
}
}
)
}
@Composable
@ -393,6 +421,7 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
scope.option,
scope.value,
scope.setValue,
scope.selectionWarningEnabled,
onDismissDialog = scope.dismissDialog
) inner@{
var hidePresetsDialog by rememberSaveable {
@ -614,7 +643,8 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
elementEditor,
elementOption,
value = item.value,
setValue = { items[index] = item.copy(value = it) }
setValue = { items[index] = item.copy(value = it) },
selectionWarningEnabled = scope.selectionWarningEnabled
) {
ListItem(
modifier = Modifier.combinedClickable(

View File

@ -0,0 +1,17 @@
package app.revanced.manager.ui.component.patches
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
import app.revanced.manager.ui.component.SafeguardDialog
@Composable
fun SelectionWarningDialog(
onDismiss: () -> Unit
) {
SafeguardDialog(
onDismiss = onDismiss,
title = R.string.warning,
body = stringResource(R.string.selection_warning_description),
)
}

View File

@ -73,6 +73,7 @@ import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticTab
import app.revanced.manager.ui.component.patches.OptionItem
import app.revanced.manager.ui.component.patches.SelectionWarningDialog
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_INCOMPATIBLE
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNIVERSAL
@ -181,7 +182,8 @@ fun PatchesSelectorScreen(
patch = patch,
values = viewModel.getOptions(bundle, patch),
reset = { viewModel.resetOptions(bundle, patch) },
set = { key, value -> viewModel.setOption(bundle, patch, key, value) }
set = { key, value -> viewModel.setOption(bundle, patch, key, value) },
selectionWarningEnabled = viewModel.selectionWarningEnabled
)
}
@ -215,9 +217,7 @@ fun PatchesSelectorScreen(
) { patch ->
PatchItem(
patch = patch,
onOptionsDialog = {
viewModel.optionsDialog = uid to patch
},
onOptionsDialog = { viewModel.optionsDialog = uid to patch },
selected = compatible && viewModel.isSelected(
uid,
patch
@ -472,17 +472,6 @@ fun PatchesSelectorScreen(
}
}
@Composable
private fun SelectionWarningDialog(
onDismiss: () -> Unit
) {
SafeguardDialog(
onDismiss = onDismiss,
title = R.string.warning,
body = stringResource(R.string.selection_warning_description),
)
}
@Composable
private fun UniversalPatchWarningDialog(
onDismiss: () -> Unit
@ -612,6 +601,7 @@ private fun OptionsDialog(
reset: () -> Unit,
set: (String, Any?) -> Unit,
onDismissRequest: () -> Unit,
selectionWarningEnabled: Boolean
) = FullscreenDialog(onDismissRequest = onDismissRequest) {
Scaffold(
topBar = {
@ -642,7 +632,8 @@ private fun OptionsDialog(
value = value,
setValue = {
set(key, it)
}
},
selectionWarningEnabled = selectionWarningEnabled
)
}
}

View File

@ -154,7 +154,8 @@ fun RequiredOptionsScreen(
value = value,
setValue = { new ->
vm.setOption(bundle.uid, it, key, new)
}
},
selectionWarningEnabled = vm.selectionWarningEnabled
)
}
}

View File

@ -296,8 +296,8 @@ fun ImportExportSettingsScreen(
}
}
},
headline = R.string.patch_options_reset,
description = R.string.patch_options_reset_all,
headline = R.string.patch_options_reset_patches,
description = R.string.patch_options_reset_patches_description,
)
}
}

View File

@ -81,8 +81,8 @@ sealed class ResetDialogState(
)
class PatchOptionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState(
titleResId = R.string.patch_options_reset,
descriptionResId = R.string.patch_options_reset_dialog_description,
titleResId = R.string.patch_options_reset_patches,
descriptionResId = R.string.patch_options_reset_patches_dialog_description,
onConfirm = onConfirm,
dialogOptionName = dialogOptionName
)

View File

@ -95,8 +95,8 @@
<string name="suggested_version_safeguard">Require suggested app version</string>
<string name="suggested_version_safeguard_description">Enforce selection of the suggested app version</string>
<string name="suggested_version_safeguard_confirmation">Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?</string>
<string name="patch_selection_safeguard">Allow changing patch selection</string>
<string name="patch_selection_safeguard_description">Do not prevent selecting or deselecting patches</string>
<string name="patch_selection_safeguard">Allow changing patch selection and options</string>
<string name="patch_selection_safeguard_description">Do not prevent selecting or deselecting patches and customization of options</string>
<string name="patch_selection_safeguard_confirmation">Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?</string>
<string name="universal_patches_safeguard">Allow using universal patches</string>
<string name="universal_patches_safeguard_description">Do not prevent using universal patches</string>
@ -131,21 +131,22 @@
<string name="reset_patch_options">Reset patch options</string>
<string name="reset_patch_options_description">Reset the stored patch options</string>
<string name="reset_patch_selection_success">Patch selection has been reset</string>
<string name="patch_selection_reset_all">Reset all patch selection</string>
<string name="patch_selection_reset_all">Reset patch selection globally</string>
<string name="patch_selection_reset_all_dialog_description">You are about to reset all the patch selections. You will need to manually select each patch again.</string>
<string name="patch_selection_reset_all_description">Reset all the patch selections</string>
<string name="patch_selection_reset_all_description">Resets all the patch selections</string>
<string name="patch_selection_reset_package">Reset patch selection for app</string>
<string name="patch_selection_reset_package_dialog_description">You are about to reset the patch selection for the app \"%s\". You will have to manually select each patch again.</string>
<string name="patch_selection_reset_package_description">Resets patch selection for a single app</string>
<string name="patch_selection_reset_patches">Resets patch selection for a specific patches</string>
<string name="patch_selection_reset_patches">Reset patch selection (single)</string>
<string name="patch_selection_reset_patches_dialog_description">You are about to reset the patch selection for \"%s\". You will have to manually select each patch again.</string>
<string name="patch_selection_reset_patches_description">Resets the patch selection for a specific patches</string>
<string name="patch_selection_reset_patches_description">Resets the patch selection for a specific collection of patches</string>
<string name="patch_options_reset_package">Reset patch options for app</string>
<string name="patch_options_reset_package_dialog_description">You are about to reset the patch options for the app \"%s\". You will have to reapply each option again.</string>
<string name="patch_options_reset_package_description">Resets patch options for a single app</string>
<string name="patch_options_reset">Reset patch options</string>
<string name="patch_options_reset_dialog_description">You are about to reset the patch options for \"%s\". You will have to reapply each option again.</string>
<string name="patch_options_reset_all">Reset patch options for all</string>
<string name="patch_options_reset_patches">Reset patch options (single)</string>
<string name="patch_options_reset_patches_dialog_description">You are about to reset the patch options for \"%s\". You will have to reapply each option again.</string>
<string name="patch_options_reset_patches_description">Resets the patch options for a specific collection of patches</string>
<string name="patch_options_reset_all">Reset patch options globally</string>
<string name="patch_options_reset_all_dialog_description">You are about to reset patch options. You will have to reapply each option again.</string>
<string name="patch_options_reset_all_description">Resets all patch options</string>
<string name="downloader_plugins">Plugins</string>

View File

@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx3072m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@ -25,4 +25,5 @@ android.nonFinalResIds=false
# Task :app:assembleReleaseSignApk fails if this is set to true.
org.gradle.configuration-cache=false
org.gradle.caching=true
org.gradle.parallel=true
version=1.25.0-dev.1

View File

@ -1,35 +1,34 @@
[versions]
ktx = "1.16.0"
material3 = "1.3.2"
ui-tooling = "1.8.1"
viewmodel-lifecycle = "2.9.0"
ui-tooling = "1.8.3"
viewmodel-lifecycle = "2.9.2"
splash-screen = "1.0.1"
activity = "1.10.1"
appcompat = "1.7.0"
preferences-datastore = "1.1.2"
work-runtime = "2.10.1"
compose-bom = "2025.05.00"
navigation = "2.8.6"
accompanist = "0.37.0"
appcompat = "1.7.1"
preferences-datastore = "1.1.7"
work-runtime = "2.10.3"
compose-bom = "2025.07.00"
navigation = "2.9.3"
accompanist = "0.37.3"
placeholder = "1.1.2"
reorderable = "2.4.3"
serialization = "1.8.0"
reorderable = "2.5.1"
serialization = "1.9.0"
collection = "0.3.8"
datetime = "0.6.1"
room-version = "2.7.1"
room-version = "2.7.2"
revanced-patcher = "21.0.0"
revanced-library = "3.0.2"
koin = "3.5.3"
ktor = "2.3.9"
markdown-renderer = "0.30.0"
fading-edges = "1.0.4"
kotlin = "2.1.10"
android-gradle-plugin = "8.9.1"
dev-tools-gradle-plugin = "2.1.10-1.0.29"
kotlin = "2.2.0"
android-gradle-plugin = "8.11.1"
dev-tools-gradle-plugin = "2.2.0-2.0.2"
about-libraries-gradle-plugin = "12.1.2"
coil = "2.7.0"
app-icon-loader-coil = "1.5.0"
skrapeit = "1.2.2"
libsu = "6.0.0"
scrollbars = "1.0.4"
enumutil = "1.1.1"
@ -99,10 +98,6 @@ ktor-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "k
ktor-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
# HTML Scraper
skrapeit-dsl = { group = "it.skrape", name = "skrapeit-dsl", version.ref = "skrapeit" }
skrapeit-parser = { group = "it.skrape", name = "skrapeit-html-parser", version.ref = "skrapeit" }
# Markdown
markdown-renderer = { group = "com.mikepenz", name = "multiplatform-markdown-renderer-m3", version.ref = "markdown-renderer" }

Binary file not shown.

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

2
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.