Skip to content

Commit

Permalink
Add support for image alt text (#1497)
Browse files Browse the repository at this point in the history
* Display image alt text

* FIx localization style in image viewer
  • Loading branch information
micahmo authored Aug 24, 2024
1 parent f13b143 commit 13464e1
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/core/models/media.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Media {
/// Indicates the type of media it holds
MediaType mediaType;

/// Includes an alternative text-based description of the image
String? altText;

/// Gets the full-size image URL, if any
String? get imageUrl => isImageUrl(mediaUrl ?? '') ? mediaUrl : thumbnailUrl;

Expand Down
2 changes: 2 additions & 0 deletions lib/post/utils/post.dart
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ Future<PostViewMedia> parsePostView(PostView postView, bool fetchImageDimensions
media.height = size.height;
}

media.altText = postView.post.altText;

mediaList.add(media);

return PostViewMedia(postView: postView, media: mediaList);
Expand Down
1 change: 1 addition & 0 deletions lib/shared/advanced_share_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ void showAdvancedShareSheet(BuildContext context, PostViewMedia postViewMedia) a
isExpandable: true,
isComment: true,
showFullHeightImages: true,
altText: postViewMedia.media.first.altText,
),
if (_isImageCustomized(options, postViewMedia))
snapshot.hasData && !isGeneratingImage
Expand Down
1 change: 1 addition & 0 deletions lib/shared/common_markdown_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class CommonMarkdownBody extends StatelessWidget {
isComment: isComment,
showFullHeightImages: true,
maxWidth: imageMaxWidth,
altText: alt,
)
: Container(
constraints: isComment == true
Expand Down
3 changes: 3 additions & 0 deletions lib/shared/image_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ImagePreview extends StatefulWidget {
final void Function()? navigateToPost;
final bool? isComment;
final bool? read;
final String? altText;

const ImagePreview({
super.key,
Expand All @@ -42,6 +43,7 @@ class ImagePreview extends StatefulWidget {
this.navigateToPost,
this.isComment,
this.read,
this.altText,
}) : assert(url != null || bytes != null);

@override
Expand Down Expand Up @@ -75,6 +77,7 @@ class _ImagePreviewState extends State<ImagePreview> {
bytes: widget.bytes,
postId: widget.postId,
navigateToPost: widget.navigateToPost,
altText: widget.altText,
);
}
},
Expand Down
139 changes: 138 additions & 1 deletion lib/shared/image_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:math';

import 'package:expandable/expandable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

Expand All @@ -28,13 +29,15 @@ class ImageViewer extends StatefulWidget {
final Uint8List? bytes;
final int? postId;
final void Function()? navigateToPost;
final String? altText;

const ImageViewer({
super.key,
this.url,
this.bytes,
this.postId,
this.navigateToPost,
this.altText,
}) : assert(url != null || bytes != null);

get postViewMedia => null;
Expand Down Expand Up @@ -146,6 +149,7 @@ class _ImageViewerState extends State<ImageViewer> with TickerProviderStateMixin
@override
Widget build(BuildContext context) {
final ThunderState thunderState = context.read<ThunderBloc>().state;
final AppLocalizations l10n = AppLocalizations.of(context)!;

AnimationController animationController = AnimationController(duration: const Duration(milliseconds: 140), vsync: this);
Function() animationListener = () {};
Expand Down Expand Up @@ -414,7 +418,7 @@ class _ImageViewerState extends State<ImageViewer> with TickerProviderStateMixin
await Share.shareXFiles([XFile(mediaFile!.path)]);
} catch (e) {
// Tell the user that the download failed
showSnackbar(AppLocalizations.of(context)!.errorDownloadingMedia(e));
showSnackbar(l10n.errorDownloadingMedia(e));
} finally {
setState(() => isDownloadingMedia = false);
}
Expand Down Expand Up @@ -522,7 +526,140 @@ class _ImageViewerState extends State<ImageViewer> with TickerProviderStateMixin
],
),
),
if (widget.altText?.isNotEmpty == true)
Positioned(
bottom: kBottomNavigationBarHeight + 25,
width: MediaQuery.sizeOf(context).width,
child: AnimatedOpacity(
opacity: fullscreen ? 0.0 : 1.0,
duration: const Duration(milliseconds: 200),
child: Padding(
padding: const EdgeInsets.all(16),
child: ImageAltTextWrapper(altText: widget.altText!),
),
),
),
],
);
}
}

class ImageAltTextWrapper extends StatefulWidget {
final String altText;

const ImageAltTextWrapper({super.key, required this.altText});

@override
State<ImageAltTextWrapper> createState() => _ImageAltTextWrapperState();
}

class _ImageAltTextWrapperState extends State<ImageAltTextWrapper> {
final GlobalKey textKey = GlobalKey();
bool altTextIsLong = false;

@override
void initState() {
super.initState();

WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
altTextIsLong = (textKey.currentContext?.size?.height ?? 0) > 40;
});
});
}

@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final AppLocalizations l10n = AppLocalizations.of(context)!;

return AnimatedCrossFade(
crossFadeState: altTextIsLong ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: const Duration(milliseconds: 250),
firstChild: ImageAltText(key: textKey, altText: widget.altText),
secondChild: ExpandableNotifier(
child: Expandable(
expanded: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ImageAltText(altText: widget.altText),
ExpandableButton(
theme: const ExpandableThemeData(useInkWell: false),
child: Text(
l10n.showLess,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.white.withOpacity(0.5),
),
),
),
],
),
collapsed: Stack(
children: [
LimitedBox(
maxHeight: 60,
child: ShaderMask(
shaderCallback: (bounds) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black,
Colors.transparent,
Colors.transparent,
],
stops: [0.0, 0.8, 1.0],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: ImageAltText(altText: widget.altText),
),
),
Positioned(
bottom: 0,
child: ExpandableButton(
theme: const ExpandableThemeData(useInkWell: false),
child: Text(
l10n.showMore,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.white.withOpacity(0.5),
),
),
),
),
],
),
),
),
);
}
}

class ImageAltText extends StatelessWidget {
final String altText;

const ImageAltText({
super.key,
required this.altText,
});

@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);

return Text(
key: key,
altText,
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.90),
shadows: [
Shadow(
offset: const Offset(1, 1),
color: Colors.black.withOpacity(1),
blurRadius: 5.0,
)
],
),
);
}
}
1 change: 1 addition & 0 deletions lib/shared/media_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class _MediaViewState extends State<MediaView> with SingleTickerProviderStateMix
url: widget.postViewMedia.media.first.imageUrl,
postId: widget.postViewMedia.postView.post.id,
navigateToPost: widget.navigateToPost,
altText: widget.postViewMedia.media.first.altText,
);
},
),
Expand Down
3 changes: 2 additions & 1 deletion lib/utils/media/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Future<List<String>> selectImagesToUpload({bool allowMultiple = false}) async {
return [file!.path];
}

void showImageViewer(BuildContext context, {String? url, Uint8List? bytes, int? postId, void Function()? navigateToPost}) {
void showImageViewer(BuildContext context, {String? url, Uint8List? bytes, int? postId, void Function()? navigateToPost, String? altText}) {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
Expand All @@ -140,6 +140,7 @@ void showImageViewer(BuildContext context, {String? url, Uint8List? bytes, int?
bytes: bytes,
postId: postId,
navigateToPost: navigateToPost,
altText: altText,
);
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
Expand Down

0 comments on commit 13464e1

Please sign in to comment.