Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da484f0365 | ||
|
|
92e91d9fad | ||
|
|
1117cadf2a |
+8
-2
@@ -331,6 +331,12 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- fvm dart run scripts/check_coverage.dart
|
- fvm dart run scripts/check_coverage.dart
|
||||||
|
|
||||||
|
check-coverage:
|
||||||
|
desc: Run unit+widget tests with coverage, then fail if the gate is not met
|
||||||
|
deps: [test]
|
||||||
|
cmds:
|
||||||
|
- task: coverage
|
||||||
|
|
||||||
website-dev:
|
website-dev:
|
||||||
desc: Run Hugo development server
|
desc: Run Hugo development server
|
||||||
cmds:
|
cmds:
|
||||||
@@ -361,8 +367,8 @@ tasks:
|
|||||||
${SSH_USER}@${SSH_HOST}:public_html/
|
${SSH_USER}@${SSH_HOST}:public_html/
|
||||||
|
|
||||||
check-fast:
|
check-fast:
|
||||||
desc: Pre-commit checks — analyze + unit tests + widget tests (no build, no integration)
|
desc: Pre-commit checks — analyze + unit+widget tests + coverage gate (no build, no integration)
|
||||||
deps: [analyze, test, check-hygiene]
|
deps: [analyze, check-coverage, check-hygiene]
|
||||||
|
|
||||||
check-hygiene:
|
check-hygiene:
|
||||||
desc: Verify that no forbidden files (like home dir config) are tracked
|
desc: Verify that no forbidden files (like home dir config) are tracked
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ android {
|
|||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@@ -65,6 +66,8 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// Required for flutter_local_notifications and other plugins that need Java 8+ APIs on API < 26.
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||||
// integration_test is a dev dependency; the Flutter plugin loader adds it as
|
// integration_test is a dev dependency; the Flutter plugin loader adds it as
|
||||||
// debugImplementation only, but GeneratedPluginRegistrant.java (in src/main)
|
// debugImplementation only, but GeneratedPluginRegistrant.java (in src/main)
|
||||||
// references its class in all variants. Make it available for release compilation
|
// references its class in all variants. Make it available for release compilation
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Html(
|
_SafeHtml(
|
||||||
data: body.htmlBody!,
|
data: body.htmlBody!,
|
||||||
extensions: [if (!_loadRemoteImages) _BlockRemoteImagesExtension()],
|
extensions: [if (!_loadRemoteImages) _BlockRemoteImagesExtension()],
|
||||||
),
|
),
|
||||||
@@ -501,6 +501,57 @@ class _UnsubscribeChip extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders [Html] and falls back to an error message if the widget throws
|
||||||
|
/// during build, preventing a malformed body from crashing the whole screen.
|
||||||
|
class _SafeHtml extends StatefulWidget {
|
||||||
|
const _SafeHtml({required this.data, required this.extensions});
|
||||||
|
final String data;
|
||||||
|
final List<HtmlExtension> extensions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_SafeHtml> createState() => _SafeHtmlState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SafeHtmlState extends State<_SafeHtml> {
|
||||||
|
bool _failed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_failed) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.warning_amber_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Expanded(child: Text('Message body could not be rendered.')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept any build-phase throw from flutter_html for this subtree.
|
||||||
|
// We save/restore via postFrameCallback so other widgets are unaffected.
|
||||||
|
final prev = ErrorWidget.builder;
|
||||||
|
ErrorWidget.builder = (FlutterErrorDetails details) {
|
||||||
|
ErrorWidget.builder = prev;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) setState(() => _failed = true);
|
||||||
|
});
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
};
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) => ErrorWidget.builder = prev,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Html(data: widget.data, extensions: widget.extensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _BlockRemoteImagesExtension extends HtmlExtension {
|
class _BlockRemoteImagesExtension extends HtmlExtension {
|
||||||
@override
|
@override
|
||||||
Set<String> get supportedTags => {'img'};
|
Set<String> get supportedTags => {'img'};
|
||||||
|
|||||||
Reference in New Issue
Block a user