Switch to Material 3 for Android (#224)

This exploration updates the Android app to use Google's Material 3 design, and changes up the colors a bit in the process.  
I used a source color of #5D23DD in https://material-foundation.github.io/material-theme-builder/, which generates a full set of material colors that I imported into `lib/services/theme.dart`.  It should be easy to tweak any of the colors that we want, vs previously it was somewhat more difficult because they were being generated "behind the scenes".

This doesn't necessarily need to be done now, but it does align better with more modern Android design patterns and Flutter has said at some point they will stop supporting Material 2.  

Note: this should not have any impact on iOS, since we use the default `CupertinoThemeData` there, without any custom theming.

Here are some before/after comparisons of interesting screens:

Adding a new site:
|Dark Before|Dark After|Light Before|Light After|
|---|---|---|---
|<img width="390" alt="Android Studio 2025-01-22 10 43 55" src="https://github.com/user-attachments/assets/c7954b7f-a8eb-48a5-bd47-237877f078fd" />|<img width="405" alt="Android Studio 2025-01-22 11 16 48" src="https://github.com/user-attachments/assets/b69c1e6f-3464-44af-9f9c-f6377b5ce1d1" />|<img width="389" alt="image" src="https://github.com/user-attachments/assets/40f1e40f-a1f5-42e4-909c-c07b3f82df2b" />|<img width="393" alt="image" src="https://github.com/user-attachments/assets/09f8f8ce-668e-4e69-a4a9-233fb970655f" />|


Confirmation dialog:
|Before|After|
|---|---|
|<img width="264" alt="image" src="https://github.com/user-attachments/assets/7cae1abc-621d-4c59-bf36-0ce3be0af88c" />|<img width="278" alt="Android Studio 2025-01-22 11 43 19" src="https://github.com/user-attachments/assets/d461a97f-d252-494f-8b30-49d33a5a9cda" />|

Settings page:
|Dark Before|Dark After|Light Before|Light After|
|---|---|---|---|
|<img width="394" alt="Android Studio 2025-01-22 11 46 01" src="https://github.com/user-attachments/assets/edfc6c40-ee72-4a7c-b9b7-7c3025f0cdfe" />|<img width="393" alt="Android Studio 2025-01-22 11 46 35" src="https://github.com/user-attachments/assets/d1445041-bbc0-4994-98d5-3eb149afceea" />|<img width="391" alt="Android Studio 2025-01-22 11 46 09" src="https://github.com/user-attachments/assets/295e5cc4-74e2-422a-b478-a3e0bd577b60" />|<img width="391" alt="Android Studio 2025-01-22 11 46 28" src="https://github.com/user-attachments/assets/098339de-f2df-4269-84cf-e3ae2c09a51d" />|

Site with errors:
|Dark Before|Dark After|Light Before|Light After|
|---|---|---|---|
|<img width="395" alt="Android Studio 2025-01-22 11 48 50" src="https://github.com/user-attachments/assets/4f8dd05d-ccc7-44eb-96e0-ffec63975085" />|<img width="390" alt="Android Studio 2025-01-22 11 48 06" src="https://github.com/user-attachments/assets/751f35e2-b801-4ddf-9655-15f0097e05ca" />|<img width="395" alt="image" src="https://github.com/user-attachments/assets/15ac20c9-4d40-4e51-aed6-fd69a001588b" />|<img width="388" alt="Android Studio 2025-01-22 11 48 20" src="https://github.com/user-attachments/assets/780bf849-9add-4f91-bac8-3cdd5fd89337" />|

Main page / site list:
|Light Before|Light After|Light Scrolled Before|Light Scrolled After|
|---|---|---|---|
|<img width="387" alt="Android Studio 2025-01-22 11 53 07" src="https://github.com/user-attachments/assets/ca426470-00c2-4dc3-bd22-4a336c4ec8cf" />|<img width="403" alt="Android Studio 2025-01-22 11 53 41" src="https://github.com/user-attachments/assets/abf755e3-df10-453b-a439-0aa3b21b7ef9" />|<img width="389" alt="Android Studio 2025-01-22 11 53 22" src="https://github.com/user-attachments/assets/a6cdfabf-5288-49fe-ac29-e73678d34ccb" />|<img width="396" alt="Android Studio 2025-01-22 11 53 51" src="https://github.com/user-attachments/assets/11eda1f4-cf2f-4848-9690-6093223a9e7e" />|

Certificate:
|Dark Before|Dark After|Light Before|Light After|
|---|---|---|---|
|<img width="388" alt="Android Studio 2025-01-22 11 57 25" src="https://github.com/user-attachments/assets/5a1db1ba-a560-4aa6-8649-9b41d9ba25b6" />|<img width="390" alt="Android Studio 2025-01-22 11 56 44" src="https://github.com/user-attachments/assets/5e1b9200-ea13-42e5-a55d-51a1fda4522f" />|<img width="397" alt="Android Studio 2025-01-22 11 57 01" src="https://github.com/user-attachments/assets/65c52218-3f90-40c7-bb21-e499c2e0b08c" />|<img width="392" alt="Android Studio 2025-01-22 11 56 21" src="https://github.com/user-attachments/assets/554847cd-e825-4691-ac21-1549ab5e7d21" />|
This commit is contained in:
Ian VanSchooten 2025-01-23 10:49:32 -05:00 committed by GitHub
parent f290a71b94
commit 5a641be96f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 426 additions and 41 deletions

View file

@ -15,7 +15,9 @@ class DangerButton extends StatelessWidget {
return FilledButton(
onPressed: onPressed,
child: child,
style: FilledButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.error));
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
foregroundColor: Theme.of(context).colorScheme.onError));
} else {
// Workaround for https://github.com/flutter/flutter/issues/161590
final themeData = CupertinoTheme.of(context);

View file

@ -1,8 +1,7 @@
import 'dart:async';
import 'package:flutter/cupertino.dart' show CupertinoThemeData, DefaultCupertinoLocalizations, CupertinoColors;
import 'package:flutter/material.dart'
show BottomSheetThemeData, ColorScheme, Colors, DefaultMaterialLocalizations, ThemeData, ThemeMode;
import 'package:flutter/cupertino.dart' show CupertinoThemeData, DefaultCupertinoLocalizations;
import 'package:flutter/material.dart' show DefaultMaterialLocalizations, TextTheme, ThemeMode;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@ -11,6 +10,8 @@ import 'package:mobile_nebula/screens/MainScreen.dart';
import 'package:mobile_nebula/screens/EnrollmentScreen.dart';
import 'package:mobile_nebula/services/settings.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
import 'package:mobile_nebula/services/theme.dart';
import 'package:mobile_nebula/services/utils.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
@ -85,36 +86,8 @@ class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
final ThemeData lightTheme = ThemeData(
useMaterial3: false,
colorScheme: ColorScheme.fromSwatch(
brightness: Brightness.light,
primarySwatch: Colors.blueGrey,
errorColor: CupertinoColors.systemRed.resolveFrom(context),
),
primaryColor: Colors.blueGrey[900],
fontFamily: 'PublicSans',
//scaffoldBackgroundColor: Colors.grey[100],
scaffoldBackgroundColor: Colors.white,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.blueGrey[50],
),
);
final ThemeData darkTheme = ThemeData(
useMaterial3: false,
colorScheme: ColorScheme.fromSwatch(
brightness: Brightness.dark,
primarySwatch: Colors.grey,
errorColor: CupertinoColors.systemRed.resolveFrom(context),
),
primaryColor: Colors.grey[900],
fontFamily: 'PublicSans',
scaffoldBackgroundColor: Colors.grey[800],
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.grey[850],
),
);
TextTheme textTheme = Utils.createTextTheme(context, "Public Sans", "Public Sans");
MaterialTheme theme = MaterialTheme(textTheme);
return PlatformProvider(
settings: PlatformSettingsData(iosUsesMaterialWidgets: true),
@ -129,7 +102,7 @@ class _AppState extends State<App> {
material: (_, __) {
return new MaterialAppData(
themeMode: brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark,
theme: brightness == Brightness.light ? lightTheme : darkTheme,
theme: brightness == Brightness.light ? theme.light() : theme.dark(),
);
},
cupertino: (_, __) => CupertinoAppData(

389
lib/services/theme.dart Normal file
View file

@ -0,0 +1,389 @@
import "package:flutter/material.dart";
// Originally generated by https://material-foundation.github.io/material-theme-builder/
// from a source color of #5D23DD
class MaterialTheme {
final TextTheme textTheme;
const MaterialTheme(this.textTheme);
static ColorScheme lightScheme() {
return const ColorScheme(
brightness: Brightness.light,
primary: Color(4284700303),
surfaceTint: Color(4284700303),
onPrimary: Color(4294967295),
primaryContainer: Color(4293451519),
onPrimaryContainer: Color(4283121270),
secondary: Color(4284570481),
onSecondary: Color(4294967295),
secondaryContainer: Color(4293385976),
onSecondaryContainer: Color(4282991704),
tertiary: Color(4286403169),
onTertiary: Color(4294967295),
tertiaryContainer: Color(4294957540),
onTertiaryContainer: Color(4284693322),
error: Color(4290386458),
onError: Color(4294967295),
errorContainer: Color(4294957782),
onErrorContainer: Color(4287823882),
surface: Color(4294834175),
onSurface: Color(4280032032),
onSurfaceVariant: Color(4282926414),
outline: Color(4286150015),
outlineVariant: Color(4291478735),
shadow: Color(4278190080),
scrim: Color(4278190080),
inverseSurface: Color(4281478965),
inversePrimary: Color(4291673599),
primaryFixed: Color(4293451519),
onPrimaryFixed: Color(4280225864),
primaryFixedDim: Color(4291673599),
onPrimaryFixedVariant: Color(4283121270),
secondaryFixed: Color(4293385976),
onSecondaryFixed: Color(4280097067),
secondaryFixedDim: Color(4291544028),
onSecondaryFixedVariant: Color(4282991704),
tertiaryFixed: Color(4294957540),
onTertiaryFixed: Color(4281405726),
tertiaryFixedDim: Color(4293834953),
onTertiaryFixedVariant: Color(4284693322),
surfaceDim: Color(4292729056),
surfaceBright: Color(4294834175),
surfaceContainerLowest: Color(4294967295),
surfaceContainerLow: Color(4294439674),
surfaceContainer: Color(4294110452),
surfaceContainerHigh: Color(4293715694),
surfaceContainerHighest: Color(4293321193),
);
}
ThemeData light() {
return theme(lightScheme());
}
static ColorScheme lightMediumContrastScheme() {
return const ColorScheme(
brightness: Brightness.light,
primary: Color(4282002788),
surfaceTint: Color(4284700303),
onPrimary: Color(4294967295),
primaryContainer: Color(4285686943),
onPrimaryContainer: Color(4294967295),
secondary: Color(4281873223),
onSecondary: Color(4294967295),
secondaryContainer: Color(4285557376),
onSecondaryContainer: Color(4294967295),
tertiary: Color(4283444025),
onTertiary: Color(4294967295),
tertiaryContainer: Color(4287455344),
onTertiaryContainer: Color(4294967295),
error: Color(4285792262),
onError: Color(4294967295),
errorContainer: Color(4291767335),
onErrorContainer: Color(4294967295),
surface: Color(4294834175),
onSurface: Color(4279373846),
onSurfaceVariant: Color(4281873725),
outline: Color(4283715930),
outlineVariant: Color(4285492085),
shadow: Color(4278190080),
scrim: Color(4278190080),
inverseSurface: Color(4281478965),
inversePrimary: Color(4291673599),
primaryFixed: Color(4285686943),
onPrimaryFixed: Color(4294967295),
primaryFixedDim: Color(4284042373),
onPrimaryFixedVariant: Color(4294967295),
secondaryFixed: Color(4285557376),
onSecondaryFixed: Color(4294967295),
secondaryFixedDim: Color(4283912807),
onSecondaryFixedVariant: Color(4294967295),
tertiaryFixed: Color(4287455344),
onTertiaryFixed: Color(4294967295),
tertiaryFixedDim: Color(4285679960),
onTertiaryFixedVariant: Color(4294967295),
surfaceDim: Color(4291478989),
surfaceBright: Color(4294834175),
surfaceContainerLowest: Color(4294967295),
surfaceContainerLow: Color(4294439674),
surfaceContainer: Color(4293715694),
surfaceContainerHigh: Color(4292926435),
surfaceContainerHighest: Color(4292202712),
);
}
ThemeData lightMediumContrast() {
return theme(lightMediumContrastScheme());
}
static ColorScheme lightHighContrastScheme() {
return const ColorScheme(
brightness: Brightness.light,
primary: Color(4281344857),
surfaceTint: Color(4284700303),
onPrimary: Color(4294967295),
primaryContainer: Color(4283252856),
onPrimaryContainer: Color(4294967295),
secondary: Color(4281215292),
onSecondary: Color(4294967295),
secondaryContainer: Color(4283123291),
onSecondaryContainer: Color(4294967295),
tertiary: Color(4282655023),
onTertiary: Color(4294967295),
tertiaryContainer: Color(4284824908),
onTertiaryContainer: Color(4294967295),
error: Color(4284481540),
onError: Color(4294967295),
errorContainer: Color(4288151562),
onErrorContainer: Color(4294967295),
surface: Color(4294834175),
onSurface: Color(4278190080),
onSurfaceVariant: Color(4278190080),
outline: Color(4281150259),
outlineVariant: Color(4283123793),
shadow: Color(4278190080),
scrim: Color(4278190080),
inverseSurface: Color(4281478965),
inversePrimary: Color(4291673599),
primaryFixed: Color(4283252856),
onPrimaryFixed: Color(4294967295),
primaryFixedDim: Color(4281739616),
onPrimaryFixedVariant: Color(4294967295),
secondaryFixed: Color(4283123291),
onSecondaryFixed: Color(4294967295),
secondaryFixedDim: Color(4281675843),
onSecondaryFixedVariant: Color(4294967295),
tertiaryFixed: Color(4284824908),
onTertiaryFixed: Color(4294967295),
tertiaryFixedDim: Color(4283180853),
onTertiaryFixedVariant: Color(4294967295),
surfaceDim: Color(4290557887),
surfaceBright: Color(4294834175),
surfaceContainerLowest: Color(4294967295),
surfaceContainerLow: Color(4294242295),
surfaceContainer: Color(4293321193),
surfaceContainerHigh: Color(4292400090),
surfaceContainerHighest: Color(4291478989),
);
}
ThemeData lightHighContrast() {
return theme(lightHighContrastScheme());
}
static ColorScheme darkScheme() {
return const ColorScheme(
brightness: Brightness.dark,
primary: Color(4291673599),
surfaceTint: Color(4291673599),
onPrimary: Color(4281608030),
primaryContainer: Color(4283121270),
onPrimaryContainer: Color(4293451519),
secondary: Color(4291544028),
onSecondary: Color(4281478721),
secondaryContainer: Color(4282991704),
onSecondaryContainer: Color(4293385976),
tertiary: Color(4293834953),
onTertiary: Color(4282983731),
tertiaryContainer: Color(4284693322),
onTertiaryContainer: Color(4294957540),
error: Color(4294948011),
onError: Color(4285071365),
errorContainer: Color(4287823882),
onErrorContainer: Color(4294957782),
surface: Color(4279505688),
onSurface: Color(4293321193),
onSurfaceVariant: Color(4291478735),
outline: Color(4287860633),
outlineVariant: Color(4282926414),
shadow: Color(4278190080),
scrim: Color(4278190080),
inverseSurface: Color(4293321193),
inversePrimary: Color(4284700303),
primaryFixed: Color(4293451519),
onPrimaryFixed: Color(4280225864),
primaryFixedDim: Color(4291673599),
onPrimaryFixedVariant: Color(4283121270),
secondaryFixed: Color(4293385976),
onSecondaryFixed: Color(4280097067),
secondaryFixedDim: Color(4291544028),
onSecondaryFixedVariant: Color(4282991704),
tertiaryFixed: Color(4294957540),
onTertiaryFixed: Color(4281405726),
tertiaryFixedDim: Color(4293834953),
onTertiaryFixedVariant: Color(4284693322),
surfaceDim: Color(4279505688),
surfaceBright: Color(4282005566),
surfaceContainerLowest: Color(4279176467),
surfaceContainerLow: Color(4280032032),
surfaceContainer: Color(4280295204),
surfaceContainerHigh: Color(4281018671),
surfaceContainerHighest: Color(4281742394),
);
}
ThemeData dark() {
return theme(darkScheme());
}
static ColorScheme darkMediumContrastScheme() {
return const ColorScheme(
brightness: Brightness.dark,
primary: Color(4293056255),
surfaceTint: Color(4291673599),
onPrimary: Color(4280884306),
primaryContainer: Color(4288055493),
onPrimaryContainer: Color(4278190080),
secondary: Color(4292991218),
onSecondary: Color(4280754998),
secondaryContainer: Color(4287925669),
onSecondaryContainer: Color(4278190080),
tertiary: Color(4294955230),
onTertiary: Color(4282194472),
tertiaryContainer: Color(4290020244),
onTertiaryContainer: Color(4278190080),
error: Color(4294955724),
onError: Color(4283695107),
errorContainer: Color(4294923337),
onErrorContainer: Color(4278190080),
surface: Color(4279505688),
onSurface: Color(4294967295),
onSurfaceVariant: Color(4292926181),
outline: Color(4290097339),
outlineVariant: Color(4287860377),
shadow: Color(4278190080),
scrim: Color(4278190080),
inverseSurface: Color(4293321193),
inversePrimary: Color(4283187063),
primaryFixed: Color(4293451519),
onPrimaryFixed: Color(4279501629),
primaryFixedDim: Color(4291673599),
onPrimaryFixedVariant: Color(4282002788),
secondaryFixed: Color(4293385976),
onSecondaryFixed: Color(4279438880),
secondaryFixedDim: Color(4291544028),
onSecondaryFixedVariant: Color(4281873223),
tertiaryFixed: Color(4294957540),
onTertiaryFixed: Color(4280550932),
tertiaryFixedDim: Color(4293834953),
onTertiaryFixedVariant: Color(4283444025),
surfaceDim: Color(4279505688),
surfaceBright: Color(4282794826),
surfaceContainerLowest: Color(4278716172),
surfaceContainerLow: Color(4280163618),
surfaceContainer: Color(4280887085),
surfaceContainerHigh: Color(4281610808),
surfaceContainerHighest: Color(4282334531),
);
}
ThemeData darkMediumContrast() {
return theme(darkMediumContrastScheme());
}
static ColorScheme darkHighContrastScheme() {
return const ColorScheme(
brightness: Brightness.dark,
primary: Color(4294241791),
surfaceTint: Color(4291673599),
onPrimary: Color(4278190080),
primaryContainer: Color(4291410427),
onPrimaryContainer: Color(4279107636),
secondary: Color(4294241791),
onSecondary: Color(4278190080),
secondaryContainer: Color(4291280856),
onSecondaryContainer: Color(4279044122),
tertiary: Color(4294962160),
onTertiary: Color(4278190080),
tertiaryContainer: Color(4293571782),
onTertiaryContainer: Color(4280091150),
error: Color(4294962409),
onError: Color(4278190080),
errorContainer: Color(4294946468),
onErrorContainer: Color(4280418305),
surface: Color(4279505688),
onSurface: Color(4294967295),
onSurfaceVariant: Color(4294967295),
outline: Color(4294242041),
outlineVariant: Color(4291215563),
shadow: Color(4278190080),
scrim: Color(4278190080),
inverseSurface: Color(4293321193),
inversePrimary: Color(4283187063),
primaryFixed: Color(4293451519),
onPrimaryFixed: Color(4278190080),
primaryFixedDim: Color(4291673599),
onPrimaryFixedVariant: Color(4279501629),
secondaryFixed: Color(4293385976),
onSecondaryFixed: Color(4278190080),
secondaryFixedDim: Color(4291544028),
onSecondaryFixedVariant: Color(4279438880),
tertiaryFixed: Color(4294957540),
onTertiaryFixed: Color(4278190080),
tertiaryFixedDim: Color(4293834953),
onTertiaryFixedVariant: Color(4280550932),
surfaceDim: Color(4279505688),
surfaceBright: Color(4283584341),
surfaceContainerLowest: Color(4278190080),
surfaceContainerLow: Color(4280295204),
surfaceContainer: Color(4281478965),
surfaceContainerHigh: Color(4282202689),
surfaceContainerHighest: Color(4282926668),
);
}
ThemeData darkHighContrast() {
return theme(darkHighContrastScheme());
}
ThemeData theme(ColorScheme colorScheme) => ThemeData(
useMaterial3: true,
brightness: colorScheme.brightness,
colorScheme: colorScheme,
textTheme: textTheme.apply(
bodyColor: colorScheme.onSurface,
displayColor: colorScheme.onSurface,
),
scaffoldBackgroundColor: colorScheme.background,
canvasColor: colorScheme.surface,
);
List<ExtendedColor> get extendedColors => [];
}
class ExtendedColor {
final Color seed, value;
final ColorFamily light;
final ColorFamily lightHighContrast;
final ColorFamily lightMediumContrast;
final ColorFamily dark;
final ColorFamily darkHighContrast;
final ColorFamily darkMediumContrast;
const ExtendedColor({
required this.seed,
required this.value,
required this.light,
required this.lightHighContrast,
required this.lightMediumContrast,
required this.dark,
required this.darkHighContrast,
required this.darkMediumContrast,
});
}
class ColorFamily {
const ColorFamily({
required this.color,
required this.onColor,
required this.colorContainer,
required this.onColorContainer,
});
final Color color;
final Color onColor;
final Color colorContainer;
final Color onColorContainer;
}

View file

@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:google_fonts/google_fonts.dart';
class Utils {
/// Minimum size (width or height) of a interactive component
@ -82,12 +83,8 @@ class Utils {
}
static Widget trailingSaveWidget(BuildContext context, Function onPressed) {
return CupertinoButton(
child: Text('Save',
style: TextStyle(
fontWeight: FontWeight.bold,
//TODO: For some reason on android if inherit is the default of true the text color here turns to the background color
inherit: Platform.isIOS ? true : false)),
return PlatformTextButton(
child: Text('Save', style: TextStyle(fontWeight: FontWeight.bold)),
padding: Platform.isAndroid ? null : EdgeInsets.zero,
onPressed: () => onPressed());
}
@ -180,4 +177,19 @@ class Utils {
final file = File(result.files.first.path!);
return file.readAsString();
}
static TextTheme createTextTheme(BuildContext context, String bodyFontString, String displayFontString) {
TextTheme baseTextTheme = Theme.of(context).textTheme;
TextTheme bodyTextTheme = GoogleFonts.getTextTheme(bodyFontString, baseTextTheme);
TextTheme displayTextTheme = GoogleFonts.getTextTheme(displayFontString, baseTextTheme);
TextTheme textTheme = displayTextTheme.copyWith(
bodyLarge: bodyTextTheme.bodyLarge,
bodyMedium: bodyTextTheme.bodyMedium,
bodySmall: bodyTextTheme.bodySmall,
labelLarge: bodyTextTheme.labelLarge,
labelMedium: bodyTextTheme.labelMedium,
labelSmall: bodyTextTheme.labelSmall,
);
return textTheme;
}
}

View file

@ -176,6 +176,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
http:
dependency: transitive
description:

View file

@ -26,6 +26,7 @@ dependencies:
flutter_platform_widgets: ^7.0.1
path_provider: ^2.0.11
file_picker: ^8.1.2
google_fonts: ^6.2.1
uuid: ^4.4.2
package_info_plus: ^8.0.2
url_launcher: ^6.1.6