Skip to content
485 changes: 485 additions & 0 deletions app/schemas/app.revanced.manager.data.room.AppDatabase/6.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import kotlin.random.Random

@Database(
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, InstalledPatchBundle::class, OptionGroup::class, Option::class, DownloaderEntity::class],
version = 5,
version = 6,
exportSchema = true,
autoMigrations = [
AutoMigration(from = 1, to = 2),
Expand All @@ -36,7 +36,8 @@ import kotlin.random.Random
spec = AppDatabase.DeleteTrustedDownloaders::class
),
AutoMigration(from = 3, to = 4),
AutoMigration(from = 4, to = 5)
AutoMigration(from = 4, to = 5),
AutoMigration(from = 5, to = 6)
]
)
@TypeConverters(Converters::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface PatchBundleDao {
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
suspend fun remove(uid: Int)

@Query("SELECT name, version, auto_update, source, released_at FROM patch_bundles WHERE uid = :uid")
@Query("SELECT name, version, auto_update, source, released_at, use_prereleases FROM patch_bundles WHERE uid = :uid")
suspend fun getProps(uid: Int): SourceProperties?

@Upsert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ data class PatchBundleEntity(
@ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean,
@ColumnInfo(name = "released_at") val releasedAt: Long? = null,
@ColumnInfo(name = "use_prereleases", defaultValue = "0") val usePrereleases: Boolean = false,
) : SourceManager.DatabaseEntity
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface DownloaderDao {
@Query("DELETE FROM downloaders WHERE uid = :uid")
suspend fun remove(uid: Int)

@Query("SELECT name, version, auto_update, source, released_at FROM downloaders WHERE uid = :uid")
@Query("SELECT name, version, auto_update, source, released_at, use_prereleases FROM downloaders WHERE uid = :uid")
suspend fun getProps(uid: Int): SourceProperties?

@Upsert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ data class DownloaderEntity(
@ColumnInfo(name = "version") val versionHash: String? = null,
@ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean,
@ColumnInfo(name = "released_at") val releasedAt: Long? = null
@ColumnInfo(name = "released_at") val releasedAt: Long? = null,
@ColumnInfo(name = "use_prereleases", defaultValue = "0") val usePrereleases: Boolean = false
) : SourceManager.DatabaseEntity
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ data class SourceProperties(
@ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean,
@ColumnInfo(name = "released_at") val releasedAt: Long? = null,
@ColumnInfo(name = "use_prereleases") val usePrereleases: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
source = new.source,
autoUpdate = new.autoUpdate,
releasedAt = new.releasedAt,
usePrereleases = new.usePrereleases
)
)
)
Expand Down Expand Up @@ -263,6 +264,15 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
state.copy(sources = state.sources.toMutableMap().also { it[uid] = newSrc })
}

suspend fun RemoteSource<LOADED>.setUsePrereleases(value: Boolean) =
dispatchAction("Set use prereleases ($name, $value)") { state ->
updateDb(uid) { it.copy(usePrereleases = value) }
val newSrc = state.sources[uid]?.asRemoteOrNull?.copy(usePrereleases = value)
?: return@dispatchAction state

state.copy(sources = state.sources.toMutableMap().also { it[uid] = newSrc })
}

suspend fun update(
vararg sources: RemoteSource<LOADED>,
showToast: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class DownloaderRepository(
file,
SourceInfo.API.SENTINEL,
autoUpdate,
usePrereleases,
loader
) { getDownloaderUpdate() }

Expand All @@ -93,6 +94,7 @@ class DownloaderRepository(
file,
source.url.toString(),
autoUpdate,
usePrereleases,
loader
)
}
Expand All @@ -107,7 +109,8 @@ class DownloaderRepository(
versionHash = props.versionHash,
source = props.source,
autoUpdate = props.autoUpdate,
releasedAt = props.releasedAt
releasedAt = props.releasedAt,
usePrereleases = props.usePrereleases
)

