diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 69818c852..2b06b6f0a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -342,6 +342,19 @@ android:name="androidx.room.MultiInstanceInvalidationService" android:process=":bg" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt index caf363f6f..3af9badd0 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt @@ -184,4 +184,5 @@ object Action { // const val SWITCH_WAKE_LOCK = "io.nekohasekai.sagernet.SWITCH_WAKELOCK" const val RESET_UPSTREAM_CONNECTIONS = "moe.nb4a.RESET_UPSTREAM_CONNECTIONS" + const val WIDGET_UPDATE = "io.nekohasekai.sagernet.WIDGET_UPDATE" } diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt index e760983dc..a981aebbf 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt @@ -86,6 +86,9 @@ class BaseService { state = s DataStore.serviceState = s binder.stateChanged(s, msg) + io.nekohasekai.sagernet.widget.ProxyToggleWidget.updateAll( + service as Context + ) } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/ProxyToggleWidget.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/ProxyToggleWidget.kt new file mode 100644 index 000000000..1fd815308 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/ProxyToggleWidget.kt @@ -0,0 +1,89 @@ +package io.nekohasekai.sagernet.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.os.Build +import android.widget.RemoteViews +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.database.DataStore + +class ProxyToggleWidget : AppWidgetProvider() { + + companion object { + const val ACTION_TOGGLE = "io.nekohasekai.sagernet.WIDGET_TOGGLE" + + fun updateAll(context: Context) { + val manager = AppWidgetManager.getInstance(context) ?: return + val ids = manager.getAppWidgetIds( + ComponentName(context, ProxyToggleWidget::class.java) + ) + if (ids == null || ids.isEmpty()) return + val intent = Intent(context, ProxyToggleWidget::class.java) + .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE) + .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) + context.sendBroadcast(intent) + } + } + + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + appWidgetIds.forEach { id -> + updateWidget(context, appWidgetManager, id) + } + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == ACTION_TOGGLE) { + handleToggle() + return + } + super.onReceive(context, intent) + } + + private fun handleToggle() { + val state = DataStore.serviceState + when { + state.canStop -> SagerNet.stopService() + state == BaseService.State.Stopped || state == BaseService.State.Idle -> + SagerNet.startService() + } + } + + private fun updateWidget( + context: Context, + manager: AppWidgetManager, + widgetId: Int + ) { + val views = RemoteViews(context.packageName, R.layout.widget_proxy_toggle) + val state = DataStore.serviceState + + val bgRes = when { + state == BaseService.State.Connected -> R.drawable.widget_background_connected + state == BaseService.State.Connecting || state == BaseService.State.Stopping -> + R.drawable.widget_background_busy + else -> R.drawable.widget_background + } + views.setInt(R.id.widget_root, "setBackgroundResource", bgRes) + + val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + val toggleIntent = Intent(context, ProxyToggleWidget::class.java) + .setAction(ACTION_TOGGLE) + val pendingIntent = PendingIntent.getBroadcast(context, 0, toggleIntent, pendingFlags) + views.setOnClickPendingIntent(R.id.widget_root, pendingIntent) + + manager.updateAppWidget(widgetId, views) + } +} diff --git a/app/src/main/res/drawable/ic_widget_power.xml b/app/src/main/res/drawable/ic_widget_power.xml new file mode 100644 index 000000000..c0fcc4b50 --- /dev/null +++ b/app/src/main/res/drawable/ic_widget_power.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/widget_background.xml b/app/src/main/res/drawable/widget_background.xml new file mode 100644 index 000000000..04251499a --- /dev/null +++ b/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/widget_background_busy.xml b/app/src/main/res/drawable/widget_background_busy.xml new file mode 100644 index 000000000..e75b5efc8 --- /dev/null +++ b/app/src/main/res/drawable/widget_background_busy.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/widget_background_connected.xml b/app/src/main/res/drawable/widget_background_connected.xml new file mode 100644 index 000000000..971be8452 --- /dev/null +++ b/app/src/main/res/drawable/widget_background_connected.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/widget_proxy_toggle.xml b/app/src/main/res/layout/widget_proxy_toggle.xml new file mode 100644 index 000000000..6c4c7d6b1 --- /dev/null +++ b/app/src/main/res/layout/widget_proxy_toggle.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a9f0c1cba..211b21562 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -287,6 +287,14 @@ 超时 不可达 开关 + + 代理开关 + 一键切换代理开关 + 切换代理 + 已连接 + 连接中… + 停止中… + 已断开 追加 HTTP 代理至 VPN 浏览器 / 一些支持的应用 将直接使用 HTTP 代理, 而不经过虚拟网卡设备 (Android 10+) ICMPing 不可用 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index de521bcbd..8c6f5a77d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -201,6 +201,14 @@ \n%s 差異(%s) 切換器 + + 代理開關 + 一鍵切換代理開關 + 切換代理 + 已連接 + 連接中… + 停止中… + 已斷開 未分組 版本 (%s) 額外標頭 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1995ff051..cf3406827 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,14 @@ Enable Disable Switcher + + Proxy Toggle + One-click proxy toggle + Toggle proxy + Connected + Connecting… + Stopping… + Stopped Traffic sing-box Dashboard diff --git a/app/src/main/res/xml/widget_proxy_toggle_info.xml b/app/src/main/res/xml/widget_proxy_toggle_info.xml new file mode 100644 index 000000000..e93bb7980 --- /dev/null +++ b/app/src/main/res/xml/widget_proxy_toggle_info.xml @@ -0,0 +1,12 @@ + +