mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-05-01 06:54:28 +02:00
feat: partial implementation of installed apps
This commit is contained in:
parent
a3e41552f8
commit
7e1f829e3c
@ -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
|
||||
)
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
) {
|
||||
viewModel.apps.forEach {
|
||||
ApplicationItem(
|
||||
appName = "Compose Manager",
|
||||
appName = it.appName,
|
||||
appIcon = {
|
||||
AppIcon(
|
||||
drawable = context.loadIcon("app.revanced.manager.compose"),
|
||||
drawable = context.loadIcon(it.pkgName),
|
||||
contentDescription = null,
|
||||
size = 38
|
||||
)
|
||||
},
|
||||
releaseAgo = "9d ago"
|
||||
releaseAgo = it.version
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
) {
|
||||
ChangelogText(
|
||||
"""
|
||||
cossal will explode
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,12 +34,14 @@ class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : View
|
||||
get() = _latestManagerCommit?.commitDate ?: "unknown"
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
patcherUtils.getPatchedApps()
|
||||
fetchLastCommit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchLastCommit() {
|
||||
viewModelScope.launch {
|
||||
val repo = reVancedApi.getAssets().getOrNull() ?: return@launch
|
||||
private suspend fun fetchLastCommit() {
|
||||
val repo = reVancedApi.getAssets().getOrNull() ?: return
|
||||
for (asset in repo.tools) {
|
||||
when (asset.repository) {
|
||||
ghPatcher -> {
|
||||
@ -43,7 +53,6 @@ class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : View
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Assets.commitDate: String
|
||||
get() = DateUtils.getRelativeTimeSpanString(
|
||||
@ -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>
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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() }
|
@ -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()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user