override fun realNameOf(loaded: DownloaderPackage) = loaded.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class PatchBundleRepository(
file,
SourceInfo.API.SENTINEL,
autoUpdate,
usePrereleases,
PatchBundleLoader
) { getPatchesUpdate() }

Expand All @@ -91,6 +92,7 @@ class PatchBundleRepository(
file,
source.url.toString(),
autoUpdate,
usePrereleases,
PatchBundleLoader
)
}
Expand All @@ -105,7 +107,8 @@ class PatchBundleRepository(
versionHash = props.versionHash,
source = props.source,
autoUpdate = props.autoUpdate,
releasedAt = props.releasedAt
releasedAt = props.releasedAt,
usePrereleases = props.usePrereleases
)

override fun realNameOf(loaded: PatchBundle) = loaded.manifestAttributes?.name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package app.revanced.manager.domain.sources

import android.app.Application
import app.revanced.manager.R
import app.revanced.manager.data.redux.ActionContext
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.network.service.HttpService
import app.revanced.manager.network.utils.APIResponse
import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.patcher.patch.PatchBundle
import app.revanced.manager.ui.component.sources.GithubRelease
import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Instant

typealias RemotePatchBundle = RemoteSource<PatchBundle>
typealias JsonPatchBundle = JsonSource<PatchBundle>
Expand All @@ -28,23 +34,26 @@ sealed class RemoteSource<T>(
file: File,
val endpoint: String,
val autoUpdate: Boolean,
val usePrereleases: Boolean,
loader: Loader<T>
) : Source<T>(name, uid, error, file, loader), KoinComponent {
data class UpdateResult(val versionHash: String, val releasedAt: LocalDateTime)

protected val http: HttpService by inject()
protected val app: Application by inject()

protected abstract suspend fun getLatestInfo(): ReVancedAsset
abstract fun copy(
error: Throwable? = this.error,
name: String = this.name,
autoUpdate: Boolean = this.autoUpdate,
usePrereleases: Boolean = this.usePrereleases,
versionHash: String? = this.versionHash,
releasedAt: LocalDateTime? = this.releasedAt
): RemoteSource<T>

override fun copy(error: Throwable?, name: String): RemoteSource<T> =
copy(error, name, this.autoUpdate, this.versionHash, this.releasedAt)
copy(error, name, this.autoUpdate, this.usePrereleases, this.versionHash, this.releasedAt)

private suspend fun download(info: ReVancedAsset) = withContext(Dispatchers.IO) {
outputStream().use {
Expand Down Expand Up @@ -81,18 +90,59 @@ class JsonSource<T>(
file: File,
endpoint: String,
autoUpdate: Boolean,
usePrereleases: Boolean,
loader: Loader<T>
) : RemoteSource<T>(name, uid, versionHash, releasedAt, error, file, endpoint, autoUpdate, loader) {
) : RemoteSource<T>(name, uid, versionHash, releasedAt, error, file, endpoint, autoUpdate, usePrereleases, loader) {
override suspend fun getLatestInfo() = withContext(Dispatchers.IO) {
http.request<ReVancedAsset> {
url(endpoint)
}.getOrThrow()
if (!endpoint.endsWith(".json")) {
val githubMatch = Regex("^https://github\\.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(.+)$").find(endpoint)
if (githubMatch != null) {
val owner = githubMatch.groupValues[1]
val repo = githubMatch.groupValues[2]
val tag = githubMatch.groupValues[3]
val currentFilename = githubMatch.groupValues[4]
val extension = currentFilename.substringAfterLast('.', "")

try {
val releases = http.request<List<GithubRelease>> {
url("https://api.github.com/repos/$owner/$repo/releases")
}.getOrThrow()

val filteredReleases = if (usePrereleases) releases else releases.filter { !it.prerelease }
val latestRelease = filteredReleases.firstOrNull()
?: error(app.getString(R.string.github_release_none_found))

val targetAsset = latestRelease.assets.firstOrNull { it.name.endsWith(".$extension") }
?: error(app.getString(R.string.github_asset_none_found))

val date = latestRelease.createdAt?.let { Instant.parse(it).toLocalDateTime(TimeZone.UTC) }

return@withContext ReVancedAsset(
downloadUrl = targetAsset.browserDownloadUrl,
version = targetAsset.name,
description = latestRelease.name ?: app.getString(R.string.github_external_asset),
createdAt = date ?: releasedAt ?: LocalDateTime(1970, 1, 1, 0, 0, 0)
)
} catch (_: Exception) {
// Fallback to boilerplate
}
}

return@withContext ReVancedAsset(
downloadUrl = endpoint,
version = endpoint.substringAfterLast('/'),
description = app.getString(R.string.github_external_asset),
createdAt = releasedAt ?: LocalDateTime(1970, 1, 1, 0, 0, 0)
)
}
http.request<ReVancedAsset> { url(endpoint) }.getOrThrow()
}

override fun copy(
error: Throwable?,
name: String,
autoUpdate: Boolean,
usePrereleases: Boolean,
versionHash: String?,
releasedAt: LocalDateTime?
) = JsonSource(
Expand All @@ -104,6 +154,7 @@ class JsonSource<T>(
file,
endpoint,
autoUpdate,
usePrereleases,
loader
)
}
Expand All @@ -117,16 +168,18 @@ class APISource<T>(
file: File,
endpoint: String,
autoUpdate: Boolean,
usePrereleases: Boolean = false,
loader: Loader<T>,
private val getUpdate: suspend ReVancedAPI.() -> APIResponse<ReVancedAsset>
) : RemoteSource<T>(name, uid, versionHash, releasedAt, error, file, endpoint, autoUpdate, loader) {
) : RemoteSource<T>(name, uid, versionHash, releasedAt, error, file, endpoint, autoUpdate, usePrereleases, loader) {
private val api: ReVancedAPI by inject()

override suspend fun getLatestInfo() = api.getUpdate().getOrThrow()
override fun copy(
error: Throwable?,
name: String,
autoUpdate: Boolean,
usePrereleases: Boolean,
versionHash: String?,
releasedAt: LocalDateTime?
) = APISource(
Expand All @@ -138,6 +191,7 @@ class APISource<T>(
file,
endpoint,
autoUpdate,
usePrereleases,
loader,
getUpdate
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import com.eygraber.compose.placeholder.placeholder

@Composable
fun SurfaceChip(
text: String? = null
text: String? = null,
color: Color? = null,
contentColor: Color? = null
) {
Surface(
shape = MaterialTheme.shapes.small,
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
color = color ?: MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
contentColor = contentColor ?: MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.height(24.dp)
) {
Box(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,39 @@ fun SafeguardBooleanItem(
onValueChange: ((Boolean) -> Unit)? = null
) {
val value by preference.getAsState()
var showSafeguardWarning by rememberSaveable {
mutableStateOf(false)
}

val update = onValueChange ?: { coroutineScope.launch { preference.update(it) } }

SafeguardBooleanItem(
modifier = modifier,
value = value,
default = preference.default,
headline = headline,
description = description,
dialogTitle = dialogTitle,
confirmationText = confirmationText,
onValueChange = { update(it) }
)
}

@Composable
fun SafeguardBooleanItem(
modifier: Modifier = Modifier,
value: Boolean,
default: Boolean = false,
@StringRes headline: Int,
description: String,
@StringRes dialogTitle: Int,
@StringRes confirmationText: Int,
onValueChange: (Boolean) -> Unit
) {
var showSafeguardWarning by rememberSaveable { mutableStateOf(false) }

if (showSafeguardWarning) {
ConfirmDialog(
onDismiss = { showSafeguardWarning = false },
onConfirm = {
update(!value)
onValueChange(!value)
showSafeguardWarning = false
},
title = stringResource(id = dialogTitle),
Expand All @@ -72,10 +94,10 @@ fun SafeguardBooleanItem(
modifier = modifier,
value = value,
onValueChange = {
if (it != preference.default) {
if (it != default) {
showSafeguardWarning = true
} else {
update(it)
onValueChange(it)
}
},
headline = headline,
Expand Down
Loading
Loading