feat: replace mail protocol with smolmail and rebuild the UI
This commit is contained in:
parent
cb24b68069
commit
e94e4158cc
93 changed files with 6387 additions and 3479 deletions
|
|
@ -1,17 +1,17 @@
|
|||
// ignore_for_file: constant_identifier_names
|
||||
///
|
||||
/// Flutter icons FlashMailIcons
|
||||
/// Copyright (C) 2023 by original authors @ fluttericon.com, fontello.com
|
||||
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
|
||||
///
|
||||
/// To use this font, place it in your fonts/ directory and include the
|
||||
/// following in your pubspec.yaml
|
||||
///
|
||||
/// flutter:
|
||||
/// fonts:
|
||||
/// - family: FlashMailIcons
|
||||
/// fonts:
|
||||
/// - asset: fonts/FlashMailIcons.ttf
|
||||
//
|
||||
// Flutter icons FlashMailIcons
|
||||
// Copyright (C) 2023 by original authors @ fluttericon.com, fontello.com
|
||||
// This font was generated by FlutterIcon.com, which is derived from Fontello.
|
||||
//
|
||||
// To use this font, place it in your fonts/ directory and include the
|
||||
// following in your pubspec.yaml
|
||||
//
|
||||
// flutter:
|
||||
// fonts:
|
||||
// - family: FlashMailIcons
|
||||
// fonts:
|
||||
// - asset: fonts/FlashMailIcons.ttf
|
||||
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
|
|
|
|||
15
lib/shared/utils/avatar_color.dart
Normal file
15
lib/shared/utils/avatar_color.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
/// A deterministic color for [key] (e.g. a contact address), picked from
|
||||
/// [palette] — same contact always gets the same avatar color, and every
|
||||
/// choice comes from the app's own palette instead of arbitrary RGB space.
|
||||
Color colorForKey(String key, List<Color> palette) {
|
||||
if (palette.isEmpty) return Colors.grey;
|
||||
final hash = key.codeUnits.fold<int>(0, (acc, c) => (acc * 31 + c) & 0x7fffffff);
|
||||
return palette[hash % palette.length];
|
||||
}
|
||||
|
||||
/// Black or white, whichever reads clearly on [background].
|
||||
Color contrastingTextColor(Color background) {
|
||||
return background.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||
}
|
||||
37
lib/shared/utils/format.dart
Normal file
37
lib/shared/utils/format.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:intl/intl.dart";
|
||||
|
||||
/// One path for all replies so the Re: rule lives once.
|
||||
String replySubject(String subject) =>
|
||||
subject.isEmpty || subject.startsWith("Re:") ? subject : "Re: $subject";
|
||||
|
||||
String formatTime(int? seconds) {
|
||||
if (seconds == null) return "";
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000, isUtc: true);
|
||||
return DateFormat.yMMMMd().add_jm().format(date.toLocal());
|
||||
}
|
||||
|
||||
/// The full date is too wide for a list row's trailing slot — this is what
|
||||
/// message_tile.dart shows instead: just a time for today, else a short date.
|
||||
String formatShortTime(int? seconds) {
|
||||
if (seconds == null) return "";
|
||||
final date =
|
||||
DateTime.fromMillisecondsSinceEpoch(seconds * 1000, isUtc: true).toLocal();
|
||||
final now = DateTime.now();
|
||||
if (date.year == now.year && date.month == now.month && date.day == now.day) {
|
||||
return DateFormat.jm().format(date);
|
||||
}
|
||||
if (date.year == now.year) {
|
||||
return DateFormat.MMMd().format(date);
|
||||
}
|
||||
return DateFormat.yMd().format(date);
|
||||
}
|
||||
|
||||
Future<void> copyText(BuildContext context, String text) async {
|
||||
await Clipboard.setData(ClipboardData(text: text));
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(content: Text("Copied to clipboard")));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import "dart:math";
|
||||
import "dart:ui";
|
||||
|
||||
Color getRandomColor() {
|
||||
Random random = Random();
|
||||
return Color.fromRGBO(
|
||||
random.nextInt(256),
|
||||
random.nextInt(256),
|
||||
random.nextInt(256),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import "dart:math";
|
||||
|
||||
String randomString(int length) {
|
||||
const charset =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
final random = Random.secure();
|
||||
final codeUnits = List<int>.generate(
|
||||
length,
|
||||
(index) => charset.codeUnitAt(random.nextInt(charset.length)),
|
||||
);
|
||||
return String.fromCharCodes(codeUnits);
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
final RegExp emailRegex = RegExp(
|
||||
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$',
|
||||
);
|
||||
15
lib/shared/utils/snackbar.dart
Normal file
15
lib/shared/utils/snackbar.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
/// Errors get the error color explicitly — the theme's default SnackBar is
|
||||
/// neutral, so a plain SnackBar(content: ...) is *not* red anymore.
|
||||
void showErrorSnackBar(BuildContext context, String message,
|
||||
{Duration? duration}) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: scheme.error,
|
||||
duration: duration ?? const Duration(seconds: 4),
|
||||
content: Text(message, style: TextStyle(color: scheme.onError)),
|
||||
),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue