From 991837676ab64074a4abf0f7c8323c612bfc47d2 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 16 Jan 2025 08:16:23 -0500 Subject: [PATCH] Add DangerButton component (#219) This pulls out a component that we can use for "dangerous" operations like deleting, and styles it in one place. It also starts to move us slowly towards Material 3, with the rounded corners on these buttons in Android. Android: |Before Light|Before Dark|After Light|After Dark| |---|---|---|---| |Android Studio 2025-01-15 14 16 36|Android Studio 2025-01-15 14 16 47|Android Studio 2025-01-15 14 15 59|Android Studio 2025-01-15 14 16 15| iOS: |Before Light|Before Dark|After Light|After Dark| |---|---|---|---| |Simulator 2025-01-15 15 56 26|Simulator 2025-01-15 15 56 36|Simulator 2025-01-15 16 05 23|Simulator 2025-01-15 16 05 37| --- lib/components/DangerButton.dart | 28 +++++++++++++++++++ lib/main.dart | 18 ++++++++---- lib/screens/HostInfoScreen.dart | 4 +-- lib/screens/SiteDetailScreen.dart | 18 ++++++------ .../siteConfig/CertificateDetailsScreen.dart | 7 ++--- .../siteConfig/StaticHostmapScreen.dart | 15 +++++----- lib/screens/siteConfig/UnsafeRouteScreen.dart | 4 +-- 7 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 lib/components/DangerButton.dart diff --git a/lib/components/DangerButton.dart b/lib/components/DangerButton.dart new file mode 100644 index 0000000..4279c5e --- /dev/null +++ b/lib/components/DangerButton.dart @@ -0,0 +1,28 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class DangerButton extends StatelessWidget { + const DangerButton({Key? key, required this.child, this.onPressed}) : super(key: key); + + final Widget child; + final GestureTapCallback? onPressed; + + @override + Widget build(BuildContext context) { + if (Platform.isAndroid) { + return FilledButton( + onPressed: onPressed, + child: child, + style: FilledButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.error)); + } else { + // Workaround for https://github.com/flutter/flutter/issues/161590 + final themeData = CupertinoTheme.of(context); + return CupertinoTheme( + data: themeData.copyWith(primaryColor: CupertinoColors.white), + child: CupertinoButton( + child: child, onPressed: onPressed, color: CupertinoColors.systemRed.resolveFrom(context))); + } + } +} diff --git a/lib/main.dart b/lib/main.dart index a81f1a6..ad222c6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:flutter/cupertino.dart' show CupertinoThemeData, DefaultCupertinoLocalizations; +import 'package:flutter/cupertino.dart' show CupertinoThemeData, DefaultCupertinoLocalizations, CupertinoColors; import 'package:flutter/material.dart' - show BottomSheetThemeData, Colors, DefaultMaterialLocalizations, ThemeData, ThemeMode; + show BottomSheetThemeData, ColorScheme, Colors, DefaultMaterialLocalizations, ThemeData, ThemeMode; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -87,8 +87,11 @@ class _AppState extends State { Widget build(BuildContext context) { final ThemeData lightTheme = ThemeData( useMaterial3: false, - brightness: Brightness.light, - primarySwatch: Colors.blueGrey, + colorScheme: ColorScheme.fromSwatch( + brightness: Brightness.light, + primarySwatch: Colors.blueGrey, + errorColor: CupertinoColors.systemRed.resolveFrom(context), + ), primaryColor: Colors.blueGrey[900], fontFamily: 'PublicSans', //scaffoldBackgroundColor: Colors.grey[100], @@ -100,8 +103,11 @@ class _AppState extends State { final ThemeData darkTheme = ThemeData( useMaterial3: false, - brightness: Brightness.dark, - primarySwatch: Colors.grey, + colorScheme: ColorScheme.fromSwatch( + brightness: Brightness.dark, + primarySwatch: Colors.grey, + errorColor: CupertinoColors.systemRed.resolveFrom(context), + ), primaryColor: Colors.grey[900], fontFamily: 'PublicSans', scaffoldBackgroundColor: Colors.grey[800], diff --git a/lib/screens/HostInfoScreen.dart b/lib/screens/HostInfoScreen.dart index 169a22c..1c850b8 100644 --- a/lib/screens/HostInfoScreen.dart +++ b/lib/screens/HostInfoScreen.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; +import 'package:mobile_nebula/components/DangerButton.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; @@ -161,9 +162,8 @@ class _HostInfoScreenState extends State { padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, - child: PlatformElevatedButton( + child: DangerButton( child: Text('Close Tunnel'), - color: CupertinoColors.systemRed.resolveFrom(context), onPressed: () => Utils.confirmDelete(context, 'Close Tunnel?', () async { try { await widget.site.closeTunnel(hostInfo.vpnIp); diff --git a/lib/screens/SiteDetailScreen.dart b/lib/screens/SiteDetailScreen.dart index 6bf2c19..07b1ba0 100644 --- a/lib/screens/SiteDetailScreen.dart +++ b/lib/screens/SiteDetailScreen.dart @@ -17,6 +17,8 @@ import 'package:mobile_nebula/screens/siteConfig/SiteConfigScreen.dart'; import 'package:mobile_nebula/services/utils.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; +import '../components/DangerButton.dart'; + //TODO: If the site isn't active, don't respond to reloads on hostmaps //TODO: ios is now the problem with connecting screwing our ability to query the hostmap (its a race) @@ -256,14 +258,14 @@ class _SiteDetailScreenState extends State { padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, - child: PlatformElevatedButton( - child: Text('Delete'), - color: CupertinoColors.systemRed.resolveFrom(context), - onPressed: () => Utils.confirmDelete(context, 'Delete Site?', () async { - if (await _deleteSite()) { - Navigator.of(context).pop(); - } - })))); + child: DangerButton( + child: Text('Delete'), + onPressed: () => Utils.confirmDelete(context, 'Delete Site?', () async { + if (await _deleteSite()) { + Navigator.of(context).pop(); + } + }), + ))); } _listHostmap() async { diff --git a/lib/screens/siteConfig/CertificateDetailsScreen.dart b/lib/screens/siteConfig/CertificateDetailsScreen.dart index f6fb76c..6b421a5 100644 --- a/lib/screens/siteConfig/CertificateDetailsScreen.dart +++ b/lib/screens/siteConfig/CertificateDetailsScreen.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; +import 'package:mobile_nebula/components/DangerButton.dart'; import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; @@ -164,9 +165,8 @@ class _CertificateDetailsScreenState extends State { padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, - child: PlatformElevatedButton( + child: DangerButton( child: Text('Replace certificate'), - color: CupertinoColors.systemRed.resolveFrom(context), onPressed: () { Utils.openPage(context, (context) { return AddCertificateScreen( @@ -199,9 +199,8 @@ class _CertificateDetailsScreenState extends State { padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, - child: PlatformElevatedButton( + child: DangerButton( child: Text('Delete'), - color: CupertinoColors.systemRed.resolveFrom(context), onPressed: () => Utils.confirmDelete(context, title, () async { Navigator.pop(context); widget.onDelete!(); diff --git a/lib/screens/siteConfig/StaticHostmapScreen.dart b/lib/screens/siteConfig/StaticHostmapScreen.dart index f07e028..3f92209 100644 --- a/lib/screens/siteConfig/StaticHostmapScreen.dart +++ b/lib/screens/siteConfig/StaticHostmapScreen.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; +import 'package:mobile_nebula/components/DangerButton.dart'; import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/IPAndPortFormField.dart'; import 'package:mobile_nebula/components/IPFormField.dart'; @@ -119,14 +120,12 @@ class _StaticHostmapScreenState extends State { padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, - child: PlatformElevatedButton( - child: Text('Delete'), - color: CupertinoColors.systemRed.resolveFrom(context), - onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () { - Navigator.of(context).pop(); - widget.onDelete!(); - }), - ))) + child: DangerButton( + child: Text('Delete'), + onPressed: () => Utils.confirmDelete(context, 'Delete host map?', () { + Navigator.of(context).pop(); + widget.onDelete!(); + })))) : Container() ])); } diff --git a/lib/screens/siteConfig/UnsafeRouteScreen.dart b/lib/screens/siteConfig/UnsafeRouteScreen.dart index a7d3771..ab168b0 100644 --- a/lib/screens/siteConfig/UnsafeRouteScreen.dart +++ b/lib/screens/siteConfig/UnsafeRouteScreen.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/CIDRFormField.dart'; +import 'package:mobile_nebula/components/DangerButton.dart'; import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/IPFormField.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; @@ -96,9 +97,8 @@ class _UnsafeRouteScreenState extends State { padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10), child: SizedBox( width: double.infinity, - child: PlatformElevatedButton( + child: DangerButton( child: Text('Delete'), - color: CupertinoColors.systemRed.resolveFrom(context), onPressed: () => Utils.confirmDelete(context, 'Delete unsafe route?', () { Navigator.of(context).pop(); widget.onDelete!();