From 53e548336f59689a08d5378047fc02e587de9785 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Fri, 30 Dec 2022 10:55:05 -0500 Subject: [PATCH] Add ImageControl that allows for doing on-demand loading and stub holder for all images --- .../mention_autocomplete_options.dart | 6 +- lib/controls/image_control.dart | 64 +++++++++++++++++++ lib/controls/notifications_control.dart | 34 ++++++---- lib/controls/timeline/status_control.dart | 61 ++---------------- .../timeline/status_header_control.dart | 20 +++--- 5 files changed, 102 insertions(+), 83 deletions(-) create mode 100644 lib/controls/image_control.dart diff --git a/lib/controls/autocomplete/mention_autocomplete_options.dart b/lib/controls/autocomplete/mention_autocomplete_options.dart index c3f0981..3844ef4 100644 --- a/lib/controls/autocomplete/mention_autocomplete_options.dart +++ b/lib/controls/autocomplete/mention_autocomplete_options.dart @@ -1,9 +1,9 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import '../../globals.dart'; import '../../models/connection.dart'; import '../../services/connections_manager.dart'; +import '../image_control.dart'; class MentionAutocompleteOptions extends StatelessWidget { const MentionAutocompleteOptions({ @@ -50,10 +50,12 @@ class MentionAutocompleteOptions extends StatelessWidget { final user = users.elementAt(i); return ListTile( dense: true, - leading: CachedNetworkImage( + leading: ImageControl( imageUrl: user.avatarUrl.toString(), + iconOverride: const Icon(Icons.person), width: 25, height: 25, + onTap: () => onMentionUserTap(user), ), title: Text(user.name), subtitle: Text('@${user.handle}'), diff --git a/lib/controls/image_control.dart b/lib/controls/image_control.dart new file mode 100644 index 0000000..35886da --- /dev/null +++ b/lib/controls/image_control.dart @@ -0,0 +1,64 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +final _shownImageUrls = {}; + +class ImageControl extends StatefulWidget { + final String imageUrl; + final double? width; + final double? height; + final Function()? onTap; + final Function()? onDoubleTap; + final Icon? iconOverride; + + const ImageControl({ + super.key, + required this.imageUrl, + this.iconOverride, + this.width, + this.height, + this.onTap, + this.onDoubleTap, + }); + + @override + State createState() => _ImageControlState(); +} + +class _ImageControlState extends State { + var shown = false; + + void showImage() { + _shownImageUrls.add(widget.imageUrl); + setState(() { + shown = true; + }); + } + + @override + Widget build(BuildContext context) { + shown = _shownImageUrls.contains(widget.imageUrl); + final image = shown + ? CachedNetworkImage( + imageUrl: widget.imageUrl, + width: widget.width, + height: widget.height, + ) + : SizedBox( + width: widget.width, + height: widget.height, + child: Card( + color: Colors.black12, + shape: const RoundedRectangleBorder(), + child: widget.iconOverride ?? + const Icon( + Icons.image, + ), + ), + ); + return GestureDetector( + onTap: shown ? widget.onTap : showImage, + child: image, + ); + } +} diff --git a/lib/controls/notifications_control.dart b/lib/controls/notifications_control.dart index d94cc6c..51e9338 100644 --- a/lib/controls/notifications_control.dart +++ b/lib/controls/notifications_control.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:go_router/go_router.dart'; @@ -12,6 +11,7 @@ import '../services/notifications_manager.dart'; import '../services/timeline_manager.dart'; import '../utils/dateutils.dart'; import '../utils/snackbar_builder.dart'; +import 'image_control.dart'; class NotificationControl extends StatelessWidget { final UserNotification notification; @@ -43,26 +43,32 @@ class NotificationControl extends StatelessWidget { final manager = context.watch(); final fromIcon = getIt().getById(notification.fromId).fold( - onSuccess: (connection) => CachedNetworkImage( - imageUrl: connection.avatarUrl.toString(), - width: iconSize, - height: iconSize), - onError: (error) => const SizedBox( + onSuccess: (connection) => ImageControl( + imageUrl: connection.avatarUrl.toString(), + iconOverride: const Icon(Icons.person), width: iconSize, height: iconSize, - child: Icon(Icons.question_mark), + onTap: () async { + context.pushNamed(ScreenPaths.userProfile, + params: {'id': notification.fromId}); + }, + ), + onError: (error) => GestureDetector( + onTap: () async { + context.pushNamed(ScreenPaths.userProfile, + params: {'id': notification.fromId}); + }, + child: const SizedBox( + width: iconSize, + height: iconSize, + child: Icon(Icons.person), + ), ), ); return ListTile( tileColor: notification.dismissed ? null : Colors.black12, - leading: GestureDetector( - onTap: () async { - context.pushNamed(ScreenPaths.userProfile, - params: {'id': notification.fromId}); - }, - child: fromIcon, - ), + leading: fromIcon, title: GestureDetector( onTap: () async { switch (notification.type) { diff --git a/lib/controls/timeline/status_control.dart b/lib/controls/timeline/status_control.dart index 3045927..61d17c4 100644 --- a/lib/controls/timeline/status_control.dart +++ b/lib/controls/timeline/status_control.dart @@ -1,21 +1,17 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../globals.dart'; import '../../models/attachment_media_type_enum.dart'; -import '../../models/connection.dart'; import '../../models/entry_tree_item.dart'; import '../../models/timeline_entry.dart'; import '../../screens/image_viewer_screen.dart'; -import '../../services/connections_manager.dart'; import '../../services/timeline_manager.dart'; -import '../../utils/dateutils.dart'; import '../../utils/snackbar_builder.dart'; import '../../utils/url_opening_utils.dart'; +import '../image_control.dart'; import '../padding.dart'; import 'interactions_bar_control.dart'; import 'status_header_control.dart'; @@ -127,51 +123,6 @@ class _StatusControlState extends State { : Card(color: Theme.of(context).splashColor, child: body); } - Widget buildHeader(BuildContext context) { - final author = getIt() - .getById(entry.authorId) - .getValueOrElse(() => Connection()); - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CachedNetworkImage( - imageUrl: author.avatarUrl.toString(), - width: 32.0, - ), - const HorizontalPadding(), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - author.name, - style: Theme.of(context).textTheme.bodyText1, - ), - Row( - children: [ - Text( - ElapsedDateUtils.epochSecondsToString( - entry.backdatedTimestamp), - style: Theme.of(context).textTheme.caption, - ), - const HorizontalPadding(), - Icon( - isPublic ? Icons.public : Icons.lock, - color: Theme.of(context).hintColor, - size: Theme.of(context).textTheme.caption?.fontSize, - ), - ], - ), - Text( - item.id, - ), - ], - ), - ], - ); - } - Widget buildBody(BuildContext context) { return HtmlWidget( entry.body, @@ -215,17 +166,15 @@ class _StatusControlState extends State { return Text('${item.explicitType}: ${item.uri}'); } - return InkWell( + return ImageControl( + width: 250.0, + height: 250.0, + imageUrl: item.thumbnailUri.toString(), onTap: () async { Navigator.push(context, MaterialPageRoute(builder: (context) { return ImageViewerScreen(attachment: item); })); }, - child: CachedNetworkImage( - width: 250.0, - height: 250.0, - imageUrl: item.thumbnailUri.toString(), - ), ); // return Text(item.toString()); }, diff --git a/lib/controls/timeline/status_header_control.dart b/lib/controls/timeline/status_header_control.dart index 20ccb7e..5315035 100644 --- a/lib/controls/timeline/status_header_control.dart +++ b/lib/controls/timeline/status_header_control.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -9,6 +8,7 @@ import '../../routes.dart'; import '../../services/connections_manager.dart'; import '../../utils/dateutils.dart'; import '../../utils/url_opening_utils.dart'; +import '../image_control.dart'; import '../padding.dart'; class StatusHeaderControl extends StatelessWidget { @@ -37,12 +37,11 @@ class StatusHeaderControl extends StatelessWidget { .getValueOrElse(() => Connection()); return Wrap( children: [ - GestureDetector( + ImageControl( + imageUrl: author.avatarUrl.toString(), + iconOverride: Icon(Icons.person), + width: 32.0, onTap: () => goToProfile(context, author.id), - child: CachedNetworkImage( - imageUrl: author.avatarUrl.toString(), - width: 32.0, - ), ), const HorizontalPadding(), Column( @@ -81,12 +80,11 @@ class StatusHeaderControl extends StatelessWidget { const HorizontalPadding(width: 3.0), const Icon(Icons.repeat), const HorizontalPadding(width: 3.0), - GestureDetector( + ImageControl( + imageUrl: reshareAuthor.avatarUrl.toString(), + iconOverride: Icon(Icons.person), + width: 32.0, onTap: () => goToProfile(context, reshareAuthor.id), - child: CachedNetworkImage( - imageUrl: reshareAuthor.avatarUrl.toString(), - width: 32.0, - ), ), const HorizontalPadding(width: 3.0), GestureDetector(