2023-03-22 04:16:23 +00:00
|
|
|
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';
|
2023-04-27 19:19:51 +00:00
|
|
|
import 'html_text_viewer_control.dart';
|
2023-03-22 04:16:23 +00:00
|
|
|
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<SearchResultStatusControl> createState() =>
|
|
|
|
_SearchResultStatusControlState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SearchResultStatusControlState extends State<SearchResultStatusControl> {
|
|
|
|
var showContent = false;
|
|
|
|
|
|
|
|
TimelineEntry get status => widget.status;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void 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: 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) {
|
2023-04-27 19:19:51 +00:00
|
|
|
return HtmlTextViewerControl(
|
|
|
|
content: widget.status.body,
|
|
|
|
onTapUrl: (url) async =>
|
|
|
|
await openUrlStringInSystembrowser(context, url, 'link'),
|
2023-03-22 04:16:23 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget buildMediaBar(BuildContext context) {
|
|
|
|
final items = widget.status.mediaAttachments;
|
|
|
|
if (items.isEmpty) {
|
|
|
|
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 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<String>(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();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|