feat: Improve changelogs (#2968)

This commit is contained in:
Ushie
2026-03-20 22:33:52 +03:00
committed by GitHub
parent 89e6e9453b
commit fba748d84f
13 changed files with 424 additions and 150 deletions

View File

@@ -7,7 +7,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.EaseInOutQuad
import androidx.compose.animation.core.EaseInOutQuart
import androidx.compose.animation.core.EaseOut
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
@@ -28,6 +27,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import app.revanced.manager.domain.repository.ChangelogSource
import app.revanced.manager.ui.model.navigation.Announcement
import app.revanced.manager.ui.model.navigation.Announcements
import app.revanced.manager.ui.model.navigation.AppSelector
@@ -129,10 +129,28 @@ private fun ReVancedManager(vm: MainViewModel) {
NavHost(
navController = navController,
startDestination = startDestination,
enterTransition = { slideInHorizontally(animationSpec = tween(300, easing = EaseInOutQuad), initialOffsetX = { it }) },
exitTransition = { slideOutHorizontally(animationSpec = tween(300, easing = EaseOut), targetOffsetX = { -it / 3 }) },
popEnterTransition = { slideInHorizontally(animationSpec = tween(300, easing = EaseInOutQuad), initialOffsetX = { -it / 3 }) },
popExitTransition = { slideOutHorizontally(animationSpec = tween(300, easing = EaseOut), targetOffsetX = { it }) }
enterTransition = {
slideInHorizontally(
animationSpec = tween(300, easing = EaseInOutQuad),
initialOffsetX = { it })
},
exitTransition = {
slideOutHorizontally(
animationSpec = tween(300, easing = EaseOut),
targetOffsetX = { -it / 3 })
},
popEnterTransition = {
slideInHorizontally(
animationSpec = tween(
300,
easing = EaseInOutQuad
), initialOffsetX = { -it / 3 })
},
popExitTransition = {
slideOutHorizontally(
animationSpec = tween(300, easing = EaseOut),
targetOffsetX = { it })
}
) {
composable<Onboarding> {
OnboardingScreen(
@@ -178,6 +196,11 @@ private fun ReVancedManager(vm: MainViewModel) {
BundleInformationScreen(
onBackClick = navController::popBackStackSafe,
onChangelogClick = { source ->
navController.navigateComplex(
Settings.Changelogs, source
)
},
viewModel = koinViewModel { parametersOf(data.uid) }
)
}
@@ -349,7 +372,7 @@ private fun ReVancedManager(vm: MainViewModel) {
deepLinkedComposable<Settings.Updates>("settings/updates") {
UpdatesSettingsScreen(
onBackClick = navController::popBackStackSafe,
onChangelogClick = { navController.navigateSafe(Settings.Changelogs) },
onChangelogClick = { navController.navigateComplex(Settings.Changelogs, ChangelogSource.Manager) },
onUpdateClick = { navController.navigateSafe(Update()) }
)
}
@@ -383,7 +406,8 @@ private fun ReVancedManager(vm: MainViewModel) {
}
composable<Settings.Changelogs> {
ChangelogsSettingsScreen(onBackClick = navController::popBackStackSafe)
val source = it.getComplexArg<ChangelogSource>()
ChangelogsSettingsScreen(source = source, onBackClick = navController::popBackStackSafe)
}
composable<Settings.Contributors> {

View File

@@ -27,4 +27,5 @@ val repositoryModule = module {
singleOf(::WorkerRepository)
singleOf(::DownloadedAppRepository)
singleOf(::InstalledAppRepository)
singleOf(::ChangelogsRepository)
}

View File

@@ -0,0 +1,68 @@
package app.revanced.manager.domain.repository
import android.os.Parcelable
import androidx.core.net.toUri
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedAssetHistory
import app.revanced.manager.network.utils.getOrThrow
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
sealed interface ChangelogSource : Parcelable {
data object Manager : ChangelogSource {
}
data class Patches(val url: String) : ChangelogSource {
val baseUrl get() = url.toUri().let { "${it.scheme}://${it.host}" }
}
}
class ChangelogsRepository(
private val api: ReVancedAPI
) {
private var all: List<ReVancedAssetHistory> = emptyList()
private var page = 0
suspend fun loadInitial(
source: ChangelogSource,
pageSize: Int
): PageResult<ReVancedAssetHistory> {
all = when (source) {
is ChangelogSource.Manager ->
api.getAppHistory().getOrThrow()
is ChangelogSource.Patches ->
api.getPatchesHistory(source.baseUrl).getOrThrow()
}
page = 1
val items = all.take(pageSize)
return PageResult(
items = items,
hasMore = hasMore(pageSize)
)
}
fun loadNext(pageSize: Int): PageResult<ReVancedAssetHistory> {
val items = all
.drop(page * pageSize)
.take(pageSize)
page++
return PageResult(
items = items,
hasMore = hasMore(pageSize)
)
}
private fun hasMore(pageSize: Int) =
page * pageSize < all.size
}
data class PageResult<T>(
val items: List<T>,
val hasMore: Boolean
)

View File

@@ -6,6 +6,7 @@ import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.manager.base.Preference
import app.revanced.manager.network.dto.ReVancedAnnouncement
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.network.dto.ReVancedAssetHistory
import app.revanced.manager.network.dto.ReVancedGitRepository
import app.revanced.manager.network.dto.ReVancedInfo
import app.revanced.manager.network.service.HttpService
@@ -20,10 +21,11 @@ class ReVancedAPI(
private val prefs: PreferencesManager
) {
private suspend fun apiUrl() = prefs.api.get()
private val defaultApiVersion = "v5"
private suspend inline fun <reified T> request(api: String, route: String): APIResponse<T> =
private suspend inline fun <reified T> request(api: String, apiVersion: String, route: String): APIResponse<T> =
withContext(Dispatchers.IO) {
val fullUrl = "$api/v5/$route"
val fullUrl = "$api/$apiVersion/$route"
try {
Log.d("API", "Requesting: $fullUrl")
@@ -37,7 +39,7 @@ class ReVancedAPI(
}
}
private suspend inline fun <reified T> request(route: String) = request<T>(apiUrl(), route)
private suspend inline fun <reified T> request(route: String, apiVersion: String = defaultApiVersion) = request<T>(apiUrl(), apiVersion, route)
suspend fun getAnnouncements() = request<List<ReVancedAnnouncement>>("announcements")
@@ -47,8 +49,13 @@ class ReVancedAPI(
suspend fun getLatestAppInfo() =
request<ReVancedAsset>("manager${prefs.useManagerPrereleases.prereleaseString()}")
suspend fun getAppHistory() = request<List<ReVancedAssetHistory>>("manager/history")
suspend fun getPatchesUpdate() = request<ReVancedAsset>("patches${prefs.usePatchesPrereleases.prereleaseString()}")
suspend fun getPatchesHistory(apiUrl: String) =
request<List<ReVancedAssetHistory>>(apiUrl, defaultApiVersion, "patches/history")
suspend fun getDownloaderUpdate() = request<ReVancedAsset>("manager/downloaders${prefs.useDownloaderPrerelease.prereleaseString()}")
suspend fun getContributors() = request<List<ReVancedGitRepository>>("contributors")

View File

@@ -16,3 +16,10 @@ data class ReVancedAsset (
val version: String,
)
@Serializable
data class ReVancedAssetHistory(
val version: String,
@SerialName("created_at")
val createdAt: LocalDateTime,
val description: String,
)

View File

@@ -0,0 +1,205 @@
package app.revanced.manager.ui.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Campaign
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.network.dto.ReVancedAssetHistory
import app.revanced.manager.util.relativeTime
sealed interface ChangelogUiState {
data object Loading : ChangelogUiState
data class Error(val error: String) : ChangelogUiState
data class Success(
val changelogs: List<ReVancedAssetHistory>,
val hasMore: Boolean = false,
val isLoadingMore: Boolean = false,
) : ChangelogUiState
}
@Composable
fun ChangelogList(
state: ChangelogUiState,
onLoadMore: () -> Unit,
modifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
val shouldLoadMore by remember {
derivedStateOf {
val lastVisible = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
val total = listState.layoutInfo.totalItemsCount
val canScroll = listState.canScrollForward || listState.canScrollBackward
(lastVisible >= total - 2 || !canScroll) && total > 0
}
}
LaunchedEffect(shouldLoadMore, state) {
if (shouldLoadMore) onLoadMore()
}
Box(
modifier = modifier.then(Modifier.fillMaxSize()),
contentAlignment = Alignment.Center
) {
when (state) {
is ChangelogUiState.Loading -> LoadingIndicator()
is ChangelogUiState.Error -> Text(
text = state.error,
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(),
state = listState
) {
items(
items = state.changelogs,
key = { it.version }
) { changelog ->
ChangelogItem(
changelog = changelog,
showDivider = changelog != state.changelogs.last()
)
}
if (state.isLoadingMore) {
item(key = "loading_more") {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
}
}
}
}
}
}
@Composable
private fun ChangelogItem(
changelog: ReVancedAssetHistory,
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
)
}
}
}
@Composable
fun Changelog(
description: String,
version: String,
publishDate: String
) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Outlined.Campaign,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(32.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
version.removePrefix("v"),
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight(800)
),
color = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier)
Text(
"",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline,
)
Text(
publishDate,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline,
)
}
}
Markdown(
description.removeVersionHeaderIfMatches(version),
)
}
}
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

@@ -1,73 +0,0 @@
package app.revanced.manager.ui.component.settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.NewReleases
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
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.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import app.revanced.manager.ui.component.Markdown
@Composable
fun Changelog(
markdown: String,
version: String,
publishDate: String
) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 0.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Outlined.NewReleases,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.size(32.dp)
)
Text(
"${version.removePrefix("v")} ($publishDate)",
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)),
color = MaterialTheme.colorScheme.primary,
)
}
}
Markdown(
markdown,
)
}
@Composable
private fun Tag(icon: ImageVector, text: String) {
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.outline,
)
Text(
text,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline,
)
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.manager.ui.model.navigation
import android.os.Parcelable
import app.revanced.manager.domain.repository.ChangelogSource
import app.revanced.manager.network.dto.ReVancedAnnouncement
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.util.Options
@@ -101,7 +102,7 @@ object Settings {
data object About : Destination
@Serializable
data object Changelogs : Destination
data object Changelogs : ComplexParameter<ChangelogSource>
@Serializable
data object Contributors : Destination

View File

@@ -55,6 +55,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.domain.repository.ChangelogSource
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
import app.revanced.manager.domain.sources.LocalSource
import app.revanced.manager.domain.sources.Source
@@ -73,6 +74,7 @@ import app.revanced.manager.ui.viewmodel.BundleInformationViewModel
@Composable
fun BundleInformationScreen(
onBackClick: () -> Unit,
onChangelogClick: (ChangelogSource.Patches) -> Unit,
viewModel: BundleInformationViewModel
) {
val srcState = viewModel.bundle.collectAsStateWithLifecycle(null)
@@ -309,6 +311,15 @@ fun BundleInformationScreen(
trailingContent = null
)
endpoint?.let {
SettingsListItem(
headlineContent = stringResource(R.string.changelog),
onClick = {
onChangelogClick(ChangelogSource.Patches(if (src.isDefault) viewModel.prefs.api.getBlocking() else endpoint))
},
)
}
src.error?.let {
var showDialog by rememberSaveable { mutableStateOf(false) }

View File

@@ -1,9 +1,7 @@
package app.revanced.manager.ui.screen
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,18 +30,14 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
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.BottomContentBar
import app.revanced.manager.ui.component.ColumnWithScrollbarEdgeShadow
import app.revanced.manager.ui.component.settings.Changelog
import app.revanced.manager.ui.component.ChangelogList
import app.revanced.manager.ui.viewmodel.UpdateViewModel
import app.revanced.manager.ui.viewmodel.UpdateViewModel.State
import app.revanced.manager.util.relativeTime
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -61,12 +55,14 @@ fun UpdateScreen(
R.string.download,
Icons.Outlined.InstallMobile
)
State.DOWNLOADING -> Triple(onBackClick, R.string.cancel, Icons.Outlined.Cancel)
State.CAN_INSTALL -> Triple(
{ vm.installUpdate() },
R.string.install_app,
Icons.Outlined.InstallMobile
)
else -> null
}
@@ -112,7 +108,7 @@ fun UpdateScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
Column(
modifier = Modifier.padding(paddingValues),
modifier = Modifier.padding(paddingValues).fillMaxSize(),
) {
if (vm.state == State.DOWNLOADING)
LinearWavyProgressIndicator(
@@ -127,9 +123,10 @@ fun UpdateScreen(
)
}
vm.releaseInfo?.let { changelog ->
Changelog(changelog)
}
ChangelogList(
state = vm.changelogsState,
onLoadMore = vm::loadNextPage,
)
}
}
}
@@ -162,20 +159,4 @@ private fun MeteredDownloadConfirmationDialog(
icon = { Icon(Icons.Outlined.Update, null) },
text = { Text(stringResource(R.string.download_confirmation_metered)) }
)
}
@Composable
private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
ColumnWithScrollbarEdgeShadow(
modifier = Modifier
.weight(1f)
.fillMaxSize()
.padding(16.dp),
) {
Changelog(
markdown = releaseInfo.description.replace("`", ""),
version = releaseInfo.version,
publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current)
)
}
}

