Backup before removing hiddify references

This commit is contained in:
Hiddify User
2026-01-15 12:28:40 +03:00
parent f54603d129
commit 36d9e31236
231 changed files with 6648 additions and 1832 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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();

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify;
package com.umbrix.app;
interface IServiceCallback {
void onServiceStatusChanged(int status);

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify
package com.umbrix.app
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin

View File

@@ -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

View File

@@ -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

View File

@@ -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
)
)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.bg
package com.umbrix.app.bg
import android.app.Service
import android.content.Intent

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()) {

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.constant
package com.umbrix.app.constant
object Action {
const val SERVICE = "com.hiddify.app.SERVICE"

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.constant
package com.umbrix.app.constant
enum class Alert {
RequestVPNPermission,

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.constant
package com.umbrix.app.constant
object PerAppProxyMode {
const val OFF = "off"

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.constant
package com.umbrix.app.constant
object ServiceMode {
const val NORMAL = "proxy"

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.constant
package com.umbrix.app.constant
object SettingsKey {
private const val KEY_PREFIX = "flutter."

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.constant
package com.umbrix.app.constant
enum class Status {
Stopped,

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.ktx
package com.umbrix.app.ktx
import kotlin.coroutines.Continuation

View File

@@ -1,4 +1,4 @@
package com.hiddify.hiddify.ktx
package com.umbrix.app.ktx
import android.net.IpPrefix
import android.os.Build

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>