feat: select patch bundle from storage

Co-authored-by: Canny <canny1913@outlook.com>
This commit is contained in:
Ushie 2022-10-12 15:14:24 +03:00 committed by GitHub
parent 7b19a4fb83
commit 69df08e0ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 75 deletions

View File

@ -26,5 +26,10 @@
<option name="name" value="Google" /> <option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://maven.pkg.github.com/revanced/revanced-patcher" />
</remote-repository>
</component> </component>
</project> </project>

View File

@ -1,5 +1,6 @@
package app.revanced.manager.ui.screen.subscreens package app.revanced.manager.ui.screen.subscreens
import android.annotation.SuppressLint
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -9,7 +10,6 @@ import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -30,6 +30,7 @@ import app.revanced.patcher.extensions.PatchExtensions.version
import com.xinto.taxi.BackstackNavigator import com.xinto.taxi.BackstackNavigator
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PatchesSelectorSubscreen( fun PatchesSelectorSubscreen(
@ -39,7 +40,6 @@ fun PatchesSelectorSubscreen(
val patches = pvm.getFilteredPatchesAndCheckOptions() val patches = pvm.getFilteredPatchesAndCheckOptions()
var query by mutableStateOf("") var query by mutableStateOf("")
Scaffold( Scaffold(
topBar = { topBar = {
MediumTopAppBar( MediumTopAppBar(
@ -68,7 +68,7 @@ fun PatchesSelectorSubscreen(
} }
} }
) )
}, }
) { paddingValues -> ) { paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier
@ -76,60 +76,68 @@ fun PatchesSelectorSubscreen(
) { ) {
when (patchesState) { when (patchesState) {
is Resource.Success -> { is Resource.Success -> {
Box( if (patches.isNotEmpty()) {
modifier = Modifier Box(
.fillMaxWidth() modifier = Modifier
.padding(8.dp, 4.dp), .fillMaxWidth()
) { .padding(8.dp, 4.dp),
Row(
modifier = Modifier.fillMaxWidth()
) { ) {
OutlinedTextField( Row(
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth() ) {
.padding(8.dp), OutlinedTextField(
shape = RoundedCornerShape(12.dp), modifier = Modifier
value = query, .fillMaxWidth()
onValueChange = { newValue -> .padding(8.dp),
query = newValue shape = RoundedCornerShape(12.dp),
}, value = query,
leadingIcon = { onValueChange = { newValue ->
Icon(Icons.Default.Search, "Search") query = newValue
}, },
trailingIcon = { leadingIcon = {
if (query.isNotEmpty()) { Icon(Icons.Default.Search, "Search")
IconButton(onClick = { },
query = "" trailingIcon = {
}) { if (query.isNotEmpty()) {
Icon(Icons.Default.Clear, "Clear") IconButton(onClick = {
query = ""
}) {
Icon(Icons.Default.Clear, "Clear")
}
} }
} },
}, )
)
}
}
LazyColumn(Modifier.padding(0.dp, 2.dp)) {
if (query.isEmpty() || query.isBlank()) {
items(count = patches.size) {
val patch = patches[it]
val name = patch.patch.patchName
PatchCard(patch, pvm.isPatchSelected(name)) {
pvm.selectPatch(name, !pvm.isPatchSelected(name))
}
} }
} else { }
items(count = patches.size) { LazyColumn(Modifier.padding(0.dp, 2.dp)) {
val patch = patches[it]
val name = patch.patch.patchName if (query.isEmpty() || query.isBlank()) {
if (name.contains(query.lowercase())) { items(count = patches.size) {
val patch = patches[it]
val name = patch.patch.patchName
PatchCard(patch, pvm.isPatchSelected(name)) { PatchCard(patch, pvm.isPatchSelected(name)) {
pvm.selectPatch(name, !pvm.isPatchSelected(name)) pvm.selectPatch(name, !pvm.isPatchSelected(name))
} }
} }
} else {
items(count = patches.size) {
val patch = patches[it]
val name = patch.patch.patchName
if (name.contains(query.lowercase())) {
PatchCard(patch, pvm.isPatchSelected(name)) {
pvm.selectPatch(name, !pvm.isPatchSelected(name))
}
}
}
} }
} }
} }
else {
Column(Modifier.fillMaxSize(), Arrangement.Center, Alignment.CenterHorizontally) {
Text(text = "No compatible patches found.")
}
}
} }
else -> LoadingIndicator(null) else -> LoadingIndicator(null)
} }

View File

@ -1,38 +1,62 @@
package app.revanced.manager.ui.screen.subscreens package app.revanced.manager.ui.screen.subscreens
import androidx.compose.foundation.* import android.widget.Toast
import androidx.compose.foundation.layout.* import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.SourceItem import app.revanced.manager.ui.component.SourceItem
import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.theme.Typography import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
import coil.compose.AsyncImage
import com.xinto.taxi.BackstackNavigator import com.xinto.taxi.BackstackNavigator
import org.koin.androidx.compose.getViewModel
import java.nio.file.Files
import java.nio.file.StandardCopyOption
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SourceSelectorSubscreen( fun SourceSelectorSubscreen(
navigator: BackstackNavigator<AppDestination> navigator: BackstackNavigator<AppDestination>,
pvm: PatcherScreenViewModel = getViewModel()
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
state = rememberTopAppBarState(), state = rememberTopAppBarState(),
canScroll = { true } canScroll = { true }
) )
val context = LocalContext.current
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri ->
val patchesFile = context.cacheDir.resolve("patches.jar")
Files.copy(
context.contentResolver.openInputStream(uri),
patchesFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
pvm.patchBundleFile = patchesFile.absolutePath
pvm.loadPatches0()
navigator.pop()
return@rememberLauncherForActivityResult
}
Toast.makeText(context, "Couldn't load local patch bundle.", Toast.LENGTH_SHORT).show()
}
Scaffold( Scaffold(
modifier = Modifier modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
@ -61,7 +85,7 @@ fun SourceSelectorSubscreen(
) { ) {
ListItem( ListItem(
modifier = Modifier modifier = Modifier
.clickable { /* TODO */ }, .clickable { filePicker.launch(arrayOf("application/java-archive")) },
headlineText = { Text(stringResource(R.string.select_bundle_from_storage)) }, headlineText = { Text(stringResource(R.string.select_bundle_from_storage)) },
leadingContent = { leadingContent = {
Icon( Icon(

View File

@ -5,6 +5,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.revanced.manager.Variables.patches import app.revanced.manager.Variables.patches
@ -12,7 +13,6 @@ import app.revanced.manager.Variables.selectedAppPackage
import app.revanced.manager.Variables.selectedPatches import app.revanced.manager.Variables.selectedPatches
import app.revanced.manager.api.API import app.revanced.manager.api.API
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.util.tag
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.options import app.revanced.patcher.extensions.PatchExtensions.options
@ -24,7 +24,8 @@ import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class PatcherScreenViewModel(private val app: Application, private val api: API) : ViewModel() { class PatcherScreenViewModel(private val app: Application, private val api: API) : ViewModel() {
private lateinit var patchBundleFile: String lateinit var patchBundleFile: String
private val tag = "ReVanced Manager"
init { init {
viewModelScope.launch { viewModelScope.launch {
@ -66,13 +67,13 @@ class PatcherScreenViewModel(private val app: Application, private val api: API)
fun getSelectedPackageInfo(): PackageInfo? { fun getSelectedPackageInfo(): PackageInfo? {
if (selectedAppPackage.value.isPresent) { return if (selectedAppPackage.value.isPresent) {
return app.packageManager.getPackageArchiveInfo( app.packageManager.getPackageArchiveInfo(
selectedAppPackage.value.get().publicSourceDir, selectedAppPackage.value.get().publicSourceDir,
PackageManager.GET_META_DATA PackageManager.GET_META_DATA
) )
} else { } else {
return null null
} }
} }
@ -95,21 +96,27 @@ class PatcherScreenViewModel(private val app: Application, private val api: API)
patchBundleFile = file.absolutePath patchBundleFile = file.absolutePath
loadPatches0() loadPatches0()
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ReVancedManager", "An error occurred while loading patches", e) Log.e(tag, "An error occurred while loading patches", e)
} }
} }
fun loadPatches0() { fun loadPatches0() {
val patchClasses = PatchBundle.Dex( try {
patchBundleFile, DexClassLoader( val patchClasses = PatchBundle.Dex(
patchBundleFile, patchBundleFile, DexClassLoader(
app.codeCacheDir.absolutePath, patchBundleFile,
null, app.codeCacheDir.absolutePath,
javaClass.classLoader null,
) javaClass.classLoader
).loadPatches() )
patches.value = Resource.Success(patchClasses) ).loadPatches()
Log.d("ReVanced Manager", "Finished loading patches") patches.value = Resource.Success(patchClasses)
} catch (e: Exception) {
Toast.makeText(app, "Failed to load patch bundle.", Toast.LENGTH_LONG).show()
Log.e(tag, "Failed to load patch bundle.", e)
return
}
Toast.makeText(app, "Successfully loaded patch bundle.", Toast.LENGTH_LONG).show()
} }
fun getFilteredPatchesAndCheckOptions(): List<PatchClass> { fun getFilteredPatchesAndCheckOptions(): List<PatchClass> {