mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-03-13 08:41:57 +08:00
Compare commits
9 Commits
feat/chang
...
v1.26.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e03f3666c8 | ||
|
|
323d2170ef | ||
|
|
940669b3e4 | ||
|
|
81e5a9315d | ||
|
|
b38d0b7022 | ||
|
|
b838851ebe | ||
|
|
00b3c59572 | ||
|
|
fc30ba5e83 | ||
|
|
32228404c4 |
@@ -1,3 +1,10 @@
|
||||
# api [1.0.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/api@1.0.0-dev.1...api@1.0.0-dev.2) (2026-02-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to Patcher v22 ([#2939](https://github.com/ReVanced/revanced-manager/issues/2939)) ([8667051](https://github.com/ReVanced/revanced-manager/commit/8667051283f934a32ac7b7cc76178397dc45a0e6))
|
||||
|
||||
# api 1.0.0-dev.1 (2026-02-19)
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version = 1.0.0-dev.1
|
||||
version = 1.0.0-dev.2
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
# app [1.26.0-dev.26](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.25...v1.26.0-dev.26) (2026-02-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve InstalledAppsScreen caching/perf ([#2963](https://github.com/ReVanced/revanced-manager/issues/2963)) ([323d217](https://github.com/ReVanced/revanced-manager/commit/323d2170efa0e3f2c8d2245ff08a2e92c52cb9d7))
|
||||
|
||||
# app [1.26.0-dev.25](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.24...v1.26.0-dev.25) (2026-02-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve AppSelectorScreen caching/perf ([#2961](https://github.com/ReVanced/revanced-manager/issues/2961)) ([81e5a93](https://github.com/ReVanced/revanced-manager/commit/81e5a9315db4c858fa29163d15457af97163967a))
|
||||
|
||||
# app [1.26.0-dev.24](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.23...v1.26.0-dev.24) (2026-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Incorrect patcher string value ([f19080a](https://github.com/ReVanced/revanced-manager/commit/f19080a9862be4600860b67cd981b9a3a56b21e7))
|
||||
* Never show again toggle doing nothing for Update dialog ([6f4219c](https://github.com/ReVanced/revanced-manager/commit/6f4219c01ba43b70e130f210c8d48978a6056f68))
|
||||
* Release builds not working ([00b3c59](https://github.com/ReVanced/revanced-manager/commit/00b3c59572760993f69f2663456237faafff0283))
|
||||
* Remove quotes from strings ([fc30ba5](https://github.com/ReVanced/revanced-manager/commit/fc30ba5e83f27329b05dc81f484bb8f7471b6a99))
|
||||
* we actually don't want to exclude reflection symbols ([3222840](https://github.com/ReVanced/revanced-manager/commit/32228404c4ef38bb8931d5cfdf6a7aefde23a89c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add announcements ([#2948](https://github.com/ReVanced/revanced-manager/issues/2948)) ([813df46](https://github.com/ReVanced/revanced-manager/commit/813df46847620bd854dccc52ff244388b3385e17))
|
||||
* Update to Patcher v22 ([#2939](https://github.com/ReVanced/revanced-manager/issues/2939)) ([8667051](https://github.com/ReVanced/revanced-manager/commit/8667051283f934a32ac7b7cc76178397dc45a0e6))
|
||||
|
||||
# app [1.26.0-dev.23](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.22...v1.26.0-dev.23) (2026-02-19)
|
||||
|
||||
|
||||
|
||||
@@ -257,9 +257,6 @@ android {
|
||||
|
||||
// Desktop AAPT binaries
|
||||
excludes += "/prebuilt/**"
|
||||
|
||||
// Reflection symbol list (https://stackoverflow.com/a/41073782/13964629)
|
||||
excludes += "/**/*.kotlin_builtins"
|
||||
}
|
||||
jniLibs {
|
||||
// 32-bit x86 is dead
|
||||
|
||||
@@ -1 +1 @@
|
||||
version = 1.26.0-dev.23
|
||||
version = 1.26.0-dev.26
|
||||
|
||||
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
@@ -11,4 +11,7 @@
|
||||
-dontwarn com.google.j2objc.annotations.*
|
||||
-dontwarn java.awt.**
|
||||
-dontwarn javax.**
|
||||
-dontwarn org.slf4j.**
|
||||
-dontwarn org.slf4j.**
|
||||
|
||||
-dontwarn org.immutables.value.Generated
|
||||
-dontwarn reactor.blockhound.integration.BlockHoundIntegration
|
||||
|
||||
@@ -20,23 +20,16 @@ class ReVancedAPI(
|
||||
) {
|
||||
private suspend fun apiUrl() = prefs.api.get()
|
||||
|
||||
private suspend inline fun <reified T> request(
|
||||
api: String,
|
||||
route: String,
|
||||
version: String? = "v4"
|
||||
): APIResponse<T> =
|
||||
private suspend inline fun <reified T> request(api: String, route: String): APIResponse<T> =
|
||||
withContext(
|
||||
Dispatchers.IO
|
||||
) {
|
||||
client.request {
|
||||
url("$api/$version/$route")
|
||||
url("$api/v4/$route")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun <reified T> request(
|
||||
route: String,
|
||||
version: String = "v4"
|
||||
) = request<T>(apiUrl(), route, version)
|
||||
private suspend inline fun <reified T> request(route: String) = request<T>(apiUrl(), route)
|
||||
|
||||
suspend fun getAnnouncements() = request<List<ReVancedAnnouncement>>("announcements")
|
||||
|
||||
@@ -48,8 +41,6 @@ class ReVancedAPI(
|
||||
suspend fun getLatestAppInfo() =
|
||||
request<ReVancedAsset>("manager?prerelease=${prefs.useManagerPrereleases.get()}")
|
||||
|
||||
suspend fun getAppHistory() = request<ReVancedAsset>("patches/history", "dev")
|
||||
|
||||
suspend fun getPatchesUpdate() = request<ReVancedAsset>("patches?prerelease=${prefs.usePatchesPrereleases.get()}")
|
||||
|
||||
suspend fun getContributors() = request<List<ReVancedGitRepository>>("contributors")
|
||||
|
||||
@@ -7,8 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CalendarToday
|
||||
import androidx.compose.material.icons.outlined.Campaign
|
||||
import androidx.compose.material.icons.outlined.NewReleases
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -22,7 +21,7 @@ import app.revanced.manager.ui.component.Markdown
|
||||
|
||||
@Composable
|
||||
fun Changelog(
|
||||
description: String,
|
||||
markdown: String,
|
||||
version: String,
|
||||
publishDate: String
|
||||
) {
|
||||
@@ -35,32 +34,22 @@ fun Changelog(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Campaign,
|
||||
imageVector = Icons.Outlined.NewReleases,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
)
|
||||
Text(
|
||||
version.removePrefix("v"),
|
||||
"${version.removePrefix("v")} ($publishDate)",
|
||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Tag(
|
||||
Icons.Outlined.CalendarToday,
|
||||
publishDate
|
||||
)
|
||||
}
|
||||
Markdown(
|
||||
description.removeVersionHeaderIfMatches(version),
|
||||
)
|
||||
}
|
||||
Markdown(
|
||||
markdown,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -81,16 +70,4 @@ private fun Tag(icon: ImageVector, text: String) {
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun String.removeVersionHeaderIfMatches(version: String): String {
|
||||
val firstNewlineIndex = indexOf('\n')
|
||||
if (firstNewlineIndex == -1) return this
|
||||
|
||||
val firstLine = substring(0, firstNewlineIndex).trim()
|
||||
val versionWithoutPrefix = version.removePrefix("v")
|
||||
|
||||
if (!firstLine.contains(versionWithoutPrefix)) return this
|
||||
|
||||
return substring(firstNewlineIndex + 1).trimStart()
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -68,20 +68,11 @@ fun AppSelectorScreen(
|
||||
uri?.let(vm::handleStorageResult)
|
||||
}
|
||||
|
||||
val suggestedVersions by vm.suggestedAppVersions.collectAsStateWithLifecycle(emptyMap())
|
||||
val suggestedVersions by vm.suggestedAppVersions.collectAsStateWithLifecycle()
|
||||
|
||||
var filterText by rememberSaveable { mutableStateOf("") }
|
||||
var search by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val appList by vm.appList.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val filteredAppList = remember(appList, filterText) {
|
||||
appList.filter { app ->
|
||||
(vm.loadLabel(app.packageInfo)).contains(
|
||||
filterText,
|
||||
true
|
||||
) or app.packageName.contains(filterText, true)
|
||||
}
|
||||
}
|
||||
val appList by vm.apps.collectAsStateWithLifecycle()
|
||||
val appsListFiltered by vm.filteredApps.collectAsStateWithLifecycle()
|
||||
|
||||
vm.nonSuggestedVersionDialogSubject?.let {
|
||||
NonSuggestedVersionDialog(
|
||||
@@ -90,17 +81,20 @@ fun AppSelectorScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (search)
|
||||
if (search) {
|
||||
val filterText by vm.filterText.collectAsState()
|
||||
|
||||
SearchView(
|
||||
query = filterText,
|
||||
onQueryChange = { filterText = it },
|
||||
onQueryChange = vm::setFilterText,
|
||||
onActiveChange = { search = it },
|
||||
placeholder = { Text(stringResource(R.string.search_apps)) }
|
||||
) {
|
||||
if (appList.isNotEmpty() && filterText.isNotEmpty()) {
|
||||
val appsFiltered = appsListFiltered
|
||||
if (!appsFiltered.isNullOrEmpty() && filterText.isNotEmpty()) {
|
||||
LazyColumnWithScrollbar(modifier = Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = filteredAppList,
|
||||
items = appsFiltered,
|
||||
key = { it.packageName }
|
||||
) { app ->
|
||||
ListItem(
|
||||
@@ -109,9 +103,9 @@ fun AppSelectorScreen(
|
||||
},
|
||||
leadingContent = {
|
||||
AppIcon(
|
||||
app.packageInfo,
|
||||
null,
|
||||
Modifier.size(36.dp)
|
||||
packageInfo = app.packageInfo,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = { AppLabel(app.packageInfo) },
|
||||
@@ -152,6 +146,7 @@ fun AppSelectorScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
@@ -198,16 +193,33 @@ fun AppSelectorScreen(
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
if (appList.isNotEmpty()) {
|
||||
val apps = appList
|
||||
if (apps == null) {
|
||||
item(key = "LOADING") {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
} else if (apps.isNotEmpty()) {
|
||||
items(
|
||||
items = appList,
|
||||
key = { it.packageName }
|
||||
items = apps,
|
||||
key = { "APP-" + it.packageName },
|
||||
contentType = { "APP" },
|
||||
) { app ->
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
onSelect(app.packageName)
|
||||
},
|
||||
leadingContent = { AppIcon(app.packageInfo, null, Modifier.size(36.dp)) },
|
||||
leadingContent = {
|
||||
AppIcon(
|
||||
packageInfo = app.packageInfo,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = {
|
||||
AppLabel(
|
||||
app.packageInfo,
|
||||
@@ -231,15 +243,6 @@ fun AppSelectorScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize(), contentAlignment = Alignment.Center
|
||||
) {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -30,7 +31,7 @@ fun InstalledAppsScreen(
|
||||
onAppClick: (InstalledApp) -> Unit,
|
||||
viewModel: InstalledAppsViewModel = koinViewModel()
|
||||
) {
|
||||
val installedApps by viewModel.apps.collectAsStateWithLifecycle(initialValue = null)
|
||||
val installedApps by viewModel.apps.collectAsStateWithLifecycle()
|
||||
|
||||
Column {
|
||||
LazyColumnWithScrollbar(
|
||||
@@ -38,38 +39,40 @@ fun InstalledAppsScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = if (installedApps.isNullOrEmpty()) Arrangement.Center else Arrangement.Top,
|
||||
) {
|
||||
installedApps?.let { installedApps ->
|
||||
if (installedApps.isNotEmpty()) {
|
||||
items(
|
||||
installedApps,
|
||||
key = { it.currentPackageName }
|
||||
) { installedApp ->
|
||||
viewModel.packageInfoMap[installedApp.currentPackageName].let { packageInfo ->
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { onAppClick(installedApp) },
|
||||
leadingContent = {
|
||||
AppIcon(
|
||||
packageInfo,
|
||||
contentDescription = null,
|
||||
Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = { AppLabel(packageInfo, defaultText = null) },
|
||||
supportingContent = { Text(installedApp.currentPackageName) }
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.no_patched_apps_found),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
}
|
||||
val apps = installedApps
|
||||
if (apps == null) {
|
||||
item(key = "LOADING") {
|
||||
LoadingIndicator()
|
||||
}
|
||||
} else if (apps.isNotEmpty()) {
|
||||
items(
|
||||
apps,
|
||||
key = { "APP-" + it.currentPackageName },
|
||||
contentType = { "APP" },
|
||||
) { installedApp ->
|
||||
val packageInfo = viewModel.packageInfoMap[installedApp.currentPackageName]
|
||||
|
||||
} ?: item { LoadingIndicator() }
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { onAppClick(installedApp) },
|
||||
leadingContent = {
|
||||
AppIcon(
|
||||
packageInfo,
|
||||
contentDescription = null,
|
||||
Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = { AppLabel(packageInfo, defaultText = null) },
|
||||
supportingContent = { Text(installedApp.currentPackageName) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item(key = "NONE") {
|
||||
Text(
|
||||
text = stringResource(R.string.no_patched_apps_found),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
|
||||
)
|
||||
) {
|
||||
Changelog(
|
||||
description = releaseInfo.description.replace("`", ""),
|
||||
markdown = releaseInfo.description.replace("`", ""),
|
||||
version = releaseInfo.version,
|
||||
publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current)
|
||||
)
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package app.revanced.manager.ui.screen.settings.update
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -20,12 +17,10 @@ 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.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.LoadingIndicator
|
||||
import app.revanced.manager.ui.component.settings.Changelog
|
||||
import app.revanced.manager.ui.viewmodel.ChangelogUiState
|
||||
import app.revanced.manager.ui.viewmodel.ChangelogsViewModel
|
||||
import app.revanced.manager.util.relativeTime
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -48,61 +43,22 @@ fun ChangelogsSettingsScreen(
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
ColumnWithScrollbar(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = if (vm.releaseInfo == null) Arrangement.Center else Arrangement.Top
|
||||
) {
|
||||
when (val state = vm.state) {
|
||||
is ChangelogUiState.Loading -> LoadingIndicator()
|
||||
|
||||
is ChangelogUiState.Error -> Text(
|
||||
text = state.message,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
is ChangelogUiState.Success -> {
|
||||
if (state.changelogs.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_changelogs_found),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
} else {
|
||||
LazyColumnWithScrollbar(modifier = Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = state.changelogs,
|
||||
key = { it.version }
|
||||
) { changelog ->
|
||||
ChangelogItem(
|
||||
changelog = changelog,
|
||||
showDivider = changelog != state.changelogs.last()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
vm.releaseInfo?.let { info ->
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Changelog(
|
||||
markdown = info.description.replace("`", ""),
|
||||
version = info.version,
|
||||
publishDate = info.createdAt.relativeTime(LocalContext.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChangelogItem(
|
||||
changelog: ReVancedAsset,
|
||||
showDivider: Boolean
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Changelog(
|
||||
description = changelog.description,
|
||||
version = changelog.version,
|
||||
publishDate = changelog.createdAt.relativeTime(LocalContext.current)
|
||||
)
|
||||
if (showDivider) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(top = 32.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
} ?: LoadingIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,12 @@ import app.revanced.manager.util.PM
|
||||
import app.revanced.manager.util.toast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
@@ -40,16 +44,51 @@ class AppSelectorViewModel(
|
||||
"input.apk"
|
||||
).also(File::delete)
|
||||
}
|
||||
val appList = pm.appList
|
||||
|
||||
val filterTextFlow = MutableStateFlow("")
|
||||
val filterText: StateFlow<String> = filterTextFlow
|
||||
|
||||
val apps = pm.appList.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
val filteredApps = filterText
|
||||
.combine(apps) { filter, apps ->
|
||||
if (apps == null || filter.isBlank()) {
|
||||
apps
|
||||
} else {
|
||||
apps.filter { app ->
|
||||
app.packageName.contains(filter, true) ||
|
||||
loadLabel(app.packageInfo).contains(filter)
|
||||
}
|
||||
}
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(
|
||||
stopTimeoutMillis = 0,
|
||||
replayExpirationMillis = 10_000,
|
||||
),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
private val storageSelectionChannel = Channel<SelectedApp.Local>()
|
||||
val storageSelectionFlow = storageSelectionChannel.receiveAsFlow()
|
||||
|
||||
val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default)
|
||||
val suggestedAppVersions = patchBundleRepository.suggestedVersions.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = emptyMap(),
|
||||
)
|
||||
|
||||
var nonSuggestedVersionDialogSubject by mutableStateOf<SelectedApp.Local?>(null)
|
||||
private set
|
||||
|
||||
fun setFilterText(filter: String) {
|
||||
filterTextFlow.value = filter
|
||||
}
|
||||
|
||||
fun loadLabel(app: PackageInfo?) = with(pm) { app?.label() ?: "Not installed" }
|
||||
|
||||
fun dismissNonSuggestedVersionDialog() {
|
||||
|
||||
@@ -12,57 +12,18 @@ import app.revanced.manager.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.network.utils.getOrThrow
|
||||
import app.revanced.manager.util.uiSafe
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
sealed interface ChangelogUiState {
|
||||
data object Loading : ChangelogUiState
|
||||
data class Success(val changelogs: List<ReVancedAsset>) : ChangelogUiState
|
||||
data class Error(val message: String) : ChangelogUiState
|
||||
}
|
||||
|
||||
class ChangelogsViewModel(
|
||||
private val api: ReVancedAPI,
|
||||
private val app: Application,
|
||||
) : ViewModel() {
|
||||
val mock = listOf(
|
||||
ReVancedAsset(
|
||||
version = "v1.25.2",
|
||||
createdAt = LocalDateTime.parse("2025-07-07T13:01:12"),
|
||||
description = """
|
||||
## [1.25.2](https://github.com/ReVanced/revanced-manager/compare/v1.25.0...v1.25.1) (2025-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Disable Impeller Flutter render engine correctly to fix rendering issues
|
||||
""".trimIndent(),
|
||||
downloadUrl = "https://github.com/ReVanced/revanced-manager/releases/download/v1.25.1/revanced-manager-1.25.1.apk"
|
||||
),
|
||||
ReVancedAsset(
|
||||
version = "v1.25.1",
|
||||
createdAt = LocalDateTime.parse("2025-07-07T13:01:12"),
|
||||
description = """
|
||||
## [1.25.1](https://github.com/ReVanced/revanced-manager/compare/v1.25.0...v1.25.1) (2025-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Disable Impeller Flutter render engine correctly to fix rendering issues
|
||||
""".trimIndent(),
|
||||
downloadUrl = "https://github.com/ReVanced/revanced-manager/releases/download/v1.25.1/revanced-manager-1.25.1.apk"
|
||||
)
|
||||
)
|
||||
|
||||
var state: ChangelogUiState by mutableStateOf(ChangelogUiState.Loading)
|
||||
var releaseInfo: ReVancedAsset? by mutableStateOf(null)
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
uiSafe(app, R.string.changelog_download_fail, "Failed to download changelog") {
|
||||
api.getLatestAppInfo().getOrThrow()
|
||||
state = ChangelogUiState.Success(changelogs = mock)
|
||||
}
|
||||
if (state is ChangelogUiState.Loading) {
|
||||
state = ChangelogUiState.Error(app.getString(R.string.changelog_download_fail))
|
||||
releaseInfo = api.getLatestAppInfo().getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@ import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.data.room.apps.installed.InstallType
|
||||
import app.revanced.manager.domain.installer.RootServiceException
|
||||
import app.revanced.manager.data.room.apps.installed.InstalledApp
|
||||
import app.revanced.manager.domain.installer.RootInstaller
|
||||
import app.revanced.manager.domain.installer.RootServiceException
|
||||
import app.revanced.manager.domain.repository.InstalledAppRepository
|
||||
import app.revanced.manager.util.PM
|
||||
import app.revanced.manager.util.collectEach
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -20,32 +23,42 @@ class InstalledAppsViewModel(
|
||||
private val pm: PM,
|
||||
private val rootInstaller: RootInstaller
|
||||
) : ViewModel() {
|
||||
val apps = installedAppsRepository.getAll().flowOn(Dispatchers.IO)
|
||||
val apps = installedAppsRepository.getAll()
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
val packageInfoMap = mutableStateMapOf<String, PackageInfo?>()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
apps.collectEach { installedApp ->
|
||||
packageInfoMap[installedApp.currentPackageName] = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (
|
||||
installedApp.installType == InstallType.MOUNT && !rootInstaller.isAppInstalled(installedApp.currentPackageName)
|
||||
) {
|
||||
installedAppsRepository.delete(installedApp)
|
||||
return@withContext null
|
||||
}
|
||||
} catch (_: RootServiceException) { }
|
||||
apps.filterNotNull().collectLatest(::fetchPackageInfos)
|
||||
}
|
||||
}
|
||||
|
||||
val packageInfo = pm.getPackageInfo(installedApp.currentPackageName)
|
||||
|
||||
if (packageInfo == null && installedApp.installType != InstallType.MOUNT) {
|
||||
installedAppsRepository.delete(installedApp)
|
||||
private suspend fun fetchPackageInfos(apps: List<InstalledApp>) {
|
||||
for (app in apps) {
|
||||
packageInfoMap[app.currentPackageName] = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (app.installType == InstallType.MOUNT &&
|
||||
!rootInstaller.isAppInstalled(app.currentPackageName)
|
||||
) {
|
||||
installedAppsRepository.delete(app)
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
packageInfo
|
||||
} catch (_: RootServiceException) {
|
||||
}
|
||||
|
||||
val packageInfo = pm.getPackageInfo(app.currentPackageName)
|
||||
|
||||
if (packageInfo == null && app.installType != InstallType.MOUNT) {
|
||||
installedAppsRepository.delete(app)
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
packageInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,24 +113,24 @@ Second \"item\" text"</string>
|
||||
<string name="safeguards">Safeguards</string>
|
||||
<string name="patch_compat_check">Disable version compatibility check</string>
|
||||
<string name="patch_compat_check_description">Do not restrict patches to compatible app versions</string>
|
||||
<string name="patch_compat_check_confirmation">"Selecting incompatible patches can result in a broken app.
|
||||
<string name="patch_compat_check_confirmation">Selecting incompatible patches can result in a broken app.
|
||||
|
||||
Do you want to proceed anyways?"</string>
|
||||
Do you want to proceed anyways?</string>
|
||||
<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.
|
||||
<string name="suggested_version_safeguard_confirmation">Selecting an app that is not the suggested version may cause unexpected issues.
|
||||
|
||||
Do you want to proceed anyways?"</string>
|
||||
Do you want to proceed anyways?</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.
|
||||
<string name="patch_selection_safeguard_confirmation">Changing the selection of patches may cause unexpected issues.
|
||||
|
||||
Enable anyways?"</string>
|
||||
Enable 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>
|
||||
<string name="universal_patches_safeguard_confirmation">"Universal patches are not as well tested as those that target specific apps.
|
||||
<string name="universal_patches_safeguard_confirmation">Universal patches are not as well tested as those that target specific apps.
|
||||
|
||||
Enable anyways?"</string>
|
||||
Enable anyways?</string>
|
||||
<string name="import_keystore">Import keystore</string>
|
||||
<string name="import_keystore_description">Import a custom keystore</string>
|
||||
<string name="import_keystore_dialog_title">Enter keystore credentials</string>
|
||||
@@ -146,9 +146,9 @@ Enable anyways?"</string>
|
||||
<string name="export_keystore_success">Exported keystore</string>
|
||||
<string name="regenerate_keystore">Regenerate keystore</string>
|
||||
<string name="regenerate_keystore_description">Generate a new keystore</string>
|
||||
<string name="regenerate_keystore_dialog_description">"You are about to regenerate your keystore the manager will use during the patching process.
|
||||
<string name="regenerate_keystore_dialog_description">You are about to regenerate your keystore the manager will use during the patching process.
|
||||
|
||||
You will not be able to update the previously installed apps from this source."</string>
|
||||
You will not be able to update the previously installed apps from this source.</string>
|
||||
<string name="regenerate_keystore_success">The keystore has been successfully replaced</string>
|
||||
<string name="import_patch_selection">Import patch selection</string>
|
||||
<string name="import_patch_selection_description">Import patch selection from a JSON file</string>
|
||||
@@ -273,23 +273,23 @@ You will not be able to update the previously installed apps from this source."<
|
||||
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
|
||||
<string name="patch_options_reset_toast">Patch options have been reset</string>
|
||||
<string name="non_suggested_version_warning_title">Non suggested version</string>
|
||||
<string name="non_suggested_version_warning_description">"The version of the app you have selected does not match the suggested version.
|
||||
<string name="non_suggested_version_warning_description">The version of the app you have selected does not match the suggested version.
|
||||
Please use the suggested version: %s
|
||||
|
||||
To continue anyway, disable \"Require suggested app version\" in the advanced settings."</string>
|
||||
To continue anyway, disable \"Require suggested app version\" in the advanced settings.</string>
|
||||
<string name="selection_warning_title">Stop using defaults?</string>
|
||||
<string name="selection_warning_description">"It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.
|
||||
<string name="selection_warning_description">It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.
|
||||
|
||||
You need to turn on \"Allow changing patch selection and options\" in the advanced settings before toggling patches."</string>
|
||||
<string name="universal_patch_warning_description">"Universal patches have a more generalized use and do not work as reliably as patches that target specific apps. You may encounter issues while using them.
|
||||
You need to turn on \"Allow changing patch selection and options\" in the advanced settings before toggling patches.</string>
|
||||
<string name="universal_patch_warning_description">Universal patches have a more generalized use and do not work as reliably as patches that target specific apps. You may encounter issues while using them.
|
||||
|
||||
You need to turn on \"Allow using universal patches\" in the advanced settings before using universal patches."</string>
|
||||
You need to turn on \"Allow using universal patches\" in the advanced settings before using universal patches.</string>
|
||||
<string name="this_version">This version</string>
|
||||
<string name="universal">Any app</string>
|
||||
<string name="search_patches">Search patches</string>
|
||||
<string name="app_version_not_compatible">"This patch is not compatible with the selected app version (%1$s)
|
||||
<string name="app_version_not_compatible">This patch is not compatible with the selected app version (%1$s)
|
||||
|
||||
It is only compatible with the following version(s): %2$s"</string>
|
||||
It is only compatible with the following version(s): %2$s</string>
|
||||
<string name="continue_with_version">Continue with this version?</string>
|
||||
<string name="version_not_compatible">Not all patches are compatible with this version (%s). Do you want to continue anyway?</string>
|
||||
<string name="download_application">Download application?</string>
|
||||
@@ -434,8 +434,8 @@ It is only compatible with the following version(s): %2$s"</string>
|
||||
<string name="save_with_count">Save (%1$s)</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="empty">Empty</string>
|
||||
<string name="installing_message">"Tap on <b>Update</b> when prompted.
|
||||
ReVanced Manager will close when updating."</string>
|
||||
<string name="installing_message">Tap on <b>Update</b> when prompted.
|
||||
ReVanced Manager will close when updating.</string>
|
||||
<string name="no_changelogs_found">No changelogs found</string>
|
||||
<string name="just_now">Just now</string>
|
||||
<string name="minutes_ago">%sm ago</string>
|
||||
@@ -455,9 +455,9 @@ ReVanced Manager will close when updating."</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.
|
||||
<string name="download_confirmation_metered">You are currently on a metered connection, and data charges from your service provider may apply.
|
||||
|
||||
Do you still want to continue?"</string>
|
||||
Do you still want to continue?</string>
|
||||
<string name="download_update_confirmation">Download update?</string>
|
||||
<string name="no_contributors_found">No contributors found</string>
|
||||
<string name="select">Select</string>
|
||||
@@ -495,9 +495,9 @@ Do you still want to continue?"</string>
|
||||
<string name="patches_prereleases">Use pre-releases</string>
|
||||
<string name="patches_prereleases_description">Use pre-release versions of %s</string>
|
||||
<string name="patches_url">Patches URL</string>
|
||||
<string name="incompatible_patches_dialog">"These patches are not compatible with the selected app version (%1$s).
|
||||
<string name="incompatible_patches_dialog">These patches are not compatible with the selected app version (%1$s).
|
||||
|
||||
Click on the patches to see more details."</string>
|
||||
Click on the patches to see more details.</string>
|
||||
<string name="incompatible_patch">Incompatible patch</string>
|
||||
<string name="any_version">Any</string>
|
||||
<string name="never_show_again">Never show again</string>
|
||||
|
||||
@@ -23,7 +23,7 @@ koin = "4.1.1"
|
||||
ktor = "3.3.3"
|
||||
markdown-renderer = "0.39.0"
|
||||
fading-edges = "1.0.4"
|
||||
kotlin = "2.3.0"
|
||||
kotlin = "2.3.10"
|
||||
android-gradle-plugin = "8.13.2"
|
||||
dev-tools-gradle-plugin = "2.3.4"
|
||||
about-libraries = "13.2.1"
|
||||
|
||||
Reference in New Issue
Block a user