mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-04-29 22:14:28 +02:00
refactor: code cleanup and api refactor
This commit is contained in:
parent
0448a1dc87
commit
e4fe4a6a40
@ -25,11 +25,6 @@ android {
|
||||
compileSdk = 33
|
||||
buildToolsVersion = "33.0.0"
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
disable += "DialogFragmentCallbacksDetector"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.revanced.manager.compose"
|
||||
minSdk = 26
|
||||
@ -38,6 +33,10 @@ android {
|
||||
versionName = "0.0.1"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
buildConfigField("String", "REVANCED_API_URL", "\"https://releases.revanced.app\"")
|
||||
buildConfigField("String", "GITHUB_API_URL", "\"https://api.github.com\"")
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@ -81,24 +80,24 @@ dependencies {
|
||||
implementation("androidx.core:core-splashscreen:1.0.0")
|
||||
|
||||
// AndroidX activity
|
||||
implementation("androidx.activity:activity-compose:1.6.0")
|
||||
implementation("androidx.activity:activity-compose:1.6.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.7.1")
|
||||
|
||||
// Koin
|
||||
val koinVersion = "3.2.2"
|
||||
val koinVersion = "3.3.0"
|
||||
implementation("io.insert-koin:koin-android:$koinVersion")
|
||||
implementation("io.insert-koin:koin-androidx-compose:3.2.1")
|
||||
implementation("io.insert-koin:koin-androidx-compose:3.3.0")
|
||||
implementation("io.insert-koin:koin-androidx-workmanager:$koinVersion")
|
||||
|
||||
// Compose
|
||||
val composeVersion = "1.3.0-rc01"
|
||||
val composeVersion = "1.4.0-alpha01"
|
||||
implementation("androidx.compose.ui:ui:$composeVersion")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
|
||||
implementation("androidx.compose.material3:material3:1.0.0-rc01")
|
||||
implementation("androidx.compose.material3:material3:1.1.0-alpha01")
|
||||
implementation("androidx.compose.material:material-icons-extended:${composeVersion}")
|
||||
|
||||
// Accompanist
|
||||
val accompanistVersion = "0.26.5-rc"
|
||||
val accompanistVersion = "0.27.0"
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
||||
implementation("com.google.accompanist:accompanist-placeholder-material:$accompanistVersion")
|
||||
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
|
||||
@ -108,7 +107,7 @@ dependencies {
|
||||
implementation("io.coil-kt:coil-compose:2.2.2")
|
||||
|
||||
// KotlinX
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||
|
||||
// Taxi (navigation)
|
||||
implementation("com.github.X1nto:Taxi:1.2.0")
|
||||
@ -118,18 +117,16 @@ dependencies {
|
||||
|
||||
// Signing & aligning
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
implementation("com.android.tools.build:apksig:7.4.0-beta02")
|
||||
implementation("com.android.tools.build:apksig:8.0.0-alpha07")
|
||||
|
||||
// Licenses
|
||||
implementation("com.mikepenz:aboutlibraries-compose:10.5.1")
|
||||
|
||||
// ListenableFuture
|
||||
implementation("com.google.guava:guava:31.1-android")
|
||||
implementation("androidx.concurrent:concurrent-futures:1.1.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4")
|
||||
|
||||
// Networking
|
||||
implementation("com.vk.knet:core:1.0")
|
||||
implementation("com.vk.knet:cronet:1.0")
|
||||
implementation("com.vk.knet:okcronet:1.0")
|
||||
// Ktor
|
||||
val ktorVersion = "2.1.3"
|
||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||
}
|
||||
|
@ -1,12 +1,6 @@
|
||||
package app.revanced.manager
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
@ -18,12 +12,14 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import app.revanced.manager.preferences.PreferencesManager
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.screen.MainDashboardScreen
|
||||
import app.revanced.manager.ui.screen.subscreens.*
|
||||
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.util.requestAllFilesAccess
|
||||
import app.revanced.manager.util.requestIgnoreBatteryOptimizations
|
||||
import com.xinto.taxi.Taxi
|
||||
import com.xinto.taxi.rememberBackstackNavigator
|
||||
import org.koin.android.ext.android.inject
|
||||
@ -35,7 +31,6 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
permissions()
|
||||
setContent {
|
||||
ReVancedManagerTheme(
|
||||
dynamicColor = prefs.dynamicColor,
|
||||
@ -65,26 +60,4 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun permissions() {
|
||||
|
||||
fun request(string: String) {
|
||||
val intent = Intent(string)
|
||||
intent.addCategory("android.intent.category.DEFAULT")
|
||||
intent.data = Uri.fromParts("package", applicationContext.packageName, null)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivityForResult(intent, 1)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||
request(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
} else {
|
||||
requestPermissions(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 1)
|
||||
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
|
||||
}
|
||||
val pm = applicationContext.getSystemService(POWER_SERVICE) as PowerManager
|
||||
if (!pm.isIgnoringBatteryOptimizations(applicationContext.packageName)) {
|
||||
request(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ package app.revanced.manager
|
||||
|
||||
import android.app.Application
|
||||
import app.revanced.manager.di.*
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.workmanager.koin.workManagerFactory
|
||||
import org.koin.core.context.startKoin
|
||||
@ -13,7 +15,15 @@ class ManagerApplication : Application() {
|
||||
startKoin {
|
||||
androidContext(this@ManagerApplication)
|
||||
workManagerFactory()
|
||||
modules(httpModule, preferencesModule, viewModelModule, repositoryModule, workerModule)
|
||||
modules(
|
||||
httpModule,
|
||||
preferencesModule,
|
||||
viewModelModule,
|
||||
repositoryModule,
|
||||
workerModule,
|
||||
patcherModule,
|
||||
serviceModule
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package app.revanced.manager
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import java.util.*
|
||||
|
||||
object Variables {
|
||||
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
|
||||
val selectedPatches = mutableStateListOf<String>()
|
||||
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading)
|
||||
val patchesState by patches
|
||||
}
|
@ -1,56 +1,55 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import android.content.Context
|
||||
import com.vk.knet.core.Knet
|
||||
import com.vk.knet.core.utils.ByteArrayPool
|
||||
import com.vk.knet.cornet.CronetKnetEngine
|
||||
import com.vk.knet.cornet.config.CronetCache
|
||||
import com.vk.knet.cornet.config.CronetQuic
|
||||
import com.vk.knet.cornet.pool.buffer.CronetNativeByteBufferPool
|
||||
import android.util.Log
|
||||
import app.revanced.manager.util.tag
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.okhttp.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.plugins.logging.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Cache
|
||||
import okhttp3.Dns
|
||||
import okhttp3.Protocol
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
|
||||
val httpModule = module {
|
||||
fun client(appContext: Context) = CronetKnetEngine.Build(appContext) {
|
||||
client {
|
||||
setCache(CronetCache.Disk(appContext.filesDir, 1024 * 1024 * 10))
|
||||
|
||||
enableHttp2(true)
|
||||
enableQuic(
|
||||
CronetQuic()
|
||||
)
|
||||
|
||||
useBrotli(true)
|
||||
connectTimeout(15, TimeUnit.SECONDS)
|
||||
writeTimeout(15, TimeUnit.SECONDS)
|
||||
readTimeout(15, TimeUnit.SECONDS)
|
||||
|
||||
nativePool(CronetNativeByteBufferPool.DEFAULT)
|
||||
arrayPool(ByteArrayPool.DEFAULT)
|
||||
|
||||
maxConcurrentRequests(50)
|
||||
maxConcurrentRequestsPerHost(10)
|
||||
|
||||
followRedirects(true)
|
||||
followSslRedirects(true)
|
||||
fun provideHttpClient(context: Context, json: Json) = HttpClient(OkHttp) {
|
||||
engine {
|
||||
config {
|
||||
dns(object : Dns {
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
val addresses = Dns.SYSTEM.lookup(hostname)
|
||||
return if (hostname == "raw.githubusercontent.com") {
|
||||
addresses.filterIsInstance<Inet4Address>()
|
||||
} else {
|
||||
addresses
|
||||
}
|
||||
}
|
||||
})
|
||||
cache(Cache(context.cacheDir.resolve("cache").also { it.mkdirs() }, 1024 * 1024 * 100))
|
||||
followRedirects(true)
|
||||
followSslRedirects(true)
|
||||
}
|
||||
}
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
}
|
||||
}
|
||||
|
||||
fun json() = Json {
|
||||
fun provideJson() = Json {
|
||||
encodeDefaults = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
single {
|
||||
client(androidContext())
|
||||
}
|
||||
single {
|
||||
json()
|
||||
}
|
||||
single {
|
||||
Knet.Build(get<CronetKnetEngine>())
|
||||
provideHttpClient(androidContext(), get())
|
||||
}
|
||||
singleOf(::provideJson)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import android.content.Context
|
||||
import app.revanced.manager.preferences.PreferencesManager
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val patcherModule = module {
|
||||
singleOf(::PatcherUtils)
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import app.revanced.manager.network.api.GitHubAPI
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.domain.repository.GithubRepositoryImpl
|
||||
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
|
||||
import app.revanced.manager.network.api.ManagerAPI
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val repositoryModule = module {
|
||||
singleOf(::GitHubAPI)
|
||||
singleOf(::ReVancedAPI)
|
||||
singleOf(::GithubRepositoryImpl)
|
||||
singleOf(::ReVancedRepositoryImpl)
|
||||
singleOf(::ManagerAPI)
|
||||
}
|
29
app/src/main/java/app/revanced/manager/di/ServiceModule.kt
Normal file
29
app/src/main/java/app/revanced/manager/di/ServiceModule.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import app.revanced.manager.network.service.*
|
||||
import io.ktor.client.*
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val serviceModule = module {
|
||||
|
||||
fun provideGithubService(
|
||||
client: HttpService,
|
||||
): GithubService {
|
||||
return GithubServiceImpl(
|
||||
client = client,
|
||||
)
|
||||
}
|
||||
|
||||
fun provideReVancedService(
|
||||
client: HttpService,
|
||||
): ReVancedService {
|
||||
return ReVancedServiceImpl(
|
||||
client = client,
|
||||
)
|
||||
}
|
||||
|
||||
single { provideGithubService(get()) }
|
||||
single { provideReVancedService(get()) }
|
||||
singleOf(::HttpService)
|
||||
}
|
@ -7,8 +7,8 @@ import org.koin.dsl.module
|
||||
val viewModelModule = module {
|
||||
viewModelOf(::SettingsViewModel)
|
||||
viewModelOf(::DashboardViewModel)
|
||||
viewModelOf(::PatcherScreenViewModel)
|
||||
viewModelOf(::AppSelectorViewModel)
|
||||
viewModelOf(::PatchesSelectorViewModel)
|
||||
viewModelOf(::PatchingScreenViewModel)
|
||||
viewModelOf(::ContributorsViewModel)
|
||||
}
|
@ -6,5 +6,5 @@ import org.koin.androidx.workmanager.dsl.worker
|
||||
import org.koin.dsl.module
|
||||
|
||||
val workerModule = module {
|
||||
worker { PatcherWorker(androidContext(), get(), get(), get(), get()) }
|
||||
worker { PatcherWorker(androidContext(), get(), get(), get()) }
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package app.revanced.manager.domain.manager
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import app.revanced.manager.domain.manager.base.BasePreferenceManager
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.util.ghIntegrations
|
||||
import app.revanced.manager.util.ghPatches
|
||||
|
||||
/**
|
||||
* @author Hyperion Authors, zt64
|
||||
*/
|
||||
class PreferencesManager(
|
||||
sharedPreferences: SharedPreferences
|
||||
) : BasePreferenceManager(sharedPreferences) {
|
||||
var dynamicColor by booleanPreference("dynamic_color", true)
|
||||
var theme by enumPreference("theme", Theme.SYSTEM)
|
||||
var srcPatches by stringPreference("src_patches", ghPatches)
|
||||
var srcIntegrations by stringPreference("src_integrations", ghIntegrations)
|
||||
}
|
@ -1,28 +1,15 @@
|
||||
package app.revanced.manager.preferences
|
||||
package app.revanced.manager.domain.manager.base
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.edit
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.util.ghIntegrations
|
||||
import app.revanced.manager.util.ghPatches
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class PreferencesManager(
|
||||
sharedPreferences: SharedPreferences
|
||||
) : BasePreferenceManager(sharedPreferences) {
|
||||
var dynamicColor by booleanPreference("dynamic_color", true)
|
||||
var theme by enumPreference("theme", Theme.SYSTEM)
|
||||
var srcPatches by stringPreference("src_patches", ghPatches)
|
||||
var srcIntegrations by stringPreference("src_integrations", ghIntegrations)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Hyperion Authors, zt64
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class BasePreferenceManager(
|
||||
private val prefs: SharedPreferences
|
||||
) {
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.manager.domain.repository
|
||||
|
||||
import app.revanced.manager.network.api.PatchesAsset
|
||||
import app.revanced.manager.network.dto.GithubContributor
|
||||
import app.revanced.manager.network.dto.GithubReleases
|
||||
import app.revanced.manager.network.service.GithubService
|
||||
import app.revanced.manager.network.utils.APIResponse
|
||||
|
||||
interface GithubRepository {
|
||||
suspend fun getReleases(repo: String): APIResponse<GithubReleases>
|
||||
|
||||
suspend fun getContributors(repo: String): APIResponse<List<GithubContributor>>
|
||||
|
||||
suspend fun findAsset(repo: String, file: String): PatchesAsset
|
||||
}
|
||||
|
||||
class GithubRepositoryImpl(
|
||||
private val service: GithubService
|
||||
) : GithubRepository {
|
||||
override suspend fun getReleases(repo: String) = service.getReleases(repo)
|
||||
|
||||
override suspend fun getContributors(repo: String) = service.getContributors(repo)
|
||||
|
||||
override suspend fun findAsset(repo: String, file: String) = service.findAsset(repo, file)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.manager.domain.repository
|
||||
|
||||
import app.revanced.manager.network.api.PatchesAsset
|
||||
import app.revanced.manager.network.dto.ReVancedReleases
|
||||
import app.revanced.manager.network.dto.ReVancedRepositories
|
||||
import app.revanced.manager.network.service.ReVancedService
|
||||
import app.revanced.manager.network.utils.APIResponse
|
||||
|
||||
interface ReVancedRepository {
|
||||
suspend fun getAssets(): APIResponse<ReVancedReleases>
|
||||
|
||||
suspend fun getContributors(): APIResponse<ReVancedRepositories>
|
||||
|
||||
suspend fun findAsset(repo: String, file: String): PatchesAsset
|
||||
}
|
||||
|
||||
class ReVancedRepositoryImpl(
|
||||
private val service: ReVancedService
|
||||
) : ReVancedRepository {
|
||||
override suspend fun getAssets() = service.getAssets()
|
||||
|
||||
override suspend fun getContributors() = service.getContributors()
|
||||
|
||||
override suspend fun findAsset(repo: String, file: String) = service.findAsset(repo, file)
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package app.revanced.manager.network.api
|
||||
|
||||
import android.util.Log
|
||||
import app.revanced.manager.network.dto.github.Release
|
||||
import app.revanced.manager.util.body
|
||||
import app.revanced.manager.util.get
|
||||
import app.revanced.manager.util.tag
|
||||
import com.vk.knet.core.Knet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
class GitHubAPI(
|
||||
private val client: Knet,
|
||||
private val json: Json
|
||||
) {
|
||||
|
||||
private suspend fun findAsset(repo: String, file: String): PatchesAsset {
|
||||
val release = getLatestRelease(repo)
|
||||
val asset = release.assets.findAsset(file) ?: throw MissingAssetException()
|
||||
return PatchesAsset(release, asset)
|
||||
}
|
||||
|
||||
private fun List<Release.Asset>.findAsset(file: String) = find { asset ->
|
||||
(asset.name.contains(file) && !asset.name.contains("-sources") && !asset.name.contains("-javadoc"))
|
||||
}
|
||||
|
||||
suspend fun downloadAsset(
|
||||
workdir: File,
|
||||
repo: String,
|
||||
name: String
|
||||
): File {
|
||||
val asset = findAsset(repo, name).asset
|
||||
val out = workdir.resolve(asset.name)
|
||||
if (out.exists()) {
|
||||
Log.d(
|
||||
tag,
|
||||
"Skipping downloading asset ${asset.name} because it exists in cache!"
|
||||
)
|
||||
return out
|
||||
}
|
||||
Log.d(tag, "Downloading asset ${asset.name} from GitHub API.")
|
||||
val file = client.get(asset.downloadUrl).body!!.asBytes()
|
||||
out.writeBytes(file)
|
||||
return out
|
||||
}
|
||||
|
||||
private suspend fun getLatestRelease(repo: String): Release {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val releases = client.get("$baseUrl/$repo/releases").body<List<Release>>(json)
|
||||
releases.first()
|
||||
}
|
||||
}
|
||||
|
||||
data class PatchesAsset(
|
||||
val release: Release,
|
||||
val asset: Release.Asset
|
||||
)
|
||||
|
||||
private companion object {
|
||||
private const val baseUrl = "https://api.github.com/repos"
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package app.revanced.manager.network.api
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import android.webkit.URLUtil
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.domain.repository.GithubRepositoryImpl
|
||||
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.util.ghIntegrations
|
||||
import app.revanced.manager.util.ghPatches
|
||||
import app.revanced.manager.util.tag
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.util.cio.*
|
||||
import io.ktor.utils.io.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class ManagerAPI(
|
||||
val app: Application,
|
||||
private val prefs: PreferencesManager,
|
||||
private val patcherUtils: PatcherUtils,
|
||||
private val gitHubAPI: GithubRepositoryImpl,
|
||||
private val reVancedAPI: ReVancedRepositoryImpl,
|
||||
private val client: HttpClient
|
||||
) {
|
||||
|
||||
private suspend fun downloadAsset(
|
||||
workdir: File, downloadUrl: String
|
||||
): File {
|
||||
val name = URLUtil.guessFileName(downloadUrl, null, null)
|
||||
val out = workdir.resolve(name)
|
||||
client.get(downloadUrl).bodyAsChannel().copyAndClose(out.writeChannel())
|
||||
return out
|
||||
}
|
||||
|
||||
suspend fun downloadPatches() = withContext(Dispatchers.Main) {
|
||||
try {
|
||||
val asset =
|
||||
if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar")
|
||||
else gitHubAPI.findAsset(prefs.srcPatches!!, ".jar")
|
||||
asset.run {
|
||||
downloadAsset(app.cacheDir, downloadUrl).run {
|
||||
patcherUtils.run {
|
||||
patchBundleFile = absolutePath
|
||||
loadPatchBundle(absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "An error occurred while downloading patches", e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadIntegrations(workdir: File) = withContext(Dispatchers.IO) {
|
||||
val asset = if (prefs.srcIntegrations!! == ghIntegrations) {
|
||||
reVancedAPI.findAsset(prefs.srcIntegrations!!, ".apk")
|
||||
} else gitHubAPI.findAsset(prefs.srcIntegrations!!, ".apk")
|
||||
asset.run {
|
||||
val file = downloadAsset(workdir, downloadUrl)
|
||||
workdir.resolve(name).writeBytes(file.readBytes())
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
data class PatchesAsset(
|
||||
val downloadUrl: String, val name: String
|
||||
)
|
||||
|
||||
class MissingAssetException : Exception()
|
@ -1,84 +0,0 @@
|
||||
package app.revanced.manager.network.api
|
||||
|
||||
import android.util.Log
|
||||
import app.revanced.manager.network.dto.revanced.Assets
|
||||
import app.revanced.manager.network.dto.revanced.Repositories
|
||||
import app.revanced.manager.network.dto.revanced.Tools
|
||||
import app.revanced.manager.util.body
|
||||
import app.revanced.manager.util.get
|
||||
import app.revanced.manager.util.tag
|
||||
import com.vk.knet.core.Knet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
class ReVancedAPI(
|
||||
private val client: Knet,
|
||||
private val json: Json
|
||||
) {
|
||||
|
||||
suspend fun ping(): Boolean {
|
||||
return try {
|
||||
withContext(Dispatchers.Default) {
|
||||
client.get("$apiUrl/").statusCode == 200
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "ReVanced API isn't available at the moment. switching to GitHub API")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchAssets(): Tools {
|
||||
return withContext(Dispatchers.IO) {
|
||||
client.get("$apiUrl/tools").body(json)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchContributors(): Repositories {
|
||||
return withContext(Dispatchers.IO) {
|
||||
client.get("$apiUrl/contributors").body(json)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun findAsset(repo: String, file: String): PatchesAsset {
|
||||
val asset = fetchAssets().tools.findAsset(repo, file) ?: throw MissingAssetException()
|
||||
return PatchesAsset(asset)
|
||||
}
|
||||
|
||||
private fun List<Assets>.findAsset(repo: String, file: String) = find { asset ->
|
||||
(asset.name.contains(file) && asset.repository.contains(repo))
|
||||
}
|
||||
|
||||
suspend fun downloadAsset(
|
||||
workdir: File,
|
||||
repo: String,
|
||||
name: String,
|
||||
): File {
|
||||
val asset = findAsset(repo, name).asset
|
||||
val out = workdir.resolve(asset.name)
|
||||
if (out.exists()) {
|
||||
Log.d(
|
||||
tag,
|
||||
"Skipping downloading asset ${asset.name} because it exists in cache!"
|
||||
)
|
||||
return out
|
||||
}
|
||||
Log.d(tag, "Downloading asset ${asset.name} from ReVanced API.")
|
||||
val file = client.get(asset.downloadUrl).body!!.asBytes()
|
||||
out.writeBytes(file)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val apiUrl = "https://releases.rvcd.win"
|
||||
}
|
||||
}
|
||||
|
||||
data class PatchesAsset(
|
||||
val asset: Assets
|
||||
)
|
||||
|
||||
|
||||
class MissingAssetException : Exception()
|
@ -0,0 +1,12 @@
|
||||
package app.revanced.manager.network.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class GithubContributor(
|
||||
@SerialName("login") val login: String,
|
||||
@SerialName("avatar_url") val avatar_url: String,
|
||||
@SerialName("html_url") val url: String,
|
||||
@SerialName("contributions") val contributions: Int
|
||||
)
|
@ -1,10 +1,10 @@
|
||||
package app.revanced.manager.network.dto.github
|
||||
package app.revanced.manager.network.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class Release(
|
||||
class GithubReleases(
|
||||
val assets: List<Asset>,
|
||||
) {
|
||||
@Serializable
|
@ -1,21 +1,21 @@
|
||||
package app.revanced.manager.network.dto.revanced
|
||||
package app.revanced.manager.network.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class Repositories(
|
||||
@SerialName("repositories") val repositories: List<Repository>,
|
||||
class ReVancedRepositories(
|
||||
@SerialName("repositories") val repositories: List<ReVancedRepository>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Repository(
|
||||
class ReVancedRepository(
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("contributors") val contributors: List<Contributor>,
|
||||
@SerialName("contributors") val contributors: List<ReVancedContributor>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Contributor(
|
||||
class ReVancedContributor(
|
||||
@SerialName("login") val username: String,
|
||||
@SerialName("avatar_url") val avatarUrl: String,
|
||||
)
|
@ -1,10 +1,10 @@
|
||||
package app.revanced.manager.network.dto.revanced
|
||||
package app.revanced.manager.network.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class Tools(
|
||||
class ReVancedReleases(
|
||||
@SerialName("tools") val tools: List<Assets>,
|
||||
)
|
||||
|
@ -0,0 +1,54 @@
|
||||
package app.revanced.manager.network.service
|
||||
|
||||
import app.revanced.manager.BuildConfig
|
||||
import app.revanced.manager.network.api.MissingAssetException
|
||||
import app.revanced.manager.network.api.PatchesAsset
|
||||
import app.revanced.manager.network.dto.GithubContributor
|
||||
import app.revanced.manager.network.dto.GithubReleases
|
||||
import app.revanced.manager.network.utils.APIResponse
|
||||
import app.revanced.manager.network.utils.getOrNull
|
||||
import app.revanced.manager.network.utils.getOrThrow
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface GithubService {
|
||||
suspend fun getReleases(repo: String): APIResponse<GithubReleases>
|
||||
|
||||
suspend fun getContributors(repo: String): APIResponse<List<GithubContributor>>
|
||||
|
||||
suspend fun findAsset(repo: String, file: String): PatchesAsset
|
||||
}
|
||||
|
||||
class GithubServiceImpl(
|
||||
private val client: HttpService,
|
||||
) : GithubService {
|
||||
|
||||
override suspend fun getReleases(repo: String): APIResponse<GithubReleases> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
client.request {
|
||||
url("${baseUrl}/$repo/releases")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getContributors(repo: String): APIResponse<List<GithubContributor>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
client.request {
|
||||
url("$baseUrl/$repo/contributors")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findAsset(repo: String, file: String): PatchesAsset {
|
||||
val releases = getReleases(repo).getOrNull() ?: throw Exception("Cannot retrieve assets")
|
||||
|
||||
val asset = releases.assets.find { asset ->
|
||||
(asset.name.contains(file) && !asset.name.contains("-sources") && !asset.name.contains("-javadoc"))
|
||||
} ?: throw MissingAssetException()
|
||||
return asset.run { PatchesAsset(downloadUrl, name) }
|
||||
}
|
||||
companion object {
|
||||
private const val baseUrl = BuildConfig.GITHUB_API_URL
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package app.revanced.manager.network.service
|
||||
|
||||
import android.util.Log
|
||||
import app.revanced.manager.network.utils.APIError
|
||||
import app.revanced.manager.network.utils.APIFailure
|
||||
import app.revanced.manager.network.utils.APIResponse
|
||||
import app.revanced.manager.util.tag
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* @author Aliucord Authors, DiamondMiner88
|
||||
*/
|
||||
class HttpService(
|
||||
val json: Json,
|
||||
val http: HttpClient,
|
||||
) {
|
||||
suspend inline fun <reified T> request(builder: HttpRequestBuilder.() -> Unit = {}): APIResponse<T> {
|
||||
var body: String? = null
|
||||
|
||||
val response = try {
|
||||
val response = http.request(builder)
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
body = response.bodyAsText()
|
||||
|
||||
if (T::class == String::class) {
|
||||
return APIResponse.Success(body as T)
|
||||
}
|
||||
|
||||
APIResponse.Success(json.decodeFromString<T>(body))
|
||||
} else {
|
||||
body = try {
|
||||
response.bodyAsText()
|
||||
} catch (t: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
Log.e(tag, "Failed to fetch: API error, http status: ${response.status}, body: $body")
|
||||
APIResponse.Error(APIError(response.status, body))
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Log.e(tag, "Failed to fetch: error: $t, body: $body")
|
||||
APIResponse.Failure(APIFailure(t, body))
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package app.revanced.manager.network.service
|
||||
|
||||
import app.revanced.manager.BuildConfig
|
||||
import app.revanced.manager.network.api.MissingAssetException
|
||||
import app.revanced.manager.network.api.PatchesAsset
|
||||
import app.revanced.manager.network.dto.ReVancedReleases
|
||||
import app.revanced.manager.network.dto.ReVancedRepositories
|
||||
import app.revanced.manager.network.utils.APIResponse
|
||||
import app.revanced.manager.network.utils.getOrNull
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface ReVancedService {
|
||||
suspend fun getAssets(): APIResponse<ReVancedReleases>
|
||||
|
||||
suspend fun getContributors(): APIResponse<ReVancedRepositories>
|
||||
|
||||
suspend fun findAsset(repo: String, file: String): PatchesAsset
|
||||
}
|
||||
|
||||
class ReVancedServiceImpl(
|
||||
private val client: HttpService,
|
||||
) : ReVancedService {
|
||||
override suspend fun getAssets(): APIResponse<ReVancedReleases> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
client.request {
|
||||
url("$apiUrl/tools")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getContributors(): APIResponse<ReVancedRepositories> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
client.request {
|
||||
url("$apiUrl/contributors")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findAsset(repo: String, file: String): PatchesAsset {
|
||||
val releases = getAssets().getOrNull() ?: throw Exception("Cannot retrieve assets")
|
||||
val asset = releases.tools.find { asset ->
|
||||
(asset.name.contains(file) && asset.repository.contains(repo))
|
||||
} ?: throw MissingAssetException()
|
||||
return PatchesAsset(asset.downloadUrl, asset.name)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val apiUrl = BuildConfig.REVANCED_API_URL
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package app.revanced.manager.network.utils
|
||||
|
||||
import io.ktor.http.*
|
||||
|
||||
/**
|
||||
* @author Aliucord Authors, DiamondMiner88
|
||||
*/
|
||||
|
||||
sealed interface APIResponse<T> {
|
||||
data class Success<T>(val data: T) : APIResponse<T>
|
||||
data class Error<T>(val error: APIError) : APIResponse<T>
|
||||
data class Failure<T>(val error: APIFailure) : APIResponse<T>
|
||||
}
|
||||
|
||||
class APIError(code: HttpStatusCode, body: String?) : Error("HTTP Code $code, Body: $body")
|
||||
|
||||
class APIFailure(error: Throwable, body: String?) : Error(body, error)
|
||||
|
||||
inline fun <T, R> APIResponse<T>.fold(
|
||||
success: (T) -> R,
|
||||
error: (APIError) -> R,
|
||||
failure: (APIFailure) -> R
|
||||
): R {
|
||||
return when (this) {
|
||||
is APIResponse.Success -> success(this.data)
|
||||
is APIResponse.Error -> error(this.error)
|
||||
is APIResponse.Failure -> failure(this.error)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, R> APIResponse<T>.fold(
|
||||
success: (T) -> R,
|
||||
fail: (Error) -> R,
|
||||
): R {
|
||||
return when (this) {
|
||||
is APIResponse.Success -> success(data)
|
||||
is APIResponse.Error -> fail(error)
|
||||
is APIResponse.Failure -> fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <T, R> APIResponse<T>.transform(block: (T) -> R): APIResponse<R> {
|
||||
return if (this !is APIResponse.Success) {
|
||||
// Error and Failure do not use the generic value
|
||||
this as APIResponse<R>
|
||||
} else {
|
||||
APIResponse.Success(block(data))
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> APIResponse<T>.getOrThrow(): T {
|
||||
return fold(
|
||||
success = { it },
|
||||
fail = { throw it }
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <T> APIResponse<T>.getOrNull(): T? {
|
||||
return fold(
|
||||
success = { it },
|
||||
fail = { null }
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <T, R> APIResponse<T>.chain(block: (T) -> APIResponse<R>): APIResponse<R> {
|
||||
return if (this !is APIResponse.Success) {
|
||||
// Error and Failure do not use the generic value
|
||||
this as APIResponse<R>
|
||||
} else {
|
||||
block(data)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <T, R> APIResponse<T>.chain(secondary: APIResponse<R>): APIResponse<R> {
|
||||
return if (secondary is APIResponse.Success) {
|
||||
secondary
|
||||
} else {
|
||||
// Error and Failure do not use the generic value
|
||||
this as APIResponse<R>
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package app.revanced.manager.patcher
|
||||
|
||||
import android.app.Application
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.util.patch.PatchBundle
|
||||
import dalvik.system.DexClassLoader
|
||||
import java.util.*
|
||||
|
||||
class PatcherUtils(val app: Application) {
|
||||
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading)
|
||||
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
|
||||
val selectedPatches = mutableStateListOf<String>()
|
||||
lateinit var patchBundleFile: String
|
||||
|
||||
fun cleanup() {
|
||||
patches.value = Resource.Success(emptyList())
|
||||
selectedAppPackage.value = Optional.empty()
|
||||
selectedPatches.clear()
|
||||
}
|
||||
|
||||
fun loadPatchBundle(file: String? = patchBundleFile) {
|
||||
try {
|
||||
if (this::patchBundleFile.isInitialized) {
|
||||
val patchClasses = PatchBundle.Dex(
|
||||
file!!, DexClassLoader(
|
||||
file, app.codeCacheDir.absolutePath, null, javaClass.classLoader
|
||||
)
|
||||
).loadPatches()
|
||||
patches.value = Resource.Success(patchClasses)
|
||||
} else throw IllegalStateException("No patch bundle(s) selected.")
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Failed to load patch bundle.", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSelectedPackageInfo(): PackageInfo? {
|
||||
return if (selectedAppPackage.value.isPresent) {
|
||||
app.packageManager.getPackageArchiveInfo(
|
||||
selectedAppPackage.value.get().publicSourceDir, PackageManager.GET_META_DATA
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun findPatchesByIds(ids: Iterable<String>): List<Class<out Patch<Context>>> {
|
||||
val (patches) = patches.value as? Resource.Success ?: return listOf()
|
||||
return patches.filter { patch -> ids.any { it == patch.patchName } }
|
||||
}
|
||||
}
|
@ -15,20 +15,15 @@ import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.Variables.patches
|
||||
import app.revanced.manager.Variables.selectedAppPackage
|
||||
import app.revanced.manager.Variables.selectedPatches
|
||||
import app.revanced.manager.network.api.GitHubAPI
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.api.ManagerAPI
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.patcher.aapt.Aapt
|
||||
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.preferences.PreferencesManager
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.ui.viewmodel.Logging
|
||||
import app.revanced.manager.util.ghIntegrations
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
@ -41,14 +36,12 @@ import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class PatcherWorker(
|
||||
context: Context,
|
||||
parameters: WorkerParameters,
|
||||
private val reVancedAPI: ReVancedAPI,
|
||||
private val gitHubAPI: GitHubAPI,
|
||||
private val prefs: PreferencesManager
|
||||
private val managerAPI: ManagerAPI,
|
||||
private val patcherUtils: PatcherUtils
|
||||
) : CoroutineWorker(context, parameters), KoinComponent {
|
||||
val tag = "ReVanced Manager"
|
||||
private val workdir = createWorkDir()
|
||||
@ -118,10 +111,10 @@ class PatcherWorker(
|
||||
applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() }
|
||||
val reVancedFolder =
|
||||
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }
|
||||
val appInfo = selectedAppPackage.value.get()
|
||||
val appInfo = patcherUtils.selectedAppPackage.value.get()
|
||||
|
||||
Logging.log += "Checking prerequisites\n"
|
||||
val patches = findPatchesByIds(selectedPatches)
|
||||
val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches)
|
||||
if (patches.isEmpty()) return true
|
||||
|
||||
|
||||
@ -131,16 +124,7 @@ class PatcherWorker(
|
||||
val outputFile = File(applicationContext.filesDir, "output.apk")
|
||||
val cacheDirectory = workdir.resolve("cache")
|
||||
|
||||
val integrations = if (prefs.srcIntegrations != ghIntegrations || !reVancedAPI.ping()) {
|
||||
Logging.log += "Downloading integrations from GitHub API\n"
|
||||
gitHubAPI.downloadAsset(integrationsCacheDir, prefs.srcIntegrations!!, ".apk")
|
||||
} else {
|
||||
Logging.log += "Downloading integrations from ReVanced API\n"
|
||||
reVancedAPI.downloadAsset(
|
||||
integrationsCacheDir,
|
||||
prefs.srcIntegrations!!, ".apk"
|
||||
)
|
||||
}
|
||||
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
|
||||
|
||||
Logging.log += "Copying base.apk from device\n"
|
||||
withContext(Dispatchers.IO) {
|
||||
@ -217,12 +201,13 @@ class PatcherWorker(
|
||||
withContext(Dispatchers.IO) {
|
||||
Files.copy(
|
||||
outputFile.inputStream(),
|
||||
reVancedFolder.resolve(appInfo.packageName + "-" + LocalDateTime.now() + ".apk")
|
||||
reVancedFolder.resolve(appInfo.packageName + ".apk")
|
||||
.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
Logging.log += "Copied file to storage!\n"
|
||||
patcherUtils.cleanup()
|
||||
} finally {
|
||||
Log.d(tag, "Deleting workdir")
|
||||
workdir.deleteRecursively()
|
||||
@ -232,14 +217,9 @@ class PatcherWorker(
|
||||
return false
|
||||
}
|
||||
|
||||
private fun findPatchesByIds(ids: Iterable<String>): List<Class<out Patch<app.revanced.patcher.data.Context>>> {
|
||||
val (patches) = patches.value as? Resource.Success ?: return listOf()
|
||||
return patches.filter { patch -> ids.any { it == patch.patchName } }
|
||||
}
|
||||
|
||||
|
||||
private fun createWorkDir(): File {
|
||||
return applicationContext.filesDir.resolve("tmp-${System.currentTimeMillis()}")
|
||||
return applicationContext.cacheDir.resolve("tmp-${System.currentTimeMillis()}")
|
||||
.also { it.mkdirs() }
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.network.dto.revanced.Contributor
|
||||
import app.revanced.manager.network.dto.ReVancedContributor
|
||||
import app.revanced.manager.ui.viewmodel.ContributorsViewModel
|
||||
import coil.compose.AsyncImage
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
@ -27,7 +27,7 @@ import org.koin.androidx.compose.getViewModel
|
||||
@ExperimentalMaterial3Api
|
||||
fun ContributorsCard(
|
||||
title: String,
|
||||
data: SnapshotStateList<Contributor>,
|
||||
data: SnapshotStateList<ReVancedContributor>,
|
||||
vm: ContributorsViewModel = getViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
@ -9,18 +9,18 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import app.revanced.patcher.annotation.Package
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
@Composable
|
||||
fun PatchCompatibilityDialog(
|
||||
patchClass: PatchClass, pvm: PatcherScreenViewModel = getViewModel(), onClose: () -> Unit
|
||||
patchClass: PatchClass, patcherUtils: PatcherUtils = get(), onClose: () -> Unit
|
||||
) {
|
||||
val patch = patchClass.patch
|
||||
val packageName = pvm.getSelectedPackageInfo()?.packageName
|
||||
val packageName = patcherUtils.getSelectedPackageInfo()?.packageName
|
||||
AlertDialog(onDismissRequest = onClose, shape = RoundedCornerShape(12.dp), title = {
|
||||
Text(stringResource(id = R.string.unsupported), textAlign = TextAlign.Center)
|
||||
}, text = {
|
||||
|
@ -16,15 +16,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NewPatcherScreen(
|
||||
onClickAppSelector: () -> Unit,
|
||||
onClickPatchSelector: () -> Unit,
|
||||
viewModel: PatcherScreenViewModel = getViewModel()
|
||||
patcherUtils: PatcherUtils = get()
|
||||
) {
|
||||
var validBundle = false
|
||||
Column(
|
||||
@ -75,7 +75,7 @@ fun NewPatcherScreen(
|
||||
fontSize = 13.sp
|
||||
)
|
||||
Text(
|
||||
text = viewModel.getSelectedPackageInfo()!!.applicationInfo.name,
|
||||
text = patcherUtils.getSelectedPackageInfo()!!.applicationInfo.name,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
}
|
||||
|
@ -12,13 +12,13 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.Variables.patches
|
||||
import app.revanced.manager.Variables.selectedAppPackage
|
||||
import app.revanced.manager.Variables.selectedPatches
|
||||
import app.revanced.manager.network.api.ManagerAPI
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.ui.component.FloatingActionButton
|
||||
import app.revanced.manager.ui.component.SplitAPKDialog
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||
import org.koin.androidx.compose.get
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -28,25 +28,26 @@ fun PatcherScreen(
|
||||
onClickPatchSelector: () -> Unit,
|
||||
onClickPatch: () -> Unit,
|
||||
onClickSourceSelector: () -> Unit,
|
||||
viewModel: PatcherScreenViewModel = getViewModel()
|
||||
patcherUtils: PatcherUtils = get(),
|
||||
psvm: PatchesSelectorViewModel = getViewModel(),
|
||||
managerAPI: ManagerAPI = get()
|
||||
) {
|
||||
val selectedAmount = selectedPatches.size
|
||||
val selectedAppPackage by selectedAppPackage
|
||||
val selectedAmount = patcherUtils.selectedPatches.size
|
||||
val selectedAppPackage by patcherUtils.selectedAppPackage
|
||||
val hasAppSelected = selectedAppPackage.isPresent
|
||||
val patchesLoaded = patches.value is Resource.Success
|
||||
val patchesLoaded = patcherUtils.patches.value is Resource.Success
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(patchesLoaded) {
|
||||
if (!patchesLoaded) {
|
||||
managerAPI.downloadPatches()
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
enabled = hasAppSelected && viewModel.anyPatchSelected(),
|
||||
onClick = {
|
||||
if (viewModel.checkSplitApk()) {
|
||||
showDialog = true
|
||||
} else {
|
||||
onClickPatch(); viewModel.loadPatches0()
|
||||
}
|
||||
}, // TODO: replace this with something better
|
||||
enabled = hasAppSelected && psvm.anyPatchSelected(),
|
||||
onClick = { onClickPatch(); patcherUtils.loadPatchBundle() }, // TODO: replace this with something better
|
||||
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
||||
text = { Text(text = "Patch") }
|
||||
)
|
||||
@ -114,7 +115,7 @@ fun PatcherScreen(
|
||||
Text(
|
||||
text = if (!hasAppSelected) {
|
||||
"Select an application first."
|
||||
} else if (viewModel.anyPatchSelected()) {
|
||||
} else if (psvm.anyPatchSelected()) {
|
||||
"$selectedAmount patches selected."
|
||||
} else {
|
||||
stringResource(R.string.card_patches_body_patches)
|
||||
|
@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.preferences.PreferencesManager
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
import app.revanced.manager.ui.component.SocialItem
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
|
@ -16,18 +16,20 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.Variables.patchesState
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.ui.component.LoadingIndicator
|
||||
import app.revanced.manager.ui.component.PatchCompatibilityDialog
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.theme.Typography
|
||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||
import app.revanced.patcher.extensions.PatchExtensions.description
|
||||
import app.revanced.patcher.extensions.PatchExtensions.options
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.extensions.PatchExtensions.version
|
||||
import com.xinto.taxi.BackstackNavigator
|
||||
import org.koin.androidx.compose.get
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@ -35,9 +37,11 @@ import org.koin.androidx.compose.getViewModel
|
||||
@Composable
|
||||
fun PatchesSelectorSubscreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
pvm: PatcherScreenViewModel = getViewModel(),
|
||||
psvm: PatchesSelectorViewModel = getViewModel(),
|
||||
patcherUtils: PatcherUtils = get()
|
||||
) {
|
||||
val patches = pvm.getFilteredPatchesAndCheckOptions()
|
||||
val patchesState by patcherUtils.patches
|
||||
val patches = psvm.getFilteredPatches()
|
||||
var query by mutableStateOf("")
|
||||
|
||||
Scaffold(
|
||||
@ -59,9 +63,9 @@ fun PatchesSelectorSubscreen(
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
pvm.selectAllPatches(patches, !pvm.anyPatchSelected())
|
||||
psvm.selectAllPatches(patches, !psvm.anyPatchSelected())
|
||||
}) {
|
||||
if (!pvm.anyPatchSelected()) Icon(
|
||||
if (!psvm.anyPatchSelected()) Icon(
|
||||
Icons.Default.SelectAll,
|
||||
contentDescription = null
|
||||
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
||||
@ -115,8 +119,8 @@ fun PatchesSelectorSubscreen(
|
||||
items(count = patches.size) {
|
||||
val patch = patches[it]
|
||||
val name = patch.patch.patchName
|
||||
PatchCard(patch, pvm.isPatchSelected(name)) {
|
||||
pvm.selectPatch(name, !pvm.isPatchSelected(name))
|
||||
PatchCard(patch, psvm.isPatchSelected(name)) {
|
||||
psvm.selectPatch(name, !psvm.isPatchSelected(name))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -124,8 +128,8 @@ fun PatchesSelectorSubscreen(
|
||||
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))
|
||||
PatchCard(patch, psvm.isPatchSelected(name)) {
|
||||
psvm.selectPatch(name, !psvm.isPatchSelected(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,7 +194,7 @@ fun PatchCard(patchClass: PatchClass, isSelected: Boolean, onSelected: () -> Uni
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
if (patchClass.hasPatchOptions) {
|
||||
if (patchClass.patch.options != null) {
|
||||
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
|
||||
IconButton(onClick = { }, modifier = Modifier.size(24.dp)) {
|
||||
Icon(
|
||||
|
@ -20,11 +20,11 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.component.SourceItem
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import com.xinto.taxi.BackstackNavigator
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import org.koin.androidx.compose.get
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
@ -32,7 +32,7 @@ import java.nio.file.StandardCopyOption
|
||||
@Composable
|
||||
fun SourceSelectorSubscreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
pvm: PatcherScreenViewModel = getViewModel()
|
||||
patcherUtils: PatcherUtils = get()
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
state = rememberTopAppBarState(),
|
||||
@ -48,8 +48,11 @@ fun SourceSelectorSubscreen(
|
||||
patchesFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
pvm.patchBundleFile = patchesFile.absolutePath
|
||||
pvm.loadPatches0()
|
||||
patchesFile.absolutePath.also {
|
||||
patcherUtils.patchBundleFile = it
|
||||
patcherUtils.loadPatchBundle(it)
|
||||
}
|
||||
|
||||
navigator.pop()
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
|
@ -8,28 +8,30 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.Variables
|
||||
import app.revanced.manager.Variables.patches
|
||||
import app.revanced.manager.Variables.selectedAppPackage
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class AppSelectorViewModel(
|
||||
val app: Application,
|
||||
val app: Application, patcherUtils: PatcherUtils
|
||||
) : ViewModel() {
|
||||
|
||||
val filteredApps = mutableListOf<ApplicationInfo>()
|
||||
val patches = patcherUtils.patches
|
||||
private val selectedAppPackage = patcherUtils.selectedAppPackage
|
||||
private val selectedPatches = patcherUtils.selectedPatches
|
||||
|
||||
init {
|
||||
viewModelScope.launch { filterApps() }
|
||||
}
|
||||
|
||||
private fun filterApps() {
|
||||
private suspend fun filterApps() = withContext(Dispatchers.Main) {
|
||||
try {
|
||||
val (patches) = patches.value as Resource.Success
|
||||
patches.forEach patch@{ patch ->
|
||||
@ -61,23 +63,25 @@ class AppSelectorViewModel(
|
||||
|
||||
fun setSelectedAppPackage(appId: ApplicationInfo) {
|
||||
selectedAppPackage.value.ifPresent { s ->
|
||||
if (s != appId) Variables.selectedPatches.clear()
|
||||
if (s != appId) selectedPatches.clear()
|
||||
}
|
||||
selectedAppPackage.value = Optional.of(appId)
|
||||
}
|
||||
|
||||
fun setSelectedAppPackageFromFile(file: Uri?) {
|
||||
val apkDir = app.filesDir.resolve("input.apk").toPath()
|
||||
Files.copy(
|
||||
app.contentResolver.openInputStream(file!!),
|
||||
apkDir,
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
setSelectedAppPackage(
|
||||
app.packageManager.getPackageArchiveInfo(
|
||||
apkDir.toString(),
|
||||
PackageManager.GET_META_DATA
|
||||
)!!.applicationInfo
|
||||
)
|
||||
try {
|
||||
val apkDir = app.cacheDir.resolve(File(file!!.path!!).name)
|
||||
app.contentResolver.openInputStream(file)!!.run {
|
||||
copyTo(apkDir.outputStream())
|
||||
close()
|
||||
}
|
||||
setSelectedAppPackage(
|
||||
app.packageManager.getPackageArchiveInfo(
|
||||
apkDir.path, PackageManager.GET_META_DATA
|
||||
)!!.applicationInfo
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Failed to load apk", e)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,28 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.revanced.Contributor
|
||||
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
|
||||
import app.revanced.manager.network.dto.ReVancedContributor
|
||||
import app.revanced.manager.network.utils.getOrNull
|
||||
import app.revanced.manager.util.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ContributorsViewModel(
|
||||
private val app: Application,
|
||||
private val reVancedAPI: ReVancedAPI
|
||||
private val app: Application, private val reVancedAPI: ReVancedRepositoryImpl
|
||||
) : ViewModel() {
|
||||
val patcherContributorsList = mutableStateListOf<Contributor>()
|
||||
val patchesContributorsList = mutableStateListOf<Contributor>()
|
||||
val cliContributorsList = mutableStateListOf<Contributor>()
|
||||
val managerContributorsList = mutableStateListOf<Contributor>()
|
||||
val integrationsContributorsList = mutableStateListOf<Contributor>()
|
||||
val patcherContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||
val patchesContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||
val cliContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||
val managerContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||
val integrationsContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||
|
||||
private fun loadContributors() {
|
||||
viewModelScope.launch {
|
||||
val contributors = reVancedAPI.fetchContributors()
|
||||
val contributors = reVancedAPI.getContributors().getOrNull() ?: return@launch
|
||||
contributors.repositories.forEach { repo ->
|
||||
when (repo.name) {
|
||||
ghCli -> {
|
||||
@ -65,7 +66,7 @@ class ContributorsViewModel(
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
loadContributors()
|
||||
loadContributors()
|
||||
}
|
||||
}
|
||||
}
|
@ -2,24 +2,21 @@ package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.format.DateUtils
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.revanced.Assets
|
||||
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.util.ghManager
|
||||
import app.revanced.manager.util.ghPatcher
|
||||
import app.revanced.manager.util.tag
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() {
|
||||
class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : ViewModel() {
|
||||
private var _latestPatcherCommit: Assets? by mutableStateOf(null)
|
||||
val patcherCommitDate: String
|
||||
get() = _latestPatcherCommit?.commitDate ?: "unknown"
|
||||
@ -34,8 +31,7 @@ class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() {
|
||||
|
||||
private fun fetchLastCommit() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val repo = withContext(Dispatchers.Default) { reVancedApi.fetchAssets() }
|
||||
val repo = reVancedApi.getAssets().getOrNull() ?: return@launch
|
||||
for (asset in repo.tools) {
|
||||
when (asset.repository) {
|
||||
ghPatcher -> {
|
||||
@ -46,9 +42,6 @@ class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Failed to fetch latest patcher release", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,164 +0,0 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.Variables.patches
|
||||
import app.revanced.manager.Variables.selectedAppPackage
|
||||
import app.revanced.manager.Variables.selectedPatches
|
||||
import app.revanced.manager.network.api.GitHubAPI
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.preferences.PreferencesManager
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.util.ghPatches
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import app.revanced.patcher.extensions.PatchExtensions.options
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.util.patch.PatchBundle
|
||||
import dalvik.system.DexClassLoader
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class PatcherScreenViewModel(
|
||||
private val app: Application,
|
||||
private val reVancedApi: ReVancedAPI,
|
||||
private val gitHubAPI: GitHubAPI,
|
||||
private val prefs: PreferencesManager
|
||||
) : ViewModel() {
|
||||
lateinit var patchBundleFile: String
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
loadPatches()
|
||||
}
|
||||
}
|
||||
|
||||
fun selectPatch(patchId: String, state: Boolean) {
|
||||
if (state) selectedPatches.add(patchId)
|
||||
else selectedPatches.remove(patchId)
|
||||
}
|
||||
|
||||
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
||||
patchList.forEach { patch ->
|
||||
val patchId = patch.patch.patchName
|
||||
if (selectAll && !patch.unsupported) selectedPatches.add(patchId)
|
||||
else selectedPatches.remove(patchId)
|
||||
}
|
||||
}
|
||||
|
||||
fun setOption(patch: PatchClass, key: String, value: String) {
|
||||
patch.patch.options?.set(key, value)
|
||||
for (option in patch.patch.options!!) {
|
||||
println(option.key + option.value + option.title + option.description)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOption(patch: PatchClass, key: String) {
|
||||
patch.patch.options?.get(key)
|
||||
}
|
||||
|
||||
fun isPatchSelected(patchId: String): Boolean {
|
||||
return selectedPatches.contains(patchId)
|
||||
}
|
||||
|
||||
fun anyPatchSelected(): Boolean {
|
||||
return !selectedPatches.isEmpty()
|
||||
}
|
||||
|
||||
|
||||
fun getSelectedPackageInfo(): PackageInfo? {
|
||||
return if (selectedAppPackage.value.isPresent) {
|
||||
app.packageManager.getPackageArchiveInfo(
|
||||
selectedAppPackage.value.get().publicSourceDir,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun checkSplitApk(): Boolean {
|
||||
if (getSelectedPackageInfo()!!.applicationInfo!!.metaData!!.getBoolean(
|
||||
"com.android.vending.splits.required",
|
||||
false
|
||||
)
|
||||
) {
|
||||
Log.d(tag, "APK is split.")
|
||||
return true
|
||||
}
|
||||
Log.d(tag, "APK is not split.")
|
||||
return false
|
||||
}
|
||||
|
||||
private fun loadPatches() = viewModelScope.launch {
|
||||
try {
|
||||
val file = if (prefs.srcPatches != ghPatches || !reVancedApi.ping()) {
|
||||
gitHubAPI.downloadAsset(app.cacheDir, prefs.srcPatches!!, ".jar")
|
||||
} else {
|
||||
reVancedApi.downloadAsset(app.cacheDir, prefs.srcPatches!!, ".jar")
|
||||
}
|
||||
patchBundleFile = file.absolutePath
|
||||
loadPatches0()
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "An error occurred while loading patches", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadPatches0() {
|
||||
try {
|
||||
val patchClasses = PatchBundle.Dex(
|
||||
patchBundleFile, DexClassLoader(
|
||||
patchBundleFile,
|
||||
app.codeCacheDir.absolutePath,
|
||||
null,
|
||||
javaClass.classLoader
|
||||
)
|
||||
).loadPatches()
|
||||
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> {
|
||||
return buildList {
|
||||
val selected = getSelectedPackageInfo() ?: return@buildList
|
||||
val (patches) = patches.value as? Resource.Success ?: return@buildList
|
||||
patches.forEach patch@{ patch ->
|
||||
var unsupported = false
|
||||
var hasPatchOptions = false
|
||||
if (patch.options != null) {
|
||||
hasPatchOptions = true
|
||||
Log.d(tag, "${patch.patchName} has patch options.")
|
||||
}
|
||||
patch.compatiblePackages?.forEach { pkg ->
|
||||
// if we detect unsupported once, don't overwrite it
|
||||
if (pkg.name == selected.packageName) {
|
||||
if (!unsupported)
|
||||
unsupported =
|
||||
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
|
||||
add(PatchClass(patch, unsupported, hasPatchOptions))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class PatchClass(
|
||||
val patch: Class<out Patch<Context>>,
|
||||
val unsupported: Boolean,
|
||||
val hasPatchOptions: Boolean,
|
||||
) : Parcelable
|
@ -0,0 +1,63 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class PatchesSelectorViewModel(
|
||||
private val patcherUtils: PatcherUtils
|
||||
) : ViewModel() {
|
||||
private val selectedPatches = patcherUtils.selectedPatches
|
||||
|
||||
fun isPatchSelected(patchId: String): Boolean {
|
||||
return selectedPatches.contains(patchId)
|
||||
}
|
||||
|
||||
fun anyPatchSelected(): Boolean {
|
||||
return !selectedPatches.isEmpty()
|
||||
}
|
||||
|
||||
fun selectPatch(patchId: String, state: Boolean) {
|
||||
if (state) selectedPatches.add(patchId)
|
||||
else selectedPatches.remove(patchId)
|
||||
}
|
||||
|
||||
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
||||
patchList.forEach { patch ->
|
||||
val patchId = patch.patch.patchName
|
||||
if (selectAll && !patch.unsupported) selectedPatches.add(patchId)
|
||||
else selectedPatches.remove(patchId)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilteredPatches(): List<PatchClass> {
|
||||
return buildList {
|
||||
val selected = patcherUtils.getSelectedPackageInfo() ?: return@buildList
|
||||
val (patches) = patcherUtils.patches.value as? Resource.Success ?: return@buildList
|
||||
patches.forEach patch@{ patch ->
|
||||
var unsupported = false
|
||||
patch.compatiblePackages?.forEach { pkg ->
|
||||
// if we detect unsupported once, don't overwrite it
|
||||
if (pkg.name == selected.packageName) {
|
||||
if (!unsupported)
|
||||
unsupported =
|
||||
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
|
||||
add(PatchClass(patch, unsupported))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class PatchClass(
|
||||
val patch: Class<out Patch<Context>>,
|
||||
val unsupported: Boolean,
|
||||
) : Parcelable
|
@ -1,6 +1,6 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
|
@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import app.revanced.manager.preferences.PreferencesManager
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.util.ghOrganization
|
||||
import app.revanced.manager.util.openUrl
|
||||
|
@ -1,23 +0,0 @@
|
||||
package app.revanced.manager.util
|
||||
|
||||
import com.vk.knet.core.Knet
|
||||
import com.vk.knet.core.http.HttpMethod
|
||||
import com.vk.knet.core.http.HttpPayload
|
||||
import com.vk.knet.core.http.HttpRequest
|
||||
import com.vk.knet.core.http.HttpResponse
|
||||
import com.vk.knet.core.http.body.request.HttpRequestBody
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
fun Knet.get(
|
||||
url: String,
|
||||
headers: Map<String, List<String>> = emptyMap(),
|
||||
body: HttpRequestBody? = null,
|
||||
payload: Map<HttpPayload, Any>? = null
|
||||
): HttpResponse {
|
||||
return execute(HttpRequest(HttpMethod.GET, url, headers, body, payload))
|
||||
}
|
||||
|
||||
inline fun <reified T> HttpResponse.body(json: Json = Json.Default): T {
|
||||
return json.decodeFromString(body!!.toString())
|
||||
}
|
@ -1,11 +1,46 @@
|
||||
package app.revanced.manager.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.core.net.toUri
|
||||
|
||||
|
||||
fun Context.openUrl(url: String) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}
|
||||
|
||||
fun Context.requestAllFilesAccess() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
startActivity(Intent(
|
||||
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||
Uri.fromParts("package", applicationContext.packageName, null)
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
addCategory("android.intent.category.DEFAULT")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
fun Context.requestIgnoreBatteryOptimizations() {
|
||||
startActivity(Intent(
|
||||
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||
Uri.fromParts("package", packageName, null)
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
addCategory("android.intent.category.DEFAULT")
|
||||
})
|
||||
}
|
||||
|
||||
fun Context.isIgnoringOptimizations(): Boolean {
|
||||
val pm = getSystemService(ComponentActivity.POWER_SERVICE) as PowerManager
|
||||
return pm.isIgnoringBatteryOptimizations(packageName)
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="278"
|
||||
android:viewportHeight="278">
|
||||
<group android:scaleX="0.75"
|
||||
android:scaleY="0.75"
|
||||
android:translateX="34.75"
|
||||
android:translateY="34.75">
|
||||
<path
|
||||
android:pathData="M0.06,0.83h277v277h-277z"
|
||||
android:fillColor="#111623"/>
|
||||
<path
|
||||
android:pathData="M108.01,80.13H174.31V95.81H108.01V80.13ZM96.91,95.81V80.13C96.91,73.98 101.88,69 108.01,69H174.31C180.44,69 185.41,73.98 185.41,80.13V95.81H189.78C198.97,95.81 206.43,103.29 206.43,112.5V189.31C206.43,198.53 198.97,206 189.78,206H92.32C83.12,206 75.67,198.53 75.67,189.31V184.16H127.18C133.31,184.16 138.28,179.18 138.28,173.03V164.13C138.28,157.98 133.31,153 127.18,153H75.67V112.5C75.67,103.29 83.12,95.81 92.32,95.81H96.91Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M75.67,184.16H56.53C50.4,184.16 45.43,179.18 45.43,173.03V164.13C45.43,157.98 50.4,153 56.53,153H75.67V184.16Z"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="45.43"
|
||||
android:startY="132.25"
|
||||
android:endX="87.19"
|
||||
android:endY="221.04"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF353DFF"/>
|
||||
<item android:offset="1" android:color="#FF3DDCFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M75.67,184.16H56.53C50.4,184.16 45.43,179.18 45.43,173.03V164.13C45.43,157.98 50.4,153 56.53,153H75.67V184.16Z"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="45.43"
|
||||
android:startY="132.25"
|
||||
android:endX="87.19"
|
||||
android:endY="221.04"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF353DFF"/>
|
||||
<item android:offset="1" android:color="#FF3DDCFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
@ -1,44 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="278dp"
|
||||
android:height="278dp"
|
||||
android:viewportWidth="278"
|
||||
android:viewportHeight="278">
|
||||
<path
|
||||
android:pathData="M0.06,0.83h277v277h-277z"
|
||||
android:fillColor="#111623"/>
|
||||
<path
|
||||
android:pathData="M108.01,80.13H174.31V95.81H108.01V80.13ZM96.91,95.81V80.13C96.91,73.98 101.88,69 108.01,69H174.31C180.44,69 185.41,73.98 185.41,80.13V95.81H189.78C198.97,95.81 206.43,103.29 206.43,112.5V189.31C206.43,198.53 198.97,206 189.78,206H92.32C83.12,206 75.67,198.53 75.67,189.31V184.16H127.18C133.31,184.16 138.28,179.18 138.28,173.03V164.13C138.28,157.98 133.31,153 127.18,153H75.67V112.5C75.67,103.29 83.12,95.81 92.32,95.81H96.91Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M75.67,184.16H56.53C50.4,184.16 45.43,179.18 45.43,173.03V164.13C45.43,157.98 50.4,153 56.53,153H75.67V184.16Z"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="45.43"
|
||||
android:startY="132.25"
|
||||
android:endX="87.19"
|
||||
android:endY="221.04"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF353DFF"/>
|
||||
<item android:offset="1" android:color="#FF3DDCFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M75.67,184.16H56.53C50.4,184.16 45.43,179.18 45.43,173.03V164.13C45.43,157.98 50.4,153 56.53,153H75.67V184.16Z"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="45.43"
|
||||
android:startY="132.25"
|
||||
android:endX="87.19"
|
||||
android:endY="221.04"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF353DFF"/>
|
||||
<item android:offset="1" android:color="#FF3DDCFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
@ -1,7 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="278dp"
|
||||
android:height="278dp"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="278"
|
||||
android:viewportHeight="278">
|
||||
<path
|
||||
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
@ -7,71 +7,25 @@
|
||||
<string name="settings">Settings</string>
|
||||
<string name="updates">Updates</string>
|
||||
<string name="manager">Manager</string>
|
||||
<string name="update_manager">Update Manager</string>
|
||||
<string name="update_patch_bundle">Update Patches</string>
|
||||
<string name="patched_apps">Installed</string>
|
||||
<string name="update_all">Update All</string>
|
||||
<string name="updates_available">Available updates</string>
|
||||
<string name="expand">Expand</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="app_selector_title">Select an app…</string>
|
||||
<string name="app_bar_open_discord">Discord</string>
|
||||
<string name="app_bar_open_github">GitHub</string>
|
||||
<string name="card_announcement_header">Announcement</string>
|
||||
<string name="card_commits_header">Latest updates</string>
|
||||
<string name="card_logs_header">Logs</string>
|
||||
<string name="card_application_header">Select Application</string>
|
||||
<string name="card_patches_header">Select Patches</string>
|
||||
<string name="card_options_header">Options</string>
|
||||
<string name="card_contributors_header">Contributors</string>
|
||||
<string name="card_commits_body_patcher">Patcher:</string>
|
||||
<string name="card_commits_body_manager">Manager:</string>
|
||||
<string name="card_announcement_body_placeholder">This is an example text for previewing ReVanced Manager design that will be replaced by dynamic announcements in the future.</string>
|
||||
<string name="card_options_body_root">Root</string>
|
||||
<string name="card_options_body_use_installed">Use installed YouTube app</string>
|
||||
<string name="card_application_not_loaded">Loading applications.</string>
|
||||
<string name="card_application_not_selected">No application selected.</string>
|
||||
<string name="card_patches_body_source">No patches source selected.</string>
|
||||
<string name="card_patches_body_patches">No patches selected.</string>
|
||||
<string name="card_application_body_selected">Selected:</string>
|
||||
<string name="card_logs_body">Click here to view patcher logs.</string>
|
||||
<string name="card_credits_body">Click here to view people who have contributed to the ReVanced Project.</string>
|
||||
<string name="loading_body">One moment, please…</string>
|
||||
<string name="loading_fetching_patches">Fetching patches</string>
|
||||
<string name="unsupported">Unsupported version</string>
|
||||
<string name="only_compatible">This patch is only compatible with version: </string>
|
||||
<string name="card_announcement_button_changelog">Changelog</string>
|
||||
<string name="button_patch">Patch</string>
|
||||
<string name="navigation_dashboard">Dashboard</string>
|
||||
<string name="navigation_patcher">Patcher</string>
|
||||
<string name="screen_logs_title">Logs</string>
|
||||
<string name="screen_contributors_title">Contributors</string>
|
||||
<string name="screen_credits_team_manager">ReVanced Manager</string>
|
||||
<string name="screen_credits_team_website">Website</string>
|
||||
<string name="screen_credits_team">Team</string>
|
||||
<string name="screen_credits_translators">Translators</string>
|
||||
<string name="screen_credits_team_patcher">Patcher</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="help_translate">Help translate</string>
|
||||
<string name="whats_new">What\'s New</string>
|
||||
<string name="ic_non_selected">No patches are selected!</string>
|
||||
<string name="contributor_image">Contributor image</string>
|
||||
<string name="no_contributors">No contributors</string>
|
||||
<string name="screen_settings_title">Settings</string>
|
||||
<string name="screen_about_title">About</string>
|
||||
<string name="about">About</string>
|
||||
<string name="navigation_more">More</string>
|
||||
<string name="cli_contributors">CLI</string>
|
||||
<string name="patcher_contributors">Patcher</string>
|
||||
<string name="patches_contributors">Patches</string>
|
||||
<string name="manager_contributors">Manager</string>
|
||||
<string name="integrations_contributors">Integrations</string>
|
||||
<string name="dropdown_button">Dropdown Button</string>
|
||||
<string name="app_version">Version</string>
|
||||
<string name="faq">FAQ</string>
|
||||
<string name="version_info">Version info</string>
|
||||
<string name="unsupported_version">Unsupported version</string>
|
||||
<string name="compatible_versions">Compatible app versions</string>
|
||||
<string name="patcher_notification_title">Patching</string>
|
||||
<string name="patcher_notification_message">ReVanced Manager is patching</string>
|
||||
<string name="theme">Theme</string>
|
||||
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
Loading…
x
Reference in New Issue
Block a user