mirror of
https://github.com/ReVanced/revanced-manager-downloader-template.git
synced 2026-01-10 21:36:19 +00:00
feat: Update to account for API changes
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -124,3 +124,7 @@ node_modules/
|
||||
|
||||
# Ignore IDEA files
|
||||
.idea/
|
||||
|
||||
.kotlin/
|
||||
|
||||
local.properties
|
||||
|
||||
19
README.md
19
README.md
@@ -63,13 +63,11 @@
|
||||

|
||||

|
||||
|
||||
Template repository for ReVanced Manager downloader.
|
||||
Template repository for ReVanced Manager downloader plugins.
|
||||
|
||||
## ❓ About
|
||||
|
||||
This is a template to create a new ReVanced Manager downloader repository.
|
||||
|
||||
For an example repository, see [TODO](https://github.com/revanced/revanced-manager).
|
||||
This is a template to create a new ReVanced Manager downloader repository. An example implementation is included.
|
||||
|
||||
## 🚀 Get started
|
||||
|
||||
@@ -108,7 +106,7 @@ To develop and release ReVanced Manager downloader using this template, some thi
|
||||
- Commits on the `dev` branch and `main` branch are automatically released
|
||||
via the [release.yml](.github/workflows/release.yml) workflow, which is also responsible for generating the changelog
|
||||
and updating the version of ReVanced Manager downloader. It is triggered by pushing to the `dev` or `main` branch.
|
||||
The workflow uses the `publish` task to publish the release of ReVanced Patches
|
||||
The workflow uses the `publish` task to publish the release.
|
||||
- The `publish` task depends on the `assembleRelease` task, so it will be run automatically when publishing a release.
|
||||
|
||||
## 📚 Everything else
|
||||
@@ -126,6 +124,17 @@ Follow the steps below to build ReVanced Manager downloader template:
|
||||
1. Run `git clone git@github.com:ReVanced/revanced-manager-downloader-template.git` to clone the repository
|
||||
2. Run `gradlew assembleRelease` to build the project
|
||||
|
||||
> [!NOTE]
|
||||
> If the build fails due to authentication, you may need to authenticate to GitHub Packages.
|
||||
> Create a PAT with the scope `read:packages` [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced) and add your token to ~/.gradle/gradle.properties.
|
||||
>
|
||||
> Example `gradle.properties` file:
|
||||
>
|
||||
> ```properties
|
||||
> gpr.user = user
|
||||
> gpr.key = key
|
||||
> ```
|
||||
|
||||
## 📜 Licence
|
||||
|
||||
ReVanced Manager downloader template is licensed under the GPLv3 licence.
|
||||
|
||||
@@ -1,36 +1,26 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
publishing
|
||||
signing
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.compose.activity)
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.ui.tooling)
|
||||
|
||||
compileOnly(project(":downloader-plugin"))
|
||||
compileOnly(libs.plugin.api)
|
||||
}
|
||||
|
||||
android {
|
||||
val packageName = "app.revanced.manager.plugin.downloader.example"
|
||||
|
||||
namespace = packageName
|
||||
compileSdk = 34
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = packageName
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
targetSdk = 35
|
||||
versionName = version.toString()
|
||||
versionCode = versionName!!.filter { it.isDigit() }.toInt()
|
||||
|
||||
buildConfigField("String", "PLUGIN_PACKAGE_NAME", "\"$packageName\"")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -64,11 +54,6 @@ android {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
outputs.all {
|
||||
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||
|
||||
@@ -4,3 +4,5 @@ android.nonFinalResIds=false
|
||||
kotlin.code.style = official
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
|
||||
version = 1.0.0
|
||||
@@ -1,20 +1,11 @@
|
||||
[versions]
|
||||
android = "8.5.2"
|
||||
compose-activity = "1.9.1"
|
||||
compose-bom = "2024.08.00"
|
||||
kotlin = "2.0.20"
|
||||
material3 = "1.3.0-rc01"
|
||||
ui-tooling = "1.6.8"
|
||||
plugin-api = "1.0.0"
|
||||
android-gradle-plugin = "8.7.3"
|
||||
kotlin = "2.1.0"
|
||||
|
||||
[libraries]
|
||||
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
|
||||
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||
compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3"}
|
||||
compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" }
|
||||
plugin-api = { group = "app.revanced", name = "revanced-manager-downloader-api", version.ref = "plugin-api" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "android" }
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
||||
|
||||
@@ -7,14 +7,12 @@
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
tools:targetApi="34">
|
||||
|
||||
<activity
|
||||
android:name=".InteractionActivity"
|
||||
android:exported="true"
|
||||
android:permission="app.revanced.manager.permission.PLUGIN_HOST"
|
||||
android:theme="@android:style/Theme.DeviceDefault" />
|
||||
tools:targetApi="35">
|
||||
|
||||
<!--
|
||||
ReVanced Manager will look for a plugin definition in this class.
|
||||
CHeck the file for more information.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="app.revanced.manager.plugin.downloader.class"
|
||||
android:value="app.revanced.manager.plugin.downloader.example.ExampleDownloaderKt" />
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
@file:Suppress("Unused")
|
||||
|
||||
package app.revanced.manager.plugin.downloader.example
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import app.revanced.manager.plugin.downloader.App
|
||||
import app.revanced.manager.plugin.downloader.example.BuildConfig.PLUGIN_PACKAGE_NAME
|
||||
import app.revanced.manager.plugin.downloader.DownloaderContext
|
||||
import app.revanced.manager.plugin.downloader.downloader
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import kotlin.io.path.Path
|
||||
|
||||
// TODO: document API, change dispatcher.
|
||||
|
||||
@Parcelize
|
||||
class InstalledApp(
|
||||
override val packageName: String,
|
||||
override val version: String,
|
||||
internal val apkPath: String
|
||||
) : App(packageName, version)
|
||||
|
||||
fun installedAppDownloader(context: DownloaderContext) = downloader<InstalledApp> {
|
||||
val pm = context.androidContext.packageManager
|
||||
|
||||
get { packageName, version ->
|
||||
val packageInfo = try {
|
||||
pm.getPackageInfo(packageName, 0)
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
return@get null
|
||||
}
|
||||
|
||||
requestUserInteraction().launch(Intent().apply {
|
||||
setClassName(
|
||||
PLUGIN_PACKAGE_NAME,
|
||||
InteractionActivity::class.java.canonicalName!!
|
||||
)
|
||||
})
|
||||
|
||||
InstalledApp(
|
||||
packageName,
|
||||
packageInfo.versionName,
|
||||
packageInfo.applicationInfo.sourceDir
|
||||
).takeIf { version == null || it.version == version }
|
||||
}
|
||||
|
||||
download {
|
||||
// Simulate download progress
|
||||
for (i in 0..5) {
|
||||
reportProgress(i.megaBytes, 5.megaBytes)
|
||||
delay(1000L)
|
||||
}
|
||||
|
||||
Files.copy(Path(it.apkPath), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
}
|
||||
|
||||
private val Int.megaBytes get() = times(1_000_000).toLong()
|
||||
@@ -1,65 +0,0 @@
|
||||
package app.revanced.manager.plugin.downloader.example
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
class InteractionActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
|
||||
val colorScheme = if (isDarkTheme) darkColorScheme() else lightColorScheme()
|
||||
|
||||
MaterialTheme(colorScheme) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("User interaction example") }
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
Text("This is an example interaction.")
|
||||
Row {
|
||||
TextButton(
|
||||
onClick = {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
) {
|
||||
Text("Continue")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
@file:Suppress("Unused")
|
||||
|
||||
package app.revanced.manager.plugin.downloader.example
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.content.res.AssetFileDescriptor
|
||||
import app.revanced.manager.plugin.downloader.*
|
||||
|
||||
// This file contains an example downloader implementation using the system file picker.
|
||||
// Remember to update the Android manifest if you move the definition file.
|
||||
|
||||
val exampleDownloader = Downloader {
|
||||
get { packageName, version ->
|
||||
// Use the requestStartActivity API to open the system file picker and get the resulting content URI.
|
||||
val uri = requestStartActivity(
|
||||
Intent(Intent.ACTION_GET_CONTENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/vnd.android.package-archive")
|
||||
)?.data ?: return@get null
|
||||
|
||||
println("Package name: $packageName, version: $version")
|
||||
|
||||
// We assume the user has selected the correct version, but this might not be the case.
|
||||
// Real plugins should verify the version and package name if possible.
|
||||
uri to version
|
||||
}
|
||||
|
||||
// Get an Android context. This is only used for reading the file that the user selected.
|
||||
// This hack should not be necessary in a real plugin.
|
||||
@SuppressLint("PrivateApi")
|
||||
val application = with(Class.forName("android.app.ActivityThread")) {
|
||||
val activityThread = getMethod("currentActivityThread")(null)
|
||||
getMethod("getApplication")(activityThread) as Application
|
||||
}
|
||||
download { uri ->
|
||||
// Open the file and return an InputStream to it along with the size.
|
||||
val fd = application.contentResolver.openAssetFileDescriptor(uri, "r")!!
|
||||
AssetFileDescriptor.AutoCloseInputStream(fd) to fd.length.takeIf { it > 0L }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user