mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-05-09 18:11:26 +08:00
Initial: initial commit
This commit is contained in:
47
common/build.gradle.kts
Normal file
47
common/build.gradle.kts
Normal file
@@ -0,0 +1,47 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = buildTargetSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk = buildMinSdkVersion
|
||||
targetSdk = buildTargetSdkVersion
|
||||
|
||||
versionCode = buildVersionCode
|
||||
versionName = buildVersionName
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":hideapi"))
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation("androidx.core:core-ktx:$ktxVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
0
common/consumer-rules.pro
Normal file
0
common/consumer-rules.pro
Normal file
21
common/proguard-rules.pro
vendored
Normal file
21
common/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
10
common/src/main/AndroidManifest.xml
Normal file
10
common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.kr328.clash.common">
|
||||
|
||||
<permission
|
||||
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
|
||||
android:description="@string/receive_broadcasts_of_clash"
|
||||
android:label="@string/receive_clash_broadcasts"
|
||||
android:protectionLevel="privileged|signature" />
|
||||
<uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" />
|
||||
</manifest>
|
||||
14
common/src/main/java/com/github/kr328/clash/common/Global.kt
Normal file
14
common/src/main/java/com/github/kr328/clash/common/Global.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.github.kr328.clash.common
|
||||
|
||||
import android.app.Application
|
||||
|
||||
object Global {
|
||||
val application: Application
|
||||
get() = application_
|
||||
|
||||
private lateinit var application_: Application
|
||||
|
||||
fun init(application: Application) {
|
||||
this.application_ = application
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.app.ActivityThread
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
|
||||
val Application.currentProcessName: String
|
||||
get() {
|
||||
if (Build.VERSION.SDK_INT >= 28)
|
||||
return Application.getProcessName()
|
||||
|
||||
return try {
|
||||
ActivityThread.currentProcessName()
|
||||
} catch (throwable: Throwable) {
|
||||
Log.w("Resolve process name: $throwable")
|
||||
|
||||
packageName
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
fun Context.getColorCompat(@ColorRes id: Int): Int {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
this.getColor(id)
|
||||
} else {
|
||||
resources.getColor(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, id)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.Spanned
|
||||
|
||||
fun fromHtmlCompat(content: String): Spanned {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT)
|
||||
} else {
|
||||
Html.fromHtml(content)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.os.Build
|
||||
|
||||
fun pendingIntentFlags(flags: Int, immutable: Boolean = false): Int {
|
||||
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M && immutable) {
|
||||
flags or PendingIntent.FLAG_IMMUTABLE
|
||||
} else {
|
||||
flags
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
|
||||
val PackageInfo.versionCodeCompat: Long
|
||||
get() {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
longVersionCode
|
||||
} else {
|
||||
versionCode.toLong()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
val Configuration.preferredLocale: Locale
|
||||
get() {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
locales[0]
|
||||
} else {
|
||||
locale
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
|
||||
fun Context.startForegroundServiceCompat(intent: Intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(intent)
|
||||
} else {
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
126
common/src/main/java/com/github/kr328/clash/common/compat/UI.kt
Normal file
126
common/src/main/java/com/github/kr328/clash/common/compat/UI.kt
Normal file
@@ -0,0 +1,126 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
import android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
import android.view.Window
|
||||
import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
|
||||
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
||||
import android.view.WindowManager
|
||||
|
||||
var Window.isSystemBarsTranslucentCompat: Boolean
|
||||
get() {
|
||||
throw UnsupportedOperationException("set value only")
|
||||
}
|
||||
set(value) {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
setDecorFitsSystemWindows(!value)
|
||||
} else {
|
||||
decorView.systemUiVisibility =
|
||||
if (value) {
|
||||
decorView.systemUiVisibility or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
} else {
|
||||
decorView.systemUiVisibility and
|
||||
(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION).inv()
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
if (value) {
|
||||
attributes.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
} else {
|
||||
attributes.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Window.isLightStatusBarsCompat: Boolean
|
||||
get() {
|
||||
throw UnsupportedOperationException("set value only")
|
||||
}
|
||||
@TargetApi(23)
|
||||
set(value) {
|
||||
if (value) {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
decorView.windowInsetsController?.apply {
|
||||
setSystemBarsAppearance(
|
||||
APPEARANCE_LIGHT_STATUS_BARS,
|
||||
APPEARANCE_LIGHT_STATUS_BARS
|
||||
)
|
||||
}
|
||||
} else {
|
||||
decorView.systemUiVisibility =
|
||||
decorView.systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
decorView.windowInsetsController?.apply {
|
||||
setSystemBarsAppearance(
|
||||
0,
|
||||
APPEARANCE_LIGHT_STATUS_BARS
|
||||
)
|
||||
}
|
||||
} else {
|
||||
decorView.systemUiVisibility =
|
||||
decorView.systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Window.isLightNavigationBarCompat: Boolean
|
||||
get() {
|
||||
throw UnsupportedOperationException("set value only")
|
||||
}
|
||||
@TargetApi(27)
|
||||
set(value) {
|
||||
if (value) {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
decorView.windowInsetsController?.apply {
|
||||
setSystemBarsAppearance(
|
||||
APPEARANCE_LIGHT_NAVIGATION_BARS,
|
||||
APPEARANCE_LIGHT_NAVIGATION_BARS
|
||||
)
|
||||
}
|
||||
} else {
|
||||
decorView.systemUiVisibility =
|
||||
decorView.systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
}
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
decorView.windowInsetsController?.apply {
|
||||
setSystemBarsAppearance(
|
||||
0,
|
||||
APPEARANCE_LIGHT_NAVIGATION_BARS
|
||||
)
|
||||
}
|
||||
} else {
|
||||
decorView.systemUiVisibility =
|
||||
decorView.systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Window.isAllowForceDarkCompat: Boolean
|
||||
get() {
|
||||
return if (Build.VERSION.SDK_INT >= 29) {
|
||||
decorView.isForceDarkAllowed
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
decorView.isForceDarkAllowed = value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.os.Build
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StyleRes
|
||||
|
||||
var TextView.textAppearance: Int
|
||||
get() = throw UnsupportedOperationException("set value only")
|
||||
set(@StyleRes value) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
setTextAppearance(value)
|
||||
} else {
|
||||
setTextAppearance(context, value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.kr328.clash.common.constants
|
||||
|
||||
import com.github.kr328.clash.common.util.packageName
|
||||
|
||||
object Authorities {
|
||||
val STATUS_PROVIDER = "$packageName.status"
|
||||
val SETTINGS_PROVIDER = "$packageName.settings"
|
||||
val FILES_PROVIDER = "$packageName.files"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.kr328.clash.common.constants
|
||||
|
||||
import android.content.ComponentName
|
||||
import com.github.kr328.clash.common.util.packageName
|
||||
|
||||
object Components {
|
||||
val MAIN_ACTIVITY = ComponentName(packageName, "$packageName.MainActivity")
|
||||
val PROPERTIES_ACTIVITY = ComponentName(packageName, "$packageName.PropertiesActivity")
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.kr328.clash.common.constants
|
||||
|
||||
import com.github.kr328.clash.common.util.packageName
|
||||
|
||||
object Intents {
|
||||
// Public
|
||||
val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL"
|
||||
|
||||
const val EXTRA_NAME = "name"
|
||||
|
||||
// Self
|
||||
val ACTION_SERVICE_RECREATED = "$packageName.intent.action.CLASH_RECREATED"
|
||||
val ACTION_CLASH_STARTED = "$packageName.intent.action.CLASH_STARTED"
|
||||
val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED"
|
||||
val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP"
|
||||
val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED"
|
||||
val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE"
|
||||
val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES"
|
||||
val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED"
|
||||
val ACTION_OVERRIDE_CHANGED = "$packageName.intent.action.OVERRIDE_CHANGED"
|
||||
|
||||
const val EXTRA_STOP_REASON = "stop_reason"
|
||||
const val EXTRA_UUID = "uuid"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.github.kr328.clash.common.constants
|
||||
|
||||
import com.github.kr328.clash.common.util.packageName
|
||||
|
||||
object Metadata {
|
||||
val GEOIP_FILE_NAME = "$packageName.GEOIP_FILE_NAME"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.github.kr328.clash.common.constants
|
||||
|
||||
import com.github.kr328.clash.common.util.packageName
|
||||
|
||||
object Permissions {
|
||||
val RECEIVE_SELF_BROADCASTS = "$packageName.permission.RECEIVE_BROADCASTS"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.github.kr328.clash.common.id
|
||||
|
||||
object UndefinedIds {
|
||||
private const val PREFIX = 0x14000000
|
||||
private const val MASK = 0x00FFFFFF
|
||||
|
||||
private var current: Int = 0
|
||||
|
||||
@Synchronized
|
||||
fun next(): Int {
|
||||
current = ((current and MASK) + 1 or PREFIX)
|
||||
|
||||
return current
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.kr328.clash.common.log
|
||||
|
||||
object Log {
|
||||
private const val TAG = "ClashForAndroid"
|
||||
|
||||
fun i(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.i(TAG, message, throwable)
|
||||
|
||||
fun w(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.w(TAG, message, throwable)
|
||||
|
||||
fun e(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.e(TAG, message, throwable)
|
||||
|
||||
fun d(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.d(TAG, message, throwable)
|
||||
|
||||
fun v(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.v(TAG, message, throwable)
|
||||
|
||||
fun f(message: String, throwable: Throwable) =
|
||||
android.util.Log.wtf(message, throwable)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.github.kr328.clash.common.store
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
|
||||
class SharedPreferenceProvider(private val preferences: SharedPreferences) : StoreProvider {
|
||||
override fun getInt(key: String, defaultValue: Int): Int {
|
||||
return preferences.getInt(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setInt(key: String, value: Int) {
|
||||
preferences.edit {
|
||||
putInt(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLong(key: String, defaultValue: Long): Long {
|
||||
return preferences.getLong(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setLong(key: String, value: Long) {
|
||||
preferences.edit {
|
||||
putLong(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getString(key: String, defaultValue: String): String {
|
||||
return preferences.getString(key, defaultValue)!!
|
||||
}
|
||||
|
||||
override fun setString(key: String, value: String) {
|
||||
preferences.edit {
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStringSet(key: String, defaultValue: Set<String>): Set<String> {
|
||||
return preferences.getStringSet(key, defaultValue)!!
|
||||
}
|
||||
|
||||
override fun setStringSet(key: String, value: Set<String>) {
|
||||
preferences.edit {
|
||||
putStringSet(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBoolean(key: String, defaultValue: Boolean): Boolean {
|
||||
return preferences.getBoolean(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setBoolean(key: String, value: Boolean) {
|
||||
preferences.edit {
|
||||
putBoolean(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun SharedPreferences.asStoreProvider(): StoreProvider {
|
||||
return SharedPreferenceProvider(this)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.github.kr328.clash.common.store
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class Store(val provider: StoreProvider) {
|
||||
interface Delegate<T> {
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
|
||||
}
|
||||
|
||||
fun int(key: String, defaultValue: Int): Delegate<Int> {
|
||||
return object : Delegate<Int> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
|
||||
return provider.getInt(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
|
||||
provider.setInt(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun long(key: String, defaultValue: Long): Delegate<Long> {
|
||||
return object : Delegate<Long> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
|
||||
return provider.getLong(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) {
|
||||
provider.setLong(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun string(key: String, defaultValue: String): Delegate<String> {
|
||||
return object : Delegate<String> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
|
||||
return provider.getString(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
|
||||
provider.setString(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stringSet(key: String, defaultValue: Set<String>): Delegate<Set<String>> {
|
||||
return object : Delegate<Set<String>> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Set<String> {
|
||||
return provider.getStringSet(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Set<String>) {
|
||||
provider.setStringSet(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun boolean(key: String, defaultValue: Boolean): Delegate<Boolean> {
|
||||
return object : Delegate<Boolean> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
|
||||
return provider.getBoolean(key, defaultValue)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
|
||||
provider.setBoolean(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Enum<T>> enum(key: String, defaultValue: T, values: Array<T>): Delegate<T> {
|
||||
return object : Delegate<T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val name = provider.getString(key, defaultValue.name)
|
||||
|
||||
return values.find { name == it.name } ?: defaultValue
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
provider.setString(key, value.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> typedString(key: String, from: (String) -> T?, to: (T?) -> String): Delegate<T?> {
|
||||
return object : Delegate<T?> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
|
||||
val value = provider.getString(key, to(null))
|
||||
|
||||
return from(value)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
||||
provider.setString(key, to(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.github.kr328.clash.common.store
|
||||
|
||||
interface StoreProvider {
|
||||
fun getInt(key: String, defaultValue: Int): Int
|
||||
fun setInt(key: String, value: Int)
|
||||
|
||||
fun getLong(key: String, defaultValue: Long): Long
|
||||
fun setLong(key: String, value: Long)
|
||||
|
||||
fun getString(key: String, defaultValue: String): String
|
||||
fun setString(key: String, value: String)
|
||||
|
||||
fun getStringSet(key: String, defaultValue: Set<String>): Set<String>
|
||||
fun setStringSet(key: String, value: Set<String>)
|
||||
|
||||
fun getBoolean(key: String, defaultValue: Boolean): Boolean
|
||||
fun setBoolean(key: String, value: Boolean)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.kr328.clash.common.util
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.common.Global
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
val KClass<*>.componentName: ComponentName
|
||||
get() = ComponentName(Global.application.packageName, this.java.name)
|
||||
|
||||
val KClass<*>.intent: Intent
|
||||
get() = Intent(Global.application, this.java)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.kr328.clash.common.util
|
||||
|
||||
import com.github.kr328.clash.common.Global
|
||||
|
||||
val packageName: String = Global.application.packageName
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.github.kr328.clash.common.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import java.util.*
|
||||
|
||||
fun Intent.grantPermissions(read: Boolean = true, write: Boolean = true): Intent {
|
||||
var flags = 0
|
||||
|
||||
if (read)
|
||||
flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
|
||||
if (write)
|
||||
flags = flags or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
addFlags(flags)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
var Intent.fileName: String?
|
||||
get() {
|
||||
return data?.takeIf { it.scheme == "file" }?.schemeSpecificPart
|
||||
}
|
||||
set(value) {
|
||||
data = Uri.fromParts("file", value, null)
|
||||
}
|
||||
|
||||
var Intent.uuid: UUID?
|
||||
get() {
|
||||
return data?.takeIf { it.scheme == "uuid" }?.schemeSpecificPart?.let(UUID::fromString)
|
||||
}
|
||||
set(value) {
|
||||
data = Uri.fromParts("uuid", value.toString(), null)
|
||||
}
|
||||
|
||||
fun Intent.setUUID(uuid: UUID): Intent {
|
||||
this.uuid = uuid
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun Intent.setFileName(fileName: String): Intent {
|
||||
this.fileName = fileName
|
||||
|
||||
return this
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.github.kr328.clash.common.util
|
||||
|
||||
import android.os.Binder
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
private class SliceParcelableListBpBinder(val list: List<Parcelable>, val flags: Int) : Binder() {
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, tFlags: Int): Boolean {
|
||||
when (code) {
|
||||
TRANSACTION_GET_ITEMS -> {
|
||||
reply ?: return false
|
||||
|
||||
val offset = data.readInt()
|
||||
val chunk = data.readInt()
|
||||
|
||||
val end = (offset + chunk).coerceAtMost(list.size)
|
||||
|
||||
reply.writeInt(end - offset)
|
||||
|
||||
for (i in offset until end) {
|
||||
list[i].writeToParcel(reply, flags)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TRANSACTION_GET_ITEMS = 10
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Parcelable> List<T>.writeToParcelSlice(parcel: Parcel, flags: Int) {
|
||||
val bp = SliceParcelableListBpBinder(this, flags)
|
||||
|
||||
parcel.writeInt(size)
|
||||
parcel.writeStrongBinder(bp)
|
||||
}
|
||||
|
||||
fun <T : Parcelable> Parcelable.Creator<T>.createListFromParcelSlice(
|
||||
parcel: Parcel,
|
||||
flags: Int,
|
||||
chunk: Int,
|
||||
): List<T> {
|
||||
val total = parcel.readInt()
|
||||
val remote = parcel.readStrongBinder()
|
||||
val result = ArrayList<T>(total)
|
||||
|
||||
var offset = 0
|
||||
|
||||
while (offset < total) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
|
||||
try {
|
||||
data.writeInt(offset)
|
||||
data.writeInt(chunk)
|
||||
|
||||
if (!remote.transact(
|
||||
SliceParcelableListBpBinder.TRANSACTION_GET_ITEMS,
|
||||
data,
|
||||
reply,
|
||||
flags
|
||||
)
|
||||
) {
|
||||
break
|
||||
}
|
||||
|
||||
val size = reply.readInt()
|
||||
|
||||
repeat(size) {
|
||||
result.add(createFromParcel(reply))
|
||||
}
|
||||
|
||||
offset += size
|
||||
|
||||
if (size == 0)
|
||||
break
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.github.kr328.clash.common.util
|
||||
|
||||
val PatternFileName = Regex("[^*&%\\n\\r/]+")
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.github.kr328.clash.common.util
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun CoroutineScope.ticker(period: Long): Channel<Long> {
|
||||
val channel = Channel<Long>(Channel.RENDEZVOUS)
|
||||
|
||||
launch {
|
||||
try {
|
||||
while (isActive) {
|
||||
channel.send(System.currentTimeMillis())
|
||||
|
||||
delay(period)
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return channel
|
||||
}
|
||||
5
common/src/main/res/values-zh/strings.xml
Normal file
5
common/src/main/res/values-zh/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="receive_clash_broadcasts">接收 Clash 广播</string>
|
||||
<string name="receive_broadcasts_of_clash">接收来自 Clash 内部的广播</string>
|
||||
</resources>
|
||||
5
common/src/main/res/values/strings.xml
Normal file
5
common/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="receive_clash_broadcasts">Receive Clash Broadcasts</string>
|
||||
<string name="receive_broadcasts_of_clash">Receive broadcasts of clash services</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user