diff --git a/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt b/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt index 5949e53..540ab01 100644 --- a/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt +++ b/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt @@ -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.Loading) val filteredPatches = mutableStateListOf() val selectedAppPackage = mutableStateOf(Optional.empty()) val selectedAppPackagePath = mutableStateOf(null) val selectedPatches = mutableStateListOf() + val patchedAppsFile = reVancedFolder.resolve("apps.json") + val patchedApps = mutableStateListOf() 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 = 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, 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 ) diff --git a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt index b9bc8fb..3506b04 100644 --- a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt +++ b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt @@ -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() diff --git a/app/src/main/java/app/revanced/manager/ui/component/ApplicationItem.kt b/app/src/main/java/app/revanced/manager/ui/component/ApplicationItem.kt index 028825b..500a471 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/ApplicationItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/ApplicationItem.kt @@ -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)) } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt index 71af8d5..4147881 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt @@ -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() - ) } } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/subscreens/AppSelectorSubscreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/subscreens/AppSelectorSubscreen.kt index bcb3a42..bdae895 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/subscreens/AppSelectorSubscreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/subscreens/AppSelectorSubscreen.kt @@ -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 diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt index 13eb22d..233b204 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt @@ -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 + } + } } } @@ -56,4 +65,12 @@ class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : View @SuppressLint("ConstantLocale") val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()) } -} \ No newline at end of file +} + +@Serializable +class PatchedApp( + val appName: String, + val pkgName: String, + val version: String, + val appliedPatches: List +) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt index e18a266..a2442d1 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt @@ -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) diff --git a/app/src/main/java/app/revanced/manager/util/Constants.kt b/app/src/main/java/app/revanced/manager/util/Constants.kt index dd53b8b..e50d75b 100644 --- a/app/src/main/java/app/revanced/manager/util/Constants.kt +++ b/app/src/main/java/app/revanced/manager/util/Constants.kt @@ -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" @@ -7,4 +9,6 @@ const val ghPatches = "$team/revanced-patches" const val ghPatcher = "$team/revanced-patcher" const val ghManager = "$team/revanced-manager" const val ghIntegrations = "$team/revanced-integrations" -const val tag = "ReVanced Manager" \ No newline at end of file +const val tag = "ReVanced Manager" +val reVancedFolder = + Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() } \ 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 80ea9d2..22f2070 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -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 @@ -19,4 +20,8 @@ fun Context.loadIcon(string: String): Drawable? { } catch (e: NameNotFoundException) { null } +} + +fun Context.appName(info: ApplicationInfo): String { + return packageManager.getApplicationLabel(info).toString() } \ No newline at end of file