1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.vauth.foxyvpn.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.content.res.Configuration
import android.net.VpnService
import android.widget.RemoteViews
import com.vauth.foxyvpn.FoxyVpnApp
import com.vauth.foxyvpn.MainActivity
import com.vauth.foxyvpn.R
import com.vauth.foxyvpn.data.SettingsStore
import com.vauth.foxyvpn.data.TokenStore
import com.vauth.foxyvpn.data.model.ConnectionState
import com.vauth.foxyvpn.ui.theme.ThemeMode
import com.vauth.foxyvpn.vpn.FoxyVpnService
class QuickConnectWidgetProvider : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
for (appWidgetId in appWidgetIds) {
updateWidget(context, appWidgetManager, appWidgetId, FoxyVpnService.state.value)
}
}
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if (intent.action == ACTION_TOGGLE_VPN) {
handleToggle(context)
}
}
private fun handleToggle(context: Context) {
when (FoxyVpnService.state.value) {
ConnectionState.CONNECTED, ConnectionState.CONNECTING -> {
FoxyVpnService.stop(context)
}
ConnectionState.DISCONNECTED -> {
val app = context.applicationContext as? FoxyVpnApp
val tokenStore = app?.tokenStore ?: TokenStore(context)
if (!tokenStore.hasStoredSession()) {
launchMainActivity(context)
return
}
val settingsStore = app?.settingsStore ?: SettingsStore(context)
if (settingsStore.proxyOnlyMode) {
FoxyVpnService.start(context)
return
}
val prepareIntent = VpnService.prepare(context)
if (prepareIntent != null) {
launchMainActivity(context)
} else {
FoxyVpnService.start(context)
}
}
}
}
private fun launchMainActivity(context: Context) {
val intent = Intent(context, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
context.startActivity(intent)
}
companion object {
const val ACTION_TOGGLE_VPN = "com.vauth.foxyvpn.action.TOGGLE_VPN"
fun updateAll(context: Context, state: ConnectionState) {
val appWidgetManager = AppWidgetManager.getInstance(context) ?: return
val componentName = ComponentName(context, QuickConnectWidgetProvider::class.java)
val appWidgetIds = runCatching { appWidgetManager.getAppWidgetIds(componentName) }.getOrNull() ?: return
if (appWidgetIds.isEmpty()) return
for (appWidgetId in appWidgetIds) {
updateWidget(context, appWidgetManager, appWidgetId, state)
}
}
private fun updateWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
state: ConnectionState,
) {
val views = RemoteViews(context.packageName, R.layout.widget_quick_connect)
val toggleIntent = Intent(context, QuickConnectWidgetProvider::class.java).apply {
action = ACTION_TOGGLE_VPN
}
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
toggleIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
views.setOnClickPendingIntent(R.id.widget_root, pendingIntent)
views.setOnClickPendingIntent(R.id.widget_button, pendingIntent)
val settingsStore = (context.applicationContext as? FoxyVpnApp)?.settingsStore ?: SettingsStore(context)
val systemDark = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
val isDark = when (settingsStore.themeMode) {
ThemeMode.SYSTEM -> systemDark
ThemeMode.LIGHT -> false
ThemeMode.DARK, ThemeMode.AMOLED -> true
}
val bgRes = when (state) {
ConnectionState.CONNECTED, ConnectionState.DISCONNECTED ->
if (isDark) R.drawable.widget_bg_disconnected_dark else R.drawable.widget_bg_disconnected_light
ConnectionState.CONNECTING ->
if (isDark) R.drawable.widget_bg_connecting_dark else R.drawable.widget_bg_connecting_light
}
val iconColor = when (state) {
ConnectionState.CONNECTED -> if (isDark) 0xFF7BDF92.toInt() else 0xFF1B6E2F.toInt()
ConnectionState.CONNECTING -> if (isDark) 0xFFFFC46B.toInt() else 0xFF8A5A00.toInt()
ConnectionState.DISCONNECTED -> if (isDark) 0xFFE9E0E4.toInt() else 0xFF1E1A1D.toInt()
}
views.setImageViewResource(R.id.widget_background, bgRes)
views.setImageViewResource(R.id.widget_button, android.R.drawable.ic_lock_lock)
views.setInt(R.id.widget_button, "setColorFilter", iconColor)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}