View File

@@ -1,36 +1,26 @@
package app.revanced.manager.ui.screen.settings.update
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.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
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.domain.repository.ChangelogSource
import app.revanced.manager.ui.component.AppTopBar
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.component.ChangelogList
import app.revanced.manager.ui.viewmodel.ChangelogsViewModel
import app.revanced.manager.util.relativeTime
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChangelogsSettingsScreen(
source: ChangelogSource,
onBackClick: () -> Unit,
vm: ChangelogsViewModel = koinViewModel()
vm: ChangelogsViewModel = koinViewModel { parametersOf(source) }
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
@@ -44,22 +34,6 @@ fun ChangelogsSettingsScreen(
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
ColumnWithScrollbar(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = if (vm.releaseInfo == null) Arrangement.Center else Arrangement.Top
) {
vm.releaseInfo?.let { info ->
Column(modifier = Modifier.padding(16.dp)) {
Changelog(
markdown = info.description.replace("`", ""),
version = info.version,
publishDate = info.createdAt.relativeTime(LocalContext.current)
)
}
} ?: LoadingIndicator()
}
ChangelogList(modifier = Modifier.padding(paddingValues), state = vm.state, onLoadMore =vm::loadNextPage)
}
}

View File

@@ -7,24 +7,54 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.domain.repository.ChangelogSource
import app.revanced.manager.domain.repository.ChangelogsRepository
import app.revanced.manager.ui.component.ChangelogUiState
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.launch
class ChangelogsViewModel(
private val api: ReVancedAPI,
private val repository: ChangelogsRepository,
private val app: Application,
private val source: ChangelogSource,
) : ViewModel() {
var releaseInfo: ReVancedAsset? by mutableStateOf(null)
var state: ChangelogUiState by mutableStateOf(ChangelogUiState.Loading)
private set
private val pageSize = 2
init {
viewModelScope.launch {
uiSafe(app, R.string.changelog_download_fail, "Failed to download changelog") {
releaseInfo = api.getLatestAppInfo().getOrThrow()
val result = repository.loadInitial(source, pageSize)
state = ChangelogUiState.Success(
changelogs = result.items,
hasMore = result.hasMore
)
}
if (state is ChangelogUiState.Loading) {
state = ChangelogUiState.Error(
app.getString(R.string.changelog_download_fail)
)
}
}
}
fun loadNextPage() {
val current = state as? ChangelogUiState.Success ?: return
if (current.isLoadingMore || !current.hasMore) return
state = current.copy(isLoadingMore = true)
val result = repository.loadNext(pageSize)
state = current.copy(
changelogs = current.changelogs + result.items,
isLoadingMore = false,
hasMore = result.hasMore
)
}
}

