mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
feat: updater changelogs (#48)
--------- Co-authored-by: Aunali321 <aunvakil.aa@gmail.com>
This commit is contained in:
parent
d9d83df9de
commit
fe5e191cb5
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
@ -11,7 +11,8 @@ val viewModelModule = module {
|
||||
viewModelOf(::AppSelectorViewModel)
|
||||
viewModelOf(::SourcesViewModel)
|
||||
viewModelOf(::InstallerViewModel)
|
||||
viewModelOf(::UpdateSettingsViewModel)
|
||||
viewModelOf(::UpdateProgressViewModel)
|
||||
viewModelOf(::ManagerUpdateChangelogViewModel)
|
||||
viewModelOf(::ImportExportViewModel)
|
||||
viewModelOf(::ContributorViewModel)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<GithubAsset>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GithubAsset(
|
||||
@SerialName("download_count") val downloadCount: Int,
|
||||
)
|
@ -5,11 +5,11 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class ReVancedReleases(
|
||||
@SerialName("tools") val tools: List<Assets>,
|
||||
@SerialName("tools") val tools: List<Asset>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Assets(
|
||||
class Asset(
|
||||
@SerialName("repository") val repository: String,
|
||||
@SerialName("version") val version: String,
|
||||
@SerialName("timestamp") val timestamp: String,
|
||||
|
@ -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<GithubChangelog> = withContext(Dispatchers.IO) {
|
||||
client.request {
|
||||
url("https://api.github.com/repos/revanced/$repo/releases/latest")
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
112
app/src/main/java/app/revanced/manager/ui/component/Markdown.kt
Normal file
112
app/src/main/java/app/revanced/manager/ui/component/Markdown.kt
Normal file
@ -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) {
|
||||
"""<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Markdown</title>
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
|
||||
<style>
|
||||
body {
|
||||
color: #${textColor.hexCode};
|
||||
}
|
||||
a {
|
||||
color: #${linkColor.hexCode}!important;
|
||||
}
|
||||
a.anchor {
|
||||
display: none;
|
||||
}
|
||||
.highlight pre, pre {
|
||||
word-wrap: ${if (wrap) "break-word" else "normal"};
|
||||
white-space: ${if (wrap) "pre-wrap" else "pre"};
|
||||
}
|
||||
h2 {
|
||||
color: #${headingColor.hexCode};
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0.15px;
|
||||
}
|
||||
ul {
|
||||
margin-left: 0px;
|
||||
padding-left: 18px;
|
||||
}
|
||||
li {
|
||||
margin-left: 2px;
|
||||
}
|
||||
::marker {
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
color: #${textColor.hexCode};
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
$source
|
||||
</body>
|
||||
</html>"""
|
||||
}
|
@ -26,6 +26,9 @@ sealed interface SettingsDestination : Parcelable {
|
||||
@Parcelize
|
||||
object UpdateProgress : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object UpdateChangelog : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Contributors: SettingsDestination
|
||||
|
||||
|
@ -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() },
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<SettingsDestination>,
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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<Int, Set<String>>
|
||||
typealias Options = Map<Int, Map<String, Map<String, Any?>>>
|
||||
@ -93,4 +95,13 @@ inline fun <T, reified R, C> Flow<Iterable<T>>.flatMapLatestAndCombine(
|
||||
combine(iterable.map(transformer)) {
|
||||
combiner(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -133,10 +133,13 @@
|
||||
<string name="update_notifications">Update notifications</string>
|
||||
<string name="update_notifications_description">Dialog on app launch + badges</string>
|
||||
<string name="changelog">Changelog</string>
|
||||
<string name="changelog_loading">Loading changelog</string>
|
||||
<string name="changelog_download_fail">Failed to download changelog: %s</string>
|
||||
<string name="changelog_description">Check out the latest changes in this update</string>
|
||||
<string name="battery_optimization_notification">Battery optimization must be turned off in order for ReVanced Manager to work correctly in the background. Tap here to turn off.</string>
|
||||
<string name="installing_manager_update">Installing update…</string>
|
||||
<string name="downloading_manager_update">Downloading update…</string>
|
||||
<string name="download_manager_failed">Failed to download update: %s</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="installing_message">Tap on <b>Update</b> when prompted. \n ReVanced Manager will close when updating.</string>
|
||||
|
Loading…
Reference in New Issue
Block a user