import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import '../models/timeline_entry.dart'; import '../utils/clipboard_utils.dart'; import '../utils/url_opening_utils.dart'; import 'html_text_viewer_control.dart'; import 'media_attachment_viewer_control.dart'; import 'padding.dart'; import 'timeline/link_preview_control.dart'; import 'timeline/status_header_control.dart'; class SearchResultStatusControl extends StatefulWidget { static final _logger = Logger('$SearchResultStatusControl'); final TimelineEntry status; final Future Function() goToPostFunction; const SearchResultStatusControl(this.status, this.goToPostFunction, {super.key}); @override State createState() => _SearchResultStatusControlState(); } class _SearchResultStatusControlState extends State { var showContent = false; TimelineEntry get status => widget.status; @override void initState() { super.initState(); showContent = widget.status.spoilerText.isEmpty; } @override Widget build(BuildContext context) { SearchResultStatusControl._logger .finest('Building ${widget.status.toShortString()}'); const otherPadding = 8.0; final body = Container( decoration: BoxDecoration( color: Theme.of(context).dialogBackgroundColor, border: Border.all(width: 0.5), borderRadius: BorderRadius.circular(5.0), boxShadow: [ BoxShadow( color: Theme.of(context).dividerColor, blurRadius: 2, offset: const Offset(4, 4), spreadRadius: 0.1, blurStyle: BlurStyle.normal, ) ], ), child: Padding( padding: const EdgeInsets.all(5.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: StatusHeaderControl( entry: widget.status, showIsCommentText: true, ), ), buildMenuControl(context), ], ), const VerticalPadding( height: 5, ), if (status.spoilerText.isNotEmpty) TextButton( onPressed: () { setState(() { showContent = !showContent; }); }, child: Text( 'Content Summary: ${status.spoilerText} (Click to ${showContent ? "Hide" : "Show"}}')), if (showContent) ...[ buildBody(context), const VerticalPadding( height: 5, ), if (status.linkPreviewData != null) LinkPreviewControl(preview: status.linkPreviewData!), buildMediaBar(context), ], const VerticalPadding( height: 5, ), const VerticalPadding( height: 5, ), ], ), ), ); return Padding( padding: const EdgeInsets.only( left: otherPadding, right: otherPadding, top: otherPadding, bottom: otherPadding, ), child: body, ); } Widget buildBody(BuildContext context) { return HtmlTextViewerControl( content: widget.status.body, onTapUrl: (url) async => await openUrlStringInSystembrowser(context, url, 'link'), ); } Widget buildMediaBar(BuildContext context) { final items = widget.status.mediaAttachments; if (items.isEmpty) { return const SizedBox(); } // A Link Preview with only one media attachment will have a duplicate image // even though it points to different resources server side. So we don't // want to render it twice. if (widget.status.linkPreviewData != null && items.length == 1) { return const SizedBox(); } return SizedBox( height: 250.0, child: ListView.separated( scrollDirection: Axis.horizontal, itemBuilder: (context, index) { return MediaAttachmentViewerControl( attachments: items, index: index, ); }, separatorBuilder: (context, index) { return const HorizontalPadding(); }, itemCount: items.length)); } Widget buildMenuControl(BuildContext context) { const goToPost = 'Open Post'; const copyText = 'Copy Post Text'; const copyUrl = 'Copy URL'; const openExternal = 'Open In Browser'; final options = [ goToPost, copyText, openExternal, copyUrl, ]; return PopupMenuButton(onSelected: (menuOption) async { if (!context.mounted) { return; } switch (menuOption) { case goToPost: await widget.goToPostFunction(); break; case openExternal: await openUrlStringInSystembrowser( context, widget.status.externalLink, 'Status', ); break; case copyUrl: await copyToClipboard( context: context, text: widget.status.externalLink, message: 'Status link copied to clipboard', ); break; case copyText: await copyToClipboard( context: context, text: widget.status.body, message: 'Status text copied to clipboard', ); break; default: //do nothing } }, itemBuilder: (context) { return options .map((o) => PopupMenuItem(value: o, child: Text(o))) .toList(); }); } }