From fe5e191cb5487b30ee582d220e82b87f1c4d6137 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Fri, 7 Jul 2023 10:56:04 +0200 Subject: [PATCH] feat: updater changelogs (#48) --------- Co-authored-by: Aunali321 --- app/build.gradle.kts | 3 + .../revanced/manager/di/RepositoryModule.kt | 6 +- .../app/revanced/manager/di/ServiceModule.kt | 2 + .../revanced/manager/di/ViewModelModule.kt | 3 +- .../domain/repository/GithubRepository.kt | 7 ++ .../manager/network/api/ManagerAPI.kt | 19 +-- .../manager/network/dto/GithubChangelog.kt | 16 +++ .../manager/network/dto/ReVancedReleases.kt | 4 +- .../manager/network/service/GithubService.kt | 15 +++ .../network/service/ReVancedService.kt | 6 +- .../revanced/manager/ui/component/Markdown.kt | 112 +++++++++++++++++ .../ui/destination/SettingsDestination.kt | 3 + .../manager/ui/screen/SettingsScreen.kt | 10 +- .../settings/update/ManagerUpdateChangelog.kt | 115 ++++++++++++++++++ .../settings/update/UpdateProgressScreen.kt | 98 +++++++++++++++ .../{ => update}/UpdatesSettingsScreen.kt | 100 ++------------- .../ManagerUpdateChangelogViewModel.kt | 58 +++++++++ .../ui/viewmodel/UpdateProgressViewModel.kt | 52 ++++++++ .../ui/viewmodel/UpdateSettingsViewModel.kt | 38 ------ .../java/app/revanced/manager/util/Util.kt | 13 +- app/src/main/res/values/strings.xml | 3 + 21 files changed, 528 insertions(+), 155 deletions(-) create mode 100644 app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt create mode 100644 app/src/main/java/app/revanced/manager/network/dto/GithubChangelog.kt create mode 100644 app/src/main/java/app/revanced/manager/network/service/GithubService.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/component/Markdown.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/screen/settings/update/ManagerUpdateChangelog.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateProgressScreen.kt rename app/src/main/java/app/revanced/manager/ui/screen/settings/{ => update}/UpdatesSettingsScreen.kt (50%) create mode 100644 app/src/main/java/app/revanced/manager/ui/viewmodel/ManagerUpdateChangelogViewModel.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateProgressViewModel.kt delete mode 100644 app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateSettingsViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b0fa98a977..cf979affdb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,6 +82,7 @@ dependencies { //implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion") //implementation("com.google.accompanist:accompanist-placeholder-material:$accompanistVersion") implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion") + implementation("com.google.accompanist:accompanist-webview:$accompanistVersion") //implementation("com.google.accompanist:accompanist-flowlayout:$accompanistVersion") //implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion") @@ -128,4 +129,6 @@ dependencies { implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + // Markdown to HTML + implementation("org.jetbrains:markdown:0.4.1") } diff --git a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt index 7aa7699ae4..d07ee1e5e9 100644 --- a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt +++ b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt @@ -1,17 +1,15 @@ package app.revanced.manager.di import app.revanced.manager.data.platform.FileSystem -import app.revanced.manager.domain.repository.PatchSelectionRepository -import app.revanced.manager.domain.repository.ReVancedRepository +import app.revanced.manager.domain.repository.* import app.revanced.manager.network.api.ManagerAPI -import app.revanced.manager.domain.repository.SourcePersistenceRepository -import app.revanced.manager.domain.repository.SourceRepository import app.revanced.manager.domain.worker.WorkerRepository import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val repositoryModule = module { singleOf(::ReVancedRepository) + singleOf(::GithubRepository) singleOf(::ManagerAPI) singleOf(::FileSystem) singleOf(::SourcePersistenceRepository) 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 6333d9f847..78b99c2725 100644 --- a/app/src/main/java/app/revanced/manager/di/ServiceModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt @@ -1,5 +1,6 @@ package app.revanced.manager.di +import app.revanced.manager.network.service.GithubService import app.revanced.manager.network.service.HttpService import app.revanced.manager.network.service.ReVancedService import org.koin.core.module.dsl.singleOf @@ -16,4 +17,5 @@ val serviceModule = module { single { provideReVancedService(get()) } singleOf(::HttpService) + singleOf(::GithubService) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt index 8ad4e32cfe..d5348b67aa 100644 --- a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt @@ -11,7 +11,8 @@ val viewModelModule = module { viewModelOf(::AppSelectorViewModel) viewModelOf(::SourcesViewModel) viewModelOf(::InstallerViewModel) - viewModelOf(::UpdateSettingsViewModel) + viewModelOf(::UpdateProgressViewModel) + viewModelOf(::ManagerUpdateChangelogViewModel) viewModelOf(::ImportExportViewModel) viewModelOf(::ContributorViewModel) } diff --git a/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt new file mode 100644 index 0000000000..d279711555 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt @@ -0,0 +1,7 @@ +package app.revanced.manager.domain.repository + +import app.revanced.manager.network.service.GithubService + +class GithubRepository(private val service: GithubService) { + suspend fun getChangelog(repo: String) = service.getChangelog(repo) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt b/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt index 9ed37bfaaf..1c64051fa0 100644 --- a/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt +++ b/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt @@ -17,7 +17,6 @@ import io.ktor.utils.io.* import java.io.File class ManagerAPI( - private val app: Application, private val client: HttpClient, private val revancedRepository: ReVancedRepository ) { @@ -27,7 +26,7 @@ class ManagerAPI( private suspend fun downloadAsset(downloadUrl: String, saveLocation: File) { client.get(downloadUrl) { - onDownload { bytesSentTotal, contentLength, -> + onDownload { bytesSentTotal, contentLength -> downloadProgress = (bytesSentTotal.toFloat() / contentLength.toFloat()) downloadedSize = bytesSentTotal totalSize = contentLength @@ -51,19 +50,9 @@ class ManagerAPI( return patchBundleAsset.version to integrationsAsset.version } - suspend fun downloadManager(): File? { - try { - val managerAsset = revancedRepository.findAsset(ghManager, ".apk") - val managerFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).also { it.mkdirs() } - .resolve("revanced-manager.apk") - downloadAsset(managerAsset.downloadUrl, managerFile) - println("Downloaded manager at ${managerFile.absolutePath}") - return managerFile - } catch (e: Exception) { - Log.e(tag, "Failed to download manager", e) - app.toast("Failed to download manager") - } - return null + suspend fun downloadManager(location: File) { + val managerAsset = revancedRepository.findAsset(ghManager, ".apk") + downloadAsset(managerAsset.downloadUrl, location) } } 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 new file mode 100644 index 0000000000..52789017f9 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/dto/GithubChangelog.kt @@ -0,0 +1,16 @@ +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/ReVancedReleases.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedReleases.kt index 8b1ddea8de..684a956d07 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/ReVancedReleases.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedReleases.kt @@ -5,11 +5,11 @@ import kotlinx.serialization.Serializable @Serializable class ReVancedReleases( - @SerialName("tools") val tools: List, + @SerialName("tools") val tools: List, ) @Serializable -class Assets( +class Asset( @SerialName("repository") val repository: String, @SerialName("version") val version: String, @SerialName("timestamp") val timestamp: String, diff --git a/app/src/main/java/app/revanced/manager/network/service/GithubService.kt b/app/src/main/java/app/revanced/manager/network/service/GithubService.kt new file mode 100644 index 0000000000..2c293848ee --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/service/GithubService.kt @@ -0,0 +1,15 @@ +package app.revanced.manager.network.service + +import app.revanced.manager.network.dto.GithubChangelog +import app.revanced.manager.network.utils.APIResponse +import io.ktor.client.request.url +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class GithubService(private val client: HttpService) { + suspend fun getChangelog(repo: String): APIResponse = withContext(Dispatchers.IO) { + client.request { + url("https://api.github.com/repos/revanced/$repo/releases/latest") + } + } +} \ 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 index 949c2a1324..539c6f7b04 100644 --- a/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt +++ b/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt @@ -1,7 +1,7 @@ package app.revanced.manager.network.service import app.revanced.manager.network.api.MissingAssetException -import app.revanced.manager.network.dto.Assets +import app.revanced.manager.network.dto.Asset import app.revanced.manager.network.dto.ReVancedReleases import app.revanced.manager.network.dto.ReVancedRepositories import app.revanced.manager.network.utils.APIResponse @@ -30,14 +30,14 @@ class ReVancedService( } } - suspend fun findAsset(repo: String, file: String): Assets { + suspend fun findAsset(repo: String, file: String): Asset { val releases = getAssets().getOrThrow() val asset = releases.tools.find { asset -> (asset.name.contains(file) && asset.repository.contains(repo)) } ?: throw MissingAssetException() - return Assets(asset.repository, asset.version, asset.timestamp, asset.name,asset.size, asset.downloadUrl, asset.content_type) + return asset } private companion object { diff --git a/app/src/main/java/app/revanced/manager/ui/component/Markdown.kt b/app/src/main/java/app/revanced/manager/ui/component/Markdown.kt new file mode 100644 index 0000000000..ec8e5bfe12 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/Markdown.kt @@ -0,0 +1,112 @@ +package app.revanced.manager.ui.component + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.ViewGroup +import android.webkit.WebResourceRequest +import android.webkit.WebView +import androidx.compose.foundation.background +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import app.revanced.manager.util.hexCode +import app.revanced.manager.util.openUrl +import com.google.accompanist.web.AccompanistWebViewClient +import com.google.accompanist.web.WebView +import com.google.accompanist.web.rememberWebViewStateWithHTMLData + +@Composable +@SuppressLint("ClickableViewAccessibility") +fun Markdown( + text: String, + modifier: Modifier = Modifier +) { + val ctx = LocalContext.current + val state = rememberWebViewStateWithHTMLData(data = generateMdHtml(source = text)) + val client = remember { + object : AccompanistWebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest? + ): Boolean { + if (request != null) ctx.openUrl(request.url.toString()) + return true + } + } + } + + WebView( + state, + modifier = Modifier + .background(Color.Transparent) + .then(modifier), + client = client, + onCreated = { + it.setBackgroundColor(android.graphics.Color.TRANSPARENT) + it.isVerticalScrollBarEnabled = false + it.isHorizontalScrollBarEnabled = false + it.setOnTouchListener { _, event -> event.action == MotionEvent.ACTION_MOVE } + it.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + ) +} + +@Composable +fun generateMdHtml( + source: String, + wrap: Boolean = false, + headingColor: Color = MaterialTheme.colorScheme.onSurface, + textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, + linkColor: Color = MaterialTheme.colorScheme.primary +) = remember(source, wrap, headingColor, textColor, linkColor) { + """ + + + Markdown + + + + + $source + + """ +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt b/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt index 2b83fcf407..707218339d 100644 --- a/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt +++ b/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt @@ -26,6 +26,9 @@ sealed interface SettingsDestination : Parcelable { @Parcelize object UpdateProgress : SettingsDestination + @Parcelize + object UpdateChangelog : SettingsDestination + @Parcelize object Contributors: SettingsDestination diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt index 52207ddd38..12686322ae 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt @@ -39,6 +39,9 @@ import app.revanced.manager.R import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.destination.SettingsDestination import app.revanced.manager.ui.screen.settings.* +import app.revanced.manager.ui.screen.settings.update.ManagerUpdateChangelog +import app.revanced.manager.ui.screen.settings.update.UpdateProgressScreen +import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen import app.revanced.manager.ui.viewmodel.SettingsViewModel import dev.olshevski.navigation.reimagined.* import org.koin.androidx.compose.getViewModel @@ -98,7 +101,8 @@ fun SettingsScreen( is SettingsDestination.Updates -> UpdatesSettingsScreen( onBackClick = { navController.pop() }, - navController = navController + onChangelogClick = { navController.navigate(SettingsDestination.UpdateChangelog) }, + onUpdateClick = { navController.navigate(SettingsDestination.UpdateProgress) } ) is SettingsDestination.Downloads -> DownloadsSettingsScreen( @@ -119,6 +123,10 @@ fun SettingsScreen( { navController.pop() }, ) + is SettingsDestination.UpdateChangelog -> ManagerUpdateChangelog( + onBackClick = { navController.pop() }, + ) + is SettingsDestination.Contributors -> ContributorScreen( onBackClick = { navController.pop() }, ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ManagerUpdateChangelog.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ManagerUpdateChangelog.kt new file mode 100644 index 0000000000..a1b1f86233 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/ManagerUpdateChangelog.kt @@ -0,0 +1,115 @@ +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.Row +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Campaign +import androidx.compose.material.icons.outlined.FileDownload +import androidx.compose.material.icons.outlined.Sell +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +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 +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.Markdown +import app.revanced.manager.ui.viewmodel.ManagerUpdateChangelogViewModel +import org.koin.androidx.compose.getViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ManagerUpdateChangelog( + onBackClick: () -> Unit, + vm: ManagerUpdateChangelogViewModel = getViewModel() +) { + Scaffold( + topBar = { + AppTopBar( + title = stringResource(R.string.changelog), + onBackClick = onBackClick + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(start = 16.dp, end = 16.dp, top = 16.dp) + .verticalScroll(rememberScrollState()) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 4.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Outlined.Campaign, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .size(32.dp) + ) + Text( + vm.changelog.version.removePrefix("v"), + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.primary, + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Outlined.Sell, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Text( + vm.changelog.version, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.outline, + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Outlined.FileDownload, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Text( + vm.formattedDownloadCount, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.outline, + ) + } + } + Markdown( + vm.changelogHtml, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateProgressScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateProgressScreen.kt new file mode 100644 index 0000000000..5ea51ebe0b --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateProgressScreen.kt @@ -0,0 +1,98 @@ +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.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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import app.revanced.manager.R +import app.revanced.manager.ui.component.AppTopBar +import app.revanced.manager.ui.viewmodel.UpdateProgressViewModel +import org.koin.androidx.compose.getViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@Stable +fun UpdateProgressScreen( + onBackClick: () -> Unit, + vm: UpdateProgressViewModel = getViewModel() +) { + Scaffold( + topBar = { + AppTopBar( + title = stringResource(R.string.updates), + onBackClick = onBackClick + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(vertical = 16.dp, horizontal = 24.dp) + .verticalScroll(rememberScrollState()), + ) { + Text( + text = if (vm.isInstalling) stringResource(R.string.installing_manager_update) else stringResource( + R.string.downloading_manager_update + ), style = MaterialTheme.typography.headlineMedium + ) + LinearProgressIndicator( + progress = vm.downloadProgress / 100f, + modifier = Modifier + .padding(vertical = 16.dp) + .fillMaxWidth() + ) + Text( + text = if (!vm.isInstalling) "${vm.downloadedSize.div(1000000)} MB / ${ + vm.totalSize.div( + 1000000 + ) + } MB (${vm.downloadProgress.toInt()}%)" else stringResource(R.string.installing_message), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.outline, + modifier = Modifier.align(Alignment.CenterHorizontally), + textAlign = TextAlign.Center + ) + Text( + text = "This update adds many functionality and fixes many issues in Manager. New experiment toggles are also added, they can be found in Settings > Advanced. Please submit some feedback in Settings > About > Submit issues or feedback. Thank you, everyone!", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(vertical = 32.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.weight(1f)) + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + TextButton( + onClick = onBackClick, + ) { + Text(text = stringResource(R.string.cancel)) + } + Button(onClick = vm::installUpdate, enabled = vm.finished) { + Text(text = stringResource(R.string.update)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/UpdatesSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt similarity index 50% rename from app/src/main/java/app/revanced/manager/ui/screen/settings/UpdatesSettingsScreen.kt rename to app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt index ca95c528a1..18b9fc9750 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/UpdatesSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt @@ -1,4 +1,4 @@ -package app.revanced.manager.ui.screen.settings +package app.revanced.manager.ui.screen.settings.update import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -6,7 +6,6 @@ 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 @@ -15,53 +14,44 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Update -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import app.revanced.manager.R import app.revanced.manager.ui.component.AppTopBar -import app.revanced.manager.ui.destination.SettingsDestination -import app.revanced.manager.ui.viewmodel.UpdateSettingsViewModel -import dev.olshevski.navigation.reimagined.NavController -import dev.olshevski.navigation.reimagined.navigate -import org.koin.androidx.compose.getViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun UpdatesSettingsScreen( onBackClick: () -> Unit, - navController: NavController, + onChangelogClick: () -> Unit, + onUpdateClick: () -> Unit, ) { val listItems = listOf( Triple( stringResource(R.string.update_channel), stringResource(R.string.update_channel_description), - third = { /*TODO*/ }), + third = { /*TODO*/ } + ), Triple( stringResource(R.string.update_notifications), stringResource(R.string.update_notifications_description), - third = { /*TODO*/ }), + third = { /*TODO*/ } + ), Triple( stringResource(R.string.changelog), stringResource(R.string.changelog_description), - third = { /*TODO*/ }), + third = onChangelogClick + ), ) @@ -80,9 +70,7 @@ fun UpdatesSettingsScreen( .verticalScroll(rememberScrollState()) ) { UpdateNotification( - onClick = { - navController.navigate(SettingsDestination.UpdateProgress) - } + onClick = onUpdateClick ) listItems.forEach { (title, description, onClick) -> @@ -136,72 +124,4 @@ fun UpdateNotification( ) } } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun UpdateProgressScreen( - onBackClick: () -> Unit, - vm: UpdateSettingsViewModel = getViewModel() -) { - Scaffold( - topBar = { - AppTopBar( - title = stringResource(R.string.updates), - onBackClick = onBackClick - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(vertical = 16.dp, horizontal = 24.dp), - ) { - var isInstalling by remember { mutableStateOf(false) } - isInstalling = vm.downloadProgress >= 100 - - Text( - text = if (isInstalling) stringResource(R.string.installing_manager_update) else stringResource( - R.string.downloading_manager_update - ), style = MaterialTheme.typography.headlineMedium - ) - LinearProgressIndicator( - progress = vm.downloadProgress / 100f, - modifier = Modifier - .padding(vertical = 16.dp) - .fillMaxWidth() - ) - Text( - text = if (!isInstalling) "${vm.downloadedSize.div(1000000)} MB / ${vm.totalSize.div(1000000)} MB (${vm.downloadProgress.toInt()}%)" else stringResource(R.string.installing_message), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.outline, - modifier = Modifier.align(Alignment.CenterHorizontally), - textAlign = TextAlign.Center - ) - Text( - text = "This update adds many functionality and fixes many issues in Manager. New experiment toggles are also added, they can be found in Settings > Advanced. Please submit some feedback in Settings > About > Submit issues or feedback. Thank you, everyone!", - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(vertical = 32.dp), - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.weight(1f)) - Row( - verticalAlignment = Alignment.Bottom, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - ) { - TextButton( - onClick = { /*TODO*/ }, - ) { - Text(text = stringResource(R.string.cancel)) - } - Button(onClick = { - vm.installUpdate() - }) { - Text(text = stringResource(R.string.update)) - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/ManagerUpdateChangelogViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/ManagerUpdateChangelogViewModel.kt new file mode 100644 index 0000000000..e4d774007e --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/ManagerUpdateChangelogViewModel.kt @@ -0,0 +1,58 @@ +package app.revanced.manager.ui.viewmodel + +import android.app.Application +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.revanced.manager.R +import app.revanced.manager.domain.repository.GithubRepository +import app.revanced.manager.network.dto.GithubChangelog +import app.revanced.manager.network.utils.getOrThrow +import app.revanced.manager.util.uiSafe +import kotlinx.coroutines.launch +import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor +import org.intellij.markdown.html.HtmlGenerator +import org.intellij.markdown.parser.MarkdownParser + +class ManagerUpdateChangelogViewModel( + private val githubRepository: GithubRepository, + private val app: Application, +) : ViewModel() { + private val markdownFlavour = GFMFlavourDescriptor() + private val markdownParser = MarkdownParser(flavour = markdownFlavour) + + var changelog by mutableStateOf( + GithubChangelog( + "...", + app.getString(R.string.changelog_loading), + emptyList() + ) + ) + private set + val formattedDownloadCount by derivedStateOf { + val downloadCount = changelog.assets.firstOrNull()?.downloadCount?.toDouble() ?: 0.0 + if (downloadCount > 1000) { + val roundedValue = + (downloadCount / 100).toInt() / 10.0 // Divide by 100 and round to one decimal place + "${roundedValue}k" + } else { + downloadCount.toString() + } + } + val changelogHtml by derivedStateOf { + val markdown = changelog.body + val parsedTree = markdownParser.buildMarkdownTreeFromString(markdown) + HtmlGenerator(markdown, parsedTree, markdownFlavour).generateHtml() + } + + init { + viewModelScope.launch { + uiSafe(app, R.string.changelog_download_fail, "Failed to download changelog") { + changelog = githubRepository.getChangelog("revanced-manager").getOrThrow() + } + } + } +} diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateProgressViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateProgressViewModel.kt new file mode 100644 index 0000000000..42956ae203 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateProgressViewModel.kt @@ -0,0 +1,52 @@ +package app.revanced.manager.ui.viewmodel + +import android.app.Application +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.revanced.manager.R +import app.revanced.manager.network.api.ManagerAPI +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import app.revanced.manager.util.PM +import app.revanced.manager.util.uiSafe +import kotlinx.coroutines.withContext +import java.io.File + +class UpdateProgressViewModel( + app: Application, + private val managerAPI: ManagerAPI, + private val pm: PM +) : ViewModel() { + + val downloadProgress by derivedStateOf { managerAPI.downloadProgress?.times(100) ?: 0f } + val downloadedSize by derivedStateOf { managerAPI.downloadedSize ?: 0L } + val totalSize by derivedStateOf { managerAPI.totalSize ?: 0L } + val isInstalling by derivedStateOf { downloadProgress >= 100 } + var finished by mutableStateOf(false) + private set + + private val location = File.createTempFile("updater", ".apk", app.cacheDir) + private val job = viewModelScope.launch { + uiSafe(app, R.string.download_manager_failed, "Failed to download manager") { + withContext(Dispatchers.IO) { + managerAPI.downloadManager(location) + } + finished = true + } + } + + fun installUpdate() { + pm.installApp(listOf(location)) + } + + override fun onCleared() { + super.onCleared() + + job.cancel() + location.delete() + } +} diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateSettingsViewModel.kt deleted file mode 100644 index c3e1a04362..0000000000 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateSettingsViewModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -package app.revanced.manager.ui.viewmodel - -import android.os.Environment -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import app.revanced.manager.network.api.ManagerAPI -import app.revanced.manager.util.PM -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.io.File - -class UpdateSettingsViewModel( - private val managerAPI: ManagerAPI, - private val pm: PM -) : ViewModel() { - val downloadProgress get() = (managerAPI.downloadProgress?.times(100)) ?: 0f - val downloadedSize get() = managerAPI.downloadedSize ?: 0L - val totalSize get() = managerAPI.totalSize ?: 0L - private fun downloadLatestManager() { - viewModelScope.launch(Dispatchers.IO) { - managerAPI.downloadManager() - } - } - fun installUpdate() { - pm.installApp( - apks = listOf( - File( - (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/revanced-manager.apk") - .toString()) - ), - ) - ) - } - - init { - downloadLatestManager() - } -} \ No newline at end of file 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 79ebc371c1..aeabbecda6 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -7,6 +7,7 @@ import android.graphics.drawable.Drawable import android.util.Log import android.widget.Toast import androidx.annotation.StringRes +import androidx.compose.ui.graphics.Color import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner @@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch +import java.util.Locale typealias PatchesSelection = Map> typealias Options = Map>> @@ -93,4 +95,13 @@ inline fun Flow>.flatMapLatestAndCombine( combine(iterable.map(transformer)) { combiner(it) } -} \ No newline at end of file +} + +val Color.hexCode: String + inline get() { + val a: Int = (alpha * 255).toInt() + val r: Int = (red * 255).toInt() + val g: Int = (green * 255).toInt() + val b: Int = (blue * 255).toInt() + return java.lang.String.format(Locale.getDefault(), "%02X%02X%02X%02X", r, g, b, a) + } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5ca1a0aac8..44f72b095b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -133,10 +133,13 @@ Update notifications Dialog on app launch + badges Changelog + Loading changelog + Failed to download changelog: %s Check out the latest changes in this update Battery optimization must be turned off in order for ReVanced Manager to work correctly in the background. Tap here to turn off. Installing update… Downloading update… + Failed to download update: %s Cancel Update Tap on Update when prompted. \n ReVanced Manager will close when updating.