Compare commits

..

9 Commits

Author SHA1 Message Date
semantic-release-bot
e03f3666c8 chore: Release v1.26.0-dev.26 [skip ci]
# 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](323d2170ef))
2026-02-27 23:09:46 +00:00
rushii
323d2170ef feat: improve InstalledAppsScreen caching/perf (#2963)
Co-authored-by: planshim <100317079+planshim@users.noreply.github.com>
2026-02-28 02:00:38 +03:00
semantic-release-bot
940669b3e4 chore: Release v1.26.0-dev.25 [skip ci]
# 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](81e5a9315d))
2026-02-27 22:08:17 +00:00
rushii
81e5a9315d feat: improve AppSelectorScreen caching/perf (#2961)
Co-authored-by: planshim <100317079+planshim@users.noreply.github.com>
2026-02-28 00:59:47 +03:00
semantic-release-bot
b38d0b7022 chore: Release v1.26.0-dev.24 [skip ci]
# 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](f19080a986))
* Never show again toggle doing nothing for Update dialog ([6f4219c](6f4219c01b))
* Release builds not working ([00b3c59](00b3c59572))
* Remove quotes from strings ([fc30ba5](fc30ba5e83))
* we actually don't want to exclude reflection symbols ([3222840](32228404c4))

### Features

* Add announcements ([#2948](https://github.com/ReVanced/revanced-manager/issues/2948)) ([813df46](813df46847))
* Update to Patcher v22 ([#2939](https://github.com/ReVanced/revanced-manager/issues/2939)) ([8667051](8667051283))
2026-02-27 15:59:57 +00:00
semantic-release-bot
b838851ebe chore: Release API v1.0.0-dev.2 [skip ci]
# 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](8667051283))
2026-02-27 15:59:00 +00:00
Ax333l
00b3c59572 fix: Release builds not working 2026-02-27 16:50:49 +01:00
Ushie
fc30ba5e83 fix: Remove quotes from strings 2026-02-27 08:52:59 +03:00
Plance Shimney
32228404c4 fix: we actually don't want to exclude reflection symbols 2026-02-25 20:36:58 +03:00
17 changed files with 241 additions and 260 deletions

View File

@@ -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)

View File

@@ -1 +1 @@
version = 1.0.0-dev.1
version = 1.0.0-dev.2

View File

@@ -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)

View File

@@ -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

View File

@@ -1 +1 @@
version = 1.26.0-dev.23
version = 1.26.0-dev.26

View File

@@ -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

View File

@@ -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")

View File

@@ -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()
}

View File

@@ -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()
}
}
}
}

View File

@@ -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
)
}
}
}
}
}

View File

@@ -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)
)

View File

@@ -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()
}
}
}

View File

@@ -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() {

View File

@@ -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()
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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>

View File

@@ -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"