diff --git a/client/android/app/src/main/res/values-night-v31/styles.xml b/client/android/app/src/main/res/values-night-v31/styles.xml
index c2eb1a2d..15edd042 100644
--- a/client/android/app/src/main/res/values-night-v31/styles.xml
+++ b/client/android/app/src/main/res/values-night-v31/styles.xml
@@ -4,7 +4,11 @@
diff --git a/client/android/app/src/main/res/values-night/styles.xml b/client/android/app/src/main/res/values-night/styles.xml
index dbc9ea9f..a58bd353 100644
--- a/client/android/app/src/main/res/values-night/styles.xml
+++ b/client/android/app/src/main/res/values-night/styles.xml
@@ -7,7 +7,11 @@
- @drawable/launch_background
- false
- false
- - false
+ - true
+ - @android:color/transparent
+ - @android:color/transparent
+ - false
+ - false
- shortEdges
diff --git a/client/android/app/src/main/res/values-v31/styles.xml b/client/android/app/src/main/res/values-v31/styles.xml
index 937de5fc..447fbea3 100644
--- a/client/android/app/src/main/res/values-v31/styles.xml
+++ b/client/android/app/src/main/res/values-v31/styles.xml
@@ -4,7 +4,11 @@
diff --git a/client/android/app/src/main/res/values/styles.xml b/client/android/app/src/main/res/values/styles.xml
index 0d1fa8fc..2562bf60 100644
--- a/client/android/app/src/main/res/values/styles.xml
+++ b/client/android/app/src/main/res/values/styles.xml
@@ -7,7 +7,11 @@
- @drawable/launch_background
- false
- false
- - false
+ - true
+ - @android:color/transparent
+ - @android:color/transparent
+ - false
+ - false
- shortEdges
diff --git a/client/assets/next/rive/add_instance.riv b/client/assets/next/rive/add_instance.riv
index 5c40ea27..adfc50c3 100644
Binary files a/client/assets/next/rive/add_instance.riv and b/client/assets/next/rive/add_instance.riv differ
diff --git a/client/assets/next/rive/biometric.riv b/client/assets/next/rive/biometric.riv
new file mode 100644
index 00000000..e0f41373
Binary files /dev/null and b/client/assets/next/rive/biometric.riv differ
diff --git a/client/assets/next/rive/biometric_sad.riv b/client/assets/next/rive/biometric_sad.riv
new file mode 100644
index 00000000..4cbb260f
Binary files /dev/null and b/client/assets/next/rive/biometric_sad.riv differ
diff --git a/client/assets/next/rive/biometric_unavailable.riv b/client/assets/next/rive/biometric_unavailable.riv
new file mode 100644
index 00000000..cbecc633
Binary files /dev/null and b/client/assets/next/rive/biometric_unavailable.riv differ
diff --git a/client/ios/VPNExtension/Shared.swift b/client/ios/VPNExtension/Shared.swift
index 43fb683c..dc691b4a 100644
--- a/client/ios/VPNExtension/Shared.swift
+++ b/client/ios/VPNExtension/Shared.swift
@@ -48,7 +48,7 @@ public struct ActiveTunnelData: Codable {
var locationId: Int
var instanceId: Int
var traffic: TunnelTraffic
-
+
init(fromConfig: TunnelStartData) {
self.locationId = fromConfig.locationId
self.instanceId = fromConfig.instanceId
diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart
index 75a88885..9afcabd7 100644
--- a/client/lib/open/screens/instance/instance_screen.dart
+++ b/client/lib/open/screens/instance/instance_screen.dart
@@ -584,6 +584,7 @@ class _LocationItem extends HookConsumerWidget {
location: location,
wireguardPlugin: wireguardPlugin,
biometricsStatus: biometricStatus,
+ db: ref.read(databaseProvider),
);
talker.debug(
"Location ${location.name} (${location.id}) connected",
diff --git a/client/lib/open/screens/instance/next_instance_screen.dart b/client/lib/open/screens/instance/next_instance_screen.dart
index 1854ae08..e6531a28 100644
--- a/client/lib/open/screens/instance/next_instance_screen.dart
+++ b/client/lib/open/screens/instance/next_instance_screen.dart
@@ -4,16 +4,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mobile/data/db/database.dart';
+import 'package:mobile/data/db/enums.dart';
import 'package:mobile/data/plugin/plugin.dart';
import 'package:mobile/open/api.dart';
import 'package:mobile/open/riverpod/biometrics_state.dart';
import 'package:mobile/open/riverpod/plugin/plugin.dart';
import 'package:mobile/open/screens/instance/services/tunnel_service.dart';
+import 'package:mobile/open/screens/instance/widgets/connection_conflict_dialog.dart';
import 'package:mobile/open/screens/instance/widgets/delete_instance_dialog.dart';
+import 'package:mobile/open/screens/instance/widgets/next_connect_dialog.dart';
import 'package:mobile/open/screens/instance/widgets/next_refresh_instance_dialog.dart';
import 'package:mobile/open/screens/mfa/remote_mfa_qr_screen.dart';
import 'package:mobile/open/widgets/next/icons/next_icon.dart';
import 'package:mobile/open/widgets/next/next_app_bar.dart';
+import 'package:mobile/open/widgets/next/next_bottom_sheet.dart';
import 'package:mobile/open/widgets/next/next_drawer.dart';
import 'package:mobile/open/widgets/next/next_location_card.dart';
import 'package:mobile/open/widgets/next/next_menu.dart';
@@ -380,7 +384,7 @@ class _InstanceAppBar extends StatelessWidget implements PreferredSizeWidget {
Size get preferredSize => Size.fromHeight(NextAppBar.baseHeight + topPadding);
}
-class _LocationList extends StatelessWidget {
+class _LocationList extends HookConsumerWidget {
final _ScreenData data;
final PluginTunnelEventData? activeTunnel;
final WireguardPlugin wireguardPlugin;
@@ -393,39 +397,81 @@ class _LocationList extends StatelessWidget {
required this.biometricStatus,
});
- Future _onDisconnect(Location location) async {
- try {
- await wireguardPlugin.closeTunnel();
- talker.debug("Disconnected from ${location.name}");
- } catch (e) {
- talker.error("Failed to disconnect", e);
- SnackbarService.showError("Failed to disconnect");
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final loadingLocationId = useState(null);
+
+ Future onDisconnect(Location location) async {
+ try {
+ await wireguardPlugin.closeTunnel();
+ talker.debug("Disconnected from ${location.name}");
+ } catch (e) {
+ talker.error("Failed to disconnect", e);
+ SnackbarService.showError("Failed to disconnect");
+ }
}
- }
- Future _onConnect(BuildContext context, Location location) async {
- try {
- final permissionsGranted = await wireguardPlugin.requestPermissions();
- if (permissionsGranted) {
+ Future onConnect(BuildContext context, Location location) async {
+ loadingLocationId.value = location.id;
+ try {
+ if (activeTunnel != null) {
+ final bool? changeConnection = await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return const ConnectionConflictDialog();
+ },
+ );
+
+ if (changeConnection != true) {
+ loadingLocationId.value = null;
+ return;
+ }
+
+ await wireguardPlugin.closeTunnel();
+ }
+
if (context.mounted) {
- await TunnelService.connect(
+ final result = await showNextBottomSheet