From c7d183ee8d331bc8d587d5c2b577b82dd743c5b5 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 23 Jun 2024 17:27:40 +0200 Subject: [PATCH 01/10] feat: rename debug build to `ReVanced Manager (dev)` --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e11dd0e607..279a681de7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,7 +29,7 @@ android { buildTypes { debug { applicationIdSuffix = ".debug" - resValue("string", "app_name", "ReVanced Manager Debug") + resValue("string", "app_name", "ReVanced Manager (dev)") buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L") } From 16fea5960592f4d482e8c560e561ff824973362f Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 23 Jun 2024 20:17:42 +0200 Subject: [PATCH 02/10] fix: scrolling in patch selector --- .../revanced/manager/ui/screen/PatchesSelectorScreen.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt index 8a7988ff1a..24a5e53020 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt @@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons @@ -94,6 +94,8 @@ fun PatchesSelectorScreen( derivedStateOf { vm.selectionIsValid(bundles) } } + val patchLazyListStates = remember(bundles) { List(bundles.size) { LazyListState() } } + if (showBottomSheet) { ModalBottomSheet( onDismissRequest = { @@ -255,7 +257,6 @@ fun PatchesSelectorScreen( } } - val patchLazyListState = rememberLazyListState() Scaffold( topBar = { AppTopBar( @@ -284,7 +285,7 @@ fun PatchesSelectorScreen( ExtendedFloatingActionButton( text = { Text(stringResource(R.string.save)) }, icon = { Icon(Icons.Outlined.Save, null) }, - expanded = patchLazyListState.isScrollingUp, + expanded = patchLazyListStates[pagerState.currentPage].isScrollingUp, onClick = { // TODO: only allow this if all required options have been set. onSave(vm.getCustomSelection(), vm.getOptions()) @@ -328,7 +329,7 @@ fun PatchesSelectorScreen( LazyColumnWithScrollbar( modifier = Modifier.fillMaxSize(), - state = patchLazyListState + state = patchLazyListStates[index] ) { patchList( uid = bundle.uid, From 269fa791368e6fe764e423bd450a6789cb93c2a6 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 23 Jun 2024 21:01:20 +0200 Subject: [PATCH 03/10] feat: improve patcher screen labels --- .../java/app/revanced/manager/patcher/Session.kt | 9 ++++----- .../manager/patcher/worker/PatcherWorker.kt | 4 ++-- .../manager/ui/viewmodel/PatcherViewModel.kt | 6 +----- app/src/main/res/values/plurals.xml | 6 +++--- app/src/main/res/values/strings.xml | 15 +++++++-------- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/patcher/Session.kt b/app/src/main/java/app/revanced/manager/patcher/Session.kt index da6d9a401a..4393794db2 100644 --- a/app/src/main/java/app/revanced/manager/patcher/Session.kt +++ b/app/src/main/java/app/revanced/manager/patcher/Session.kt @@ -47,7 +47,7 @@ class Session( var nextPatchIndex = 0 updateProgress( - name = androidContext.getString(R.string.applying_patch, selectedPatches[nextPatchIndex]), + name = androidContext.getString(R.string.executing_patch, selectedPatches[nextPatchIndex]), state = State.RUNNING ) @@ -56,7 +56,7 @@ class Session( if (exception != null) { updateProgress( - name = androidContext.getString(R.string.failed_to_apply_patch, patch.name), + name = androidContext.getString(R.string.failed_to_execute_patch, patch.name), state = State.FAILED, message = exception.stackTraceToString() ) @@ -72,7 +72,7 @@ class Session( selectedPatches.getOrNull(nextPatchIndex)?.let { nextPatch -> updateProgress( - name = androidContext.getString(R.string.applying_patch, nextPatch.name) + name = androidContext.getString(R.string.executing_patch, nextPatch.name) ) } @@ -82,7 +82,7 @@ class Session( updateProgress( state = State.COMPLETED, name = androidContext.resources.getQuantityString( - R.plurals.patches_applied, + R.plurals.patches_executed, selectedPatches.size, selectedPatches.size ) @@ -105,7 +105,6 @@ class Session( logger.info("Merging integrations") acceptIntegrations(integrations.toSet()) acceptPatches(selectedPatches.toSet()) - updateProgress(state = State.COMPLETED) // Merging logger.info("Applying patches...") applyPatchesVerbose(selectedPatches.sortedBy { it.name }) 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 d825a18e46..bb91cf1136 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 @@ -184,11 +184,11 @@ class PatcherWorker( Log.i(tag, "Patching succeeded".logFmt()) Result.success() } catch (e: ProcessRuntime.RemoteFailureException) { - Log.e(tag, "An exception occured in the remote process while patching. ${e.originalStackTrace}".logFmt()) + Log.e(tag, "An exception occurred in the remote process while patching. ${e.originalStackTrace}".logFmt()) updateProgress(state = State.FAILED, message = e.originalStackTrace) Result.failure() } catch (e: Exception) { - Log.e(tag, "An exception occured while patching".logFmt(), e) + Log.e(tag, "An exception occurred while patching".logFmt(), e) updateProgress(state = State.FAILED, message = e.stackTraceToString()) Result.failure() } finally { diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt index 8b504dfb1f..15a51a2016 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt @@ -316,13 +316,9 @@ class PatcherViewModel( context.getString(R.string.patcher_step_unpack), StepCategory.PREPARING ), - Step( - context.getString(R.string.patcher_step_integrations), - StepCategory.PREPARING - ), Step( - context.getString(R.string.apply_patches), + context.getString(R.string.execute_patches), StepCategory.PATCHING ), diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml index 7ea3d8c6e4..798c83effe 100644 --- a/app/src/main/res/values/plurals.xml +++ b/app/src/main/res/values/plurals.xml @@ -4,8 +4,8 @@ %d patch %d patches - - Applied %d patch - Applied %d patches + + Executed %d patch + Executed %d patches \ 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 db49a9d672..f77fc91ee0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -162,7 +162,7 @@ Continue anyways Download another version Download app - Download APK + Download APK file Failed to download patch bundle: %s Failed to load updated patch bundle: %s Failed to update integrations: %s @@ -248,16 +248,15 @@ Preparing Load patches - Unpack APK - Merge Integrations + Read APK file Patching Saving - Write patched APK - Sign APK + Write patched APK file + Sign patched APK file Patching in progress… - Apply patches - Applying %s - Failed to apply %s + Execute patches + Execute %s + Failed to execute %s completed failed From 75fcdb139cf52991f6863ec8018fb6a791ec2f7b Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 25 Jun 2024 23:49:28 +0200 Subject: [PATCH 04/10] fix: use proper update icon --- .../manager/ui/component/bundle/BundleInformationDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt index 59b119f37b..7634fb647a 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt @@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.DeleteOutline -import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.Update import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -86,7 +86,7 @@ fun BundleInformationDialog( if (!isLocal) { IconButton(onClick = onRefreshButton) { Icon( - Icons.Outlined.Refresh, + Icons.Outlined.Update, stringResource(R.string.refresh) ) } From 5455cf20ab236795f144483b79b57b83516f9d19 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 25 Jun 2024 23:54:59 +0200 Subject: [PATCH 05/10] feat: rename main bundle to `Default` --- .../domain/repository/PatchBundlePersistenceRepository.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt index 8ea5733ddb..2784841ce3 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt @@ -5,7 +5,6 @@ import app.revanced.manager.data.room.AppDatabase.Companion.generateUid import app.revanced.manager.data.room.bundles.PatchBundleEntity import app.revanced.manager.data.room.bundles.Source import app.revanced.manager.data.room.bundles.VersionInfo -import io.ktor.http.* import kotlinx.coroutines.flow.distinctUntilChanged class PatchBundlePersistenceRepository(db: AppDatabase) { @@ -47,7 +46,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) { private companion object { val defaultSource = PatchBundleEntity( uid = 0, - name = "Main", + name = "Default", versionInfo = VersionInfo(), source = Source.API, autoUpdate = false From a12c5c583bf2b2787ed0e72084f5af490cea3da5 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Tue, 2 Jul 2024 15:43:06 +0200 Subject: [PATCH 06/10] fix: add bounds checks in patch selector --- .../app/revanced/manager/ui/screen/PatchesSelectorScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt index 24a5e53020..8ddd6d7322 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt @@ -285,7 +285,7 @@ fun PatchesSelectorScreen( ExtendedFloatingActionButton( text = { Text(stringResource(R.string.save)) }, icon = { Icon(Icons.Outlined.Save, null) }, - expanded = patchLazyListStates[pagerState.currentPage].isScrollingUp, + expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp ?: true, onClick = { // TODO: only allow this if all required options have been set. onSave(vm.getCustomSelection(), vm.getOptions()) @@ -325,6 +325,8 @@ fun PatchesSelectorScreen( state = pagerState, userScrollEnabled = true, pageContent = { index -> + // Avoid crashing if the lists have not been fully initialized yet. + if (index > bundles.lastIndex || bundles.size != patchLazyListStates.size) return@HorizontalPager val bundle = bundles[index] LazyColumnWithScrollbar( From 1ce56af3b160439ebc19aa6e6e662d2dfc02cbd3 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Tue, 2 Jul 2024 21:50:28 +0200 Subject: [PATCH 07/10] feat: get bundle information from jar manifest (#2027) --- .../data/room/bundles/PatchBundleDao.kt | 5 +- .../domain/bundles/LocalPatchBundle.kt | 13 +++- .../domain/bundles/PatchBundleSource.kt | 62 ++++++++++++++++--- .../domain/bundles/RemotePatchBundle.kt | 12 +--- .../PatchBundlePersistenceRepository.kt | 7 ++- .../repository/PatchBundleRepository.kt | 14 ++--- .../manager/patcher/patch/PatchBundle.kt | 13 ++++ .../ui/component/bundle/BaseBundleDialog.kt | 50 ++++++++------- .../bundle/BundleInformationDialog.kt | 15 +++-- .../manager/ui/component/bundle/BundleItem.kt | 11 ++-- .../ui/component/bundle/BundleSelector.kt | 5 +- .../ui/component/bundle/ImportBundleDialog.kt | 20 +++--- .../revanced/manager/ui/model/BundleInfo.kt | 2 +- .../manager/ui/screen/DashboardScreen.kt | 10 +-- .../ui/viewmodel/DashboardViewModel.kt | 19 +++--- .../manager/ui/viewmodel/MainViewModel.kt | 2 +- app/src/main/res/values/strings.xml | 2 + 17 files changed, 162 insertions(+), 100 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt index 28f54e5c07..77de9b0311 100644 --- a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt +++ b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt @@ -9,7 +9,7 @@ interface PatchBundleDao { suspend fun all(): List @Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid") - fun getPropsById(uid: Int): Flow + fun getPropsById(uid: Int): Flow @Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid") suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) @@ -17,6 +17,9 @@ interface PatchBundleDao { @Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid") suspend fun setAutoUpdate(uid: Int, value: Boolean) + @Query("UPDATE patch_bundles SET name = :value WHERE uid = :uid") + suspend fun setName(uid: Int, value: String) + @Query("DELETE FROM patch_bundles WHERE uid != 0") suspend fun purgeCustomBundles() diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt index 9b6d1d60fb..1d8b41f326 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt @@ -7,7 +7,8 @@ import java.io.InputStream import java.nio.file.Files import java.nio.file.StandardCopyOption -class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSource(name, id, directory) { +class LocalPatchBundle(name: String, id: Int, directory: File) : + PatchBundleSource(name, id, directory) { suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) { withContext(Dispatchers.IO) { patches?.let { inputStream -> @@ -16,10 +17,16 @@ class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSour } } integrations?.let { - Files.copy(it, this@LocalPatchBundle.integrationsFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + it, + this@LocalPatchBundle.integrationsFile.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) } } - reload() + reload()?.also { + saveVersion(it.readManifestAttribute("Version"), null) + } } } diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt b/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt index ebff077c6c..712d653461 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt @@ -1,12 +1,20 @@ package app.revanced.manager.domain.bundles +import android.app.Application import android.util.Log +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import app.revanced.manager.R +import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository import app.revanced.manager.patcher.patch.PatchBundle import app.revanced.manager.util.tag import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.io.File import java.io.OutputStream @@ -14,13 +22,21 @@ import java.io.OutputStream * A [PatchBundle] source. */ @Stable -sealed class PatchBundleSource(val name: String, val uid: Int, directory: File) { +sealed class PatchBundleSource(initialName: String, val uid: Int, directory: File) : KoinComponent { + protected val configRepository: PatchBundlePersistenceRepository by inject() + private val app: Application by inject() protected val patchesFile = directory.resolve("patches.jar") protected val integrationsFile = directory.resolve("integrations.apk") private val _state = MutableStateFlow(load()) val state = _state.asStateFlow() + private val _nameFlow = MutableStateFlow(initialName) + val nameFlow = + _nameFlow.map { it.ifEmpty { app.getString(if (isDefault) R.string.bundle_name_default else R.string.bundle_name_fallback) } } + + suspend fun getName() = nameFlow.first() + /** * Returns true if the bundle has been downloaded to local storage. */ @@ -42,13 +58,38 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File) return try { State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists))) } catch (t: Throwable) { - Log.e(tag, "Failed to load patch bundle $name", t) + Log.e(tag, "Failed to load patch bundle with UID $uid", t) State.Failed(t) } } - fun reload() { - _state.value = load() + suspend fun reload(): PatchBundle? { + val newState = load() + _state.value = newState + + val bundle = newState.patchBundleOrNull() + // Try to read the name from the patch bundle manifest if the bundle does not have a name. + if (bundle != null && _nameFlow.value.isEmpty()) { + bundle.readManifestAttribute("Name")?.let { setName(it) } + } + + return bundle + } + + /** + * Create a flow that emits the [app.revanced.manager.data.room.bundles.BundleProperties] of this [PatchBundleSource]. + * The flow will emit null if the associated [PatchBundleSource] is deleted. + */ + fun propsFlow() = configRepository.getProps(uid) + suspend fun getProps() = configRepository.getProps(uid).first()!! + + suspend fun currentVersion() = getProps().versionInfo + protected suspend fun saveVersion(patches: String?, integrations: String?) = + configRepository.updateVersion(uid, patches, integrations) + + suspend fun setName(name: String) { + configRepository.setName(uid, name) + _nameFlow.value = name } sealed interface State { @@ -61,9 +102,12 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File) } } - companion object { - val PatchBundleSource.isDefault get() = uid == 0 - val PatchBundleSource.asRemoteOrNull get() = this as? RemotePatchBundle - fun PatchBundleSource.propsOrNullFlow() = asRemoteOrNull?.propsFlow() ?: flowOf(null) + companion object Extensions { + val PatchBundleSource.isDefault inline get() = uid == 0 + val PatchBundleSource.asRemoteOrNull inline get() = this as? RemotePatchBundle + val PatchBundleSource.nameState + @Composable inline get() = nameFlow.collectAsStateWithLifecycle( + "" + ) } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt index 1c857051d3..8bbc230d59 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt @@ -2,7 +2,6 @@ package app.revanced.manager.domain.bundles import androidx.compose.runtime.Stable import app.revanced.manager.data.room.bundles.VersionInfo -import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository import app.revanced.manager.network.api.ReVancedAPI import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType import app.revanced.manager.network.dto.BundleAsset @@ -15,17 +14,14 @@ import io.ktor.client.request.url import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.io.File @Stable sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpoint: String) : - PatchBundleSource(name, id, directory), KoinComponent { - private val configRepository: PatchBundlePersistenceRepository by inject() + PatchBundleSource(name, id, directory) { protected val http: HttpService by inject() protected abstract suspend fun getLatestInfo(): BundleInfo @@ -70,17 +66,11 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo true } - private suspend fun currentVersion() = configRepository.getProps(uid).first().versionInfo - private suspend fun saveVersion(patches: String, integrations: String) = - configRepository.updateVersion(uid, patches, integrations) - suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) { arrayOf(patchesFile, integrationsFile).forEach(File::delete) reload() } - fun propsFlow() = configRepository.getProps(uid) - suspend fun setAutoUpdate(value: Boolean) = configRepository.setAutoUpdate(uid, value) companion object { diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt index 2784841ce3..4b853ecf5d 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt @@ -22,7 +22,6 @@ class PatchBundlePersistenceRepository(db: AppDatabase) { suspend fun reset() = dao.reset() - suspend fun create(name: String, source: Source, autoUpdate: Boolean = false) = PatchBundleEntity( uid = generateUid(), @@ -36,17 +35,19 @@ class PatchBundlePersistenceRepository(db: AppDatabase) { suspend fun delete(uid: Int) = dao.remove(uid) - suspend fun updateVersion(uid: Int, patches: String, integrations: String) = + suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) = dao.updateVersion(uid, patches, integrations) suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value) + suspend fun setName(uid: Int, name: String) = dao.setName(uid, name) + fun getProps(id: Int) = dao.getPropsById(id).distinctUntilChanged() private companion object { val defaultSource = PatchBundleEntity( uid = 0, - name = "Default", + name = "", versionInfo = VersionInfo(), source = Source.API, autoUpdate = false diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt index 73debb820d..4f3ec2f5ff 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt @@ -137,16 +137,16 @@ class PatchBundleRepository( private fun addBundle(patchBundle: PatchBundleSource) = _sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } } - suspend fun createLocal(name: String, patches: InputStream, integrations: InputStream?) { - val id = persistenceRepo.create(name, SourceInfo.Local).uid - val bundle = LocalPatchBundle(name, id, directoryOf(id)) + suspend fun createLocal(patches: InputStream, integrations: InputStream?) { + val uid = persistenceRepo.create("", SourceInfo.Local).uid + val bundle = LocalPatchBundle("", uid, directoryOf(uid)) bundle.replace(patches, integrations) addBundle(bundle) } - suspend fun createRemote(name: String, url: String, autoUpdate: Boolean) { - val entity = persistenceRepo.create(name, SourceInfo.from(url), autoUpdate) + suspend fun createRemote(url: String, autoUpdate: Boolean) { + val entity = persistenceRepo.create("", SourceInfo.from(url), autoUpdate) addBundle(entity.load()) } @@ -174,8 +174,8 @@ class PatchBundleRepository( getBundlesByType().forEach { launch { - if (!it.propsFlow().first().autoUpdate) return@launch - Log.d(tag, "Updating patch bundle: ${it.name}") + if (!it.getProps().autoUpdate) return@launch + Log.d(tag, "Updating patch bundle: ${it.getName()}") it.update() } } diff --git a/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt b/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt index a463998f75..8dbcf1539b 100644 --- a/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt @@ -5,6 +5,8 @@ import app.revanced.manager.util.tag import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.patch.Patch import java.io.File +import java.io.IOException +import java.util.jar.JarFile class PatchBundle(val patchesJar: File, val integrations: File?) { private val loader = object : Iterable> { @@ -25,6 +27,17 @@ class PatchBundle(val patchesJar: File, val integrations: File?) { */ val patches = loader.map(::PatchInfo) + /** + * The [java.util.jar.Manifest] of [patchesJar]. + */ + private val manifest = try { + JarFile(patchesJar).use { it.manifest } + } catch (_: IOException) { + null + } + + fun readManifestAttribute(name: String) = manifest?.mainAttributes?.getValue(name) + /** * Load all patches compatible with the specified package. */ diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt index 32f625e2cd..c2e1fecad1 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt @@ -30,7 +30,7 @@ import app.revanced.manager.util.isDebuggable fun BaseBundleDialog( modifier: Modifier = Modifier, isDefault: Boolean, - name: String, + name: String?, onNameChange: ((String) -> Unit)? = null, remoteUrl: String?, onRemoteUrlChange: ((String) -> Unit)? = null, @@ -52,32 +52,34 @@ fun BaseBundleDialog( ) .then(modifier) ) { - var showNameInputDialog by rememberSaveable { - mutableStateOf(false) - } - if (showNameInputDialog) { - TextInputDialog( - initial = name, - title = stringResource(R.string.bundle_input_name), - onDismissRequest = { - showNameInputDialog = false - }, - onConfirm = { - showNameInputDialog = false - onNameChange?.invoke(it) - }, - validator = { - it.length in 1..19 + if (name != null) { + var showNameInputDialog by rememberSaveable { + mutableStateOf(false) + } + if (showNameInputDialog) { + TextInputDialog( + initial = name, + title = stringResource(R.string.bundle_input_name), + onDismissRequest = { + showNameInputDialog = false + }, + onConfirm = { + showNameInputDialog = false + onNameChange?.invoke(it) + }, + validator = { + it.length in 1..19 + } + ) + } + BundleListItem( + headlineText = stringResource(R.string.bundle_input_name), + supportingText = name.ifEmpty { stringResource(R.string.field_not_set) }, + modifier = Modifier.clickable(enabled = onNameChange != null) { + showNameInputDialog = true } ) } - BundleListItem( - headlineText = stringResource(R.string.bundle_input_name), - supportingText = name.ifEmpty { stringResource(R.string.field_not_set) }, - modifier = Modifier.clickable(enabled = onNameChange != null) { - showNameInputDialog = true - } - ) remoteUrl?.takeUnless { isDefault }?.let { url -> var showUrlInputDialog by rememberSaveable { diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt index 7634fb647a..1c67f360d7 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt @@ -23,9 +23,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R import app.revanced.manager.domain.bundles.LocalPatchBundle import app.revanced.manager.domain.bundles.PatchBundleSource -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.isDefault -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.propsOrNullFlow +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -44,7 +44,7 @@ fun BundleInformationDialog( bundle.state.map { it.patchBundleOrNull()?.patches?.size ?: 0 } }.collectAsStateWithLifecycle(0) val props by remember(bundle) { - bundle.propsOrNullFlow() + bundle.propsFlow() }.collectAsStateWithLifecycle(null) if (viewCurrentBundlePatches) { @@ -63,10 +63,12 @@ fun BundleInformationDialog( dismissOnBackPress = true ) ) { + val bundleName by bundle.nameState + Scaffold( topBar = { BundleTopBar( - title = bundle.name, + title = bundleName, onBackClick = onDismissRequest, onBackIcon = { Icon( @@ -98,7 +100,8 @@ fun BundleInformationDialog( BaseBundleDialog( modifier = Modifier.padding(paddingValues), isDefault = bundle.isDefault, - name = bundle.name, + name = bundleName, + onNameChange = { composableScope.launch { bundle.setName(it) } }, remoteUrl = bundle.asRemoteOrNull?.endpoint, patchCount = patchCount, version = props?.versionInfo?.patches, diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt index 6cb13891bc..31f4fef679 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R import app.revanced.manager.domain.bundles.PatchBundleSource -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.propsOrNullFlow +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState import kotlinx.coroutines.flow.map @OptIn(ExperimentalFoundationApi::class) @@ -45,8 +45,9 @@ fun BundleItem( val state by bundle.state.collectAsStateWithLifecycle() val version by remember(bundle) { - bundle.propsOrNullFlow().map { props -> props?.versionInfo?.patches } + bundle.propsFlow().map { props -> props?.versionInfo?.patches } }.collectAsStateWithLifecycle(null) + val name by bundle.nameState if (viewBundleDialogPage) { BundleInformationDialog( @@ -77,10 +78,10 @@ fun BundleItem( } } else null, - headlineContent = { Text(text = bundle.name) }, + headlineContent = { Text(name) }, supportingContent = { state.patchBundleOrNull()?.patches?.size?.let { patchCount -> - Text(text = pluralStringResource(R.plurals.patch_count, patchCount, patchCount)) + Text(pluralStringResource(R.plurals.patch_count, patchCount, patchCount)) } }, trailingContent = { @@ -95,7 +96,7 @@ fun BundleItem( icon?.let { (vector, description) -> Icon( - imageVector = vector, + vector, contentDescription = stringResource(description), modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.error diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt index 55da7e0f8a..8ea55e22f4 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt @@ -12,10 +12,12 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import app.revanced.manager.domain.bundles.PatchBundleSource +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -51,6 +53,7 @@ fun BundleSelector(bundles: List, onFinish: (PatchBundleSourc ) } bundles.forEach { + val name by it.nameState Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, @@ -62,7 +65,7 @@ fun BundleSelector(bundles: List, onFinish: (PatchBundleSourc } ) { Text( - text = it.name, + name, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface ) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt index 1402cf8276..481056df3d 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt @@ -35,11 +35,10 @@ import app.revanced.manager.util.JAR_MIMETYPE @Composable fun ImportBundleDialog( onDismissRequest: () -> Unit, - onRemoteSubmit: (String, String, Boolean) -> Unit, - onLocalSubmit: (String, Uri, Uri?) -> Unit, + onRemoteSubmit: (String, Boolean) -> Unit, + onLocalSubmit: (Uri, Uri?) -> Unit, initialBundleType: BundleType ) { - var name by rememberSaveable { mutableStateOf("") } var remoteUrl by rememberSaveable { mutableStateOf("") } var autoUpdate by rememberSaveable { mutableStateOf(true) } var bundleType by rememberSaveable { mutableStateOf(initialBundleType) } @@ -48,7 +47,7 @@ fun ImportBundleDialog( val inputsAreValid by remember { derivedStateOf { - name.isNotEmpty() && if (bundleType == BundleType.Local) patchBundle != null else remoteUrl.isNotEmpty() + if (bundleType == BundleType.Local) patchBundle != null else remoteUrl.isNotEmpty() } } @@ -56,6 +55,7 @@ fun ImportBundleDialog( rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { patchBundle = it } } + fun launchPatchActivity() { patchActivityLauncher.launch(JAR_MIMETYPE) } @@ -64,6 +64,7 @@ fun ImportBundleDialog( rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { integrations = it } } + fun launchIntegrationsActivity() { integrationsActivityLauncher.launch(APK_MIMETYPE) } @@ -91,12 +92,8 @@ fun ImportBundleDialog( enabled = inputsAreValid, onClick = { when (bundleType) { - BundleType.Local -> onLocalSubmit( - name, - patchBundle!!, - integrations - ) - BundleType.Remote -> onRemoteSubmit(name, remoteUrl, autoUpdate) + BundleType.Local -> onLocalSubmit(patchBundle!!, integrations) + BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate) } }, modifier = Modifier.padding(end = 16.dp) @@ -110,8 +107,7 @@ fun ImportBundleDialog( BaseBundleDialog( modifier = Modifier.padding(paddingValues), isDefault = false, - name = name, - onNameChange = { name = it }, + name = null, remoteUrl = remoteUrl.takeUnless { bundleType == BundleType.Local }, onRemoteUrlChange = { remoteUrl = it }, patchCount = 0, diff --git a/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt b/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt index db29572444..e8dc938d7f 100644 --- a/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt +++ b/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt @@ -75,7 +75,7 @@ data class BundleInfo( targetList.add(it) } - BundleInfo(source.name, source.uid, supported, unsupported, universal) + BundleInfo(source.getName(), source.uid, supported, unsupported, universal) } } } 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 03b0e26c84..59ed6f4efc 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 @@ -46,7 +46,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R import app.revanced.manager.data.room.apps.installed.InstalledApp -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.isDefault +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault import app.revanced.manager.patcher.aapt.Aapt import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AutoUpdatesDialog @@ -102,13 +102,13 @@ fun DashboardScreen( ImportBundleDialog( onDismissRequest = ::dismiss, - onLocalSubmit = { name, patches, integrations -> + onLocalSubmit = { patches, integrations -> dismiss() - vm.createLocalSource(name, patches, integrations) + vm.createLocalSource(patches, integrations) }, - onRemoteSubmit = { name, url, autoUpdate -> + onRemoteSubmit = { url, autoUpdate -> dismiss() - vm.createRemoteSource(name, url, autoUpdate) + vm.createRemoteSource(url, autoUpdate) }, initialBundleType = it ) 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 22b83770f3..c2dd6d6419 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 @@ -12,7 +12,7 @@ import androidx.lifecycle.viewModelScope import app.revanced.manager.R import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.domain.bundles.PatchBundleSource -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull import app.revanced.manager.domain.bundles.RemotePatchBundle import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.domain.repository.PatchBundleRepository @@ -80,20 +80,17 @@ class DashboardViewModel( fun cancelSourceSelection() { selectedSources.clear() } - fun createLocalSource(name: String, patchBundle: Uri, integrations: Uri?) = + fun createLocalSource(patchBundle: Uri, integrations: Uri?) = viewModelScope.launch { contentResolver.openInputStream(patchBundle)!!.use { patchesStream -> - val integrationsStream = integrations?.let { contentResolver.openInputStream(it) } - try { - patchBundleRepository.createLocal(name, patchesStream, integrationsStream) - } finally { - integrationsStream?.close() + integrations?.let { contentResolver.openInputStream(it) }.use { integrationsStream -> + patchBundleRepository.createLocal(patchesStream, integrationsStream) } } } - fun createRemoteSource(name: String, apiUrl: String, autoUpdate: Boolean) = - viewModelScope.launch { patchBundleRepository.createRemote(name, apiUrl, autoUpdate) } + fun createRemoteSource(apiUrl: String, autoUpdate: Boolean) = + viewModelScope.launch { patchBundleRepository.createRemote(apiUrl, autoUpdate) } fun delete(bundle: PatchBundleSource) = viewModelScope.launch { patchBundleRepository.remove(bundle) } @@ -107,9 +104,9 @@ class DashboardViewModel( RemotePatchBundle.updateFailMsg ) { if (bundle.update()) - app.toast(app.getString(R.string.bundle_update_success, bundle.name)) + app.toast(app.getString(R.string.bundle_update_success, bundle.getName())) else - app.toast(app.getString(R.string.bundle_update_unavailable, bundle.name)) + app.toast(app.getString(R.string.bundle_update_unavailable, bundle.getName())) } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt index b512749abf..4f0fbcd96b 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt @@ -11,7 +11,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.revanced.manager.R -import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull +import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull import app.revanced.manager.domain.manager.KeystoreManager import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.domain.repository.PatchBundleRepository diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f77fc91ee0..6d3dc16f95 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,8 @@ Missing Error + Default + Unnamed %1$s • %2$d available patches From d8248cc915745f618ffb1f3a2d56d9a6f62a189d Mon Sep 17 00:00:00 2001 From: Ax333l Date: Wed, 3 Jul 2024 12:27:39 +0200 Subject: [PATCH 08/10] fix: import bundles on another thread --- .../manager/domain/repository/PatchBundleRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt index 4f3ec2f5ff..f40d6c0bfb 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt @@ -137,7 +137,7 @@ class PatchBundleRepository( private fun addBundle(patchBundle: PatchBundleSource) = _sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } } - suspend fun createLocal(patches: InputStream, integrations: InputStream?) { + suspend fun createLocal(patches: InputStream, integrations: InputStream?) = withContext(Dispatchers.Default) { val uid = persistenceRepo.create("", SourceInfo.Local).uid val bundle = LocalPatchBundle("", uid, directoryOf(uid)) @@ -145,7 +145,7 @@ class PatchBundleRepository( addBundle(bundle) } - suspend fun createRemote(url: String, autoUpdate: Boolean) { + suspend fun createRemote(url: String, autoUpdate: Boolean) = withContext(Dispatchers.Default) { val entity = persistenceRepo.create("", SourceInfo.from(url), autoUpdate) addBundle(entity.load()) } From 8d3d500b7b2dee95fbb02aab49404a8d41eb2dd3 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Wed, 3 Jul 2024 13:54:37 +0200 Subject: [PATCH 09/10] feat: add ability to share debug logs --- .../screen/settings/AdvancedSettingsScreen.kt | 11 +++++ .../ui/viewmodel/AdvancedSettingsViewModel.kt | 49 ++++++++++++++++++- app/src/main/res/values/strings.xml | 4 ++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt index f46849c8e2..3817bd12eb 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt @@ -2,6 +2,8 @@ package app.revanced.manager.ui.screen.settings import android.app.ActivityManager import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -86,6 +88,15 @@ fun AdvancedSettingsScreen( } ) + val exportDebugLogsLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { + it?.let(vm::exportDebugLogs) + } + SettingsListItem( + headlineContent = stringResource(R.string.debug_logs_export), + modifier = Modifier.clickable { exportDebugLogsLauncher.launch(vm.debugLogFileName) } + ) + GroupHeader(stringResource(R.string.patcher)) BooleanItem( preference = vm.prefs.useProcessRuntime, diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt index 9efed1dd67..5db843c0a7 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt @@ -1,21 +1,41 @@ package app.revanced.manager.ui.viewmodel import android.app.Application +import android.net.Uri +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.revanced.manager.R +import app.revanced.manager.domain.bundles.RemotePatchBundle import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.domain.repository.PatchBundleRepository -import app.revanced.manager.domain.bundles.RemotePatchBundle +import app.revanced.manager.util.tag +import app.revanced.manager.util.toast import app.revanced.manager.util.uiSafe +import com.github.pgreze.process.Redirect +import com.github.pgreze.process.process +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter class AdvancedSettingsViewModel( val prefs: PreferencesManager, private val app: Application, private val patchBundleRepository: PatchBundleRepository ) : ViewModel() { + val debugLogFileName: String + get() { + val time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()) + + return "revanced-manager_logcat_$time" + } + fun setApiUrl(value: String) = viewModelScope.launch(Dispatchers.Default) { if (value == prefs.api.get()) return@launch @@ -32,4 +52,31 @@ class AdvancedSettingsViewModel( fun resetBundles() = viewModelScope.launch { patchBundleRepository.reset() } + + fun exportDebugLogs(target: Uri) = viewModelScope.launch { + val exitCode = try { + withContext(Dispatchers.IO) { + app.contentResolver.openOutputStream(target)!!.bufferedWriter().use { writer -> + val consumer = Redirect.Consume { flow -> + flow.onEach { + writer.write(it) + }.flowOn(Dispatchers.IO).collect() + } + + process("logcat", "-d", stdout = consumer).resultCode + } + } + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Log.e(tag, "Got exception while exporting logs", e) + app.toast(app.getString(R.string.debug_logs_export_failed)) + return@launch + } + + if (exitCode == 0) + app.toast(app.getString(R.string.debug_logs_export_success)) + else + app.toast(app.getString(R.string.debug_logs_export_read_failed, exitCode)) + } } \ 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 6d3dc16f95..5b833f6af4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -137,6 +137,10 @@ This is faster and allows Patcher to use more memory. Patcher process memory limit The max amount of memory that the Patcher process can use (in megabytes) + Export debug logs + Failed to read logs (exit code %d) + Failed to export logs + Exported logs API URL Set custom API URL You may have issues with features when using a custom API URL. From 48fe3a707eed24f1634ff009b787254976ea0220 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Wed, 3 Jul 2024 14:46:00 +0200 Subject: [PATCH 10/10] fix: import export screen UX --- .../manager/domain/manager/KeystoreManager.kt | 8 +++++--- .../manager/ui/viewmodel/ImportExportViewModel.kt | 12 ++++++++++-- app/src/main/res/values/strings.xml | 7 ++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt index bba8035f24..c1c4700dd1 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt @@ -49,15 +49,17 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) { ) ) ) - keystorePath.outputStream().use { - ks.store(it, null) + withContext(Dispatchers.IO) { + keystorePath.outputStream().use { + ks.store(it, null) + } } updatePrefs(DEFAULT, DEFAULT) } suspend fun import(cn: String, pass: String, keystore: InputStream): Boolean { - val keystoreData = keystore.readBytes() + val keystoreData = withContext(Dispatchers.IO) { keystore.readBytes() } try { val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt index d568317d51..9bf085626a 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt @@ -55,14 +55,17 @@ class ImportExportViewModel( fun resetOptionsForPackage(packageName: String) = viewModelScope.launch { optionsRepository.clearOptionsForPackage(packageName) + app.toast(app.getString(R.string.patch_options_reset_toast)) } fun clearOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch { optionsRepository.clearOptionsForPatchBundle(patchBundle.uid) + app.toast(app.getString(R.string.patch_options_reset_toast)) } fun resetOptions() = viewModelScope.launch { optionsRepository.reset() + app.toast(app.getString(R.string.patch_options_reset_toast)) } fun startKeystoreImport(content: Uri) = viewModelScope.launch { @@ -98,6 +101,7 @@ class ImportExportViewModel( private suspend fun tryKeystoreImport(cn: String, pass: String, path: Path): Boolean { path.inputStream().use { stream -> if (keystoreManager.import(cn, pass, stream)) { + app.toast(app.getString(R.string.import_keystore_success)) cancelKeystoreImport() return true } @@ -116,6 +120,7 @@ class ImportExportViewModel( fun exportKeystore(target: Uri) = viewModelScope.launch { keystoreManager.export(contentResolver.openOutputStream(target)!!) + app.toast(app.getString(R.string.export_keystore_success)) } fun regenerateKeystore() = viewModelScope.launch { @@ -123,8 +128,9 @@ class ImportExportViewModel( app.toast(app.getString(R.string.regenerate_keystore_success)) } - fun resetSelection() = viewModelScope.launch(Dispatchers.Default) { - selectionRepository.reset() + fun resetSelection() = viewModelScope.launch { + withContext(Dispatchers.Default) { selectionRepository.reset() } + app.toast(app.getString(R.string.reset_patch_selection_success)) } fun executeSelectionAction(target: Uri) = viewModelScope.launch { @@ -173,6 +179,7 @@ class ImportExportViewModel( } selectionRepository.import(bundleUid, selection) + app.toast(app.getString(R.string.import_patch_selection_success)) } } @@ -191,6 +198,7 @@ class ImportExportViewModel( Json.Default.encodeToStream(selection, it) } } + app.toast(app.getString(R.string.export_patch_selection_success)) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b833f6af4..4fd92ba93e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,20 +81,25 @@ Password Import Wrong keystore credentials + Imported keystore Export keystore Export the current keystore No keystore to export + Exported keystore Regenerate keystore Generate a new keystore The keystore has been successfully replaced Import patch selection Import patch selection from a JSON file Could not import patch selection: %s + Imported patch selection Export patch selection - Export patch selection from a JSON file + Export patch selection to a JSON file Could not export patch selection: %s + Exported patch selection Reset patch selection Reset the stored patch selection + Patch selection has been reset Reset patch options for app Resets patch options for a single app Resets patch options for bundle