kopia lustrzana https://gitlab.com/mysocialportal/relatica
				
				
				
			Add ImageControl that allows for doing on-demand loading and stub holder for all images
							rodzic
							
								
									ac6602dc45
								
							
						
					
					
						commit
						53e548336f
					
				|  | @ -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}'), | ||||
|  |  | |||
|  | @ -0,0 +1,64 @@ | |||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| final _shownImageUrls = <String>{}; | ||||
| 
 | ||||
| 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<ImageControl> createState() => _ImageControlState(); | ||||
| } | ||||
| 
 | ||||
| class _ImageControlState extends State<ImageControl> { | ||||
|   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, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -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<NotificationsManager>(); | ||||
|     final fromIcon = | ||||
|         getIt<ConnectionsManager>().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) { | ||||
|  |  | |||
|  | @ -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<StatusControl> { | |||
|         : Card(color: Theme.of(context).splashColor, child: body); | ||||
|   } | ||||
| 
 | ||||
|   Widget buildHeader(BuildContext context) { | ||||
|     final author = getIt<ConnectionsManager>() | ||||
|         .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<StatusControl> { | |||
|                 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()); | ||||
|             }, | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Hank Grabowski
						Hank Grabowski