android: quick settings tile states and widget sizes as in the Final 2026 mockup

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-12 10:25:38 +07:00
1 parent 53623ebb4a
commit f193661e68
5 files changed
+57 -18

No files matched your search

@@ -23,6 +23,7 @@ import org.strongswan.android.logic.updates.UpdateManager;
import org.strongswan.android.ui.compose.ShxAppKt; import org.strongswan.android.ui.compose.ShxAppKt;
import org.strongswan.android.ui.compose.ShxNavigation; import org.strongswan.android.ui.compose.ShxNavigation;
import org.strongswan.android.ui.compose.ShxTab; import org.strongswan.android.ui.compose.ShxTab;
import android.service.quicksettings.TileService;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen; import androidx.core.splashscreen.SplashScreen;
@@ -121,6 +122,11 @@ public class MainActivity extends AppCompatActivity
intent.removeExtra(EXTRA_ADD_SERVER); intent.removeExtra(EXTRA_ADD_SERVER);
ShxNavigation.INSTANCE.requestAddServer(); ShxNavigation.INSTANCE.requestAddServer();
} }
if (intent != null && TileService.ACTION_QS_TILE_PREFERENCES.equals(intent.getAction()))
{
intent.setAction(null);
ShxNavigation.INSTANCE.request(ShxTab.PROFILES);
}
if (intent != null && intent.getBooleanExtra(EXTRA_OPEN_SERVERS, false)) if (intent != null && intent.getBooleanExtra(EXTRA_OPEN_SERVERS, false))
{ {
intent.removeExtra(EXTRA_OPEN_SERVERS); intent.removeExtra(EXTRA_OPEN_SERVERS);
@@ -152,7 +152,12 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
{ /* always get the plain profile without cached password */ { /* always get the plain profile without cached password */
profile = mDataSource.getVpnProfile(profile.getUUID()); profile = mDataSource.getVpnProfile(profile.getUUID());
} }
/* reconnect the profile in case of an error */ /* a problem: the tap opens the app, which explains it (F_QsTile) */
if (mService.getErrorState() != VpnStateService.ErrorState.NO_ERROR)
{
openApp(new Intent(this, MainActivity.class));
return;
}
if (mService.getErrorState() == VpnStateService.ErrorState.NO_ERROR) if (mService.getErrorState() == VpnStateService.ErrorState.NO_ERROR)
{ {
switch (mService.getState()) switch (mService.getState())
@@ -212,11 +217,16 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
return; return;
} }
} }
Intent intent = new Intent(this, MainActivity.class); /* no servers yet: straight to «Add server» */
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); openApp(new Intent(this, MainActivity.class).putExtra(MainActivity.EXTRA_ADD_SERVER, getProfile() == null));
}
private void openApp(Intent intent)
{
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
{ {
startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)); startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT));
} }
else else
{ {
@@ -255,11 +265,28 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
return; return;
} }
/* F_QsTile: always «SHX VPN», the state in the subtitle */
tile.setLabel(getString(R.string.tile_label));
String subtitle;
if (error != VpnStateService.ErrorState.NO_ERROR) if (error != VpnStateService.ErrorState.NO_ERROR)
{ {
tile.setState(Tile.STATE_INACTIVE); tile.setState(Tile.STATE_INACTIVE);
tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_warning)); tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_warning));
tile.setLabel(getString(R.string.tile_connect)); switch (error)
{
case UNREACHABLE:
case LOOKUP_FAILED:
subtitle = getString(R.string.tile_no_answer);
break;
case AUTH_FAILED:
case PEER_AUTH_FAILED:
case PASSWORD_MISSING:
subtitle = getString(R.string.tile_auth_error);
break;
default:
subtitle = getString(R.string.tile_problem);
break;
}
} }
else else
{ {
@@ -269,30 +296,25 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
case DISABLED: case DISABLED:
tile.setState(Tile.STATE_INACTIVE); tile.setState(Tile.STATE_INACTIVE);
tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_disconnected)); tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_disconnected));
tile.setLabel(getString(R.string.tile_connect)); subtitle = getString(R.string.tile_off);
break; break;
case CONNECTING: case CONNECTING:
tile.setState(Tile.STATE_ACTIVE); tile.setState(Tile.STATE_ACTIVE);
tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_connecting)); tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_connecting));
tile.setLabel(getString(R.string.tile_disconnect)); subtitle = getString(R.string.tile_connecting);
break; break;
case CONNECTED: case CONNECTED:
default:
tile.setState(Tile.STATE_ACTIVE); tile.setState(Tile.STATE_ACTIVE);
tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification)); tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification));
tile.setLabel(getString(R.string.tile_disconnect)); /* the server name, unless the screen is locked */
subtitle = profile != null && !isSecure() ? profile.getName() : getString(R.string.status_protected);
break; break;
} }
} }
if (profile != null && !isSecure())
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{ /* keep the action label, show the profile as the tile subtitle */
tile.setSubtitle(profile.getName());
}
else
{ {
tile.setLabel(profile.getName()); tile.setSubtitle(subtitle);
}
} }
tile.updateTile(); tile.updateTile();
} }
@@ -160,8 +160,8 @@ object ShxWidgetState
/* the sizes of F_Widgets: 1×1 status icon, 2×1 status and button, 4×2 detailed */ /* the sizes of F_Widgets: 1×1 status icon, 2×1 status and button, 4×2 detailed */
private val ICON = DpSize(56.dp, 56.dp) private val ICON = DpSize(56.dp, 56.dp)
private val SMALL = DpSize(150.dp, 64.dp) private val SMALL = DpSize(110.dp, 48.dp)
private val LARGE = DpSize(250.dp, 150.dp) private val LARGE = DpSize(220.dp, 110.dp)
/** Home screen widget as on F_Widgets, in three sizes. */ /** Home screen widget as on F_Widgets, in three sizes. */
class ShxWidget : GlanceAppWidget() class ShxWidget : GlanceAppWidget()
@@ -1021,4 +1021,9 @@
<string name="widget_problem">Проблема</string> <string name="widget_problem">Проблема</string>
<string name="widget_tap_to">тап — %1$s</string> <string name="widget_tap_to">тап — %1$s</string>
<string name="widget_pause">Пауза</string> <string name="widget_pause">Пауза</string>
<string name="tile_off">Выключен</string>
<string name="tile_connecting">Подключаемся…</string>
<string name="tile_no_answer">Сервер не отвечает</string>
<string name="tile_auth_error">Ошибка входа</string>
<string name="tile_problem">Проблема с подключением</string>
</resources> </resources>
@@ -1006,4 +1006,10 @@
<string name="widget_problem">Problem</string> <string name="widget_problem">Problem</string>
<string name="widget_tap_to">tap — %1$s</string> <string name="widget_tap_to">tap — %1$s</string>
<string name="widget_pause">Pause</string> <string name="widget_pause">Pause</string>
<string name="tile_off">Off</string>
<string name="tile_connecting">Connecting…</string>
<string name="tile_no_answer">Server not responding</string>
<string name="tile_auth_error">Sign-in failed</string>
<string name="tile_problem">Connection problem</string>
<string name="tile_label" translatable="false">SHX VPN</string>
</resources> </resources>