View File

@@ -13,9 +13,12 @@ import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.repository.ChangelogSource
import app.revanced.manager.domain.repository.ChangelogsRepository
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.ui.component.ChangelogUiState
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import io.ktor.client.plugins.onDownload
@@ -34,6 +37,7 @@ import ru.solrudev.ackpine.session.await
import ru.solrudev.ackpine.session.parameters.Confirmation
class UpdateViewModel(
private val changelogsRepository: ChangelogsRepository,
private val downloadOnScreenEntry: Boolean
) : ViewModel(), KoinComponent {
private val app: Application by inject()
@@ -62,12 +66,26 @@ class UpdateViewModel(
var releaseInfo: ReVancedAsset? by mutableStateOf(null)
private set
var changelogsState: ChangelogUiState by mutableStateOf(ChangelogUiState.Loading)
private val changelogsPageSize = 2
private val location = fs.tempDir.resolve("updater.apk")
init {
viewModelScope.launch {
uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") {
releaseInfo = reVancedAPI.getAppUpdate() ?: throw Exception("No update available")
releaseInfo = reVancedAPI.getAppUpdate()
?: throw Exception("No update available")
val result = changelogsRepository.loadInitial(
ChangelogSource.Manager,
changelogsPageSize
)
changelogsState = ChangelogUiState.Success(
changelogs = result.items,
hasMore = result.hasMore
)
if (downloadOnScreenEntry) {
downloadUpdate()
@@ -75,8 +93,28 @@ class UpdateViewModel(
state = State.CAN_DOWNLOAD
}
}
if (changelogsState is ChangelogUiState.Loading) {
changelogsState = ChangelogUiState.Error(
app.getString(R.string.changelog_download_fail)
)
}
}
}
fun loadNextPage() {
val current = changelogsState as? ChangelogUiState.Success ?: return
if (current.isLoadingMore || !current.hasMore) return
changelogsState = current.copy(isLoadingMore = true)
val result = changelogsRepository.loadNext(changelogsPageSize)
changelogsState = current.copy(
changelogs = current.changelogs + result.items,
isLoadingMore = false,
hasMore = result.hasMore
)
}
fun downloadUpdate(ignoreInternetCheck: Boolean = false) = viewModelScope.launch {
uiSafe(app, R.string.failed_to_download_update, "Failed to download update") {