mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-04-30 06:24: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
|
compileSdk = 33
|
||||||
buildToolsVersion = "33.0.0"
|
buildToolsVersion = "33.0.0"
|
||||||
|
|
||||||
lint {
|
|
||||||
abortOnError = false
|
|
||||||
disable += "DialogFragmentCallbacksDetector"
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "app.revanced.manager.compose"
|
applicationId = "app.revanced.manager.compose"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
@ -38,6 +33,10 @@ android {
|
|||||||
versionName = "0.0.1"
|
versionName = "0.0.1"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
|
buildConfigField("String", "REVANCED_API_URL", "\"https://releases.revanced.app\"")
|
||||||
|
buildConfigField("String", "GITHUB_API_URL", "\"https://api.github.com\"")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -81,24 +80,24 @@ dependencies {
|
|||||||
implementation("androidx.core:core-splashscreen:1.0.0")
|
implementation("androidx.core:core-splashscreen:1.0.0")
|
||||||
|
|
||||||
// AndroidX activity
|
// 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")
|
implementation("androidx.work:work-runtime-ktx:2.7.1")
|
||||||
|
|
||||||
// Koin
|
// Koin
|
||||||
val koinVersion = "3.2.2"
|
val koinVersion = "3.3.0"
|
||||||
implementation("io.insert-koin:koin-android:$koinVersion")
|
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")
|
implementation("io.insert-koin:koin-androidx-workmanager:$koinVersion")
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
val composeVersion = "1.3.0-rc01"
|
val composeVersion = "1.4.0-alpha01"
|
||||||
implementation("androidx.compose.ui:ui:$composeVersion")
|
implementation("androidx.compose.ui:ui:$composeVersion")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling:$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}")
|
implementation("androidx.compose.material:material-icons-extended:${composeVersion}")
|
||||||
|
|
||||||
// Accompanist
|
// Accompanist
|
||||||
val accompanistVersion = "0.26.5-rc"
|
val accompanistVersion = "0.27.0"
|
||||||
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
||||||
implementation("com.google.accompanist:accompanist-placeholder-material:$accompanistVersion")
|
implementation("com.google.accompanist:accompanist-placeholder-material:$accompanistVersion")
|
||||||
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
|
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
|
||||||
@ -108,7 +107,7 @@ dependencies {
|
|||||||
implementation("io.coil-kt:coil-compose:2.2.2")
|
implementation("io.coil-kt:coil-compose:2.2.2")
|
||||||
|
|
||||||
// KotlinX
|
// KotlinX
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||||
|
|
||||||
// Taxi (navigation)
|
// Taxi (navigation)
|
||||||
implementation("com.github.X1nto:Taxi:1.2.0")
|
implementation("com.github.X1nto:Taxi:1.2.0")
|
||||||
@ -118,18 +117,16 @@ dependencies {
|
|||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
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
|
// Licenses
|
||||||
implementation("com.mikepenz:aboutlibraries-compose:10.5.1")
|
implementation("com.mikepenz:aboutlibraries-compose:10.5.1")
|
||||||
|
|
||||||
// ListenableFuture
|
// Ktor
|
||||||
implementation("com.google.guava:guava:31.1-android")
|
val ktorVersion = "2.1.3"
|
||||||
implementation("androidx.concurrent:concurrent-futures:1.1.0")
|
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4")
|
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||||
// Networking
|
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
|
||||||
implementation("com.vk.knet:core:1.0")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
implementation("com.vk.knet:cronet:1.0")
|
|
||||||
implementation("com.vk.knet:okcronet:1.0")
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
package app.revanced.manager
|
package app.revanced.manager
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@ -18,12 +12,14 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
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.navigation.AppDestination
|
||||||
import app.revanced.manager.ui.screen.MainDashboardScreen
|
import app.revanced.manager.ui.screen.MainDashboardScreen
|
||||||
import app.revanced.manager.ui.screen.subscreens.*
|
import app.revanced.manager.ui.screen.subscreens.*
|
||||||
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||||
import app.revanced.manager.ui.theme.Theme
|
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.Taxi
|
||||||
import com.xinto.taxi.rememberBackstackNavigator
|
import com.xinto.taxi.rememberBackstackNavigator
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
@ -35,7 +31,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
permissions()
|
|
||||||
setContent {
|
setContent {
|
||||||
ReVancedManagerTheme(
|
ReVancedManagerTheme(
|
||||||
dynamicColor = prefs.dynamicColor,
|
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 android.app.Application
|
||||||
import app.revanced.manager.di.*
|
import app.revanced.manager.di.*
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.ImageLoaderFactory
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.androidx.workmanager.koin.workManagerFactory
|
import org.koin.androidx.workmanager.koin.workManagerFactory
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
@ -13,7 +15,15 @@ class ManagerApplication : Application() {
|
|||||||
startKoin {
|
startKoin {
|
||||||
androidContext(this@ManagerApplication)
|
androidContext(this@ManagerApplication)
|
||||||
workManagerFactory()
|
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
|
package app.revanced.manager.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.vk.knet.core.Knet
|
import android.util.Log
|
||||||
import com.vk.knet.core.utils.ByteArrayPool
|
import app.revanced.manager.util.tag
|
||||||
import com.vk.knet.cornet.CronetKnetEngine
|
import io.ktor.client.*
|
||||||
import com.vk.knet.cornet.config.CronetCache
|
import io.ktor.client.engine.okhttp.*
|
||||||
import com.vk.knet.cornet.config.CronetQuic
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
import com.vk.knet.cornet.pool.buffer.CronetNativeByteBufferPool
|
import io.ktor.client.plugins.logging.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import kotlinx.serialization.json.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.android.ext.koin.androidContext
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import java.util.concurrent.TimeUnit
|
import java.net.Inet4Address
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
val httpModule = module {
|
val httpModule = module {
|
||||||
fun client(appContext: Context) = CronetKnetEngine.Build(appContext) {
|
fun provideHttpClient(context: Context, json: Json) = HttpClient(OkHttp) {
|
||||||
client {
|
engine {
|
||||||
setCache(CronetCache.Disk(appContext.filesDir, 1024 * 1024 * 10))
|
config {
|
||||||
|
dns(object : Dns {
|
||||||
enableHttp2(true)
|
override fun lookup(hostname: String): List<InetAddress> {
|
||||||
enableQuic(
|
val addresses = Dns.SYSTEM.lookup(hostname)
|
||||||
CronetQuic()
|
return if (hostname == "raw.githubusercontent.com") {
|
||||||
)
|
addresses.filterIsInstance<Inet4Address>()
|
||||||
|
} else {
|
||||||
useBrotli(true)
|
addresses
|
||||||
connectTimeout(15, TimeUnit.SECONDS)
|
}
|
||||||
writeTimeout(15, TimeUnit.SECONDS)
|
}
|
||||||
readTimeout(15, TimeUnit.SECONDS)
|
})
|
||||||
|
cache(Cache(context.cacheDir.resolve("cache").also { it.mkdirs() }, 1024 * 1024 * 100))
|
||||||
nativePool(CronetNativeByteBufferPool.DEFAULT)
|
|
||||||
arrayPool(ByteArrayPool.DEFAULT)
|
|
||||||
|
|
||||||
maxConcurrentRequests(50)
|
|
||||||
maxConcurrentRequestsPerHost(10)
|
|
||||||
|
|
||||||
followRedirects(true)
|
followRedirects(true)
|
||||||
followSslRedirects(true)
|
followSslRedirects(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun json() = Json {
|
fun provideJson() = Json {
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
isLenient = true
|
isLenient = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
client(androidContext())
|
provideHttpClient(androidContext(), get())
|
||||||
}
|
|
||||||
single {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
single {
|
|
||||||
Knet.Build(get<CronetKnetEngine>())
|
|
||||||
}
|
}
|
||||||
|
singleOf(::provideJson)
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.manager.di
|
package app.revanced.manager.di
|
||||||
|
|
||||||
import android.content.Context
|
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.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
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
|
package app.revanced.manager.di
|
||||||
|
|
||||||
import app.revanced.manager.network.api.GitHubAPI
|
import app.revanced.manager.domain.repository.GithubRepositoryImpl
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
|
||||||
|
import app.revanced.manager.network.api.ManagerAPI
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val repositoryModule = module {
|
val repositoryModule = module {
|
||||||
singleOf(::GitHubAPI)
|
singleOf(::GithubRepositoryImpl)
|
||||||
singleOf(::ReVancedAPI)
|
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 {
|
val viewModelModule = module {
|
||||||
viewModelOf(::SettingsViewModel)
|
viewModelOf(::SettingsViewModel)
|
||||||
viewModelOf(::DashboardViewModel)
|
viewModelOf(::DashboardViewModel)
|
||||||
viewModelOf(::PatcherScreenViewModel)
|
|
||||||
viewModelOf(::AppSelectorViewModel)
|
viewModelOf(::AppSelectorViewModel)
|
||||||
|
viewModelOf(::PatchesSelectorViewModel)
|
||||||
viewModelOf(::PatchingScreenViewModel)
|
viewModelOf(::PatchingScreenViewModel)
|
||||||
viewModelOf(::ContributorsViewModel)
|
viewModelOf(::ContributorsViewModel)
|
||||||
}
|
}
|
@ -6,5 +6,5 @@ import org.koin.androidx.workmanager.dsl.worker
|
|||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val workerModule = 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 android.content.SharedPreferences
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.content.edit
|
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
|
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
|
* @author Hyperion Authors, zt64
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
|
||||||
abstract class BasePreferenceManager(
|
abstract class BasePreferenceManager(
|
||||||
private val prefs: SharedPreferences
|
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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Release(
|
class GithubReleases(
|
||||||
val assets: List<Asset>,
|
val assets: List<Asset>,
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Repositories(
|
class ReVancedRepositories(
|
||||||
@SerialName("repositories") val repositories: List<Repository>,
|
@SerialName("repositories") val repositories: List<ReVancedRepository>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Repository(
|
class ReVancedRepository(
|
||||||
@SerialName("name") val name: String,
|
@SerialName("name") val name: String,
|
||||||
@SerialName("contributors") val contributors: List<Contributor>,
|
@SerialName("contributors") val contributors: List<ReVancedContributor>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Contributor(
|
class ReVancedContributor(
|
||||||
@SerialName("login") val username: String,
|
@SerialName("login") val username: String,
|
||||||
@SerialName("avatar_url") val avatarUrl: 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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Tools(
|
class ReVancedReleases(
|
||||||
@SerialName("tools") val tools: List<Assets>,
|
@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.ForegroundInfo
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.Variables.patches
|
import app.revanced.manager.network.api.ManagerAPI
|
||||||
import app.revanced.manager.Variables.selectedAppPackage
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
import app.revanced.manager.Variables.selectedPatches
|
|
||||||
import app.revanced.manager.network.api.GitHubAPI
|
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
|
||||||
import app.revanced.manager.patcher.aapt.Aapt
|
import app.revanced.manager.patcher.aapt.Aapt
|
||||||
import app.revanced.manager.patcher.aligning.ZipAligner
|
import app.revanced.manager.patcher.aligning.ZipAligner
|
||||||
import app.revanced.manager.patcher.aligning.zip.ZipFile
|
import app.revanced.manager.patcher.aligning.zip.ZipFile
|
||||||
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
|
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
|
||||||
import app.revanced.manager.patcher.signing.Signer
|
import app.revanced.manager.patcher.signing.Signer
|
||||||
import app.revanced.manager.preferences.PreferencesManager
|
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.manager.ui.viewmodel.Logging
|
import app.revanced.manager.ui.viewmodel.Logging
|
||||||
import app.revanced.manager.util.ghIntegrations
|
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherOptions
|
import app.revanced.patcher.PatcherOptions
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
@ -41,14 +36,12 @@ import java.io.File
|
|||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
class PatcherWorker(
|
class PatcherWorker(
|
||||||
context: Context,
|
context: Context,
|
||||||
parameters: WorkerParameters,
|
parameters: WorkerParameters,
|
||||||
private val reVancedAPI: ReVancedAPI,
|
private val managerAPI: ManagerAPI,
|
||||||
private val gitHubAPI: GitHubAPI,
|
private val patcherUtils: PatcherUtils
|
||||||
private val prefs: PreferencesManager
|
|
||||||
) : CoroutineWorker(context, parameters), KoinComponent {
|
) : CoroutineWorker(context, parameters), KoinComponent {
|
||||||
val tag = "ReVanced Manager"
|
val tag = "ReVanced Manager"
|
||||||
private val workdir = createWorkDir()
|
private val workdir = createWorkDir()
|
||||||
@ -118,10 +111,10 @@ class PatcherWorker(
|
|||||||
applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() }
|
applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() }
|
||||||
val reVancedFolder =
|
val reVancedFolder =
|
||||||
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }
|
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }
|
||||||
val appInfo = selectedAppPackage.value.get()
|
val appInfo = patcherUtils.selectedAppPackage.value.get()
|
||||||
|
|
||||||
Logging.log += "Checking prerequisites\n"
|
Logging.log += "Checking prerequisites\n"
|
||||||
val patches = findPatchesByIds(selectedPatches)
|
val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches)
|
||||||
if (patches.isEmpty()) return true
|
if (patches.isEmpty()) return true
|
||||||
|
|
||||||
|
|
||||||
@ -131,16 +124,7 @@ class PatcherWorker(
|
|||||||
val outputFile = File(applicationContext.filesDir, "output.apk")
|
val outputFile = File(applicationContext.filesDir, "output.apk")
|
||||||
val cacheDirectory = workdir.resolve("cache")
|
val cacheDirectory = workdir.resolve("cache")
|
||||||
|
|
||||||
val integrations = if (prefs.srcIntegrations != ghIntegrations || !reVancedAPI.ping()) {
|
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
|
||||||
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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Logging.log += "Copying base.apk from device\n"
|
Logging.log += "Copying base.apk from device\n"
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@ -217,12 +201,13 @@ class PatcherWorker(
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Files.copy(
|
Files.copy(
|
||||||
outputFile.inputStream(),
|
outputFile.inputStream(),
|
||||||
reVancedFolder.resolve(appInfo.packageName + "-" + LocalDateTime.now() + ".apk")
|
reVancedFolder.resolve(appInfo.packageName + ".apk")
|
||||||
.toPath(),
|
.toPath(),
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Logging.log += "Copied file to storage!\n"
|
Logging.log += "Copied file to storage!\n"
|
||||||
|
patcherUtils.cleanup()
|
||||||
} finally {
|
} finally {
|
||||||
Log.d(tag, "Deleting workdir")
|
Log.d(tag, "Deleting workdir")
|
||||||
workdir.deleteRecursively()
|
workdir.deleteRecursively()
|
||||||
@ -232,14 +217,9 @@ class PatcherWorker(
|
|||||||
return false
|
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 {
|
private fun createWorkDir(): File {
|
||||||
return applicationContext.filesDir.resolve("tmp-${System.currentTimeMillis()}")
|
return applicationContext.cacheDir.resolve("tmp-${System.currentTimeMillis()}")
|
||||||
.also { it.mkdirs() }
|
.also { it.mkdirs() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
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.network.dto.revanced.Contributor
|
import app.revanced.manager.network.dto.ReVancedContributor
|
||||||
import app.revanced.manager.ui.viewmodel.ContributorsViewModel
|
import app.revanced.manager.ui.viewmodel.ContributorsViewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
@ -27,7 +27,7 @@ import org.koin.androidx.compose.getViewModel
|
|||||||
@ExperimentalMaterial3Api
|
@ExperimentalMaterial3Api
|
||||||
fun ContributorsCard(
|
fun ContributorsCard(
|
||||||
title: String,
|
title: String,
|
||||||
data: SnapshotStateList<Contributor>,
|
data: SnapshotStateList<ReVancedContributor>,
|
||||||
vm: ContributorsViewModel = getViewModel()
|
vm: ContributorsViewModel = getViewModel()
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
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.text.style.TextAlign
|
||||||
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.patcher.PatcherUtils
|
||||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
import app.revanced.manager.ui.viewmodel.PatchClass
|
||||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
|
||||||
import app.revanced.patcher.annotation.Package
|
import app.revanced.patcher.annotation.Package
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PatchCompatibilityDialog(
|
fun PatchCompatibilityDialog(
|
||||||
patchClass: PatchClass, pvm: PatcherScreenViewModel = getViewModel(), onClose: () -> Unit
|
patchClass: PatchClass, patcherUtils: PatcherUtils = get(), onClose: () -> Unit
|
||||||
) {
|
) {
|
||||||
val patch = patchClass.patch
|
val patch = patchClass.patch
|
||||||
val packageName = pvm.getSelectedPackageInfo()?.packageName
|
val packageName = patcherUtils.getSelectedPackageInfo()?.packageName
|
||||||
AlertDialog(onDismissRequest = onClose, shape = RoundedCornerShape(12.dp), title = {
|
AlertDialog(onDismissRequest = onClose, shape = RoundedCornerShape(12.dp), title = {
|
||||||
Text(stringResource(id = R.string.unsupported), textAlign = TextAlign.Center)
|
Text(stringResource(id = R.string.unsupported), textAlign = TextAlign.Center)
|
||||||
}, text = {
|
}, text = {
|
||||||
|
@ -16,15 +16,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.get
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPatcherScreen(
|
fun NewPatcherScreen(
|
||||||
onClickAppSelector: () -> Unit,
|
onClickAppSelector: () -> Unit,
|
||||||
onClickPatchSelector: () -> Unit,
|
onClickPatchSelector: () -> Unit,
|
||||||
viewModel: PatcherScreenViewModel = getViewModel()
|
patcherUtils: PatcherUtils = get()
|
||||||
) {
|
) {
|
||||||
var validBundle = false
|
var validBundle = false
|
||||||
Column(
|
Column(
|
||||||
@ -75,7 +75,7 @@ fun NewPatcherScreen(
|
|||||||
fontSize = 13.sp
|
fontSize = 13.sp
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = viewModel.getSelectedPackageInfo()!!.applicationInfo.name,
|
text = patcherUtils.getSelectedPackageInfo()!!.applicationInfo.name,
|
||||||
fontSize = 13.sp
|
fontSize = 13.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@ import androidx.compose.ui.Modifier
|
|||||||
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.Variables.patches
|
import app.revanced.manager.network.api.ManagerAPI
|
||||||
import app.revanced.manager.Variables.selectedAppPackage
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
import app.revanced.manager.Variables.selectedPatches
|
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.manager.ui.component.FloatingActionButton
|
import app.revanced.manager.ui.component.FloatingActionButton
|
||||||
import app.revanced.manager.ui.component.SplitAPKDialog
|
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
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -28,25 +28,26 @@ fun PatcherScreen(
|
|||||||
onClickPatchSelector: () -> Unit,
|
onClickPatchSelector: () -> Unit,
|
||||||
onClickPatch: () -> Unit,
|
onClickPatch: () -> Unit,
|
||||||
onClickSourceSelector: () -> Unit,
|
onClickSourceSelector: () -> Unit,
|
||||||
viewModel: PatcherScreenViewModel = getViewModel()
|
patcherUtils: PatcherUtils = get(),
|
||||||
|
psvm: PatchesSelectorViewModel = getViewModel(),
|
||||||
|
managerAPI: ManagerAPI = get()
|
||||||
) {
|
) {
|
||||||
val selectedAmount = selectedPatches.size
|
val selectedAmount = patcherUtils.selectedPatches.size
|
||||||
val selectedAppPackage by selectedAppPackage
|
val selectedAppPackage by patcherUtils.selectedAppPackage
|
||||||
val hasAppSelected = selectedAppPackage.isPresent
|
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) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(patchesLoaded) {
|
||||||
|
if (!patchesLoaded) {
|
||||||
|
managerAPI.downloadPatches()
|
||||||
|
}
|
||||||
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
enabled = hasAppSelected && viewModel.anyPatchSelected(),
|
enabled = hasAppSelected && psvm.anyPatchSelected(),
|
||||||
onClick = {
|
onClick = { onClickPatch(); patcherUtils.loadPatchBundle() }, // TODO: replace this with something better
|
||||||
if (viewModel.checkSplitApk()) {
|
|
||||||
showDialog = true
|
|
||||||
} else {
|
|
||||||
onClickPatch(); viewModel.loadPatches0()
|
|
||||||
}
|
|
||||||
}, // TODO: replace this with something better
|
|
||||||
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
||||||
text = { Text(text = "Patch") }
|
text = { Text(text = "Patch") }
|
||||||
)
|
)
|
||||||
@ -114,7 +115,7 @@ fun PatcherScreen(
|
|||||||
Text(
|
Text(
|
||||||
text = if (!hasAppSelected) {
|
text = if (!hasAppSelected) {
|
||||||
"Select an application first."
|
"Select an application first."
|
||||||
} else if (viewModel.anyPatchSelected()) {
|
} else if (psvm.anyPatchSelected()) {
|
||||||
"$selectedAmount patches selected."
|
"$selectedAmount patches selected."
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.card_patches_body_patches)
|
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.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.preferences.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.ui.component.GroupHeader
|
import app.revanced.manager.ui.component.GroupHeader
|
||||||
import app.revanced.manager.ui.component.SocialItem
|
import app.revanced.manager.ui.component.SocialItem
|
||||||
import app.revanced.manager.ui.theme.Theme
|
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.text.style.TextOverflow
|
||||||
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.Variables.patchesState
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.manager.ui.component.LoadingIndicator
|
import app.revanced.manager.ui.component.LoadingIndicator
|
||||||
import app.revanced.manager.ui.component.PatchCompatibilityDialog
|
import app.revanced.manager.ui.component.PatchCompatibilityDialog
|
||||||
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.theme.Typography
|
||||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
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.description
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.options
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.version
|
import app.revanced.patcher.extensions.PatchExtensions.version
|
||||||
import com.xinto.taxi.BackstackNavigator
|
import com.xinto.taxi.BackstackNavigator
|
||||||
|
import org.koin.androidx.compose.get
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@SuppressLint("UnrememberedMutableState")
|
@SuppressLint("UnrememberedMutableState")
|
||||||
@ -35,9 +37,11 @@ import org.koin.androidx.compose.getViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun PatchesSelectorSubscreen(
|
fun PatchesSelectorSubscreen(
|
||||||
navigator: BackstackNavigator<AppDestination>,
|
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("")
|
var query by mutableStateOf("")
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -59,9 +63,9 @@ fun PatchesSelectorSubscreen(
|
|||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
pvm.selectAllPatches(patches, !pvm.anyPatchSelected())
|
psvm.selectAllPatches(patches, !psvm.anyPatchSelected())
|
||||||
}) {
|
}) {
|
||||||
if (!pvm.anyPatchSelected()) Icon(
|
if (!psvm.anyPatchSelected()) Icon(
|
||||||
Icons.Default.SelectAll,
|
Icons.Default.SelectAll,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
||||||
@ -115,8 +119,8 @@ fun PatchesSelectorSubscreen(
|
|||||||
items(count = patches.size) {
|
items(count = patches.size) {
|
||||||
val patch = patches[it]
|
val patch = patches[it]
|
||||||
val name = patch.patch.patchName
|
val name = patch.patch.patchName
|
||||||
PatchCard(patch, pvm.isPatchSelected(name)) {
|
PatchCard(patch, psvm.isPatchSelected(name)) {
|
||||||
pvm.selectPatch(name, !pvm.isPatchSelected(name))
|
psvm.selectPatch(name, !psvm.isPatchSelected(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -124,8 +128,8 @@ fun PatchesSelectorSubscreen(
|
|||||||
val patch = patches[it]
|
val patch = patches[it]
|
||||||
val name = patch.patch.patchName
|
val name = patch.patch.patchName
|
||||||
if (name.contains(query.lowercase())) {
|
if (name.contains(query.lowercase())) {
|
||||||
PatchCard(patch, pvm.isPatchSelected(name)) {
|
PatchCard(patch, psvm.isPatchSelected(name)) {
|
||||||
pvm.selectPatch(name, !pvm.isPatchSelected(name))
|
psvm.selectPatch(name, !psvm.isPatchSelected(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,7 +194,7 @@ fun PatchCard(patchClass: PatchClass, isSelected: Boolean, onSelected: () -> Uni
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(4.dp)
|
modifier = Modifier.padding(4.dp)
|
||||||
) {
|
) {
|
||||||
if (patchClass.hasPatchOptions) {
|
if (patchClass.patch.options != null) {
|
||||||
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
|
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
|
||||||
IconButton(onClick = { }, modifier = Modifier.size(24.dp)) {
|
IconButton(onClick = { }, modifier = Modifier.size(24.dp)) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -20,11 +20,11 @@ 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.patcher.PatcherUtils
|
||||||
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.viewmodel.PatcherScreenViewModel
|
|
||||||
import com.xinto.taxi.BackstackNavigator
|
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.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ import java.nio.file.StandardCopyOption
|
|||||||
@Composable
|
@Composable
|
||||||
fun SourceSelectorSubscreen(
|
fun SourceSelectorSubscreen(
|
||||||
navigator: BackstackNavigator<AppDestination>,
|
navigator: BackstackNavigator<AppDestination>,
|
||||||
pvm: PatcherScreenViewModel = getViewModel()
|
patcherUtils: PatcherUtils = get()
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||||
state = rememberTopAppBarState(),
|
state = rememberTopAppBarState(),
|
||||||
@ -48,8 +48,11 @@ fun SourceSelectorSubscreen(
|
|||||||
patchesFile.toPath(),
|
patchesFile.toPath(),
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
)
|
)
|
||||||
pvm.patchBundleFile = patchesFile.absolutePath
|
patchesFile.absolutePath.also {
|
||||||
pvm.loadPatches0()
|
patcherUtils.patchBundleFile = it
|
||||||
|
patcherUtils.loadPatchBundle(it)
|
||||||
|
}
|
||||||
|
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
return@rememberLauncherForActivityResult
|
return@rememberLauncherForActivityResult
|
||||||
}
|
}
|
||||||
|
@ -8,28 +8,30 @@ import android.net.Uri
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.Variables
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
import app.revanced.manager.Variables.patches
|
|
||||||
import app.revanced.manager.Variables.selectedAppPackage
|
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.nio.file.Files
|
import kotlinx.coroutines.withContext
|
||||||
import java.nio.file.StandardCopyOption
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class AppSelectorViewModel(
|
class AppSelectorViewModel(
|
||||||
val app: Application,
|
val app: Application, patcherUtils: PatcherUtils
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val filteredApps = mutableListOf<ApplicationInfo>()
|
val filteredApps = mutableListOf<ApplicationInfo>()
|
||||||
|
val patches = patcherUtils.patches
|
||||||
|
private val selectedAppPackage = patcherUtils.selectedAppPackage
|
||||||
|
private val selectedPatches = patcherUtils.selectedPatches
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { filterApps() }
|
viewModelScope.launch { filterApps() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterApps() {
|
private suspend fun filterApps() = withContext(Dispatchers.Main) {
|
||||||
try {
|
try {
|
||||||
val (patches) = patches.value as Resource.Success
|
val (patches) = patches.value as Resource.Success
|
||||||
patches.forEach patch@{ patch ->
|
patches.forEach patch@{ patch ->
|
||||||
@ -61,23 +63,25 @@ class AppSelectorViewModel(
|
|||||||
|
|
||||||
fun setSelectedAppPackage(appId: ApplicationInfo) {
|
fun setSelectedAppPackage(appId: ApplicationInfo) {
|
||||||
selectedAppPackage.value.ifPresent { s ->
|
selectedAppPackage.value.ifPresent { s ->
|
||||||
if (s != appId) Variables.selectedPatches.clear()
|
if (s != appId) selectedPatches.clear()
|
||||||
}
|
}
|
||||||
selectedAppPackage.value = Optional.of(appId)
|
selectedAppPackage.value = Optional.of(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedAppPackageFromFile(file: Uri?) {
|
fun setSelectedAppPackageFromFile(file: Uri?) {
|
||||||
val apkDir = app.filesDir.resolve("input.apk").toPath()
|
try {
|
||||||
Files.copy(
|
val apkDir = app.cacheDir.resolve(File(file!!.path!!).name)
|
||||||
app.contentResolver.openInputStream(file!!),
|
app.contentResolver.openInputStream(file)!!.run {
|
||||||
apkDir,
|
copyTo(apkDir.outputStream())
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
close()
|
||||||
)
|
}
|
||||||
setSelectedAppPackage(
|
setSelectedAppPackage(
|
||||||
app.packageManager.getPackageArchiveInfo(
|
app.packageManager.getPackageArchiveInfo(
|
||||||
apkDir.toString(),
|
apkDir.path, PackageManager.GET_META_DATA
|
||||||
PackageManager.GET_META_DATA
|
|
||||||
)!!.applicationInfo
|
)!!.applicationInfo
|
||||||
)
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(tag, "Failed to load apk", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,27 +1,28 @@
|
|||||||
package app.revanced.manager.ui.viewmodel
|
package app.revanced.manager.ui.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
|
||||||
import app.revanced.manager.network.dto.revanced.Contributor
|
import app.revanced.manager.network.dto.ReVancedContributor
|
||||||
|
import app.revanced.manager.network.utils.getOrNull
|
||||||
import app.revanced.manager.util.*
|
import app.revanced.manager.util.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ContributorsViewModel(
|
class ContributorsViewModel(
|
||||||
private val app: Application,
|
private val app: Application, private val reVancedAPI: ReVancedRepositoryImpl
|
||||||
private val reVancedAPI: ReVancedAPI
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val patcherContributorsList = mutableStateListOf<Contributor>()
|
val patcherContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||||
val patchesContributorsList = mutableStateListOf<Contributor>()
|
val patchesContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||||
val cliContributorsList = mutableStateListOf<Contributor>()
|
val cliContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||||
val managerContributorsList = mutableStateListOf<Contributor>()
|
val managerContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||||
val integrationsContributorsList = mutableStateListOf<Contributor>()
|
val integrationsContributorsList = mutableStateListOf<ReVancedContributor>()
|
||||||
|
|
||||||
private fun loadContributors() {
|
private fun loadContributors() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val contributors = reVancedAPI.fetchContributors()
|
val contributors = reVancedAPI.getContributors().getOrNull() ?: return@launch
|
||||||
contributors.repositories.forEach { repo ->
|
contributors.repositories.forEach { repo ->
|
||||||
when (repo.name) {
|
when (repo.name) {
|
||||||
ghCli -> {
|
ghCli -> {
|
||||||
|
@ -2,24 +2,21 @@ package app.revanced.manager.ui.viewmodel
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
import app.revanced.manager.domain.repository.ReVancedRepositoryImpl
|
||||||
import app.revanced.manager.network.dto.revanced.Assets
|
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.ghManager
|
||||||
import app.revanced.manager.util.ghPatcher
|
import app.revanced.manager.util.ghPatcher
|
||||||
import app.revanced.manager.util.tag
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() {
|
class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : ViewModel() {
|
||||||
private var _latestPatcherCommit: Assets? by mutableStateOf(null)
|
private var _latestPatcherCommit: Assets? by mutableStateOf(null)
|
||||||
val patcherCommitDate: String
|
val patcherCommitDate: String
|
||||||
get() = _latestPatcherCommit?.commitDate ?: "unknown"
|
get() = _latestPatcherCommit?.commitDate ?: "unknown"
|
||||||
@ -34,8 +31,7 @@ class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() {
|
|||||||
|
|
||||||
private fun fetchLastCommit() {
|
private fun fetchLastCommit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
val repo = reVancedApi.getAssets().getOrNull() ?: return@launch
|
||||||
val repo = withContext(Dispatchers.Default) { reVancedApi.fetchAssets() }
|
|
||||||
for (asset in repo.tools) {
|
for (asset in repo.tools) {
|
||||||
when (asset.repository) {
|
when (asset.repository) {
|
||||||
ghPatcher -> {
|
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
|
@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
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.ui.theme.Theme
|
||||||
import app.revanced.manager.util.ghOrganization
|
import app.revanced.manager.util.ghOrganization
|
||||||
import app.revanced.manager.util.openUrl
|
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
|
package app.revanced.manager.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
fun Context.openUrl(url: String) {
|
fun Context.openUrl(url: String) {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).apply {
|
startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
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"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:width="278dp"
|
android:width="200dp"
|
||||||
android:height="278dp"
|
android:height="200dp"
|
||||||
android:viewportWidth="278"
|
android:viewportWidth="278"
|
||||||
android:viewportHeight="278">
|
android:viewportHeight="278">
|
||||||
<path
|
<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="settings">Settings</string>
|
||||||
<string name="updates">Updates</string>
|
<string name="updates">Updates</string>
|
||||||
<string name="manager">Manager</string>
|
<string name="manager">Manager</string>
|
||||||
<string name="update_manager">Update Manager</string>
|
|
||||||
<string name="update_patch_bundle">Update Patches</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="expand">Expand</string>
|
||||||
<string name="update">Update</string>
|
|
||||||
<string name="app_selector_title">Select an app…</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_application_header">Select Application</string>
|
||||||
<string name="card_patches_header">Select Patches</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_loaded">Loading applications.</string>
|
||||||
<string name="card_application_not_selected">No application selected.</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_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_body">One moment, please…</string>
|
||||||
<string name="loading_fetching_patches">Fetching patches</string>
|
|
||||||
<string name="unsupported">Unsupported version</string>
|
<string name="unsupported">Unsupported version</string>
|
||||||
<string name="only_compatible">This patch is only compatible with 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_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="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="cli_contributors">CLI</string>
|
||||||
<string name="patcher_contributors">Patcher</string>
|
<string name="patcher_contributors">Patcher</string>
|
||||||
<string name="patches_contributors">Patches</string>
|
<string name="patches_contributors">Patches</string>
|
||||||
<string name="manager_contributors">Manager</string>
|
<string name="manager_contributors">Manager</string>
|
||||||
<string name="integrations_contributors">Integrations</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="unsupported_version">Unsupported version</string>
|
||||||
<string name="compatible_versions">Compatible app versions</string>
|
|
||||||
<string name="patcher_notification_title">Patching</string>
|
<string name="patcher_notification_title">Patching</string>
|
||||||
<string name="patcher_notification_message">ReVanced Manager is patching</string>
|
<string name="patcher_notification_message">ReVanced Manager is patching</string>
|
||||||
<string name="theme">Theme</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