Backup before removing hiddify references
@@ -35,9 +35,9 @@ def flutterVersionCode = localProperties.getProperty('flutter.versionCode')?: '1
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName') ?: '1.0'
|
||||
|
||||
android {
|
||||
namespace 'com.hiddify.hiddify'
|
||||
testNamespace "test.com.hiddify.hiddify"
|
||||
compileSdkVersion 34
|
||||
namespace 'com.umbrix.app'
|
||||
testNamespace "test.com.umbrix.app"
|
||||
compileSdkVersion 35
|
||||
ndkVersion "26.1.10909125"
|
||||
|
||||
compileOptions {
|
||||
@@ -54,7 +54,7 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.hiddify.app.test"
|
||||
applicationId "com.umbrix.app"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
@@ -26,10 +27,8 @@
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="Hiddify"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="Umbrix"
|
||||
tools:targetApi="31">
|
||||
|
||||
<meta-data
|
||||
@@ -58,11 +57,6 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -74,9 +68,9 @@
|
||||
<data android:scheme="clash" />
|
||||
<data android:host="install-config" />
|
||||
<data android:scheme="clashmeta" />
|
||||
<data android:scheme="hiddify" />
|
||||
<data android:scheme="umbrix" />
|
||||
<data android:host="install-sub" />
|
||||
<data android:scheme="hiddify" />
|
||||
<data android:scheme="umbrix" />
|
||||
<data android:host="import" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -85,18 +79,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ShortcutActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:label="@string/quick_toggle"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".bg.TileService"
|
||||
android:directBootAware="true"
|
||||
@@ -131,6 +113,36 @@
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="proxy" />
|
||||
</service>
|
||||
|
||||
<!-- Widgets -->
|
||||
<receiver
|
||||
android:name=".widget.ConnectionWidget1x1"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_description_1x1">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="com.umbrix.app.widget.TOGGLE" />
|
||||
<action android:name="com.umbrix.app.SERVICE_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info_1x1" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widget.ConnectionWidget2x2"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_description_2x2">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="com.umbrix.app.widget.TOGGLE" />
|
||||
<action android:name="com.umbrix.app.SERVICE_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info_2x2" />
|
||||
</receiver>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.hiddify.hiddify;
|
||||
package com.umbrix.app;
|
||||
|
||||
import com.hiddify.hiddify.IServiceCallback;
|
||||
import com.umbrix.app.IServiceCallback;
|
||||
|
||||
interface IService {
|
||||
int getStatus();
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify;
|
||||
package com.umbrix.app;
|
||||
|
||||
interface IServiceCallback {
|
||||
void onServiceStatusChanged(int status);
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.hiddify.hiddify
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import com.hiddify.hiddify.bg.ProxyService
|
||||
import com.hiddify.hiddify.bg.VPNService
|
||||
import com.hiddify.hiddify.constant.PerAppProxyMode
|
||||
import com.hiddify.hiddify.constant.ServiceMode
|
||||
import com.hiddify.hiddify.constant.SettingsKey
|
||||
import org.json.JSONObject
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.ObjectInputStream
|
||||
|
||||
object Settings {
|
||||
|
||||
private val preferences by lazy {
|
||||
val context = Application.application.applicationContext
|
||||
context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private const val LIST_IDENTIFIER = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu"
|
||||
|
||||
var perAppProxyMode: String
|
||||
get() = preferences.getString(SettingsKey.PER_APP_PROXY_MODE, PerAppProxyMode.OFF)!!
|
||||
set(value) = preferences.edit().putString(SettingsKey.PER_APP_PROXY_MODE, value).apply()
|
||||
|
||||
val perAppProxyEnabled: Boolean
|
||||
get() = perAppProxyMode != PerAppProxyMode.OFF
|
||||
|
||||
val perAppProxyList: List<String>
|
||||
get() {
|
||||
val stringValue = if (perAppProxyMode == PerAppProxyMode.INCLUDE) {
|
||||
preferences.getString(SettingsKey.PER_APP_PROXY_INCLUDE_LIST, "")!!
|
||||
} else {
|
||||
preferences.getString(SettingsKey.PER_APP_PROXY_EXCLUDE_LIST, "")!!
|
||||
}
|
||||
if (!stringValue.startsWith(LIST_IDENTIFIER)) {
|
||||
return emptyList()
|
||||
}
|
||||
return decodeListString(stringValue.substring(LIST_IDENTIFIER.length))
|
||||
}
|
||||
|
||||
private fun decodeListString(listString: String): List<String> {
|
||||
val stream = ObjectInputStream(ByteArrayInputStream(Base64.decode(listString, 0)))
|
||||
return stream.readObject() as List<String>
|
||||
}
|
||||
|
||||
var activeConfigPath: String
|
||||
get() = preferences.getString(SettingsKey.ACTIVE_CONFIG_PATH, "")!!
|
||||
set(value) = preferences.edit().putString(SettingsKey.ACTIVE_CONFIG_PATH, value).apply()
|
||||
|
||||
var activeProfileName: String
|
||||
get() = preferences.getString(SettingsKey.ACTIVE_PROFILE_NAME, "")!!
|
||||
set(value) = preferences.edit().putString(SettingsKey.ACTIVE_PROFILE_NAME, value).apply()
|
||||
|
||||
var serviceMode: String
|
||||
get() = preferences.getString(SettingsKey.SERVICE_MODE, ServiceMode.VPN)!!
|
||||
set(value) = preferences.edit().putString(SettingsKey.SERVICE_MODE, value).apply()
|
||||
|
||||
var configOptions: String
|
||||
get() = preferences.getString(SettingsKey.CONFIG_OPTIONS, "")!!
|
||||
set(value) = preferences.edit().putString(SettingsKey.CONFIG_OPTIONS, value).apply()
|
||||
|
||||
var debugMode: Boolean
|
||||
get() = preferences.getBoolean(SettingsKey.DEBUG_MODE, false)
|
||||
set(value) = preferences.edit().putBoolean(SettingsKey.DEBUG_MODE, value).apply()
|
||||
|
||||
var disableMemoryLimit: Boolean
|
||||
get() = preferences.getBoolean(SettingsKey.DISABLE_MEMORY_LIMIT, false)
|
||||
set(value) =
|
||||
preferences.edit().putBoolean(SettingsKey.DISABLE_MEMORY_LIMIT, value).apply()
|
||||
|
||||
var dynamicNotification: Boolean
|
||||
get() = preferences.getBoolean(SettingsKey.DYNAMIC_NOTIFICATION, true)
|
||||
set(value) =
|
||||
preferences.edit().putBoolean(SettingsKey.DYNAMIC_NOTIFICATION, value).apply()
|
||||
|
||||
var systemProxyEnabled: Boolean
|
||||
get() = preferences.getBoolean(SettingsKey.SYSTEM_PROXY_ENABLED, true)
|
||||
set(value) =
|
||||
preferences.edit().putBoolean(SettingsKey.SYSTEM_PROXY_ENABLED, value).apply()
|
||||
|
||||
var startedByUser: Boolean
|
||||
get() = preferences.getBoolean(SettingsKey.STARTED_BY_USER, false)
|
||||
set(value) = preferences.edit().putBoolean(SettingsKey.STARTED_BY_USER, value).apply()
|
||||
|
||||
fun serviceClass(): Class<*> {
|
||||
return when (serviceMode) {
|
||||
ServiceMode.VPN -> VPNService::class.java
|
||||
else -> ProxyService::class.java
|
||||
}
|
||||
}
|
||||
|
||||
private var currentServiceMode : String? = null
|
||||
|
||||
suspend fun rebuildServiceMode(): Boolean {
|
||||
var newMode = ServiceMode.NORMAL
|
||||
try {
|
||||
if (serviceMode == ServiceMode.VPN) {
|
||||
newMode = ServiceMode.VPN
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
if (currentServiceMode == newMode) {
|
||||
return false
|
||||
}
|
||||
currentServiceMode = newMode
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun needVPNService(): Boolean {
|
||||
val filePath = activeConfigPath
|
||||
if (filePath.isBlank()) return false
|
||||
val content = JSONObject(File(filePath).readText())
|
||||
val inbounds = content.getJSONArray("inbounds")
|
||||
for (index in 0 until inbounds.length()) {
|
||||
val inbound = inbounds.getJSONObject(index)
|
||||
if (inbound.getString("type") == "tun") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.hiddify.hiddify
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.hiddify.hiddify.bg.BoxService
|
||||
import com.hiddify.hiddify.bg.ServiceConnection
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
|
||||
class ShortcutActivity : Activity(), ServiceConnection.Callback {
|
||||
|
||||
private val connection = ServiceConnection(this, this, false)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (intent.action == Intent.ACTION_CREATE_SHORTCUT) {
|
||||
setResult(
|
||||
RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(
|
||||
this,
|
||||
ShortcutInfoCompat.Builder(this, "toggle")
|
||||
.setIntent(
|
||||
Intent(
|
||||
this,
|
||||
ShortcutActivity::class.java
|
||||
).setAction(Intent.ACTION_MAIN)
|
||||
)
|
||||
.setIcon(
|
||||
IconCompat.createWithResource(
|
||||
this,
|
||||
R.mipmap.ic_launcher
|
||||
)
|
||||
)
|
||||
.setShortLabel(getString(R.string.quick_toggle))
|
||||
.build()
|
||||
)
|
||||
)
|
||||
finish()
|
||||
} else {
|
||||
connection.connect()
|
||||
if (Build.VERSION.SDK_INT >= 25) {
|
||||
getSystemService<ShortcutManager>()?.reportShortcutUsed("toggle")
|
||||
}
|
||||
}
|
||||
moveTaskToBack(true)
|
||||
}
|
||||
|
||||
override fun onServiceStatusChanged(status: Status) {
|
||||
when (status) {
|
||||
Status.Started -> BoxService.stop()
|
||||
Status.Stopped -> BoxService.start()
|
||||
else -> {}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
connection.disconnect()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.hiddify.hiddify.utils.CommandClient
|
||||
import com.hiddify.hiddify.utils.ParsedOutboundGroup
|
||||
import com.umbrix.app.utils.CommandClient
|
||||
import com.umbrix.app.utils.ParsedOutboundGroup
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.nekohasekai.libbox.OutboundGroup
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationManager
|
||||
@@ -8,9 +8,9 @@ import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.getSystemService
|
||||
import com.hiddify.hiddify.bg.AppChangeReceiver
|
||||
import com.umbrix.app.bg.AppChangeReceiver
|
||||
import go.Seq
|
||||
import com.hiddify.hiddify.Application as BoxApplication
|
||||
import com.umbrix.app.Application as BoxApplication
|
||||
|
||||
class Application : Application() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import com.hiddify.hiddify.constant.Alert
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.constant.Alert
|
||||
import com.umbrix.app.constant.Status
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.JSONMethodCodec
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.hiddify.hiddify.utils.CommandClient
|
||||
import com.hiddify.hiddify.utils.ParsedOutboundGroup
|
||||
import com.umbrix.app.utils.CommandClient
|
||||
import com.umbrix.app.utils.ParsedOutboundGroup
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.nekohasekai.libbox.OutboundGroup
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.util.Log
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
@@ -11,11 +11,11 @@ import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.hiddify.hiddify.bg.ServiceConnection
|
||||
import com.hiddify.hiddify.bg.ServiceNotification
|
||||
import com.hiddify.hiddify.constant.Alert
|
||||
import com.hiddify.hiddify.constant.ServiceMode
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.bg.ServiceConnection
|
||||
import com.umbrix.app.bg.ServiceNotification
|
||||
import com.umbrix.app.constant.Alert
|
||||
import com.umbrix.app.constant.ServiceMode
|
||||
import com.umbrix.app.constant.Status
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.util.Log
|
||||
import com.hiddify.hiddify.bg.BoxService
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.bg.BoxService
|
||||
import com.umbrix.app.constant.Status
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
@@ -13,7 +13,7 @@ import android.os.Build
|
||||
import android.util.Base64
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.hiddify.hiddify.Application.Companion.packageManager
|
||||
import com.umbrix.app.Application.Companion.packageManager
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
@@ -141,7 +141,8 @@ class PlatformSettingsHandler : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
packageManager.getInstalledPackages(flag)
|
||||
}
|
||||
val list = mutableListOf<AppItem>()
|
||||
installedPackages.forEach {
|
||||
for (it in installedPackages) {
|
||||
val appInfo = it.applicationInfo ?: continue
|
||||
if (it.packageName != Application.application.packageName &&
|
||||
(it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android")
|
||||
@@ -149,8 +150,8 @@ class PlatformSettingsHandler : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
list.add(
|
||||
AppItem(
|
||||
it.packageName,
|
||||
it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1
|
||||
appInfo.loadLabel(packageManager).toString(),
|
||||
appInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -17,7 +17,7 @@ object Settings {
|
||||
}
|
||||
|
||||
var perAppProxyMode: String
|
||||
get() = preferences.getString(SettingsKey.PER_APP_PROXY_MODE, PerAppProxyMode.OFF)!!
|
||||
get() = preferences.getString(SettingsKey.PER_APP_PROXY_MODE, PerAppProxyMode.EXCLUDE)!!
|
||||
set(value) = preferences.edit().putString(SettingsKey.PER_APP_PROXY_MODE, value).apply()
|
||||
|
||||
val perAppProxyEnabled: Boolean
|
||||
@@ -25,14 +25,59 @@ object Settings {
|
||||
|
||||
val perAppProxyList: List<String>
|
||||
get() {
|
||||
val key = if (perAppProxyMode == PerAppProxyMode.INCLUDE) {
|
||||
// Принудительно перечитываем preferences на случай если данные только что изменились
|
||||
val freshPrefs = Application.application.applicationContext
|
||||
.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
|
||||
|
||||
android.util.Log.d("Settings", "=== perAppProxyList called ===")
|
||||
val currentMode = freshPrefs.getString(SettingsKey.PER_APP_PROXY_MODE, PerAppProxyMode.OFF)
|
||||
android.util.Log.d("Settings", "perAppProxyMode (fresh read) = $currentMode")
|
||||
|
||||
val key = if (currentMode == PerAppProxyMode.INCLUDE) {
|
||||
SettingsKey.PER_APP_PROXY_INCLUDE_LIST
|
||||
} else {
|
||||
SettingsKey.PER_APP_PROXY_EXCLUDE_LIST
|
||||
}
|
||||
// Flutter SharedPreferences plugin сохраняет List<String> как StringSet
|
||||
// Читаем напрямую без дополнительной сериализации
|
||||
return preferences.getStringSet(key, emptySet())?.toList() ?: emptyList()
|
||||
android.util.Log.d("Settings", "Using key: $key")
|
||||
|
||||
// Flutter SharedPreferences сохраняет List<String> в специальном формате:
|
||||
// "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu" (base64: "This is the prefix for a list.") + JSON
|
||||
val stringValue = freshPrefs.getString(key, "") ?: ""
|
||||
android.util.Log.d("Settings", "Raw value: $stringValue")
|
||||
|
||||
if (stringValue.isEmpty()) {
|
||||
android.util.Log.d("Settings", "Empty value, returning emptyList()")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// Проверяем наличие префикса Flutter
|
||||
val prefix = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu"
|
||||
if (!stringValue.startsWith(prefix)) {
|
||||
android.util.Log.e("Settings", "Missing Flutter prefix!")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// Убираем префикс и парсим JSON массив
|
||||
val jsonPart = stringValue.substring(prefix.length)
|
||||
android.util.Log.d("Settings", "JSON part: $jsonPart")
|
||||
|
||||
// Flutter добавляет "!" перед JSON массивом, убираем его
|
||||
val cleanJsonPart = if (jsonPart.startsWith("!")) {
|
||||
jsonPart.substring(1)
|
||||
} else {
|
||||
jsonPart
|
||||
}
|
||||
android.util.Log.d("Settings", "Clean JSON: $cleanJsonPart")
|
||||
|
||||
return try {
|
||||
val jsonArray = JSONObject("{\"list\":$cleanJsonPart}").getJSONArray("list")
|
||||
val result = List(jsonArray.length()) { jsonArray.getString(it) }
|
||||
android.util.Log.d("Settings", "Parsed list: $result")
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("Settings", "Parse error: ${e.message}", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
var activeConfigPath: String
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hiddify.hiddify
|
||||
package com.umbrix.app
|
||||
|
||||
import android.util.Log
|
||||
import com.hiddify.hiddify.utils.CommandClient
|
||||
import com.umbrix.app.utils.CommandClient
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.JSONMethodCodec
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.hiddify.hiddify.Settings
|
||||
import com.umbrix.app.Settings
|
||||
|
||||
class AppChangeReceiver : BroadcastReceiver() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.hiddify.hiddify.Settings
|
||||
import com.umbrix.app.Settings
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
@@ -13,12 +13,12 @@ import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.hiddify.hiddify.Application
|
||||
import com.hiddify.hiddify.R
|
||||
import com.hiddify.hiddify.Settings
|
||||
import com.hiddify.hiddify.constant.Action
|
||||
import com.hiddify.hiddify.constant.Alert
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.Application
|
||||
import com.umbrix.app.R
|
||||
import com.umbrix.app.Settings
|
||||
import com.umbrix.app.constant.Action
|
||||
import com.umbrix.app.constant.Alert
|
||||
import com.umbrix.app.constant.Status
|
||||
import go.Seq
|
||||
import io.nekohasekai.libbox.BoxService
|
||||
import io.nekohasekai.libbox.CommandServer
|
||||
@@ -45,6 +45,14 @@ class BoxService(
|
||||
|
||||
private var initializeOnce = false
|
||||
private lateinit var workingDir: File
|
||||
|
||||
@Volatile
|
||||
private var currentStatus: Status = Status.Stopped
|
||||
|
||||
fun isConnected(): Boolean {
|
||||
return currentStatus == Status.Started
|
||||
}
|
||||
|
||||
private fun initialize() {
|
||||
if (initializeOnce) return
|
||||
val baseDir = Application.application.filesDir
|
||||
@@ -112,6 +120,17 @@ class BoxService(
|
||||
private var boxService: BoxService? = null
|
||||
private var commandServer: CommandServer? = null
|
||||
private var receiverRegistered = false
|
||||
|
||||
private fun updateStatus(newStatus: Status) {
|
||||
currentStatus = newStatus
|
||||
status.value = newStatus
|
||||
}
|
||||
|
||||
private fun postStatus(newStatus: Status) {
|
||||
currentStatus = newStatus
|
||||
status.postValue(newStatus)
|
||||
}
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
@@ -198,12 +217,15 @@ class BoxService(
|
||||
newService.start()
|
||||
boxService = newService
|
||||
commandServer?.setService(boxService)
|
||||
status.postValue(Status.Started)
|
||||
postStatus(Status.Started)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
notification.show(activeProfileName, R.string.status_started)
|
||||
}
|
||||
notification.start()
|
||||
|
||||
// Уведомляем виджеты о изменении состояния
|
||||
notifyWidgets(true)
|
||||
} catch (e: Exception) {
|
||||
stopAndAlert(Alert.StartService, e.message)
|
||||
return
|
||||
@@ -212,7 +234,7 @@ class BoxService(
|
||||
|
||||
override fun serviceReload() {
|
||||
notification.close()
|
||||
status.postValue(Status.Starting)
|
||||
postStatus(Status.Starting)
|
||||
val pfd = fileDescriptor
|
||||
if (pfd != null) {
|
||||
pfd.close()
|
||||
@@ -257,7 +279,7 @@ class BoxService(
|
||||
|
||||
private fun stopService() {
|
||||
if (status.value != Status.Started) return
|
||||
status.value = Status.Stopping
|
||||
updateStatus(Status.Stopping)
|
||||
if (receiverRegistered) {
|
||||
service.unregisterReceiver(receiver)
|
||||
receiverRegistered = false
|
||||
@@ -289,9 +311,12 @@ class BoxService(
|
||||
commandServer = null
|
||||
Settings.startedByUser = false
|
||||
withContext(Dispatchers.Main) {
|
||||
status.value = Status.Stopped
|
||||
updateStatus(Status.Stopped)
|
||||
service.stopSelf()
|
||||
}
|
||||
|
||||
// Уведомляем виджеты о изменении состояния
|
||||
notifyWidgets(false)
|
||||
}
|
||||
}
|
||||
override fun postServiceClose() {
|
||||
@@ -309,13 +334,28 @@ class BoxService(
|
||||
binder.broadcast { callback ->
|
||||
callback.onServiceAlert(type.ordinal, message)
|
||||
}
|
||||
status.value = Status.Stopped
|
||||
updateStatus(Status.Stopped)
|
||||
|
||||
// Уведомляем виджеты о изменении состояния
|
||||
notifyWidgets(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyWidgets(isConnected: Boolean) {
|
||||
try {
|
||||
val intent = Intent("com.umbrix.app.SERVICE_STATE_CHANGED").apply {
|
||||
putExtra("isConnected", isConnected)
|
||||
setPackage(Application.application.packageName)
|
||||
}
|
||||
Application.application.sendBroadcast(intent)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to notify widgets: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (status.value != Status.Stopped) return Service.START_NOT_STICKY
|
||||
status.value = Status.Starting
|
||||
updateStatus(Status.Starting)
|
||||
|
||||
if (!receiverRegistered) {
|
||||
ContextCompat.registerReceiver(service, receiver, IntentFilter().apply {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.net.ConnectivityManager
|
||||
@@ -8,7 +8,7 @@ import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.hiddify.hiddify.Application
|
||||
import com.umbrix.app.Application
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.net.Network
|
||||
import android.os.Build
|
||||
import com.hiddify.hiddify.Application
|
||||
import com.umbrix.app.Application
|
||||
import io.nekohasekai.libbox.InterfaceUpdateListener
|
||||
|
||||
import java.net.NetworkInterface
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.net.DnsResolver
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.system.ErrnoException
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.hiddify.hiddify.ktx.tryResumeWithException
|
||||
import com.umbrix.app.ktx.tryResumeWithException
|
||||
import io.nekohasekai.libbox.ExchangeContext
|
||||
import io.nekohasekai.libbox.LocalDNSTransport
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.hiddify.hiddify.Application
|
||||
import com.umbrix.app.Application
|
||||
import io.nekohasekai.libbox.InterfaceUpdateListener
|
||||
import io.nekohasekai.libbox.NetworkInterfaceIterator
|
||||
import io.nekohasekai.libbox.PlatformInterface
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.os.RemoteCallbackList
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.hiddify.hiddify.IService
|
||||
import com.hiddify.hiddify.IServiceCallback
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.IService
|
||||
import com.umbrix.app.IServiceCallback
|
||||
import com.umbrix.app.constant.Status
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import com.hiddify.hiddify.IService
|
||||
import com.hiddify.hiddify.IServiceCallback
|
||||
import com.hiddify.hiddify.Settings
|
||||
import com.hiddify.hiddify.constant.Action
|
||||
import com.hiddify.hiddify.constant.Alert
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.IService
|
||||
import com.umbrix.app.IServiceCallback
|
||||
import com.umbrix.app.Settings
|
||||
import com.umbrix.app.constant.Action
|
||||
import com.umbrix.app.constant.Alert
|
||||
import com.umbrix.app.constant.Status
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@@ -13,13 +13,13 @@ import androidx.annotation.StringRes
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.hiddify.hiddify.Application
|
||||
import com.hiddify.hiddify.MainActivity
|
||||
import com.hiddify.hiddify.R
|
||||
import com.hiddify.hiddify.Settings
|
||||
import com.hiddify.hiddify.constant.Action
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.hiddify.hiddify.utils.CommandClient
|
||||
import com.umbrix.app.Application
|
||||
import com.umbrix.app.MainActivity
|
||||
import com.umbrix.app.R
|
||||
import com.umbrix.app.Settings
|
||||
import com.umbrix.app.constant.Action
|
||||
import com.umbrix.app.constant.Status
|
||||
import com.umbrix.app.utils.CommandClient
|
||||
import io.nekohasekai.libbox.Libbox
|
||||
import io.nekohasekai.libbox.StatusMessage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.hiddify.hiddify.constant.Status
|
||||
import com.umbrix.app.constant.Status
|
||||
|
||||
@RequiresApi(24)
|
||||
class TileService : TileService(), ServiceConnection.Callback {
|
||||
@@ -1,15 +1,15 @@
|
||||
package com.hiddify.hiddify.bg
|
||||
package com.umbrix.app.bg
|
||||
import android.util.Log
|
||||
|
||||
import com.hiddify.hiddify.Settings
|
||||
import com.umbrix.app.Settings
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.net.ProxyInfo
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import com.hiddify.hiddify.constant.PerAppProxyMode
|
||||
import com.hiddify.hiddify.ktx.toIpPrefix
|
||||
import com.umbrix.app.constant.PerAppProxyMode
|
||||
import com.umbrix.app.ktx.toIpPrefix
|
||||
import io.nekohasekai.libbox.TunOptions
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -19,10 +19,22 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "A/VPNService"
|
||||
|
||||
@Volatile
|
||||
private var instance: VPNService? = null
|
||||
|
||||
fun isRunning(): Boolean {
|
||||
return instance != null
|
||||
}
|
||||
}
|
||||
|
||||
private val service = BoxService(this, this)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) =
|
||||
service.onStartCommand(intent, flags, startId)
|
||||
|
||||
@@ -35,6 +47,7 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
instance = null
|
||||
service.onDestroy()
|
||||
}
|
||||
|
||||
@@ -145,19 +158,29 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
|
||||
}
|
||||
|
||||
if (Settings.perAppProxyEnabled) {
|
||||
Log.d(TAG, "=== Per-App Proxy ENABLED ===")
|
||||
Log.d(TAG, "Mode: ${Settings.perAppProxyMode}")
|
||||
val appList = Settings.perAppProxyList
|
||||
Log.d(TAG, "App list: $appList")
|
||||
|
||||
if (Settings.perAppProxyMode == PerAppProxyMode.INCLUDE) {
|
||||
Log.d(TAG, "Using INCLUDE mode")
|
||||
appList.forEach {
|
||||
Log.d(TAG, "Including package: $it")
|
||||
addIncludePackage(builder,it)
|
||||
}
|
||||
Log.d(TAG, "Including self package: $packageName")
|
||||
addIncludePackage(builder,packageName)
|
||||
} else {
|
||||
Log.d(TAG, "Using EXCLUDE mode")
|
||||
appList.forEach {
|
||||
Log.d(TAG, "Excluding package: $it")
|
||||
addExcludePackage(builder,it)
|
||||
}
|
||||
//addExcludePackage(builder,packageName)
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "=== Per-App Proxy DISABLED ===")
|
||||
val includePackage = options.includePackage
|
||||
if (includePackage.hasNext()) {
|
||||
while (includePackage.hasNext()) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.constant
|
||||
package com.umbrix.app.constant
|
||||
|
||||
object Action {
|
||||
const val SERVICE = "com.hiddify.app.SERVICE"
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.constant
|
||||
package com.umbrix.app.constant
|
||||
|
||||
enum class Alert {
|
||||
RequestVPNPermission,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.constant
|
||||
package com.umbrix.app.constant
|
||||
|
||||
object PerAppProxyMode {
|
||||
const val OFF = "off"
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.constant
|
||||
package com.umbrix.app.constant
|
||||
|
||||
object ServiceMode {
|
||||
const val NORMAL = "proxy"
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.constant
|
||||
package com.umbrix.app.constant
|
||||
|
||||
object SettingsKey {
|
||||
private const val KEY_PREFIX = "flutter."
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.constant
|
||||
package com.umbrix.app.constant
|
||||
|
||||
enum class Status {
|
||||
Stopped,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.ktx
|
||||
package com.umbrix.app.ktx
|
||||
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.ktx
|
||||
package com.umbrix.app.ktx
|
||||
|
||||
import android.net.IpPrefix
|
||||
import android.os.Build
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.utils
|
||||
package com.umbrix.app.utils
|
||||
|
||||
import go.Seq
|
||||
import io.nekohasekai.libbox.CommandClient
|
||||
@@ -9,7 +9,7 @@ import io.nekohasekai.libbox.OutboundGroup
|
||||
import io.nekohasekai.libbox.OutboundGroupIterator
|
||||
import io.nekohasekai.libbox.StatusMessage
|
||||
import io.nekohasekai.libbox.StringIterator
|
||||
import com.hiddify.hiddify.ktx.toList
|
||||
import com.umbrix.app.ktx.toList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hiddify.hiddify.utils
|
||||
package com.umbrix.app.utils
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import io.nekohasekai.libbox.OutboundGroup
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.umbrix.app.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import com.umbrix.app.R
|
||||
|
||||
class ConnectionWidget1x1 : ConnectionWidgetProvider() {
|
||||
override fun getLayout(): Int = R.layout.widget_connection_1x1
|
||||
|
||||
override fun updateWidgetUI(context: Context, views: RemoteViews, isConnected: Boolean) {
|
||||
if (isConnected) {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_pause_circle_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_active)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_power_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_inactive)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.umbrix.app.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.widget.RemoteViews
|
||||
import com.umbrix.app.R
|
||||
|
||||
class ConnectionWidget2x2 : ConnectionWidgetProvider() {
|
||||
override fun getLayout(): Int = R.layout.widget_connection_2x2
|
||||
|
||||
override fun updateWidgetUI(context: Context, views: RemoteViews, isConnected: Boolean) {
|
||||
if (isConnected) {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_pause_circle_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_active)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, R.drawable.ic_power_24)
|
||||
views.setInt(R.id.widget_background, "setBackgroundResource", R.drawable.widget_bg_inactive)
|
||||
}
|
||||
|
||||
// Обновляем текст статуса
|
||||
val statusText = if (isConnected) "Connected" else "Tap to Connect"
|
||||
val statusColor = if (isConnected) Color.parseColor("#00BFA5") else Color.parseColor("#9E9E9E")
|
||||
|
||||
views.setTextViewText(R.id.widget_status, statusText)
|
||||
views.setTextColor(R.id.widget_status, statusColor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.umbrix.app.widget
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import com.umbrix.app.R
|
||||
import com.umbrix.app.bg.BoxService
|
||||
|
||||
abstract class ConnectionWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
companion object {
|
||||
private const val ACTION_TOGGLE = "com.umbrix.app.widget.TOGGLE"
|
||||
|
||||
fun updateAllWidgets(context: Context, isConnected: Boolean) {
|
||||
// Обновляем все виджеты 1x1
|
||||
updateWidgets(context, ConnectionWidget1x1::class.java, isConnected)
|
||||
// Обновляем все виджеты 2x2
|
||||
updateWidgets(context, ConnectionWidget2x2::class.java, isConnected)
|
||||
}
|
||||
|
||||
private fun updateWidgets(context: Context, widgetClass: Class<out ConnectionWidgetProvider>, isConnected: Boolean) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val componentName = ComponentName(context, widgetClass)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||
|
||||
appWidgetIds.forEach { appWidgetId ->
|
||||
val instance = widgetClass.getDeclaredConstructor().newInstance()
|
||||
instance.updateAppWidget(context, appWidgetManager, appWidgetId, isConnected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun getLayout(): Int
|
||||
|
||||
protected open fun updateWidgetUI(context: Context, views: RemoteViews, isConnected: Boolean) {
|
||||
// Переопределяется в подклассах
|
||||
}
|
||||
|
||||
protected fun updateAppWidget(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetId: Int,
|
||||
isConnected: Boolean
|
||||
) {
|
||||
val views = RemoteViews(context.packageName, getLayout())
|
||||
|
||||
// Обновляем UI
|
||||
updateWidgetUI(context, views, isConnected)
|
||||
|
||||
// Настраиваем клик на весь виджет
|
||||
val toggleIntent = Intent(context, this::class.java).apply {
|
||||
action = ACTION_TOGGLE
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
}
|
||||
val togglePendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
appWidgetId,
|
||||
toggleIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
// Устанавливаем клик на фон виджета
|
||||
views.setOnClickPendingIntent(R.id.widget_background, togglePendingIntent)
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
val isConnected = com.umbrix.app.bg.BoxService.isConnected()
|
||||
appWidgetIds.forEach { appWidgetId ->
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId, isConnected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
super.onReceive(context, intent)
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_TOGGLE -> {
|
||||
if (BoxService.isConnected()) {
|
||||
BoxService.stop()
|
||||
} else {
|
||||
BoxService.start()
|
||||
}
|
||||
}
|
||||
"com.umbrix.app.SERVICE_STATE_CHANGED" -> {
|
||||
val isConnected = intent.getBooleanExtra("isConnected", false)
|
||||
updateAllWidgets(context, isConnected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 441 B After Width: | Height: | Size: 5.5 KiB |
@@ -3,7 +3,5 @@
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<!-- Убрали логотип, будет показан Flutter виджет с круговым индикатором -->
|
||||
</layer-list>
|
||||
|
||||
BIN
android/app/src/main/res/drawable-xhdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_stat_logo.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
@@ -1,29 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="2048"
|
||||
android:viewportHeight="2048">
|
||||
<group android:scaleX="0.85"
|
||||
android:scaleY="0.85"
|
||||
android:translateX="153.6"
|
||||
android:translateY="153.6">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M645,608h795v795h-795z"/>
|
||||
<path
|
||||
android:pathData="M1230.8,787.2C1230.8,779.9 1234.6,773.1 1240.9,769.3L1408.3,668.9C1422.3,660.5 1440,670.5 1440,686.8V859C1440,870.6 1430.6,880 1419.1,880H1251.7C1240.2,880 1230.8,870.6 1230.8,859V787.2Z"
|
||||
android:fillColor="#455FE9"/>
|
||||
<path
|
||||
android:pathData="M937.9,954.6C937.9,947.3 941.8,940.4 948.1,936.7L1115.4,836.2C1129.4,827.9 1147.1,837.9 1147.1,854.2V1152V1172.9V1361.2C1147.1,1372.7 1137.7,1382.1 1126.2,1382.1H958.8C947.3,1382.1 937.9,1372.7 937.9,1361.2V1172.9V1152V954.6ZM655.2,1124.9C648.9,1128.7 645,1135.6 645,1142.9V1361.2C645,1372.7 654.4,1382.1 665.9,1382.1H833.3C844.8,1382.1 854.2,1372.7 854.2,1361.2V1042.5C854.2,1026.2 836.5,1016.2 822.5,1024.5L655.2,1124.9Z"
|
||||
android:fillColor="#455FE9"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M854.2,1172.9H728.7V1340.3H854.2V1298.4C854.2,1286.9 863.6,1277.5 875.1,1277.5H917C928.5,1277.5 937.9,1286.9 937.9,1298.4V1340.3H1105.3V1172.9H937.9V1214.7C937.9,1226.3 928.5,1235.6 917,1235.6H875.1C863.6,1235.6 854.2,1226.3 854.2,1214.7V1172.9Z"
|
||||
android:fillColor="#455FE9"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M1230.8,942.8V1361.2C1230.8,1372.7 1240.2,1382.1 1251.7,1382.1H1419.1C1430.6,1382.1 1440,1372.7 1440,1361.2V942.8C1440,931.2 1430.6,921.8 1419.1,921.8H1251.7C1240.2,921.8 1230.8,931.2 1230.8,942.8Z"
|
||||
android:fillColor="#455FE9"/>
|
||||
</group>
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:translateX="20"
|
||||
android:translateY="20">
|
||||
<path
|
||||
android:pathData="M20,10 L20,45 Q20,60 35,60 L35,60 Q50,60 50,45 L50,10 L42,10 L42,45 Q42,52 35,52 L35,52 Q28,52 28,45 L28,10 Z"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:strokeWidth="0"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
5
android/app/src/main/res/drawable/ic_pause_circle_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector android:height="24.0dip" android:width="24.0dip" android:viewportWidth="24.0" android:viewportHeight="24.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ff00ed44" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM10,16L10,16c-0.55,0 -1,-0.45 -1,-1V9c0,-0.55 0.45,-1 1,-1l0,0c0.55,0 1,0.45 1,1v6C11,15.55 10.55,16 10,16zM14,16L14,16c-0.55,0 -1,-0.45 -1,-1V9c0,-0.55 0.45,-1 1,-1l0,0c0.55,0 1,0.45 1,1v6C15,15.55 14.55,16 14,16z" />
|
||||
</vector>
|
||||
9
android/app/src/main/res/drawable/ic_play_widget.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M8,5v14l11,-7z"/>
|
||||
</vector>
|
||||
5
android/app/src/main/res/drawable/ic_power_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector android:height="24.0dip" android:width="24.0dip" android:viewportWidth="24.0" android:viewportHeight="24.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ff007aff" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2z M11.25,5.8h1.5v6.2h-1.5z M15.949,8.057l-0.874,0.874c1.693,1.693 1.686,4.439 -0.006,6.132c-1.693,1.693 -4.445,1.693 -6.132,0.006c-1.693,-1.693 -1.698,-4.452 -0.006,-6.144L8.057,8.057c-2.176,2.176 -2.176,5.71 0.006,7.893c2.176,2.176 5.71,2.176 7.886,-0.006C18.126,13.767 18.126,10.239 15.949,8.057z" android:fillType="evenOdd" />
|
||||
</vector>
|
||||
9
android/app/src/main/res/drawable/ic_power_widget.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M13,3h-2v10h2V3zM17.83,5.17l-1.42,1.42C17.99,7.86 19,9.81 19,12c0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-2.19 1.01,-4.14 2.58,-5.42L6.17,5.17C4.23,6.82 3,9.26 3,12c0,4.97 4.03,9 9,9s9,-4.03 9,-9C21,9.26 19.77,6.82 17.83,5.17z"/>
|
||||
</vector>
|
||||
9
android/app/src/main/res/drawable/ic_stop_widget.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M6,6h12v12H6z"/>
|
||||
</vector>
|
||||
@@ -3,7 +3,5 @@
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<!-- Убрали логотип, будет показан Flutter виджет с круговым индикатором -->
|
||||
</layer-list>
|
||||
|
||||
17
android/app/src/main/res/drawable/widget_bg_active.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Connected state: Teal color with stroke -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<!-- Teal - solid and vibrant -->
|
||||
<solid android:color="#00BFA5" />
|
||||
|
||||
<!-- Glow effect with lighter stroke -->
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#7ED6C6" />
|
||||
|
||||
<size
|
||||
android:width="60dp"
|
||||
android:height="60dp" />
|
||||
</shape>
|
||||
17
android/app/src/main/res/drawable/widget_bg_inactive.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Disconnected state: Gray circle with subtle elevation -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<!-- Gray with 80% opacity for disconnected state -->
|
||||
<solid android:color="#CC9E9E9E" />
|
||||
|
||||
<!-- Subtle stroke for definition -->
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#40000000" />
|
||||
|
||||
<size
|
||||
android:width="60dp"
|
||||
android:height="60dp" />
|
||||
</shape>
|
||||
32
android/app/src/main/res/drawable/widget_preview_1x1.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Фон -->
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#CC9E9E9E" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#40000000" />
|
||||
<size
|
||||
android:width="60dp"
|
||||
android:height="60dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- Иконка power по центру -->
|
||||
<item
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:gravity="center">
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ff007aff"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2z M11.25,5.8h1.5v6.2h-1.5z M15.949,8.057l-0.874,0.874c1.693,1.693 1.686,4.439 -0.006,6.132c-1.693,1.693 -4.445,1.693 -6.132,0.006c-1.693,-1.693 -1.698,-4.452 -0.006,-6.144L8.057,8.057c-2.176,2.176 -2.176,5.71 0.006,7.893c2.176,2.176 5.71,2.176 7.886,-0.006C18.126,13.767 18.126,10.239 15.949,8.057z"
|
||||
android:fillType="evenOdd" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
39
android/app/src/main/res/drawable/widget_preview_2x2.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Фон -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#00000000" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- Круг в центре -->
|
||||
<item
|
||||
android:width="60dp"
|
||||
android:height="60dp"
|
||||
android:gravity="center">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#CC9E9E9E" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#40000000" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- Иконка power по центру -->
|
||||
<item
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:gravity="center">
|
||||
<vector
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ff007aff"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2z M11.25,5.8h1.5v6.2h-1.5z M15.949,8.057l-0.874,0.874c1.693,1.693 1.686,4.439 -0.006,6.132c-1.693,1.693 -4.445,1.693 -6.132,0.006c-1.693,-1.693 -1.698,-4.452 -0.006,-6.144L8.057,8.057c-2.176,2.176 -2.176,5.71 0.006,7.893c2.176,2.176 5.71,2.176 7.886,-0.006C18.126,13.767 18.126,10.239 15.949,8.057z"
|
||||
android:fillType="evenOdd" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
17
android/app/src/main/res/layout/widget_connection_1x1.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/widget_background"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_icon"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
android:padding="10dp"
|
||||
app:srcCompat="@drawable/ic_power_24" />
|
||||
</LinearLayout>
|
||||
34
android/app/src/main/res/layout/widget_connection_2x2.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_background"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_icon"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_gravity="center"
|
||||
android:padding="22dp"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Tap to Connect"
|
||||
android:textColor="#AAAAAA"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@drawable/ic_banner_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 824 B |
|
Before Width: | Height: | Size: 2.2 KiB |
BIN
android/app/src/main/res/mipmap-ldpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 646 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#F0F3FA</color>
|
||||
<color name="ic_launcher_background">#2D3748</color>
|
||||
</resources>
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Umbrix</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="quick_toggle">Toggle</string>
|
||||
<string name="status_starting">Service starting…</string>
|
||||
<string name="status_started">Service started</string>
|
||||
<string name="widget_description_1x1">Connection Button</string>
|
||||
<string name="widget_description_2x2">Connection Widget</string>
|
||||
</resources>
|
||||
@@ -1,13 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:shortcutId="toggle"
|
||||
android:shortcutLongLabel="@string/quick_toggle"
|
||||
android:shortcutShortLabel="@string/quick_toggle">
|
||||
<intent
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetClass="com.hiddify.hiddify.ShortcutActivity"
|
||||
android:targetPackage="app.hiddify.com" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
13
android/app/src/main/res/xml/widget_info_1x1.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:targetCellWidth="1"
|
||||
android:targetCellHeight="1"
|
||||
android:updatePeriodMillis="0"
|
||||
android:initialLayout="@layout/widget_connection_1x1"
|
||||
android:description="@string/widget_description_1x1"
|
||||
android:previewImage="@drawable/widget_preview_1x1"
|
||||
android:resizeMode="none"
|
||||
android:widgetCategory="home_screen">
|
||||
</appwidget-provider>
|
||||
15
android/app/src/main/res/xml/widget_info_2x2.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="110dp"
|
||||
android:minHeight="110dp"
|
||||
android:targetCellWidth="2"
|
||||
android:targetCellHeight="2"
|
||||
android:maxResizeWidth="250dp"
|
||||
android:maxResizeHeight="250dp"
|
||||
android:updatePeriodMillis="0"
|
||||
android:initialLayout="@layout/widget_connection_2x2"
|
||||
android:description="@string/widget_description_2x2"
|
||||
android:previewImage="@drawable/widget_preview_2x2"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen">
|
||||
</appwidget-provider>
|
||||