Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,19 @@
android:name="androidx.room.MultiInstanceInvalidationService"
android:process=":bg" />

<receiver
android:name="io.nekohasekai.sagernet.widget.ProxyToggleWidget"
android:exported="false"
android:label="@string/widget_name"
android:process=":bg">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_proxy_toggle_info" />
</receiver>

</application>

</manifest>
1 change: 1 addition & 0 deletions app/src/main/java/io/nekohasekai/sagernet/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
3 changes: 3 additions & 0 deletions app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class BaseService {
state = s
DataStore.serviceState = s
binder.stateChanged(s, msg)
io.nekohasekai.sagernet.widget.ProxyToggleWidget.updateAll(
service as Context
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_widget_power.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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-2v10h2V3zm4.83,2.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,-9c0,-2.74 -1.23,-5.18 -3.17,-6.83z" />
</vector>
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/widget_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E6212133" />
<corners android:radius="24dp" />
</shape>
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/widget_background_busy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E6FF9800" />
<corners android:radius="24dp" />
</shape>
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/widget_background_connected.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E6fb7299" />
<corners android:radius="24dp" />
</shape>
17 changes: 17 additions & 0 deletions app/src/main/res/layout/widget_proxy_toggle.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/widget_background"
android:padding="8dp">

<ImageView
android:id="@+id/widget_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/widget_toggle_proxy"
android:scaleType="fitCenter"
android:src="@drawable/ic_widget_power" />

</FrameLayout>
8 changes: 8 additions & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@
<string name="connection_test_timeout">超时</string>
<string name="connection_test_unreachable">不可达</string>
<string name="tile_title">开关</string>
<!-- 桌面小组件 -->
<string name="widget_name">代理开关</string>
<string name="widget_description">一键切换代理开关</string>
<string name="widget_toggle_proxy">切换代理</string>
<string name="widget_status_connected">已连接</string>
<string name="widget_status_connecting">连接中…</string>
<string name="widget_status_stopping">停止中…</string>
<string name="widget_status_stopped">已断开</string>
<string name="append_http_proxy">追加 HTTP 代理至 VPN</string>
<string name="append_http_proxy_sum">浏览器 / 一些支持的应用 将直接使用 HTTP 代理, 而不经过虚拟网卡设备 (Android 10+)</string>
<string name="connection_test_icmp_ping_unavailable">ICMPing 不可用</string>
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@
\n%s</string>
<string name="group_diff">差異(%s)</string>
<string name="tile_title">切換器</string>
<!-- 桌面小工具 -->
<string name="widget_name">代理開關</string>
<string name="widget_description">一鍵切換代理開關</string>
<string name="widget_toggle_proxy">切換代理</string>
<string name="widget_status_connected">已連接</string>
<string name="widget_status_connecting">連接中…</string>
<string name="widget_status_stopping">停止中…</string>
<string name="widget_status_stopped">已斷開</string>
<string name="group_default">未分組</string>
<string name="version_x">版本 (%s)</string>
<string name="extra_headers">額外標頭</string>
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
<string name="quick_enable">Enable</string>
<string name="quick_disable">Disable</string>
<string name="tile_title">Switcher</string>
<!-- widget -->
<string name="widget_name">Proxy Toggle</string>
<string name="widget_description">One-click proxy toggle</string>
<string name="widget_toggle_proxy">Toggle proxy</string>
<string name="widget_status_connected">Connected</string>
<string name="widget_status_connecting">Connecting…</string>
<string name="widget_status_stopping">Stopping…</string>
<string name="widget_status_stopped">Stopped</string>
<string name="menu_traffic">Traffic</string>
<string name="menu_dashboard">sing-box Dashboard</string>
<!-- externel -->
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/res/xml/widget_proxy_toggle_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/widget_description"
android:initialLayout="@layout/widget_proxy_toggle"
android:minWidth="40dp"
android:minHeight="40dp"
android:previewLayout="@layout/widget_proxy_toggle"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:updatePeriodMillis="0"
android:widgetCategory="home_screen" />