diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 421acd0..0cb83e6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,11 +25,6 @@ android { compileSdk = 33 buildToolsVersion = "33.0.0" - lint { - abortOnError = false - disable += "DialogFragmentCallbacksDetector" - } - defaultConfig { applicationId = "app.revanced.manager.compose" minSdk = 26 @@ -38,6 +33,10 @@ android { versionName = "0.0.1" vectorDrawables.useSupportLibrary = true + + buildConfigField("String", "REVANCED_API_URL", "\"https://releases.revanced.app\"") + buildConfigField("String", "GITHUB_API_URL", "\"https://api.github.com\"") + } buildTypes { @@ -81,24 +80,24 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.0") // AndroidX activity - implementation("androidx.activity:activity-compose:1.6.0") + implementation("androidx.activity:activity-compose:1.6.1") implementation("androidx.work:work-runtime-ktx:2.7.1") // Koin - val koinVersion = "3.2.2" + val koinVersion = "3.3.0" implementation("io.insert-koin:koin-android:$koinVersion") - implementation("io.insert-koin:koin-androidx-compose:3.2.1") + implementation("io.insert-koin:koin-androidx-compose:3.3.0") implementation("io.insert-koin:koin-androidx-workmanager:$koinVersion") // Compose - val composeVersion = "1.3.0-rc01" + val composeVersion = "1.4.0-alpha01" implementation("androidx.compose.ui:ui:$composeVersion") debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion") - implementation("androidx.compose.material3:material3:1.0.0-rc01") + implementation("androidx.compose.material3:material3:1.1.0-alpha01") implementation("androidx.compose.material:material-icons-extended:${composeVersion}") // Accompanist - val accompanistVersion = "0.26.5-rc" + val accompanistVersion = "0.27.0" implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion") implementation("com.google.accompanist:accompanist-placeholder-material:$accompanistVersion") implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion") @@ -108,7 +107,7 @@ dependencies { implementation("io.coil-kt:coil-compose:2.2.2") // KotlinX - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1") // Taxi (navigation) implementation("com.github.X1nto:Taxi:1.2.0") @@ -118,18 +117,16 @@ dependencies { // Signing & aligning implementation("org.bouncycastle:bcpkix-jdk15on:1.70") - implementation("com.android.tools.build:apksig:7.4.0-beta02") + implementation("com.android.tools.build:apksig:8.0.0-alpha07") // Licenses implementation("com.mikepenz:aboutlibraries-compose:10.5.1") - // ListenableFuture - implementation("com.google.guava:guava:31.1-android") - implementation("androidx.concurrent:concurrent-futures:1.1.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4") - - // Networking - implementation("com.vk.knet:core:1.0") - implementation("com.vk.knet:cronet:1.0") - implementation("com.vk.knet:okcronet:1.0") + // Ktor + val ktorVersion = "2.1.3" + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-logging:$ktorVersion") + implementation("io.ktor:ktor-client-okhttp:$ktorVersion") + implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") } diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt index bf11f42..334366e 100644 --- a/app/src/main/java/app/revanced/manager/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/MainActivity.kt @@ -1,12 +1,6 @@ package app.revanced.manager -import android.content.Intent -import android.net.Uri -import android.os.Build import android.os.Bundle -import android.os.Environment -import android.os.PowerManager -import android.provider.Settings import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent @@ -18,12 +12,14 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import app.revanced.manager.preferences.PreferencesManager +import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.screen.MainDashboardScreen import app.revanced.manager.ui.screen.subscreens.* import app.revanced.manager.ui.theme.ReVancedManagerTheme import app.revanced.manager.ui.theme.Theme +import app.revanced.manager.util.requestAllFilesAccess +import app.revanced.manager.util.requestIgnoreBatteryOptimizations import com.xinto.taxi.Taxi import com.xinto.taxi.rememberBackstackNavigator import org.koin.android.ext.android.inject @@ -35,7 +31,6 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) - permissions() setContent { ReVancedManagerTheme( dynamicColor = prefs.dynamicColor, @@ -65,26 +60,4 @@ class MainActivity : ComponentActivity() { } } } - - private fun permissions() { - - fun request(string: String) { - val intent = Intent(string) - intent.addCategory("android.intent.category.DEFAULT") - intent.data = Uri.fromParts("package", applicationContext.packageName, null) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivityForResult(intent, 1) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { - request(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) - } else { - requestPermissions(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 1) - requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) - } - val pm = applicationContext.getSystemService(POWER_SERVICE) as PowerManager - if (!pm.isIgnoringBatteryOptimizations(applicationContext.packageName)) { - request(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - } - } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ManagerApplication.kt b/app/src/main/java/app/revanced/manager/ManagerApplication.kt index 75368bf..e2f5f2f 100644 --- a/app/src/main/java/app/revanced/manager/ManagerApplication.kt +++ b/app/src/main/java/app/revanced/manager/ManagerApplication.kt @@ -2,6 +2,8 @@ package app.revanced.manager import android.app.Application import app.revanced.manager.di.* +import coil.ImageLoader +import coil.ImageLoaderFactory import org.koin.android.ext.koin.androidContext import org.koin.androidx.workmanager.koin.workManagerFactory import org.koin.core.context.startKoin @@ -13,7 +15,15 @@ class ManagerApplication : Application() { startKoin { androidContext(this@ManagerApplication) workManagerFactory() - modules(httpModule, preferencesModule, viewModelModule, repositoryModule, workerModule) + modules( + httpModule, + preferencesModule, + viewModelModule, + repositoryModule, + workerModule, + patcherModule, + serviceModule + ) } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/Variables.kt b/app/src/main/java/app/revanced/manager/Variables.kt deleted file mode 100644 index 749c765..0000000 --- a/app/src/main/java/app/revanced/manager/Variables.kt +++ /dev/null @@ -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()) - val selectedPatches = mutableStateListOf() - val patches = mutableStateOf>>>>(Resource.Loading) - val patchesState by patches -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/HttpModule.kt b/app/src/main/java/app/revanced/manager/di/HttpModule.kt index 4ff64df..f046b95 100644 --- a/app/src/main/java/app/revanced/manager/di/HttpModule.kt +++ b/app/src/main/java/app/revanced/manager/di/HttpModule.kt @@ -1,56 +1,55 @@ package app.revanced.manager.di import android.content.Context -import com.vk.knet.core.Knet -import com.vk.knet.core.utils.ByteArrayPool -import com.vk.knet.cornet.CronetKnetEngine -import com.vk.knet.cornet.config.CronetCache -import com.vk.knet.cornet.config.CronetQuic -import com.vk.knet.cornet.pool.buffer.CronetNativeByteBufferPool +import android.util.Log +import app.revanced.manager.util.tag +import io.ktor.client.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.logging.* +import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json +import okhttp3.Cache +import okhttp3.Dns +import okhttp3.Protocol import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.singleOf import org.koin.dsl.module -import java.util.concurrent.TimeUnit +import java.net.Inet4Address +import java.net.InetAddress val httpModule = module { - fun client(appContext: Context) = CronetKnetEngine.Build(appContext) { - client { - setCache(CronetCache.Disk(appContext.filesDir, 1024 * 1024 * 10)) - - enableHttp2(true) - enableQuic( - CronetQuic() - ) - - useBrotli(true) - connectTimeout(15, TimeUnit.SECONDS) - writeTimeout(15, TimeUnit.SECONDS) - readTimeout(15, TimeUnit.SECONDS) - - nativePool(CronetNativeByteBufferPool.DEFAULT) - arrayPool(ByteArrayPool.DEFAULT) - - maxConcurrentRequests(50) - maxConcurrentRequestsPerHost(10) - - followRedirects(true) - followSslRedirects(true) + fun provideHttpClient(context: Context, json: Json) = HttpClient(OkHttp) { + engine { + config { + dns(object : Dns { + override fun lookup(hostname: String): List { + val addresses = Dns.SYSTEM.lookup(hostname) + return if (hostname == "raw.githubusercontent.com") { + addresses.filterIsInstance() + } else { + addresses + } + } + }) + cache(Cache(context.cacheDir.resolve("cache").also { it.mkdirs() }, 1024 * 1024 * 100)) + followRedirects(true) + followSslRedirects(true) + } + } + install(ContentNegotiation) { + json(json) } } - fun json() = Json { + fun provideJson() = Json { encodeDefaults = true isLenient = true ignoreUnknownKeys = true } single { - client(androidContext()) - } - single { - json() - } - single { - Knet.Build(get()) + provideHttpClient(androidContext(), get()) } + singleOf(::provideJson) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/PreferencesModule.kt b/app/src/main/java/app/revanced/manager/di/ManagerModule.kt similarity index 84% rename from app/src/main/java/app/revanced/manager/di/PreferencesModule.kt rename to app/src/main/java/app/revanced/manager/di/ManagerModule.kt index 0535224..9b8f993 100644 --- a/app/src/main/java/app/revanced/manager/di/PreferencesModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ManagerModule.kt @@ -1,7 +1,7 @@ package app.revanced.manager.di import android.content.Context -import app.revanced.manager.preferences.PreferencesManager +import app.revanced.manager.domain.manager.PreferencesManager import org.koin.core.module.dsl.singleOf import org.koin.dsl.module diff --git a/app/src/main/java/app/revanced/manager/di/PatcherModule.kt b/app/src/main/java/app/revanced/manager/di/PatcherModule.kt new file mode 100644 index 0000000..9e1ff5b --- /dev/null +++ b/app/src/main/java/app/revanced/manager/di/PatcherModule.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt index 26642c0..036e835 100644 --- a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt +++ b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt @@ -1,11 +1,13 @@ package app.revanced.manager.di -import app.revanced.manager.network.api.GitHubAPI -import app.revanced.manager.network.api.ReVancedAPI +import app.revanced.manager.domain.repository.GithubRepositoryImpl +import app.revanced.manager.domain.repository.ReVancedRepositoryImpl +import app.revanced.manager.network.api.ManagerAPI import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val repositoryModule = module { - singleOf(::GitHubAPI) - singleOf(::ReVancedAPI) + singleOf(::GithubRepositoryImpl) + singleOf(::ReVancedRepositoryImpl) + singleOf(::ManagerAPI) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/ServiceModule.kt b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt new file mode 100644 index 0000000..3bad25d --- /dev/null +++ b/app/src/main/java/app/revanced/manager/di/ServiceModule.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt index 1ab5aa0..a89c90e 100644 --- a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt @@ -7,8 +7,8 @@ import org.koin.dsl.module val viewModelModule = module { viewModelOf(::SettingsViewModel) viewModelOf(::DashboardViewModel) - viewModelOf(::PatcherScreenViewModel) viewModelOf(::AppSelectorViewModel) + viewModelOf(::PatchesSelectorViewModel) viewModelOf(::PatchingScreenViewModel) viewModelOf(::ContributorsViewModel) } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/WorkerModule.kt b/app/src/main/java/app/revanced/manager/di/WorkerModule.kt index 0c186bb..4f8178e 100644 --- a/app/src/main/java/app/revanced/manager/di/WorkerModule.kt +++ b/app/src/main/java/app/revanced/manager/di/WorkerModule.kt @@ -6,5 +6,5 @@ import org.koin.androidx.workmanager.dsl.worker import org.koin.dsl.module val workerModule = module { - worker { PatcherWorker(androidContext(), get(), get(), get(), get()) } + worker { PatcherWorker(androidContext(), get(), get(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt new file mode 100644 index 0000000..7bef3dc --- /dev/null +++ b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt @@ -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) +} diff --git a/app/src/main/java/app/revanced/manager/preferences/PreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt similarity index 84% rename from app/src/main/java/app/revanced/manager/preferences/PreferencesManager.kt rename to app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt index 8a6b576..c257d24 100644 --- a/app/src/main/java/app/revanced/manager/preferences/PreferencesManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt @@ -1,28 +1,15 @@ -package app.revanced.manager.preferences +package app.revanced.manager.domain.manager.base import android.content.SharedPreferences import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.core.content.edit -import app.revanced.manager.ui.theme.Theme -import app.revanced.manager.util.ghIntegrations -import app.revanced.manager.util.ghPatches import kotlin.reflect.KProperty -class PreferencesManager( - sharedPreferences: SharedPreferences -) : BasePreferenceManager(sharedPreferences) { - var dynamicColor by booleanPreference("dynamic_color", true) - var theme by enumPreference("theme", Theme.SYSTEM) - var srcPatches by stringPreference("src_patches", ghPatches) - var srcIntegrations by stringPreference("src_integrations", ghIntegrations) -} - /** * @author Hyperion Authors, zt64 */ -@Suppress("unused") abstract class BasePreferenceManager( private val prefs: SharedPreferences ) { diff --git a/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt new file mode 100644 index 0000000..ebd5565 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/domain/repository/GithubRepository.kt @@ -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 + + suspend fun getContributors(repo: String): APIResponse> + + 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) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/repository/ReVancedRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/ReVancedRepository.kt new file mode 100644 index 0000000..a47cda0 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/domain/repository/ReVancedRepository.kt @@ -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 + + suspend fun getContributors(): APIResponse + + 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) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/api/GitHubAPI.kt b/app/src/main/java/app/revanced/manager/network/api/GitHubAPI.kt deleted file mode 100644 index 894b404..0000000 --- a/app/src/main/java/app/revanced/manager/network/api/GitHubAPI.kt +++ /dev/null @@ -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.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>(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" - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt b/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt new file mode 100644 index 0000000..0d92200 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/api/ManagerAPI.kt @@ -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() \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt b/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt deleted file mode 100644 index 069995e..0000000 --- a/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt +++ /dev/null @@ -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.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() \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/GithubContributor.kt b/app/src/main/java/app/revanced/manager/network/dto/GithubContributor.kt new file mode 100644 index 0000000..997f11d --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/dto/GithubContributor.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/github/Release.kt b/app/src/main/java/app/revanced/manager/network/dto/GithubReleases.kt similarity index 82% rename from app/src/main/java/app/revanced/manager/network/dto/github/Release.kt rename to app/src/main/java/app/revanced/manager/network/dto/GithubReleases.kt index c9e6db4..2eb5af6 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/github/Release.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/GithubReleases.kt @@ -1,10 +1,10 @@ -package app.revanced.manager.network.dto.github +package app.revanced.manager.network.dto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class Release( +class GithubReleases( val assets: List, ) { @Serializable diff --git a/app/src/main/java/app/revanced/manager/network/dto/revanced/Contributors.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt similarity index 50% rename from app/src/main/java/app/revanced/manager/network/dto/revanced/Contributors.kt rename to app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt index 5a3c209..4e4c96a 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/revanced/Contributors.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedContributors.kt @@ -1,21 +1,21 @@ -package app.revanced.manager.network.dto.revanced +package app.revanced.manager.network.dto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class Repositories( - @SerialName("repositories") val repositories: List, +class ReVancedRepositories( + @SerialName("repositories") val repositories: List, ) @Serializable -class Repository( +class ReVancedRepository( @SerialName("name") val name: String, - @SerialName("contributors") val contributors: List, + @SerialName("contributors") val contributors: List, ) @Serializable -class Contributor( +class ReVancedContributor( @SerialName("login") val username: String, @SerialName("avatar_url") val avatarUrl: String, ) diff --git a/app/src/main/java/app/revanced/manager/network/dto/revanced/Tools.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedReleases.kt similarity index 89% rename from app/src/main/java/app/revanced/manager/network/dto/revanced/Tools.kt rename to app/src/main/java/app/revanced/manager/network/dto/ReVancedReleases.kt index bea0812..8b1ddea 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/revanced/Tools.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedReleases.kt @@ -1,10 +1,10 @@ -package app.revanced.manager.network.dto.revanced +package app.revanced.manager.network.dto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class Tools( +class ReVancedReleases( @SerialName("tools") val tools: List, ) diff --git a/app/src/main/java/app/revanced/manager/network/service/GithubService.kt b/app/src/main/java/app/revanced/manager/network/service/GithubService.kt new file mode 100644 index 0000000..6aebf95 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/service/GithubService.kt @@ -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 + + suspend fun getContributors(repo: String): APIResponse> + + suspend fun findAsset(repo: String, file: String): PatchesAsset +} + +class GithubServiceImpl( + private val client: HttpService, +) : GithubService { + + override suspend fun getReleases(repo: String): APIResponse { + return withContext(Dispatchers.IO) { + client.request { + url("${baseUrl}/$repo/releases") + } + } + } + + override suspend fun getContributors(repo: String): APIResponse> { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/service/HttpService.kt b/app/src/main/java/app/revanced/manager/network/service/HttpService.kt new file mode 100644 index 0000000..339120c --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/service/HttpService.kt @@ -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 request(builder: HttpRequestBuilder.() -> Unit = {}): APIResponse { + 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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt b/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt new file mode 100644 index 0000000..2eed4b6 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/service/ReVancedService.kt @@ -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 + + suspend fun getContributors(): APIResponse + + suspend fun findAsset(repo: String, file: String): PatchesAsset +} + +class ReVancedServiceImpl( + private val client: HttpService, +) : ReVancedService { + override suspend fun getAssets(): APIResponse { + return withContext(Dispatchers.IO) { + client.request { + url("$apiUrl/tools") + } + } + } + + override suspend fun getContributors(): APIResponse { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/utils/APIResponse.kt b/app/src/main/java/app/revanced/manager/network/utils/APIResponse.kt new file mode 100644 index 0000000..7f2661e --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/utils/APIResponse.kt @@ -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 { + data class Success(val data: T) : APIResponse + data class Error(val error: APIError) : APIResponse + data class Failure(val error: APIFailure) : APIResponse +} + +class APIError(code: HttpStatusCode, body: String?) : Error("HTTP Code $code, Body: $body") + +class APIFailure(error: Throwable, body: String?) : Error(body, error) + +inline fun APIResponse.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 APIResponse.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 APIResponse.transform(block: (T) -> R): APIResponse { + return if (this !is APIResponse.Success) { + // Error and Failure do not use the generic value + this as APIResponse + } else { + APIResponse.Success(block(data)) + } +} + +inline fun APIResponse.getOrThrow(): T { + return fold( + success = { it }, + fail = { throw it } + ) +} + +inline fun APIResponse.getOrNull(): T? { + return fold( + success = { it }, + fail = { null } + ) +} + +@Suppress("UNCHECKED_CAST") +inline fun APIResponse.chain(block: (T) -> APIResponse): APIResponse { + return if (this !is APIResponse.Success) { + // Error and Failure do not use the generic value + this as APIResponse + } else { + block(data) + } +} + +@Suppress("UNCHECKED_CAST") +inline fun APIResponse.chain(secondary: APIResponse): APIResponse { + return if (secondary is APIResponse.Success) { + secondary + } else { + // Error and Failure do not use the generic value + this as APIResponse + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt b/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt new file mode 100644 index 0000000..fc0b10b --- /dev/null +++ b/app/src/main/java/app/revanced/manager/patcher/PatcherUtils.kt @@ -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.Loading) + val selectedAppPackage = mutableStateOf(Optional.empty()) + val selectedPatches = mutableStateListOf() + 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): List>> { + val (patches) = patches.value as? Resource.Success ?: return listOf() + return patches.filter { patch -> ids.any { it == patch.patchName } } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt index 36815c1..5e0f23a 100644 --- a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt +++ b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt @@ -15,20 +15,15 @@ import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import app.revanced.manager.R -import app.revanced.manager.Variables.patches -import app.revanced.manager.Variables.selectedAppPackage -import app.revanced.manager.Variables.selectedPatches -import app.revanced.manager.network.api.GitHubAPI -import app.revanced.manager.network.api.ReVancedAPI +import app.revanced.manager.network.api.ManagerAPI +import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.patcher.aapt.Aapt import app.revanced.manager.patcher.aligning.ZipAligner import app.revanced.manager.patcher.aligning.zip.ZipFile import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry import app.revanced.manager.patcher.signing.Signer -import app.revanced.manager.preferences.PreferencesManager import app.revanced.manager.ui.Resource import app.revanced.manager.ui.viewmodel.Logging -import app.revanced.manager.util.ghIntegrations import app.revanced.patcher.Patcher import app.revanced.patcher.PatcherOptions import app.revanced.patcher.extensions.PatchExtensions.patchName @@ -41,14 +36,12 @@ import java.io.File import java.io.FileNotFoundException import java.nio.file.Files import java.nio.file.StandardCopyOption -import java.time.LocalDateTime class PatcherWorker( context: Context, parameters: WorkerParameters, - private val reVancedAPI: ReVancedAPI, - private val gitHubAPI: GitHubAPI, - private val prefs: PreferencesManager + private val managerAPI: ManagerAPI, + private val patcherUtils: PatcherUtils ) : CoroutineWorker(context, parameters), KoinComponent { val tag = "ReVanced Manager" private val workdir = createWorkDir() @@ -118,10 +111,10 @@ class PatcherWorker( applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() } val reVancedFolder = Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() } - val appInfo = selectedAppPackage.value.get() + val appInfo = patcherUtils.selectedAppPackage.value.get() Logging.log += "Checking prerequisites\n" - val patches = findPatchesByIds(selectedPatches) + val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches) if (patches.isEmpty()) return true @@ -131,16 +124,7 @@ class PatcherWorker( val outputFile = File(applicationContext.filesDir, "output.apk") val cacheDirectory = workdir.resolve("cache") - val integrations = if (prefs.srcIntegrations != ghIntegrations || !reVancedAPI.ping()) { - Logging.log += "Downloading integrations from GitHub API\n" - gitHubAPI.downloadAsset(integrationsCacheDir, prefs.srcIntegrations!!, ".apk") - } else { - Logging.log += "Downloading integrations from ReVanced API\n" - reVancedAPI.downloadAsset( - integrationsCacheDir, - prefs.srcIntegrations!!, ".apk" - ) - } + val integrations = managerAPI.downloadIntegrations(integrationsCacheDir) Logging.log += "Copying base.apk from device\n" withContext(Dispatchers.IO) { @@ -217,12 +201,13 @@ class PatcherWorker( withContext(Dispatchers.IO) { Files.copy( outputFile.inputStream(), - reVancedFolder.resolve(appInfo.packageName + "-" + LocalDateTime.now() + ".apk") + reVancedFolder.resolve(appInfo.packageName + ".apk") .toPath(), StandardCopyOption.REPLACE_EXISTING ) } Logging.log += "Copied file to storage!\n" + patcherUtils.cleanup() } finally { Log.d(tag, "Deleting workdir") workdir.deleteRecursively() @@ -232,14 +217,9 @@ class PatcherWorker( return false } - private fun findPatchesByIds(ids: Iterable): List>> { - val (patches) = patches.value as? Resource.Success ?: return listOf() - return patches.filter { patch -> ids.any { it == patch.patchName } } - } - private fun createWorkDir(): File { - return applicationContext.filesDir.resolve("tmp-${System.currentTimeMillis()}") + return applicationContext.cacheDir.resolve("tmp-${System.currentTimeMillis()}") .also { it.mkdirs() } } } diff --git a/app/src/main/java/app/revanced/manager/ui/component/ContributorsCard.kt b/app/src/main/java/app/revanced/manager/ui/component/ContributorsCard.kt index ee319c4..244655e 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/ContributorsCard.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/ContributorsCard.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R -import app.revanced.manager.network.dto.revanced.Contributor +import app.revanced.manager.network.dto.ReVancedContributor import app.revanced.manager.ui.viewmodel.ContributorsViewModel import coil.compose.AsyncImage import com.google.accompanist.flowlayout.FlowRow @@ -27,7 +27,7 @@ import org.koin.androidx.compose.getViewModel @ExperimentalMaterial3Api fun ContributorsCard( title: String, - data: SnapshotStateList, + data: SnapshotStateList, vm: ContributorsViewModel = getViewModel() ) { val context = LocalContext.current diff --git a/app/src/main/java/app/revanced/manager/ui/component/PatchCompatibilityDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/PatchCompatibilityDialog.kt index 3746f54..8145887 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/PatchCompatibilityDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/PatchCompatibilityDialog.kt @@ -9,18 +9,18 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import app.revanced.manager.R +import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.ui.viewmodel.PatchClass -import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel import app.revanced.patcher.annotation.Package import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages -import org.koin.androidx.compose.getViewModel +import org.koin.androidx.compose.get @Composable fun PatchCompatibilityDialog( - patchClass: PatchClass, pvm: PatcherScreenViewModel = getViewModel(), onClose: () -> Unit + patchClass: PatchClass, patcherUtils: PatcherUtils = get(), onClose: () -> Unit ) { val patch = patchClass.patch - val packageName = pvm.getSelectedPackageInfo()?.packageName + val packageName = patcherUtils.getSelectedPackageInfo()?.packageName AlertDialog(onDismissRequest = onClose, shape = RoundedCornerShape(12.dp), title = { Text(stringResource(id = R.string.unsupported), textAlign = TextAlign.Center) }, text = { diff --git a/app/src/main/java/app/revanced/manager/ui/screen/NewPatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/NewPatcherScreen.kt index 5ef41ce..423d908 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/NewPatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/NewPatcherScreen.kt @@ -16,15 +16,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel -import org.koin.androidx.compose.getViewModel +import app.revanced.manager.patcher.PatcherUtils +import org.koin.androidx.compose.get @OptIn(ExperimentalMaterial3Api::class) @Composable fun NewPatcherScreen( onClickAppSelector: () -> Unit, onClickPatchSelector: () -> Unit, - viewModel: PatcherScreenViewModel = getViewModel() + patcherUtils: PatcherUtils = get() ) { var validBundle = false Column( @@ -75,7 +75,7 @@ fun NewPatcherScreen( fontSize = 13.sp ) Text( - text = viewModel.getSelectedPackageInfo()!!.applicationInfo.name, + text = patcherUtils.getSelectedPackageInfo()!!.applicationInfo.name, fontSize = 13.sp ) } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt index 3ffe038..cb58481 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt @@ -12,13 +12,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R -import app.revanced.manager.Variables.patches -import app.revanced.manager.Variables.selectedAppPackage -import app.revanced.manager.Variables.selectedPatches +import app.revanced.manager.network.api.ManagerAPI +import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.ui.Resource import app.revanced.manager.ui.component.FloatingActionButton import app.revanced.manager.ui.component.SplitAPKDialog -import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel +import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel +import org.koin.androidx.compose.get import org.koin.androidx.compose.getViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -28,25 +28,26 @@ fun PatcherScreen( onClickPatchSelector: () -> Unit, onClickPatch: () -> Unit, onClickSourceSelector: () -> Unit, - viewModel: PatcherScreenViewModel = getViewModel() + patcherUtils: PatcherUtils = get(), + psvm: PatchesSelectorViewModel = getViewModel(), + managerAPI: ManagerAPI = get() ) { - val selectedAmount = selectedPatches.size - val selectedAppPackage by selectedAppPackage + val selectedAmount = patcherUtils.selectedPatches.size + val selectedAppPackage by patcherUtils.selectedAppPackage val hasAppSelected = selectedAppPackage.isPresent - val patchesLoaded = patches.value is Resource.Success + val patchesLoaded = patcherUtils.patches.value is Resource.Success var showDialog by remember { mutableStateOf(false) } + LaunchedEffect(patchesLoaded) { + if (!patchesLoaded) { + managerAPI.downloadPatches() + } + } Scaffold( floatingActionButton = { FloatingActionButton( - enabled = hasAppSelected && viewModel.anyPatchSelected(), - onClick = { - if (viewModel.checkSplitApk()) { - showDialog = true - } else { - onClickPatch(); viewModel.loadPatches0() - } - }, // TODO: replace this with something better + enabled = hasAppSelected && psvm.anyPatchSelected(), + onClick = { onClickPatch(); patcherUtils.loadPatchBundle() }, // TODO: replace this with something better icon = { Icon(Icons.Default.Build, contentDescription = "Patch") }, text = { Text(text = "Patch") } ) @@ -114,7 +115,7 @@ fun PatcherScreen( Text( text = if (!hasAppSelected) { "Select an application first." - } else if (viewModel.anyPatchSelected()) { + } else if (psvm.anyPatchSelected()) { "$selectedAmount patches selected." } else { stringResource(R.string.card_patches_body_patches) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt index 3b00fc0..51f9398 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R -import app.revanced.manager.preferences.PreferencesManager +import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.SocialItem import app.revanced.manager.ui.theme.Theme diff --git a/app/src/main/java/app/revanced/manager/ui/screen/subscreens/PatchesSelectorSubscreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/subscreens/PatchesSelectorSubscreen.kt index c662743..8a6996e 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/subscreens/PatchesSelectorSubscreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/subscreens/PatchesSelectorSubscreen.kt @@ -16,18 +16,20 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import app.revanced.manager.R -import app.revanced.manager.Variables.patchesState +import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.ui.Resource import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.PatchCompatibilityDialog import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.theme.Typography import app.revanced.manager.ui.viewmodel.PatchClass -import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel +import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.patcher.extensions.PatchExtensions.description +import app.revanced.patcher.extensions.PatchExtensions.options import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.version import com.xinto.taxi.BackstackNavigator +import org.koin.androidx.compose.get import org.koin.androidx.compose.getViewModel @SuppressLint("UnrememberedMutableState") @@ -35,9 +37,11 @@ import org.koin.androidx.compose.getViewModel @Composable fun PatchesSelectorSubscreen( navigator: BackstackNavigator, - pvm: PatcherScreenViewModel = getViewModel(), + psvm: PatchesSelectorViewModel = getViewModel(), + patcherUtils: PatcherUtils = get() ) { - val patches = pvm.getFilteredPatchesAndCheckOptions() + val patchesState by patcherUtils.patches + val patches = psvm.getFilteredPatches() var query by mutableStateOf("") Scaffold( @@ -59,9 +63,9 @@ fun PatchesSelectorSubscreen( }, actions = { IconButton(onClick = { - pvm.selectAllPatches(patches, !pvm.anyPatchSelected()) + psvm.selectAllPatches(patches, !psvm.anyPatchSelected()) }) { - if (!pvm.anyPatchSelected()) Icon( + if (!psvm.anyPatchSelected()) Icon( Icons.Default.SelectAll, contentDescription = null ) else Icon(Icons.Default.Deselect, contentDescription = null) @@ -115,8 +119,8 @@ fun PatchesSelectorSubscreen( items(count = patches.size) { val patch = patches[it] val name = patch.patch.patchName - PatchCard(patch, pvm.isPatchSelected(name)) { - pvm.selectPatch(name, !pvm.isPatchSelected(name)) + PatchCard(patch, psvm.isPatchSelected(name)) { + psvm.selectPatch(name, !psvm.isPatchSelected(name)) } } } else { @@ -124,8 +128,8 @@ fun PatchesSelectorSubscreen( val patch = patches[it] val name = patch.patch.patchName if (name.contains(query.lowercase())) { - PatchCard(patch, pvm.isPatchSelected(name)) { - pvm.selectPatch(name, !pvm.isPatchSelected(name)) + PatchCard(patch, psvm.isPatchSelected(name)) { + psvm.selectPatch(name, !psvm.isPatchSelected(name)) } } } @@ -190,7 +194,7 @@ fun PatchCard(patchClass: PatchClass, isSelected: Boolean, onSelected: () -> Uni verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp) ) { - if (patchClass.hasPatchOptions) { + if (patchClass.patch.options != null) { CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) { IconButton(onClick = { }, modifier = Modifier.size(24.dp)) { Icon( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/subscreens/SourceSelectorSubscreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/subscreens/SourceSelectorSubscreen.kt index 6ccbe8a..92b588d 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/subscreens/SourceSelectorSubscreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/subscreens/SourceSelectorSubscreen.kt @@ -20,11 +20,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R +import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.ui.component.SourceItem import app.revanced.manager.ui.navigation.AppDestination -import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel import com.xinto.taxi.BackstackNavigator -import org.koin.androidx.compose.getViewModel +import org.koin.androidx.compose.get import java.nio.file.Files import java.nio.file.StandardCopyOption @@ -32,7 +32,7 @@ import java.nio.file.StandardCopyOption @Composable fun SourceSelectorSubscreen( navigator: BackstackNavigator, - pvm: PatcherScreenViewModel = getViewModel() + patcherUtils: PatcherUtils = get() ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( state = rememberTopAppBarState(), @@ -48,8 +48,11 @@ fun SourceSelectorSubscreen( patchesFile.toPath(), StandardCopyOption.REPLACE_EXISTING ) - pvm.patchBundleFile = patchesFile.absolutePath - pvm.loadPatches0() + patchesFile.absolutePath.also { + patcherUtils.patchBundleFile = it + patcherUtils.loadPatchBundle(it) + } + navigator.pop() return@rememberLauncherForActivityResult } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt index 8e3fc69..0a082da 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt @@ -8,28 +8,30 @@ import android.net.Uri import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.revanced.manager.Variables -import app.revanced.manager.Variables.patches -import app.revanced.manager.Variables.selectedAppPackage +import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.ui.Resource import app.revanced.manager.util.tag import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.nio.file.Files -import java.nio.file.StandardCopyOption +import kotlinx.coroutines.withContext +import java.io.File import java.util.* class AppSelectorViewModel( - val app: Application, + val app: Application, patcherUtils: PatcherUtils ) : ViewModel() { val filteredApps = mutableListOf() + val patches = patcherUtils.patches + private val selectedAppPackage = patcherUtils.selectedAppPackage + private val selectedPatches = patcherUtils.selectedPatches init { viewModelScope.launch { filterApps() } } - private fun filterApps() { + private suspend fun filterApps() = withContext(Dispatchers.Main) { try { val (patches) = patches.value as Resource.Success patches.forEach patch@{ patch -> @@ -61,23 +63,25 @@ class AppSelectorViewModel( fun setSelectedAppPackage(appId: ApplicationInfo) { selectedAppPackage.value.ifPresent { s -> - if (s != appId) Variables.selectedPatches.clear() + if (s != appId) selectedPatches.clear() } selectedAppPackage.value = Optional.of(appId) } fun setSelectedAppPackageFromFile(file: Uri?) { - val apkDir = app.filesDir.resolve("input.apk").toPath() - Files.copy( - app.contentResolver.openInputStream(file!!), - apkDir, - StandardCopyOption.REPLACE_EXISTING - ) - setSelectedAppPackage( - app.packageManager.getPackageArchiveInfo( - apkDir.toString(), - PackageManager.GET_META_DATA - )!!.applicationInfo - ) + try { + val apkDir = app.cacheDir.resolve(File(file!!.path!!).name) + app.contentResolver.openInputStream(file)!!.run { + copyTo(apkDir.outputStream()) + close() + } + setSelectedAppPackage( + app.packageManager.getPackageArchiveInfo( + apkDir.path, PackageManager.GET_META_DATA + )!!.applicationInfo + ) + } catch (e: Exception) { + Log.e(tag, "Failed to load apk", e) + } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/ContributorsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/ContributorsViewModel.kt index 173b022..1a5e937 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/ContributorsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/ContributorsViewModel.kt @@ -1,27 +1,28 @@ package app.revanced.manager.ui.viewmodel import android.app.Application +import android.util.Log import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.revanced.manager.network.api.ReVancedAPI -import app.revanced.manager.network.dto.revanced.Contributor +import app.revanced.manager.domain.repository.ReVancedRepositoryImpl +import app.revanced.manager.network.dto.ReVancedContributor +import app.revanced.manager.network.utils.getOrNull import app.revanced.manager.util.* import kotlinx.coroutines.launch class ContributorsViewModel( - private val app: Application, - private val reVancedAPI: ReVancedAPI + private val app: Application, private val reVancedAPI: ReVancedRepositoryImpl ) : ViewModel() { - val patcherContributorsList = mutableStateListOf() - val patchesContributorsList = mutableStateListOf() - val cliContributorsList = mutableStateListOf() - val managerContributorsList = mutableStateListOf() - val integrationsContributorsList = mutableStateListOf() + val patcherContributorsList = mutableStateListOf() + val patchesContributorsList = mutableStateListOf() + val cliContributorsList = mutableStateListOf() + val managerContributorsList = mutableStateListOf() + val integrationsContributorsList = mutableStateListOf() private fun loadContributors() { viewModelScope.launch { - val contributors = reVancedAPI.fetchContributors() + val contributors = reVancedAPI.getContributors().getOrNull() ?: return@launch contributors.repositories.forEach { repo -> when (repo.name) { ghCli -> { @@ -65,7 +66,7 @@ class ContributorsViewModel( init { viewModelScope.launch { - loadContributors() + loadContributors() } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt index e460b14..13eb22d 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt @@ -2,24 +2,21 @@ package app.revanced.manager.ui.viewmodel import android.annotation.SuppressLint import android.text.format.DateUtils -import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.revanced.manager.network.api.ReVancedAPI -import app.revanced.manager.network.dto.revanced.Assets +import app.revanced.manager.domain.repository.ReVancedRepositoryImpl +import app.revanced.manager.network.dto.Assets +import app.revanced.manager.network.utils.getOrNull import app.revanced.manager.util.ghManager import app.revanced.manager.util.ghPatcher -import app.revanced.manager.util.tag -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.* -class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() { +class DashboardViewModel(private val reVancedApi: ReVancedRepositoryImpl) : ViewModel() { private var _latestPatcherCommit: Assets? by mutableStateOf(null) val patcherCommitDate: String get() = _latestPatcherCommit?.commitDate ?: "unknown" @@ -34,8 +31,7 @@ class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() { private fun fetchLastCommit() { viewModelScope.launch { - try { - val repo = withContext(Dispatchers.Default) { reVancedApi.fetchAssets() } + val repo = reVancedApi.getAssets().getOrNull() ?: return@launch for (asset in repo.tools) { when (asset.repository) { ghPatcher -> { @@ -46,9 +42,6 @@ class DashboardViewModel(private val reVancedApi: ReVancedAPI) : ViewModel() { } } } - } catch (e: Exception) { - Log.e(tag, "Failed to fetch latest patcher release", e) - } } } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherScreenViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherScreenViewModel.kt deleted file mode 100644 index b1ad61d..0000000 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherScreenViewModel.kt +++ /dev/null @@ -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, 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 { - 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>, - val unsupported: Boolean, - val hasPatchOptions: Boolean, -) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt new file mode 100644 index 0000000..9d670de --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt @@ -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, selectAll: Boolean) { + patchList.forEach { patch -> + val patchId = patch.patch.patchName + if (selectAll && !patch.unsupported) selectedPatches.add(patchId) + else selectedPatches.remove(patchId) + } + } + + fun getFilteredPatches(): List { + 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>, + val unsupported: Boolean, +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt index 1537d0f..1586e41 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchingScreenViewModel.kt @@ -1,6 +1,6 @@ package app.revanced.manager.ui.viewmodel -import android.app.Application +import android.app.Application import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt index 9257cb8..2a34167 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import app.revanced.manager.preferences.PreferencesManager +import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.ui.theme.Theme import app.revanced.manager.util.ghOrganization import app.revanced.manager.util.openUrl diff --git a/app/src/main/java/app/revanced/manager/util/Knet.kt b/app/src/main/java/app/revanced/manager/util/Knet.kt deleted file mode 100644 index 7faebb5..0000000 --- a/app/src/main/java/app/revanced/manager/util/Knet.kt +++ /dev/null @@ -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> = emptyMap(), - body: HttpRequestBody? = null, - payload: Map? = null -): HttpResponse { - return execute(HttpRequest(HttpMethod.GET, url, headers, body, payload)) -} - -inline fun HttpResponse.body(json: Json = Json.Default): T { - return json.decodeFromString(body!!.toString()) -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/util/Util.kt b/app/src/main/java/app/revanced/manager/util/Util.kt index 3712694..0b81ace 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -1,11 +1,46 @@ package app.revanced.manager.util +import android.annotation.SuppressLint import android.content.Context import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.PowerManager +import android.provider.Settings +import androidx.activity.ComponentActivity import androidx.core.net.toUri + fun Context.openUrl(url: String) { startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }) +} + +fun Context.requestAllFilesAccess() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + startActivity(Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + Uri.fromParts("package", applicationContext.packageName, null) + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + addCategory("android.intent.category.DEFAULT") + }) + } +} + +@SuppressLint("BatteryLife") +fun Context.requestIgnoreBatteryOptimizations() { + startActivity(Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.fromParts("package", packageName, null) + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + addCategory("android.intent.category.DEFAULT") + }) +} + +fun Context.isIgnoringOptimizations(): Boolean { + val pm = getSystemService(ComponentActivity.POWER_SERVICE) as PowerManager + return pm.isIgnoringBatteryOptimizations(packageName) } \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 56c982f..0000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable-v24/manager.xml b/app/src/main/res/drawable-v24/manager.xml deleted file mode 100644 index ea8eca4..0000000 --- a/app/src/main/res/drawable-v24/manager.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/manager.xml b/app/src/main/res/drawable/manager.xml index ea8eca4..bd486a2 100644 --- a/app/src/main/res/drawable/manager.xml +++ b/app/src/main/res/drawable/manager.xml @@ -1,7 +1,7 @@ - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4cf7757..87ee32b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,71 +7,25 @@ Settings Updates Manager - Update Manager Update Patches - Installed - Update All - Available updates Expand - Update Select an app… - Discord - GitHub - Announcement - Latest updates - Logs Select Application Select Patches - Options - Contributors - Patcher: - Manager: - This is an example text for previewing ReVanced Manager design that will be replaced by dynamic announcements in the future. - Root - Use installed YouTube app Loading applications. No application selected. - No patches source selected. No patches selected. - Selected: - Click here to view patcher logs. - Click here to view people who have contributed to the ReVanced Project. One moment, please… - Fetching patches Unsupported version This patch is only compatible with version: - Changelog - Patch - Dashboard - Patcher - Logs Contributors - ReVanced Manager - Website - Team - Translators - Patcher - Help - Help translate - What\'s New - No patches are selected! Contributor image - No contributors - Settings - About - About - More CLI Patcher Patches Manager Integrations - Dropdown Button - Version - FAQ - Version info Unsupported version - Compatible app versions Patching ReVanced Manager is patching Theme diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml deleted file mode 100644 index fa0f996..0000000 --- a/app/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 9ee9997..0000000 --- a/app/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file