relatica/lib/controls/search_result_status_contro...

234 wiersze
6.7 KiB
Dart
Czysty Zwykły widok Historia

2023-03-22 04:16:23 +00:00
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
2023-03-22 04:16:23 +00:00
import 'package:logging/logging.dart';
import '../models/timeline_entry.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/settings_services.dart';
2023-03-22 04:16:23 +00:00
import '../utils/clipboard_utils.dart';
import '../utils/snackbar_builder.dart';
2023-03-22 04:16:23 +00:00
import '../utils/url_opening_utils.dart';
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 ConsumerStatefulWidget {
2023-03-22 04:16:23 +00:00
static final _logger = Logger('$SearchResultStatusControl');
final TimelineEntry status;
final Future Function() goToPostFunction;
const SearchResultStatusControl(this.status, this.goToPostFunction,
{super.key});
@override
ConsumerState<SearchResultStatusControl> createState() =>
2023-03-22 04:16:23 +00:00
_SearchResultStatusControlState();
}
class _SearchResultStatusControlState
extends ConsumerState<SearchResultStatusControl> {
var showSpoilerControl = true;
2023-03-22 04:16:23 +00:00
var showContent = false;
var processing = false;
2023-03-22 04:16:23 +00:00
TimelineEntry get status => widget.status;
@override
void initState() {
super.initState();
showSpoilerControl = ref.read(spoilerHidingSettingProvider);
showContent =
!showSpoilerControl ? true : widget.status.spoilerText.isEmpty;
2023-03-22 04:16:23 +00:00
}
Future<void> openStatus() async {
if (processing) {
return;
}
setState(() {
processing = true;
});
buildSnackbar(context, 'Loading post thread to open');
await widget.goToPostFunction();
setState(() {
processing = false;
});
}
2023-03-22 04:16:23 +00:00
@override
Widget build(BuildContext context) {
showSpoilerControl = ref.watch(spoilerHidingSettingProvider);
2023-03-22 04:16:23 +00:00
SearchResultStatusControl._logger
.finest('Building ${widget.status.toShortString()}');
const otherPadding = 8.0;
final body = Container(
decoration: const BoxDecoration(),
2023-03-22 04:16:23 +00:00
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 (showSpoilerControl && status.spoilerText.isNotEmpty)
2023-03-22 04:16:23 +00:00
TextButton(
onPressed: () {
setState(() {
showContent = !showContent;
});
},
child: Text(
'Content Summary: ${status.spoilerText} (Click to ${showContent ? "Hide" : "Show"})')),
if (showContent || !showSpoilerControl) ...[
2023-03-22 04:16:23 +00:00
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 GestureDetector(
onTap: openStatus,
child: Padding(
padding: const EdgeInsets.only(
left: otherPadding,
right: otherPadding,
top: otherPadding,
bottom: otherPadding,
),
child: body,
2023-03-22 04:16:23 +00:00
),
);
}
Widget buildBody(BuildContext context) {
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();
}
// 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();
}
// A Diaspora reshare will have an HTML-built card with a link preview image
// to the same image as what would be in the single attachment but at a
// different link. So we don't want it to render twice.
final linkPhotoBaseUrl = Uri.https(
ref.watch(activeProfileProvider).serverName,
'photo/link',
).toString();
if (widget.status.body.contains(linkPhotoBaseUrl) && items.length == 1) {
return const SizedBox();
}
2023-03-22 04:16:23 +00:00
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();
2023-03-22 04:16:23 +00:00
},
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 openStatus;
2023-03-22 04:16:23 +00:00
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();
});
}
}