feat: partial implementation of installed apps

This commit is contained in:
Canny 2022-12-11 21:33:31 +03:00
parent a3e41552f8
commit 7e1f829e3c
No known key found for this signature in database
GPG Key ID: 395CCB0AA979F27B
9 changed files with 126 additions and 75 deletions

View File

@ -8,6 +8,8 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.viewmodel.PatchClass
import app.revanced.manager.ui.viewmodel.PatchedApp
import app.revanced.manager.util.reVancedFolder
import app.revanced.manager.util.tag
import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
@ -16,20 +18,46 @@ import app.revanced.patcher.patch.Patch
import app.revanced.patcher.util.patch.PatchBundle
import dalvik.system.DexClassLoader
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import java.util.*
class PatcherUtils(val app: Application) {
@OptIn(ExperimentalSerializationApi::class)
class PatcherUtils(val app: Application, val json: Json) {
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading)
val filteredPatches = mutableStateListOf<PatchClass>()
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
val selectedAppPackagePath = mutableStateOf<String?>(null)
val selectedPatches = mutableStateListOf<String>()
val patchedAppsFile = reVancedFolder.resolve("apps.json")
val patchedApps = mutableStateListOf<PatchedApp>()
lateinit var patchBundleFile: String
fun cleanup() {
patches.value = Resource.Loading
selectedAppPackage.value = Optional.empty()
selectedPatches.clear()
suspend fun getPatchedApps() = withContext(Dispatchers.IO) {
if (patchedAppsFile.exists()) {
val apps: List<PatchedApp> = try {
json.decodeFromStream(patchedAppsFile.inputStream())
} catch (e: Exception) {
Log.e(tag, e.stackTraceToString())
return@withContext
}
apps.forEach { app ->
if (!patchedApps.any { it.pkgName == app.pkgName }) {
patchedApps.add(app)
}
}
}
}
fun savePatchedApp(app: PatchedApp) {
patchedApps.removeIf { it.pkgName == app.pkgName }
patchedApps.add(app)
json.encodeToStream(patchedApps as List<PatchedApp>, patchedAppsFile.outputStream())
}
fun loadPatchBundle(file: String? = patchBundleFile) {
@ -50,7 +78,9 @@ class PatcherUtils(val app: Application) {
fun getSelectedPackageInfo(): PackageInfo? {
return if (selectedAppPackage.value.isPresent) {
val path = selectedAppPackage.value.get().publicSourceDir ?: selectedAppPackagePath.value ?: return null
val path =
selectedAppPackage.value.get().publicSourceDir ?: selectedAppPackagePath.value
?: return null
app.packageManager.getPackageArchiveInfo(
path, 1
)

View File

@ -7,7 +7,6 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Environment
import android.os.PowerManager
import android.util.Log
import android.view.WindowManager
@ -23,6 +22,7 @@ import app.revanced.manager.patcher.aligning.ZipAligner
import app.revanced.manager.patcher.aligning.zip.ZipFile
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
import app.revanced.manager.patcher.signing.Signer
import app.revanced.manager.util.reVancedFolder
import app.revanced.manager.util.tag
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
@ -112,8 +112,6 @@ class PatcherWorker(
applicationContext.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath
val integrationsCacheDir =
applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() }
val reVancedFolder =
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }
val appInfo = patcherUtils.selectedAppPackage.value.get()
val appPath = patcherUtils.selectedAppPackagePath.value
@ -208,7 +206,6 @@ class PatcherWorker(
)
}
log("Successfully patched!", SUCCESS)
patcherUtils.cleanup()
} finally {
Log.d(tag, "Deleting workdir")
workdir.deleteRecursively()

View File

@ -39,20 +39,20 @@ fun ApplicationItem(
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 2.dp)
.fillMaxSize()
.padding(horizontal = 14.dp, vertical = 2.dp),
verticalArrangement = Arrangement.Center
) {
Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier
.height(68.dp)
.weight(1f),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
appIcon()
Column(modifier = Modifier.padding(start = 8.dp)) {
@ -68,7 +68,7 @@ fun ApplicationItem(
)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Row {
IconButton(
modifier = Modifier.rotate(rotateState),
onClick = { expandedState = !expandedState },
@ -78,7 +78,7 @@ fun ApplicationItem(
contentDescription = stringResource(R.string.expand)
)
}
OutlinedButton(onClick = { /*TODO*/ }) {
OutlinedButton(onClick = {}) {
Text(stringResource(R.string.update))
}
}

View File

@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -16,7 +16,6 @@ import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.ui.component.AppIcon
import app.revanced.manager.ui.component.ApplicationItem
import app.revanced.manager.ui.component.ApplicationItemDualTint
import app.revanced.manager.ui.component.HeadlineWithCard
import app.revanced.manager.ui.viewmodel.DashboardViewModel
import app.revanced.manager.util.loadIcon
@ -25,10 +24,10 @@ import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DashboardScreen(viewModel: DashboardViewModel = getViewModel()) {
var showUpdates by remember { mutableStateOf(false) }
val context = LocalContext.current
val padHoriz = 16.dp
val padVert = 10.dp
Column(
modifier = Modifier
.fillMaxSize()
@ -73,10 +72,10 @@ fun DashboardScreen(viewModel: DashboardViewModel = getViewModel()) {
style = MaterialTheme.typography.headlineSmall
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip(selected = true, onClick = { /*TODO*/ }, label = {
Text(stringResource(R.string.updates_available))
})
FilterChip(selected = false, onClick = { /*TODO*/ }, label = {
//FilterChip(selected = showUpdates, onClick = { showUpdates = true }, label = {
// Text(stringResource(R.string.updates_available))
//})
FilterChip(selected = !showUpdates, onClick = { showUpdates = false }, label = {
Text(stringResource(R.string.installed))
})
}
@ -87,39 +86,19 @@ fun DashboardScreen(viewModel: DashboardViewModel = getViewModel()) {
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
ApplicationItem(
appName = "Compose Manager",
appIcon = {
AppIcon(
drawable = context.loadIcon("app.revanced.manager.compose"),
contentDescription = null,
size = 38
)
},
releaseAgo = "9d ago"
) {
ChangelogText(
"""
cossal will explode
""".trimIndent()
)
}
ApplicationItemDualTint(
appName = "Flutter Manager",
releaseAgo = "9d ago",
appIcon = {
AppIcon(
drawable = context.loadIcon("app.revanced.manager.flutter"),
contentDescription = null,
size = 38
)
viewModel.apps.forEach {
ApplicationItem(
appName = it.appName,
appIcon = {
AppIcon(
drawable = context.loadIcon(it.pkgName),
contentDescription = null,
size = 38
)
},
releaseAgo = it.version
) {
}
) {
ChangelogText(
"""
cossal will explode
""".trimIndent()
)
}
}
}

View File

@ -19,6 +19,7 @@ import app.revanced.manager.ui.component.AppMediumTopBar
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
import app.revanced.manager.util.appName
import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@ -54,10 +55,12 @@ fun AppSelectorSubscreen(
}
) { paddingValues ->
if (vm.filteredApps.isNotEmpty()) {
LazyColumn(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
LazyColumn(modifier = Modifier
.fillMaxSize()
.padding(paddingValues)) {
items(count = vm.filteredApps.size) { int ->
val app = vm.filteredApps[int]
val label = vm.applicationLabel(app)
val label = vm.app.appName(app)
val packageName = app.packageName
val same = packageName == label

View File

@ -10,13 +10,21 @@ import androidx.lifecycle.viewModelScope
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
import app.revanced.manager.network.dto.Assets
import app.revanced.manager.network.utils.getOrNull
import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.util.ghManager
import app.revanced.manager.util.ghPatcher
import io.ktor.http.*
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import java.text.SimpleDateFormat
import java.util.*
class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : ViewModel() {
class DashboardViewModel(
private val reVancedApi: ReVancedRepositoryImpl,
private val patcherUtils: PatcherUtils
) : ViewModel() {
val apps = patcherUtils.patchedApps
private var _latestPatcherCommit: Assets? by mutableStateOf(null)
val patcherCommitDate: String
get() = _latestPatcherCommit?.commitDate ?: "unknown"
@ -26,22 +34,23 @@ class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : View
get() = _latestManagerCommit?.commitDate ?: "unknown"
init {
fetchLastCommit()
viewModelScope.launch {
patcherUtils.getPatchedApps()
fetchLastCommit()
}
}
private fun fetchLastCommit() {
viewModelScope.launch {
val repo = reVancedApi.getAssets().getOrNull() ?: return@launch
for (asset in repo.tools) {
when (asset.repository) {
ghPatcher -> {
_latestPatcherCommit = asset
}
ghManager -> {
_latestManagerCommit = asset
}
}
private suspend fun fetchLastCommit() {
val repo = reVancedApi.getAssets().getOrNull() ?: return
for (asset in repo.tools) {
when (asset.repository) {
ghPatcher -> {
_latestPatcherCommit = asset
}
ghManager -> {
_latestManagerCommit = asset
}
}
}
}
@ -57,3 +66,11 @@ class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : View
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
}
}
@Serializable
class PatchedApp(
val appName: String,
val pkgName: String,
val version: String,
val appliedPatches: List<String>
)

View File

@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
@ -16,11 +17,14 @@ import androidx.work.*
import app.revanced.manager.installer.service.InstallService
import app.revanced.manager.installer.service.UninstallService
import app.revanced.manager.installer.utils.PM
import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.patcher.worker.PatcherWorker
import app.revanced.manager.util.appName
import java.io.File
class PatchingScreenViewModel(
private val app: Application,
private val patcherUtils: PatcherUtils,
) : ViewModel() {
sealed interface PatchLog {
@ -38,7 +42,7 @@ class PatchingScreenViewModel(
object Failure : Status()
}
val workManager = WorkManager.getInstance(app)
private val workManager = WorkManager.getInstance(app)
var installFailure by mutableStateOf(false)
var pmStatus by mutableStateOf(-999)
var extra by mutableStateOf("")
@ -109,12 +113,24 @@ class PatchingScreenViewModel(
fun postInstallStatus() {
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
log(PatchLog.Success("Successfully installed!"))
patcherUtils.getSelectedPackageInfo()?.let { saveApp(it) }
} else {
installFailure = true
log(PatchLog.Error("Failed to install!"))
}
}
private fun saveApp(packageInfo: PackageInfo) = patcherUtils.savePatchedApp(
packageInfo.run {
PatchedApp(
appName = app.appName(applicationInfo),
pkgName = applicationInfo.packageName,
version = versionName,
appliedPatches = patcherUtils.selectedPatches
)
}
)
override fun onCleared() {
super.onCleared()
liveData.removeObserver(observer)

View File

@ -1,5 +1,7 @@
package app.revanced.manager.util
import android.os.Environment
private const val team = "revanced"
const val ghOrganization = "https://github.com/$team"
const val ghCli = "$team/revanced-cli"
@ -8,3 +10,5 @@ const val ghPatcher = "$team/revanced-patcher"
const val ghManager = "$team/revanced-manager"
const val ghIntegrations = "$team/revanced-integrations"
const val tag = "ReVanced Manager"
val reVancedFolder =
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }

View File

@ -2,6 +2,7 @@ package app.revanced.manager.util
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Drawable
import androidx.core.net.toUri
@ -20,3 +21,7 @@ fun Context.loadIcon(string: String): Drawable? {
null
}
}
fun Context.appName(info: ApplicationInfo): String {
return packageManager.getApplicationLabel(info).toString()
}