6.7.0 - Alpha23 - 构建工具适配 Gradle 9 (同时保留 Gradle 8 兼容)
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
@file:Suppress("SpellCheckingInspection")
|
@file:Suppress("SpellCheckingInspection")
|
||||||
|
|
||||||
import com.android.build.gradle.internal.api.ApplicationVariantImpl
|
import com.android.build.api.variant.FilterConfiguration
|
||||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
|
||||||
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
||||||
import kotlin.text.RegexOption.IGNORE_CASE
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
|
||||||
@@ -12,17 +11,18 @@ plugins {
|
|||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
id("org.jetbrains.kotlin.android") /* kotlin("android") */
|
|
||||||
id("idea")
|
id("idea")
|
||||||
}
|
}
|
||||||
|
|
||||||
idea {
|
idea {
|
||||||
module {
|
module {
|
||||||
excludeDirs.addAll(listOf(
|
excludeDirs.addAll(
|
||||||
file("$rootDir/app/src/main/java/com/stardust/"),
|
listOf(
|
||||||
file("$rootDir/app/src/main/assets/modules/obsolete/"),
|
file("$rootDir/app/src/main/java/com/stardust/"),
|
||||||
file("$rootDir/app/src/main/assets-app/js-beautify/"),
|
file("$rootDir/app/src/main/assets/modules/obsolete/"),
|
||||||
))
|
file("$rootDir/app/src/main/assets-app/js-beautify/"),
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,20 +569,37 @@ android {
|
|||||||
// ! The assets division idea was accepted, and it wouldn't hurt to try. :)
|
// ! The assets division idea was accepted, and it wouldn't hurt to try. :)
|
||||||
// ! zh-CN: 资产隔离的想法可以被接受, 毕竟试一下也无妨. [笑脸符号]
|
// ! zh-CN: 资产隔离的想法可以被接受, 毕竟试一下也无妨. [笑脸符号]
|
||||||
|
|
||||||
|
// @Archived by JetBrains AI Assistant (GPT-5.3-Codex (xhigh)) on Mar 7, 2026.
|
||||||
|
// # getByName("main") {
|
||||||
|
// # assets.srcDirs("src/main/assets")
|
||||||
|
// # }
|
||||||
|
// # getByName("release") {
|
||||||
|
// # java.srcDirs("src/release/java")
|
||||||
|
// # }
|
||||||
|
// # getByName("debug") {
|
||||||
|
// # java.srcDirs("src/debug/java")
|
||||||
|
// # }
|
||||||
|
// # getByName(flavorNameApp) {
|
||||||
|
// # assets.srcDirs("src/main/assets-$flavorNameApp")
|
||||||
|
// # }
|
||||||
|
// # getByName(flavorNameInrt) {
|
||||||
|
// # assets.srcDirs("src/main/assets-$flavorNameInrt")
|
||||||
|
// # }
|
||||||
|
// !
|
||||||
getByName("main") {
|
getByName("main") {
|
||||||
assets.srcDirs("src/main/assets")
|
assets.directories.add("src/main/assets")
|
||||||
}
|
}
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
java.srcDirs("src/release/java")
|
java.directories.add("src/release/java")
|
||||||
}
|
}
|
||||||
getByName("debug") {
|
getByName("debug") {
|
||||||
java.srcDirs("src/debug/java")
|
java.directories.add("src/debug/java")
|
||||||
}
|
}
|
||||||
getByName(flavorNameApp) {
|
getByName(flavorNameApp) {
|
||||||
assets.srcDirs("src/main/assets-$flavorNameApp")
|
assets.directories.add("src/main/assets-$flavorNameApp")
|
||||||
}
|
}
|
||||||
getByName(flavorNameInrt) {
|
getByName(flavorNameInrt) {
|
||||||
assets.srcDirs("src/main/assets-$flavorNameInrt")
|
assets.directories.add("src/main/assets-$flavorNameInrt")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -651,7 +668,9 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
val proguardFiles = arrayOf<Any>(
|
val proguardFiles = arrayOf<Any>(
|
||||||
getDefaultProguardFile("proguard-android.txt"),
|
// @Archived by JetBrains AI Assistant (GPT-5.3-Codex (xhigh)) on Mar 7, 2026.
|
||||||
|
// # getDefaultProguardFile("proguard-android.txt"),
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro",
|
"proguard-rules.pro",
|
||||||
)
|
)
|
||||||
val niceSigningConfig = takeIf { signs.isValid }?.let {
|
val niceSigningConfig = takeIf { signs.isValid }?.let {
|
||||||
@@ -686,45 +705,6 @@ android {
|
|||||||
// # }
|
// # }
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
|
||||||
mergeAssetsProvider.configure {
|
|
||||||
doLast {
|
|
||||||
mapOf(
|
|
||||||
"dir" to outputDir,
|
|
||||||
"includes" to when (variantName.startsWith(flavorNameInrt)) {
|
|
||||||
true -> listOf(
|
|
||||||
"mlkit-google-ocr-models/**/*",
|
|
||||||
"mlkit_barcode_models/**/*",
|
|
||||||
"models/**/*",
|
|
||||||
"modules/obsolete/**/*",
|
|
||||||
"openccdata/**/*",
|
|
||||||
"project/**/*",
|
|
||||||
"android-devices.db",
|
|
||||||
"autojs.keystore",
|
|
||||||
"**/prob_emit.txt", // Jieba Analysis (zh-CN: 结巴分词)
|
|
||||||
"**/dict-chinese-*.db.gzip", // Jieba Analysis (zh-CN: 结巴分词)
|
|
||||||
)
|
|
||||||
else -> listOf(
|
|
||||||
"declarations/**/*",
|
|
||||||
"sample/declarations/**/*",
|
|
||||||
"modules/obsolete/**/*",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
).let { delete(fileTree(it)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs.map { it as BaseVariantOutputImpl }.forEach {
|
|
||||||
it.outputFileName = run {
|
|
||||||
val variant = this@all as ApplicationVariantImpl
|
|
||||||
val autojs = variant.applicationId.replace("^.+\\.(.+)$".toRegex(), "$1") // e.g. autojs6
|
|
||||||
val version = variant.versionName.replace("\\s".toRegex(), "-") // e.g. 6.1.0
|
|
||||||
val architecture = it.getFilter("ABI") ?: "universal"
|
|
||||||
val extension = utils.FILE_EXTENSION_APK
|
|
||||||
"$autojs-v$version-$architecture.$extension".lowercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
// Configures multiple APKs based on ABI.
|
// Configures multiple APKs based on ABI.
|
||||||
@@ -744,6 +724,74 @@ android {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidComponents {
|
||||||
|
onVariants { variant ->
|
||||||
|
val variantName = variant.name
|
||||||
|
val mergeAssetsTaskName = "merge${variantName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }}Assets"
|
||||||
|
val isInrtVariant = variantName.startsWith(flavorNameInrt, ignoreCase = true)
|
||||||
|
|
||||||
|
tasks.matching { it.name == mergeAssetsTaskName }.configureEach {
|
||||||
|
doLast {
|
||||||
|
val outputDir = when (val dir = javaClass.methods.firstOrNull {
|
||||||
|
it.name == "getOutputDir" && it.parameterTypes.isEmpty()
|
||||||
|
}?.invoke(this)) {
|
||||||
|
is File -> dir
|
||||||
|
is Directory -> dir.asFile
|
||||||
|
else -> null
|
||||||
|
} ?: return@doLast
|
||||||
|
|
||||||
|
val includes = when (isInrtVariant) {
|
||||||
|
true -> listOf(
|
||||||
|
"mlkit-google-ocr-models/**/*",
|
||||||
|
"mlkit_barcode_models/**/*",
|
||||||
|
"models/**/*",
|
||||||
|
"modules/obsolete/**/*",
|
||||||
|
"openccdata/**/*",
|
||||||
|
"project/**/*",
|
||||||
|
"android-devices.db",
|
||||||
|
"autojs.keystore",
|
||||||
|
"**/prob_emit.txt", // Jieba Analysis
|
||||||
|
"**/dict-chinese-*.db.gzip", // Jieba Analysis
|
||||||
|
)
|
||||||
|
else -> listOf(
|
||||||
|
"declarations/**/*",
|
||||||
|
"sample/declarations/**/*",
|
||||||
|
"modules/obsolete/**/*",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(
|
||||||
|
fileTree(
|
||||||
|
mapOf(
|
||||||
|
"dir" to outputDir,
|
||||||
|
"includes" to includes,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variant.outputs.forEach { output ->
|
||||||
|
val architecture = output.filters.find {
|
||||||
|
it.filterType == FilterConfiguration.FilterType.ABI
|
||||||
|
}?.identifier ?: "universal"
|
||||||
|
val outputFileNameProperty = output.javaClass.methods.firstOrNull {
|
||||||
|
it.name == "getOutputFileName" && it.parameterTypes.isEmpty()
|
||||||
|
}?.invoke(output) as? Property<*>
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(outputFileNameProperty as? Property<String>)?.set(
|
||||||
|
variant.applicationId.zip(output.versionName) { appId, versionName ->
|
||||||
|
val autojs = appId.substringAfterLast('.')
|
||||||
|
val version = versionName.replace("\\s".toRegex(), "-")
|
||||||
|
val extension = utils.FILE_EXTENSION_APK
|
||||||
|
"$autojs-v$version-$architecture.$extension".lowercase()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
withType(JavaCompile::class.java) {
|
withType(JavaCompile::class.java) {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
|
|||||||
@@ -229,6 +229,70 @@ object Utils {
|
|||||||
|
|
||||||
private fun capitalize(s: String) = "${s[0].uppercase(Locale.getDefault())}${s.substring(1)}"
|
private fun capitalize(s: String) = "${s[0].uppercase(Locale.getDefault())}${s.substring(1)}"
|
||||||
|
|
||||||
|
private fun parseBooleanOrNull(value: String?): Boolean? = when (value?.trim()?.lowercase(Locale.getDefault())) {
|
||||||
|
"1", "true", "yes", "on" -> true
|
||||||
|
"0", "false", "no", "off" -> false
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMajorVersionOrNull(version: String?): Int? {
|
||||||
|
if (version.isNullOrBlank()) return null
|
||||||
|
return version.substringBefore('.').substringBefore('-').toIntOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detectAgpVersionOrNull(): String? = runCatching {
|
||||||
|
Class.forName("com.android.Version")
|
||||||
|
.getField("ANDROID_GRADLE_PLUGIN_VERSION")
|
||||||
|
.get(null) as? String
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
private fun shouldUseBuiltInKotlin(project: Project): Boolean {
|
||||||
|
parseBooleanOrNull(project.providers.gradleProperty("useBuiltInKotlin").orNull)?.let { overridden ->
|
||||||
|
project.logInfo("[JvmConv] useBuiltInKotlin overridden by gradle property to '$overridden' for '${project.path}'")
|
||||||
|
return overridden
|
||||||
|
}
|
||||||
|
|
||||||
|
val agpVersion = detectAgpVersionOrNull()
|
||||||
|
parseMajorVersionOrNull(agpVersion)?.let { agpMajor ->
|
||||||
|
val useBuiltIn = agpMajor >= 9
|
||||||
|
project.logInfo(
|
||||||
|
"[JvmConv] AGP version='$agpVersion' (major=$agpMajor), " +
|
||||||
|
"useBuiltInKotlin='$useBuiltIn' for '${project.path}'"
|
||||||
|
)
|
||||||
|
return useBuiltIn
|
||||||
|
}
|
||||||
|
|
||||||
|
val gradleVersion = project.gradle.gradleVersion
|
||||||
|
val gradleMajor = parseMajorVersionOrNull(gradleVersion) ?: 0
|
||||||
|
val useBuiltIn = gradleMajor >= 9
|
||||||
|
project.logWarn(
|
||||||
|
"[JvmConv] Failed to determine AGP version; fallback to Gradle version='$gradleVersion' " +
|
||||||
|
"(major=$gradleMajor), useBuiltInKotlin='$useBuiltIn' for '${project.path}'"
|
||||||
|
)
|
||||||
|
return useBuiltIn
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyKotlinAndroidPluginIfNeeded(project: Project) {
|
||||||
|
val kotlinAndroidPluginId = "org.jetbrains.kotlin.android"
|
||||||
|
if (project.plugins.hasPlugin(kotlinAndroidPluginId)) {
|
||||||
|
project.logInfo("[JvmConv] '$kotlinAndroidPluginId' already applied for '${project.path}'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (shouldUseBuiltInKotlin(project)) {
|
||||||
|
project.logInfo("[JvmConv] Skip applying '$kotlinAndroidPluginId' (AGP built-in Kotlin) for '${project.path}'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
project.pluginManager.apply(kotlinAndroidPluginId)
|
||||||
|
}.onSuccess {
|
||||||
|
project.logInfo("[JvmConv] Applied '$kotlinAndroidPluginId' for '${project.path}'")
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] Failed to apply '$kotlinAndroidPluginId' for '${project.path}'", it)
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Configure Java/Kotlin target version uniformly for Android modules:
|
// Configure Java/Kotlin target version uniformly for Android modules:
|
||||||
// - Android.compileOptions.sourceCompatibility/targetCompatibility = versions.javaVersion
|
// - Android.compileOptions.sourceCompatibility/targetCompatibility = versions.javaVersion
|
||||||
// - Kotlin compile tasks jvmTarget = versions.javaVersion level
|
// - Kotlin compile tasks jvmTarget = versions.javaVersion level
|
||||||
@@ -251,6 +315,7 @@ object Utils {
|
|||||||
|
|
||||||
val installer = {
|
val installer = {
|
||||||
project.logInfo("[JvmConv] Detected Android plugin in module='${project.path}', installing configuration")
|
project.logInfo("[JvmConv] Detected Android plugin in module='${project.path}', installing configuration")
|
||||||
|
applyKotlinAndroidPluginIfNeeded(project)
|
||||||
|
|
||||||
System.getProperty("gradle.java.version.select").let { ver ->
|
System.getProperty("gradle.java.version.select").let { ver ->
|
||||||
configureJavaToolchainLanguageLevel(project, ver)
|
configureJavaToolchainLanguageLevel(project, ver)
|
||||||
@@ -288,17 +353,19 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun configureKotlinJvmToolchain(project: Project, target: String) {
|
private fun configureKotlinJvmToolchain(project: Project, target: String) {
|
||||||
project.plugins.withId("org.jetbrains.kotlin.android") {
|
val kotlinExt = project.extensions.findByName("kotlin")
|
||||||
project.extensions.findByName("kotlin")?.let { kotlinExt ->
|
if (kotlinExt == null) {
|
||||||
runCatching {
|
project.logWarn("[JvmConv] Kotlin extension not found in '${project.path}', skip Kotlin jvmToolchain")
|
||||||
kotlinExt.javaClass.getMethod("jvmToolchain", Int::class.java)
|
return
|
||||||
.invoke(kotlinExt, target.toInt())
|
}
|
||||||
}.onSuccess {
|
|
||||||
project.logInfo("[JvmConv] Kotlin jvmToolchain set to $target for '${project.path}'")
|
runCatching {
|
||||||
}.onFailure {
|
kotlinExt.javaClass.getMethod("jvmToolchain", Int::class.java)
|
||||||
project.logError("[JvmConv] Set Kotlin jvmToolchain failed on '${project.path}': ${it.message}", it)
|
.invoke(kotlinExt, target.toInt())
|
||||||
}
|
}.onSuccess {
|
||||||
}
|
project.logInfo("[JvmConv] Kotlin jvmToolchain set to $target for '${project.path}'")
|
||||||
|
}.onFailure {
|
||||||
|
project.logError("[JvmConv] Set Kotlin jvmToolchain failed on '${project.path}': ${it.message}", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ plugins {
|
|||||||
id("org.autojs.build.properties")
|
id("org.autojs.build.properties")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ plugins {
|
|||||||
id("org.autojs.build.properties")
|
id("org.autojs.build.properties")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -17,7 +16,7 @@ android {
|
|||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
getByName("main") {
|
getByName("main") {
|
||||||
java.srcDirs("src/main/java")
|
java.directories.add("src/main/java")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -21,9 +20,9 @@ android {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
named("main") {
|
named("main") {
|
||||||
// 主 Java/Kotlin 目录
|
// 主 Java/Kotlin 目录
|
||||||
java.setSrcDirs(listOf("src/main/java"))
|
java.directories.add("src/main/java")
|
||||||
// 将资源文件夹指定为 assets 目录
|
// 将资源文件夹指定为 assets 目录
|
||||||
assets.setSrcDirs(listOf("src/main/assets"))
|
assets.directories.add("src/main/assets")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
kotlin("plugin.parcelize")
|
kotlin("plugin.parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("org.autojs.build.versions")
|
id("org.autojs.build.versions")
|
||||||
id("org.autojs.build.jvm-convention")
|
id("org.autojs.build.jvm-convention")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
kotlin("plugin.parcelize")
|
kotlin("plugin.parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user