mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-01-30 17:07:02 +00:00
5a641be96f
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" />|
195 lines
6.3 KiB
Dart
195 lines
6.3 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
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
|
|
static const double minInteractiveSize = 44;
|
|
|
|
/// The background color for a page, this is the furthest back color
|
|
static Color pageBackground(BuildContext context) {
|
|
return CupertinoColors.systemGroupedBackground.resolveFrom(context);
|
|
}
|
|
|
|
/// The background color for a config item
|
|
static Color configItemBackground(BuildContext context) {
|
|
return CupertinoColors.secondarySystemGroupedBackground.resolveFrom(context);
|
|
}
|
|
|
|
/// The top and bottom border color of a config section
|
|
static Color configSectionBorder(BuildContext context) {
|
|
return CupertinoColors.secondarySystemFill.resolveFrom(context);
|
|
}
|
|
|
|
static Size textSize(String text, TextStyle style) {
|
|
final TextPainter textPainter =
|
|
TextPainter(text: TextSpan(text: text, style: style), maxLines: 1, textDirection: TextDirection.ltr)
|
|
..layout(minWidth: 0, maxWidth: double.infinity);
|
|
return textPainter.size;
|
|
}
|
|
|
|
static openPage(BuildContext context, WidgetBuilder pageToDisplayBuilder) {
|
|
Navigator.push(
|
|
context,
|
|
platformPageRoute(
|
|
context: context,
|
|
builder: pageToDisplayBuilder,
|
|
),
|
|
);
|
|
}
|
|
|
|
static String itemCountFormat(int items, {singleSuffix = "item", multiSuffix = "items"}) {
|
|
if (items == 1) {
|
|
return items.toString() + " " + singleSuffix;
|
|
}
|
|
|
|
return items.toString() + " " + multiSuffix;
|
|
}
|
|
|
|
/// Builds a simple leading widget that pops the current screen.
|
|
/// Provide your own onPressed to override that behavior, just remember you have to pop
|
|
static Widget leadingBackWidget(BuildContext context, {label = 'Back', Function? onPressed}) {
|
|
if (Platform.isIOS) {
|
|
return CupertinoButton(
|
|
child: Row(children: <Widget>[Icon(context.platformIcons.back), Text(label)]),
|
|
padding: EdgeInsets.zero,
|
|
onPressed: () {
|
|
if (onPressed == null) {
|
|
Navigator.pop(context);
|
|
} else {
|
|
onPressed();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
return IconButton(
|
|
padding: EdgeInsets.zero,
|
|
icon: Icon(context.platformIcons.back),
|
|
tooltip: label,
|
|
onPressed: () {
|
|
if (onPressed == null) {
|
|
Navigator.pop(context);
|
|
} else {
|
|
onPressed();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
static Widget trailingSaveWidget(BuildContext context, Function onPressed) {
|
|
return PlatformTextButton(
|
|
child: Text('Save', style: TextStyle(fontWeight: FontWeight.bold)),
|
|
padding: Platform.isAndroid ? null : EdgeInsets.zero,
|
|
onPressed: () => onPressed());
|
|
}
|
|
|
|
/// Simple cross platform delete confirmation dialog - can also be used to confirm throwing away a change by swapping the deleteLabel
|
|
static confirmDelete(BuildContext context, String title, Function onConfirm,
|
|
{String deleteLabel = 'Delete', String cancelLabel = 'Cancel'}) {
|
|
showDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) {
|
|
return PlatformAlertDialog(
|
|
title: Text(title),
|
|
actions: <Widget>[
|
|
PlatformDialogAction(
|
|
child: Text(deleteLabel,
|
|
style:
|
|
TextStyle(fontWeight: FontWeight.bold, color: CupertinoColors.systemRed.resolveFrom(context))),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
onConfirm();
|
|
},
|
|
),
|
|
PlatformDialogAction(
|
|
child: Text(cancelLabel),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
)
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
static popError(BuildContext context, String title, String error, {StackTrace? stack}) {
|
|
if (stack != null) {
|
|
error += '\n${stack.toString()}';
|
|
}
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) {
|
|
if (Platform.isAndroid) {
|
|
return AlertDialog(title: Text(title), content: Text(error), actions: <Widget>[
|
|
TextButton(
|
|
child: Text('Ok'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
)
|
|
]);
|
|
}
|
|
|
|
return CupertinoAlertDialog(
|
|
title: Text(title),
|
|
content: Text(error),
|
|
actions: <Widget>[
|
|
CupertinoDialogAction(
|
|
child: Text('Ok'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
)
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
static launchUrl(String url, BuildContext context) async {
|
|
if (await canLaunchUrlString(url)) {
|
|
await launchUrlString(url);
|
|
} else {
|
|
Utils.popError(context, 'Error', 'Could not launch web view');
|
|
}
|
|
}
|
|
|
|
static int ip2int(String ip) {
|
|
final parts = ip.split('.');
|
|
return int.parse(parts[3]) | int.parse(parts[2]) << 8 | int.parse(parts[1]) << 16 | int.parse(parts[0]) << 24;
|
|
}
|
|
|
|
static Future<String?> pickFile(BuildContext context) async {
|
|
await FilePicker.platform.clearTemporaryFiles();
|
|
final result = await FilePicker.platform.pickFiles(allowMultiple: false);
|
|
if (result == null) {
|
|
return null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|