From 0685479d5362f898e694524b48a0eae63f048d75 Mon Sep 17 00:00:00 2001 From: somni <82272900+somnisomni@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:57:23 +0900 Subject: [PATCH 1/3] feat: Make patch bundles list scrollable (#2322) --- .../manager/ui/screen/BundleListScreen.kt | 54 +++++++++++++++++++ .../manager/ui/screen/DashboardScreen.kt | 39 ++++---------- 2 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/app/revanced/manager/ui/screen/BundleListScreen.kt diff --git a/app/src/main/java/app/revanced/manager/ui/screen/BundleListScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/BundleListScreen.kt new file mode 100644 index 00000000..c2758e71 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/screen/BundleListScreen.kt @@ -0,0 +1,54 @@ +package app.revanced.manager.ui.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import app.revanced.manager.domain.bundles.PatchBundleSource +import app.revanced.manager.ui.component.LazyColumnWithScrollbar +import app.revanced.manager.ui.component.bundle.BundleItem + +@Composable +fun BundleListScreen( + onDelete: (PatchBundleSource) -> Unit, + onUpdate: (PatchBundleSource) -> Unit, + sources: List, + selectedSources: SnapshotStateList, + bundlesSelectable: Boolean, +) { + LazyColumnWithScrollbar( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top, + ) { + items( + sources, + key = { it.uid } + ) { source -> + BundleItem( + bundle = source, + onDelete = { + onDelete(source) + }, + onUpdate = { + onUpdate(source) + }, + selectable = bundlesSelectable, + onSelect = { + selectedSources.add(source) + }, + isBundleSelected = selectedSources.contains(source), + toggleSelection = { bundleIsNotSelected -> + if (bundleIsNotSelected) { + selectedSources.add(source) + } else { + selectedSources.remove(source) + } + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt index 51caeb25..4dc74766 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt @@ -31,7 +31,6 @@ import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AutoUpdatesDialog import app.revanced.manager.ui.component.AvailableUpdateDialog import app.revanced.manager.ui.component.NotificationCard -import app.revanced.manager.ui.component.bundle.BundleItem import app.revanced.manager.ui.component.bundle.BundleTopBar import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton import app.revanced.manager.ui.component.haptics.HapticTab @@ -264,33 +263,17 @@ fun DashboardScreen( val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList()) - Column( - modifier = Modifier.fillMaxSize(), - ) { - sources.forEach { - BundleItem( - bundle = it, - onDelete = { - vm.delete(it) - }, - onUpdate = { - vm.update(it) - }, - selectable = bundlesSelectable, - onSelect = { - vm.selectedSources.add(it) - }, - isBundleSelected = vm.selectedSources.contains(it), - toggleSelection = { bundleIsNotSelected -> - if (bundleIsNotSelected) { - vm.selectedSources.add(it) - } else { - vm.selectedSources.remove(it) - } - } - ) - } - } + BundleListScreen( + onDelete = { + vm.delete(it) + }, + onUpdate = { + vm.update(it) + }, + sources = sources, + selectedSources = vm.selectedSources, + bundlesSelectable = bundlesSelectable + ) } } } From 31fb8b14049a5b7c31849cadd48e3b828cb1eb97 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sun, 1 Dec 2024 01:13:03 +0700 Subject: [PATCH 2/3] chore: Nitpick on misspelling of comment --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 79364a6e..f66506b8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { maven("https://jitpack.io") mavenLocal() maven { - // A repository must be speficied for some reason. "registry" is a dummy. + // A repository must be specified for some reason. "registry" is a dummy. url = uri("https://maven.pkg.github.com/revanced/registry") credentials { username = System.getenv("GITHUB_ACTOR") ?: extra["gpr.user"] as String? From 9dc716b1c80dd9105ca9e8ee9de9dd6e60d52aae Mon Sep 17 00:00:00 2001 From: Ax333l Date: Thu, 12 Dec 2024 17:52:21 +0100 Subject: [PATCH 3/3] feat: switch to revanced api v4 --- app/build.gradle.kts | 1 + .../app/revanced/manager/di/ServiceModule.kt | 2 - .../domain/bundles/RemotePatchBundle.kt | 20 ++--- .../manager/network/api/ReVancedAPI.kt | 44 +++++----- .../manager/network/dto/GithubChangelog.kt | 16 ---- .../manager/network/dto/PatchBundleInfo.kt | 7 -- .../manager/network/dto/ReVancedAsset.kt | 18 ++++ .../network/dto/ReVancedContributors.kt | 10 +-- .../manager/network/dto/ReVancedInfo.kt | 9 +- .../manager/network/dto/ReVancedRelease.kt | 41 --------- .../network/service/ReVancedService.kt | 43 ---------- .../ui/component/settings/Changelog.kt | 5 -- .../settings/update/ChangelogsScreen.kt | 59 +++---------- .../ui/screen/settings/update/UpdateScreen.kt | 24 +++--- .../ui/viewmodel/ChangelogsViewModel.kt | 24 ++---- .../manager/ui/viewmodel/UpdateViewModel.kt | 37 ++------- .../java/app/revanced/manager/util/Util.kt | 83 ++++++++----------- gradle/libs.versions.toml | 2 + 18 files changed, 125 insertions(+), 320 deletions(-) delete mode 100644 app/src/main/java/app/revanced/manager/network/dto/GithubChangelog.kt delete mode 100644 app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt create mode 100644 app/src/main/java/app/revanced/manager/network/dto/ReVancedAsset.kt delete mode 100644 app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt delete mode 100644 app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9f04a1b9..79b726bb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -143,6 +143,7 @@ dependencies { // KotlinX implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.collection.immutable) + implementation(libs.kotlinx.datetime) // Room implementation(libs.room.runtime) diff --git a/app/src/main/java/app/revanced/manager/di/ServiceModule.kt b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt index c30a711f..cfda5030 100644 --- a/app/src/main/java/app/revanced/manager/di/ServiceModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt @@ -1,11 +1,9 @@ package app.revanced.manager.di import app.revanced.manager.network.service.HttpService -import app.revanced.manager.network.service.ReVancedService import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val serviceModule = module { - singleOf(::ReVancedService) singleOf(::HttpService) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt index e3214db9..9deb7bbe 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt @@ -2,7 +2,7 @@ package app.revanced.manager.domain.bundles import androidx.compose.runtime.Stable import app.revanced.manager.network.api.ReVancedAPI -import app.revanced.manager.network.dto.PatchBundleInfo +import app.revanced.manager.network.dto.ReVancedAsset import app.revanced.manager.network.service.HttpService import app.revanced.manager.network.utils.getOrThrow import io.ktor.client.request.url @@ -16,17 +16,16 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo PatchBundleSource(name, id, directory) { protected val http: HttpService by inject() - protected abstract suspend fun getLatestInfo(): PatchBundleInfo + protected abstract suspend fun getLatestInfo(): ReVancedAsset - private suspend fun download(info: PatchBundleInfo) = withContext(Dispatchers.IO) { - val (version, url) = info + private suspend fun download(info: ReVancedAsset) = withContext(Dispatchers.IO) { patchBundleOutputStream().use { http.streamTo(it) { - url(url) + url(info.downloadUrl) } } - saveVersion(version) + saveVersion(info.version) reload() } @@ -58,7 +57,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo class JsonPatchBundle(name: String, id: Int, directory: File, endpoint: String) : RemotePatchBundle(name, id, directory, endpoint) { override suspend fun getLatestInfo() = withContext(Dispatchers.IO) { - http.request { + http.request { url(endpoint) }.getOrThrow() } @@ -68,10 +67,5 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) : RemotePatchBundle(name, id, directory, endpoint) { private val api: ReVancedAPI by inject() - override suspend fun getLatestInfo() = api - .getLatestRelease("revanced-patches") - .getOrThrow() - .let { - PatchBundleInfo(it.version, it.assets.first { it.name.endsWith(".rvp") }.downloadUrl) - } + override suspend fun getLatestInfo() = api.getPatchesUpdate().getOrThrow() } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt b/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt index f52a8190..bb365580 100644 --- a/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt +++ b/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt @@ -2,37 +2,41 @@ package app.revanced.manager.network.api import android.os.Build import app.revanced.manager.domain.manager.PreferencesManager -import app.revanced.manager.network.dto.ReVancedRelease -import app.revanced.manager.network.service.ReVancedService +import app.revanced.manager.network.dto.ReVancedAsset +import app.revanced.manager.network.dto.ReVancedGitRepository +import app.revanced.manager.network.dto.ReVancedInfo +import app.revanced.manager.network.service.HttpService +import app.revanced.manager.network.utils.APIResponse import app.revanced.manager.network.utils.getOrThrow -import app.revanced.manager.network.utils.transform +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import io.ktor.client.request.url class ReVancedAPI( - private val service: ReVancedService, + private val client: HttpService, private val prefs: PreferencesManager ) { private suspend fun apiUrl() = prefs.api.get() - suspend fun getContributors() = service.getContributors(apiUrl()).transform { it.repositories } + private suspend inline fun request(api: String, route: String): APIResponse = + withContext( + Dispatchers.IO + ) { + client.request { + url("$api/v4/$route") + } + } - suspend fun getLatestRelease(name: String) = - service.getLatestRelease(apiUrl(), name).transform { it.release } - - suspend fun getReleases(name: String) = - service.getReleases(apiUrl(), name).transform { it.releases } + private suspend inline fun request(route: String) = request(apiUrl(), route) suspend fun getAppUpdate() = - getLatestRelease("revanced-manager") - .getOrThrow() - .takeIf { it.version != Build.VERSION.RELEASE } + getLatestAppInfo().getOrThrow().takeIf { it.version != Build.VERSION.RELEASE } - suspend fun getInfo(api: String? = null) = service.getInfo(api ?: apiUrl()).transform { it.info } + suspend fun getLatestAppInfo() = request("manager") + suspend fun getPatchesUpdate() = request("patches") - companion object Extensions { - fun ReVancedRelease.findAssetByType(mime: String) = - assets.singleOrNull { it.contentType == mime } ?: throw MissingAssetException(mime) - } -} + suspend fun getContributors() = request>("contributors") -class MissingAssetException(type: String) : Exception("No asset with type $type") \ No newline at end of file + suspend fun getInfo(api: String? = null) = request(api ?: apiUrl(), "about") +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/GithubChangelog.kt b/app/src/main/java/app/revanced/manager/network/dto/GithubChangelog.kt deleted file mode 100644 index 52789017..00000000 --- a/app/src/main/java/app/revanced/manager/network/dto/GithubChangelog.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.revanced.manager.network.dto - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GithubChangelog( - @SerialName("tag_name") val version: String, - @SerialName("body") val body: String, - @SerialName("assets") val assets: List -) - -@Serializable -data class GithubAsset( - @SerialName("download_count") val downloadCount: Int, -) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt b/app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt deleted file mode 100644 index 02d89919..00000000 --- a/app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.revanced.manager.network.dto - -import kotlinx.serialization.Serializable - -@Serializable -// TODO: replace this -data class PatchBundleInfo(val version: String, val url: String) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/ReVancedAsset.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedAsset.kt new file mode 100644 index 00000000..64c05f31 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedAsset.kt @@ -0,0 +1,18 @@ +package app.revanced.manager.network.dto + +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ReVancedAsset ( + @SerialName("download_url") + val downloadUrl: String, + @SerialName("created_at") + val createdAt: LocalDateTime, + @SerialName("signature_download_url") + val signatureDownloadUrl: String? = null, + val description: String, + val version: String, +) + diff --git a/app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt index 82117d96..6583ba7c 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt @@ -3,19 +3,15 @@ package app.revanced.manager.network.dto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@Serializable -data class ReVancedGitRepositories( - val repositories: List, -) - @Serializable data class ReVancedGitRepository( val name: String, + val url: String, val contributors: List, ) @Serializable data class ReVancedContributor( - @SerialName("login") val username: String, + @SerialName("name") val username: String, @SerialName("avatar_url") val avatarUrl: String, -) +) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/ReVancedInfo.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedInfo.kt index 8f7e8966..89ed7445 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/ReVancedInfo.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedInfo.kt @@ -1,12 +1,8 @@ package app.revanced.manager.network.dto +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@Serializable -data class ReVancedInfoParent( - val info: ReVancedInfo, -) - @Serializable data class ReVancedInfo( val name: String, @@ -43,7 +39,8 @@ data class ReVancedDonation( @Serializable data class ReVancedWallet( val network: String, - val currency_code: String, + @SerialName("currency_code") + val currencyCode: String, val address: String, val preferred: Boolean ) diff --git a/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt deleted file mode 100644 index 442e4107..00000000 --- a/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt +++ /dev/null @@ -1,41 +0,0 @@ -package app.revanced.manager.network.dto - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ReVancedLatestRelease( - val release: ReVancedRelease, -) - -@Serializable -data class ReVancedReleases( - val releases: List -) - -@Serializable -data class ReVancedRelease( - val metadata: ReVancedReleaseMeta, - val assets: List -) { - val version get() = metadata.tag -} - -@Serializable -data class ReVancedReleaseMeta( - @SerialName("tag_name") val tag: String, - val name: String, - val draft: Boolean, - val prerelease: Boolean, - @SerialName("created_at") val createdAt: String, - @SerialName("published_at") val publishedAt: String, - val body: String, -) - -@Serializable -data class Asset( - val name: String, - @SerialName("download_count") val downloadCount: Int, - @SerialName("browser_download_url") val downloadUrl: String, - @SerialName("content_type") val contentType: String -) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt b/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt deleted file mode 100644 index 537a3514..00000000 --- a/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt +++ /dev/null @@ -1,43 +0,0 @@ -package app.revanced.manager.network.service - -import app.revanced.manager.network.dto.ReVancedGitRepositories -import app.revanced.manager.network.dto.ReVancedInfo -import app.revanced.manager.network.dto.ReVancedInfoParent -import app.revanced.manager.network.dto.ReVancedLatestRelease -import app.revanced.manager.network.dto.ReVancedReleases -import app.revanced.manager.network.utils.APIResponse -import io.ktor.client.request.url -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class ReVancedService( - private val client: HttpService, -) { - suspend fun getLatestRelease(api: String, repo: String): APIResponse = - withContext(Dispatchers.IO) { - client.request { - url("$api/v2/$repo/releases/latest") - } - } - - suspend fun getReleases(api: String, repo: String): APIResponse = - withContext(Dispatchers.IO) { - client.request { - url("$api/v2/$repo/releases") - } - } - - suspend fun getContributors(api: String): APIResponse = - withContext(Dispatchers.IO) { - client.request { - url("$api/contributors") - } - } - - suspend fun getInfo(api: String): APIResponse = - withContext(Dispatchers.IO) { - client.request { - url("$api/v2/info") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt b/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt index 6e707ae2..af26e232 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt @@ -26,7 +26,6 @@ import app.revanced.manager.ui.component.Markdown fun Changelog( markdown: String, version: String, - downloadCount: String, publishDate: String ) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { @@ -55,10 +54,6 @@ fun Changelog( modifier = Modifier .fillMaxWidth() ) { - Tag( - Icons.Outlined.FileDownload, - downloadCount - ) Tag( Icons.Outlined.CalendarToday, publishDate diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ChangelogsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ChangelogsScreen.kt index 315bc1a1..eadd9990 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ChangelogsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ChangelogsScreen.kt @@ -5,12 +5,8 @@ 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.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -19,11 +15,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R 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.ChangelogsViewModel -import app.revanced.manager.util.formatNumber import app.revanced.manager.util.relativeTime import org.koin.androidx.compose.koinViewModel @@ -33,8 +28,6 @@ fun ChangelogsScreen( onBackClick: () -> Unit, vm: ChangelogsViewModel = koinViewModel() ) { - val changelogs = vm.changelogs - Scaffold( topBar = { AppTopBar( @@ -43,54 +36,22 @@ fun ChangelogsScreen( ) } ) { paddingValues -> - LazyColumnWithScrollbar( + ColumnWithScrollbar( modifier = Modifier .padding(paddingValues) .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = if (changelogs.isNullOrEmpty()) Arrangement.Center else Arrangement.Top + verticalArrangement = if (vm.releaseInfo == null) Arrangement.Center else Arrangement.Top ) { - if (changelogs == null) { - item { - LoadingIndicator() - } - } else if (changelogs.isEmpty()) { - item { - Text( - text = stringResource(id = R.string.no_changelogs_found), - style = MaterialTheme.typography.titleLarge + vm.releaseInfo?.let { info -> + Column(modifier = Modifier.padding(16.dp)) { + Changelog( + markdown = info.description.replace("`", ""), + version = info.version, + publishDate = info.createdAt.relativeTime(LocalContext.current) ) } - } else { - val lastChangelog = changelogs.last() - items( - changelogs, - key = { it.version } - ) { changelog -> - ChangelogItem(changelog, lastChangelog) - } - } - } - } -} - -@Composable -fun ChangelogItem( - changelog: ChangelogsViewModel.Changelog, - lastChangelog: ChangelogsViewModel.Changelog -) { - Column(modifier = Modifier.padding(16.dp)) { - Changelog( - markdown = changelog.body.replace("`", ""), - version = changelog.version, - downloadCount = changelog.downloadCount.formatNumber(), - publishDate = changelog.publishDate.relativeTime(LocalContext.current) - ) - if (changelog != lastChangelog) { - HorizontalDivider( - modifier = Modifier.padding(top = 32.dp), - color = MaterialTheme.colorScheme.outlineVariant - ) + } ?: LoadingIndicator() } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt index 4b41dbd9..693adc6a 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt @@ -33,12 +33,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.BuildConfig 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.settings.Changelog import app.revanced.manager.ui.viewmodel.UpdateViewModel -import app.revanced.manager.ui.viewmodel.UpdateViewModel.Changelog import app.revanced.manager.ui.viewmodel.UpdateViewModel.State -import app.revanced.manager.util.formatNumber import app.revanced.manager.util.relativeTime import com.gigamole.composefadingedges.content.FadingEdgesContentType import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfig @@ -77,10 +76,10 @@ fun UpdateScreen( ) { Header( vm.state, - vm.changelog, + vm.releaseInfo, DownloadData(vm.downloadProgress, vm.downloadedSize, vm.totalSize) ) - vm.changelog?.let { changelog -> + vm.releaseInfo?.let { changelog -> HorizontalDivider() Changelog(changelog) } ?: Spacer(modifier = Modifier.weight(1f)) @@ -118,7 +117,7 @@ private fun MeteredDownloadConfirmationDialog( } @Composable -private fun Header(state: State, changelog: Changelog?, downloadData: DownloadData) { +private fun Header(state: State, releaseInfo: ReVancedAsset?, downloadData: DownloadData) { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Text( text = stringResource(state.title), @@ -134,11 +133,11 @@ private fun Header(state: State, changelog: Changelog?, downloadData: DownloadDa style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) - changelog?.let { changelog -> + releaseInfo?.version?.let { Text( text = stringResource( - id = R.string.new_version, - changelog.version.replace("v", "") + R.string.new_version, + it.replace("v", "") ), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant @@ -170,7 +169,7 @@ private fun Header(state: State, changelog: Changelog?, downloadData: DownloadDa } @Composable -private fun ColumnScope.Changelog(changelog: Changelog) { +private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) { val scrollState = rememberScrollState() Column( modifier = Modifier @@ -194,10 +193,9 @@ private fun ColumnScope.Changelog(changelog: Changelog) { ) ) { Changelog( - markdown = changelog.body.replace("`", ""), - version = changelog.version, - downloadCount = changelog.downloadCount.formatNumber(), - publishDate = changelog.publishDate.relativeTime(LocalContext.current) + markdown = releaseInfo.description.replace("`", ""), + version = releaseInfo.version, + publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current) ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt index aa9c878c..bfe1bbfa 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt @@ -8,9 +8,8 @@ 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.api.ReVancedAPI.Extensions.findAssetByType -import app.revanced.manager.network.utils.getOrNull -import app.revanced.manager.util.APK_MIMETYPE +import app.revanced.manager.network.dto.ReVancedAsset +import app.revanced.manager.network.utils.getOrThrow import app.revanced.manager.util.uiSafe import kotlinx.coroutines.launch @@ -18,27 +17,14 @@ class ChangelogsViewModel( private val api: ReVancedAPI, private val app: Application, ) : ViewModel() { - var changelogs: List? by mutableStateOf(null) + var releaseInfo: ReVancedAsset? by mutableStateOf(null) + private set init { viewModelScope.launch { uiSafe(app, R.string.changelog_download_fail, "Failed to download changelog") { - changelogs = api.getReleases("revanced-manager").getOrNull().orEmpty().map { release -> - Changelog( - release.version, - release.findAssetByType(APK_MIMETYPE).downloadCount, - release.metadata.publishedAt, - release.metadata.body - ) - } + releaseInfo = api.getLatestAppInfo().getOrThrow() } } } - - data class Changelog( - val version: String, - val downloadCount: Int, - val publishDate: String, - val body: String, - ) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt index 5ea4db74..f4dc457f 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageInstaller -import android.util.Log import androidx.annotation.StringRes import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -19,16 +18,10 @@ import app.revanced.manager.R import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.network.api.ReVancedAPI -import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType -import app.revanced.manager.network.dto.ReVancedRelease +import app.revanced.manager.network.dto.ReVancedAsset import app.revanced.manager.network.service.HttpService -import app.revanced.manager.network.utils.getOrThrow import app.revanced.manager.service.InstallService -import app.revanced.manager.service.UninstallService -import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.PM -import app.revanced.manager.util.simpleMessage -import app.revanced.manager.util.tag import app.revanced.manager.util.toast import app.revanced.manager.util.uiSafe import io.ktor.client.plugins.onDownload @@ -38,7 +31,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import java.io.File class UpdateViewModel( private val downloadOnScreenEntry: Boolean @@ -65,23 +57,14 @@ class UpdateViewModel( var installError by mutableStateOf("") - var changelog: Changelog? by mutableStateOf(null) + var releaseInfo: ReVancedAsset? by mutableStateOf(null) + private set private val location = fs.tempDir.resolve("updater.apk") - private var release: ReVancedRelease? = null private val job = viewModelScope.launch { uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") { - withContext(Dispatchers.IO) { - val response = reVancedAPI.getAppUpdate() ?: throw Exception("No update available") + releaseInfo = reVancedAPI.getAppUpdate() ?: throw Exception("No update available") - release = response - changelog = Changelog( - response.version, - response.findAssetByType(APK_MIMETYPE).downloadCount, - response.metadata.publishedAt, - response.metadata.body - ) - } if (downloadOnScreenEntry) { downloadUpdate() } else { @@ -92,16 +75,15 @@ class UpdateViewModel( fun downloadUpdate(ignoreInternetCheck: Boolean = false) = viewModelScope.launch { uiSafe(app, R.string.failed_to_download_update, "Failed to download update") { + val release = releaseInfo!! withContext(Dispatchers.IO) { if (!networkInfo.isSafe() && !ignoreInternetCheck) { showInternetCheckDialog = true } else { state = State.DOWNLOADING - val asset = release?.findAssetByType(APK_MIMETYPE) - ?: throw Exception("couldn't find asset to download") http.download(location) { - url(asset.downloadUrl) + url(release.downloadUrl) onDownload { bytesSentTotal, contentLength -> downloadedSize = bytesSentTotal totalSize = contentLength @@ -153,13 +135,6 @@ class UpdateViewModel( location.delete() } - data class Changelog( - val version: String, - val downloadCount: Int, - val publishDate: String, - val body: String, - ) - enum class State(@StringRes val title: Int, val showCancel: Boolean = false) { CAN_DOWNLOAD(R.string.update_available), DOWNLOADING(R.string.downloading_manager_update, true), diff --git a/app/src/main/java/app/revanced/manager/util/Util.kt b/app/src/main/java/app/revanced/manager/util/Util.kt index 8fb13c88..5cb379e3 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -3,11 +3,6 @@ package app.revanced.manager.util import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo -import android.icu.number.Notation -import android.icu.number.NumberFormatter -import android.icu.number.Precision -import android.icu.text.CompactDecimalFormat -import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.StringRes @@ -40,11 +35,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch -import java.time.Duration -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.format.MonthNames +import kotlinx.datetime.format.char +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime import java.util.Locale typealias PatchSelection = Map> @@ -134,53 +131,43 @@ suspend fun Flow>.collectEach(block: suspend (T) -> Unit) { } } -fun Int.formatNumber(): String { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - NumberFormatter.with() - .notation(Notation.compactShort()) - .decimal(NumberFormatter.DecimalSeparatorDisplay.ALWAYS) - .precision(Precision.fixedFraction(1)) - .locale(Locale.getDefault()) - .format(this) - .toString() - } else { - val compact = CompactDecimalFormat.getInstance( - Locale.getDefault(), CompactDecimalFormat.CompactStyle.SHORT - ) - compact.maximumFractionDigits = 1 - compact.format(this) - } -} - -fun String.relativeTime(context: Context): String { +fun LocalDateTime.relativeTime(context: Context): String { try { - val currentTime = ZonedDateTime.now(ZoneId.of("UTC")) - val inputDateTime = ZonedDateTime.parse(this) - val duration = Duration.between(inputDateTime, currentTime) + val now = Clock.System.now() + val duration = now - this.toInstant(TimeZone.UTC) return when { - duration.toMinutes() < 1 -> context.getString(R.string.just_now) - duration.toMinutes() < 60 -> context.getString(R.string.minutes_ago, duration.toMinutes().toString()) - duration.toHours() < 24 -> context.getString(R.string.hours_ago, duration.toHours().toString()) - duration.toDays() < 30 -> context.getString(R.string.days_ago, duration.toDays().toString()) - else -> { - val formatter = DateTimeFormatter.ofPattern("MMM d") - val formattedDate = inputDateTime.format(formatter) - if (inputDateTime.year != currentTime.year) { - val yearFormatter = DateTimeFormatter.ofPattern(", yyyy") - val formattedYear = inputDateTime.format(yearFormatter) - "$formattedDate$formattedYear" - } else { - formattedDate + duration.inWholeMinutes < 1 -> context.getString(R.string.just_now) + duration.inWholeMinutes < 60 -> context.getString( + R.string.minutes_ago, + duration.inWholeMinutes.toString() + ) + + duration.inWholeHours < 24 -> context.getString( + R.string.hours_ago, + duration.inWholeHours.toString() + ) + + duration.inWholeHours < 30 -> context.getString( + R.string.days_ago, + duration.inWholeDays.toString() + ) + + else -> LocalDateTime.Format { + monthName(MonthNames.ENGLISH_ABBREVIATED) + char(' ') + dayOfMonth() + if (now.toLocalDateTime(TimeZone.UTC).year != this@relativeTime.year) { + chars(", ") + year() } - } + }.format(this) } - } catch (e: DateTimeParseException) { + } catch (e: IllegalArgumentException) { return context.getString(R.string.invalid_date) } } - const val isScrollingUpSensitivity = 10 @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c4dafcc..47d9b401 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ placeholder = "1.1.2" reorderable = "1.5.2" serialization = "1.7.3" collection = "0.3.8" +datetime = "0.6.0" room-version = "2.6.1" revanced-patcher = "21.0.0" revanced-library = "3.0.2" @@ -68,6 +69,7 @@ placeholder-material3 = { group = "io.github.fornewid", name = "placeholder-mate # Kotlinx kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } kotlinx-collection-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collection" } +kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "datetime" } # Room room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room-version" }