Files
GitJournal/lib/widgets/images/markdown_image.dart
Vishesh Handa 7fd1c99287 Use Provider's context.read/watch
Instead of the legacy Provider.of. Less scope of bugs this way and the
code is so much nicer to read.
2023-12-06 08:20:40 +01:00

217 lines
8.4 KiB
Dart

/*
* SPDX-FileCopyrightText: 2020-2021 Roland Fredenhagen <important@van-fredenhagen.de>
* SPDX-FileCopyrightText: 2020-2021 Vishesh Handa <me@vhanda.in>
*
* SPDX-License-Identifier: Apache-2.0
*/
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:gitjournal/l10n.dart';
import 'package:gitjournal/logger/logger.dart';
import 'package:gitjournal/settings/markdown_renderer_config.dart';
import 'package:gitjournal/widgets/images/image_caption.dart';
import 'package:gitjournal/widgets/images/image_details.dart';
import 'package:gitjournal/widgets/images/themable_image.dart';
import 'package:path/path.dart' as p;
import 'package:provider/provider.dart';
import 'package:universal_io/io.dart';
class MarkdownImage extends StatelessWidget {
final double? width;
final double? height;
final String altText;
final String tooltip;
// FIXME: Avoid using dynamic!
final Future<dynamic> data;
const MarkdownImage._(
this.data, this.width, this.height, String? altText, String? tooltip)
: altText = altText ?? "",
tooltip = tooltip ?? "";
factory MarkdownImage(Uri uri, String imageDirectory,
{double? width, double? height, String? altText, String? titel}) {
final file = ((uri.isScheme("http") || uri.isScheme("https"))
? DefaultCacheManager().getSingleFile(uri.toString())
: Future.sync(() =>
File.fromUri(Uri.parse(p.join(imageDirectory, uri.toString())))));
final data = file.then(
(value) => value.path.endsWith(".svg") ? value.readAsString() : file);
return MarkdownImage._(data, width, height, altText, titel);
}
@override
Widget build(BuildContext context) {
final settings = context.watch<MarkdownRendererConfig>();
final theme = Theme.of(context);
final dark = theme.brightness == Brightness.dark;
// Test for override tags in AltText/Tooltip
ThemeOverride override = ThemeOverride.None;
if (settings.themeOverrideTagLocation == SettingsImageTextType.Alt ||
settings.themeOverrideTagLocation == SettingsImageTextType.AltTool) {
if (hasTag(altText, settings.doThemeTags)) {
override = ThemeOverride.Do;
} else if (hasTag(altText, settings.doNotThemeTags)) {
override = ThemeOverride.No;
}
}
if (settings.themeOverrideTagLocation == SettingsImageTextType.Tooltip ||
settings.themeOverrideTagLocation == SettingsImageTextType.AltTool) {
if (hasTag(tooltip, settings.doThemeTags)) {
override = ThemeOverride.Do;
} else if (hasTag(tooltip, settings.doNotThemeTags)) {
override = ThemeOverride.No;
}
}
return LayoutBuilder(
builder: (context, constraints) {
final small =
constraints.maxWidth < MediaQuery.of(context).size.width - 40;
final image = FutureBuilder(
future: data,
builder: (context, snapshot) {
if (snapshot.hasError) {
String errorMessage = snapshot.error.toString();
Log.e(errorMessage);
if (snapshot.error is HttpExceptionWithStatus) {
final httpError = snapshot.error as HttpExceptionWithStatus;
errorMessage = context.loc.widgetsImageRendererHttpError(
httpError.statusCode.toString(),
httpError.uri.toString());
}
return SizedBox(
width: width,
height: height,
child: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.error,
color: theme.colorScheme.error,
size: 36,
),
Text(
errorMessage,
style: theme.textTheme.bodyLarge!
.copyWith(color: theme.colorScheme.error),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
]),
),
),
);
}
if (snapshot.hasData) {
Widget im;
if (snapshot.data is String) {
im = ThemableImage.svg(
snapshot.data as String,
width: width ?? MediaQuery.of(context).size.width,
height: height,
themingMethod: override == ThemeOverride.No ||
settings.themeVectorGraphics ==
SettingsThemeVectorGraphics.Off &&
override != ThemeOverride.Do
? ThemingMethod.none
: dark
? settings.themeVectorGraphics ==
SettingsThemeVectorGraphics.Filter
? ThemingMethod.filter
: ThemingMethod.invertBrightness
: settings.matchCanvasColor
? ThemingMethod.wToBg
: ThemingMethod.none,
themingCondition: settings.themeSvgWithBackground ||
override == ThemeOverride.Do
? ThemingCondition.none
: ThemingCondition.noBackground,
colorCondition: settings.vectorGraphicsAdjustColors ==
SettingsVectorGraphicsAdjustColors.All
? ColorCondition.all
: settings.vectorGraphicsAdjustColors ==
SettingsVectorGraphicsAdjustColors.BnW
? ColorCondition.bw
: ColorCondition.gray,
bg: settings.matchCanvasColor
? theme.canvasColor
: Colors.black,
);
} else {
im = ThemableImage.image(
snapshot.data as File,
width: width,
height: height,
doTheme: (settings.themeRasterGraphics ||
override == ThemeOverride.Do) &&
override != ThemeOverride.No &&
dark,
bg: theme.canvasColor,
);
}
return GestureDetector(
child: im,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageDetails(
im as ThemableImage,
captionText(context, altText, tooltip),
),
),
);
},
);
}
return SizedBox(
width: width,
height: height,
child: const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator())));
});
if (shouldCaption(context, altText, tooltip)) {
if (small || !settings.overlayCaption) {
return Column(
children: [image, ImageCaption(altText, tooltip, false)]);
} else {
return Stack(
alignment: AlignmentDirectional.bottomEnd,
children: [image, ImageCaption(altText, tooltip, true)]);
}
}
return image;
},
);
}
}
Color getOverlayBackgroundColor(
MarkdownRendererConfig settings, ThemeData theme,
{Color? light, Color? dark}) {
return theme.brightness == Brightness.dark
? settings.transparentCaption
? Colors.black.withAlpha(100)
: dark ?? theme.canvasColor
: settings.transparentCaption
? Colors.white.withAlpha(100)
: light ?? theme.canvasColor;
}
enum ThemeOverride { None, Do, No }