Skip to content
Merged
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
12 changes: 11 additions & 1 deletion client/android/app/src/main/res/values-night-v31/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#4F79FA</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
Expand All @@ -18,5 +22,11 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>
12 changes: 11 additions & 1 deletion client/android/app/src/main/res/values-night/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
Expand All @@ -18,5 +22,11 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>
12 changes: 11 additions & 1 deletion client/android/app/src/main/res/values-v31/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#4F79FA</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
Expand All @@ -18,5 +22,11 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>
12 changes: 11 additions & 1 deletion client/android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
Expand All @@ -18,5 +22,11 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>
Binary file modified client/assets/next/rive/add_instance.riv
Binary file not shown.
Binary file added client/assets/next/rive/biometric.riv
Binary file not shown.
Binary file added client/assets/next/rive/biometric_sad.riv
Binary file not shown.
Binary file added client/assets/next/rive/biometric_unavailable.riv
Binary file not shown.
2 changes: 1 addition & 1 deletion client/ios/VPNExtension/Shared.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions client/lib/open/screens/instance/instance_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
109 changes: 82 additions & 27 deletions client/lib/open/screens/instance/next_instance_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -393,39 +397,81 @@ class _LocationList extends StatelessWidget {
required this.biometricStatus,
});

Future<void> _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<int?>(null);

Future<void> 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<void> _onConnect(BuildContext context, Location location) async {
try {
final permissionsGranted = await wireguardPlugin.requestPermissions();
if (permissionsGranted) {
Future<void> onConnect(BuildContext context, Location location) async {
loadingLocationId.value = location.id;
try {
if (activeTunnel != null) {
final bool? changeConnection = await showDialog<bool>(
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<Map<String, dynamic>>(
context: context,
instance: data.instance,
location: location,
wireguardPlugin: wireguardPlugin,
biometricsStatus: biometricStatus,
child: NextConnectDialog(
instance: data.instance,
location: location,
),
);
talker.debug("Connected to ${location.name}");

if (result == null) {
loadingLocationId.value = null;
return;
}

final permissionsGranted = await wireguardPlugin.requestPermissions();
if (permissionsGranted) {
if (context.mounted) {
// both preferences are passed explicitly - TunnelService persists
// them once the tunnel is up, so the legacy dialogs are never
// reachable from this path
await TunnelService.connect(
context: context,
instance: data.instance,
location: location,
wireguardPlugin: wireguardPlugin,
biometricsStatus: biometricStatus,
db: ref.read(databaseProvider),
trafficMethod: result['traffic'] as RoutingMethod,
mfaMethod: result['mfa'] as MfaMethod?,
);
talker.debug("Connected to ${location.name}");
}
}
}
} catch (e) {
talker.error("Failed to connect", e);
SnackbarService.showError("Failed to connect");
} finally {
loadingLocationId.value = null;
}
} catch (e) {
talker.error("Failed to connect", e);
SnackbarService.showError("Failed to connect");
}
}

@override
Widget build(BuildContext context) {
final locations = data.locations;
final connectedLocation = locations.firstWhereOrNull(
(l) =>
Expand Down Expand Up @@ -461,7 +507,12 @@ class _LocationList extends StatelessWidget {
location: connectedLocation,
isConnected: true,
routingMethod: activeTunnel?.traffic,
onDisconnectTap: () => _onDisconnect(connectedLocation),
// gated so a stored preference never renders an MFA chip on a
// location whose MFA has since been disabled server-side
mfaMethod: TunnelService.checkMfaEnabled(connectedLocation)
? connectedLocation.mfaMethod
: null,
onDisconnectTap: () => onDisconnect(connectedLocation),
),
const SizedBox(height: NextSpacing.xl),
],
Expand All @@ -477,7 +528,11 @@ class _LocationList extends StatelessWidget {
child: NextLocationCard(
location: location,
isConnected: false,
onConnectTap: () => _onConnect(context, location),
loading: loadingLocationId.value == location.id,
mfaMethod: TunnelService.checkMfaEnabled(location)
? location.mfaMethod
: null,
onConnectTap: () => onConnect(context, location),
),
),
),
Expand Down
Loading