import 'package:flutter/material.dart'; import 'package:localization/localization.dart'; import 'package:loris/business_logic/account/account.dart'; import 'package:loris/business_logic/timeline/media.dart'; import 'package:loris/dialogues/full_post_view.dart'; import 'package:loris/dialogues/makepost.dart'; import 'package:loris/dialogues/profile_view.dart'; import 'package:loris/partials/interaction_button.dart'; import 'package:loris/partials/media_attachment.dart'; import 'package:loris/partials/post_options.dart'; import 'package:loris/partials/post_text_renderer.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../business_logic/timeline/timeline.dart' as tl; import '../business_logic/interactions/interactions.dart' as interactions; class Post extends StatefulWidget { const Post({ required this.model, Key? key, this.reblogVisible = true, this.hideSensitive = true, this.hideActionBar = false, }) : super(key: key); final tl.PostModel model; final bool reblogVisible; final bool hideSensitive; final bool hideActionBar; @override State createState() => _PostState(); } class _PostState extends State { @override Widget build(BuildContext context) { List c = []; c.addAll([ DisplayName(account: widget.model.account), SizedBox( width: double.maxFinite, child: PostBody( model: widget.model, sensitive: widget.model.sensitive, content: widget.model.content, spoilerText: widget.model.spoilerText, media: widget.model.attachments, forceShow: !widget.hideSensitive, ), ), ]); if (!widget.hideActionBar) { c.add( PostActionBar(model: widget.model), ); } c.add( RebloggedBy( account: widget.model.rebloggedBy, reblogVisible: widget.reblogVisible, ), ); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: c, ); } } class DisplayName extends StatelessWidget { const DisplayName({ required this.account, this.isReblog = false, this.openInBrowser = false, Key? key, }) : super(key: key); final AccountModel account; final bool isReblog; final bool openInBrowser; @override Widget build(BuildContext context) { TextStyle? usernameStyle; if (isReblog) { usernameStyle = Theme.of(context).textTheme.bodyMedium; } else { usernameStyle = Theme.of(context).textTheme.displaySmall; } return InkWell( onTap: !openInBrowser ? () { showDialog( context: context, builder: (context) => ProfileView(model: account), ); } : (() => launchUrlString(account.url)), child: Row( children: [ ProfilePic(url: account.avatar), const SizedBox( width: 8, ), Flexible( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ SelectableText( account.displayName, style: usernameStyle, ), SelectableText( account.acct, style: Theme.of(context).textTheme.bodySmall, ), ], ), ), ], ), ); } } class RebloggedBy extends StatelessWidget { const RebloggedBy({ required this.account, Key? key, this.reblogVisible = true, }) : super(key: key); final AccountModel? account; final bool reblogVisible; @override Widget build(BuildContext context) { if (account != null && reblogVisible) { return Column( children: [ Row( children: [ const Icon(Icons.repeat), Flexible( child: SelectableText("reblogged-by".i18n()), ), ], ), const SizedBox( height: 8, ), DisplayName( account: account!, isReblog: true, ), ], ); } else { return const SizedBox.shrink(); } } } class ProfilePic extends StatelessWidget { const ProfilePic({required this.url, Key? key}) : super(key: key); final String url; @override Widget build(BuildContext context) { const double width = 64; if (url.isNotEmpty) { return ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( fit: BoxFit.cover, url, errorBuilder: (context, error, stackTrace) => const Icon( Icons.cruelty_free, size: width, ), height: width, width: width, ), ); } else { return const Icon( Icons.cruelty_free, size: width, ); } } } class PostBody extends StatefulWidget { const PostBody({ required this.sensitive, required this.spoilerText, required this.content, required this.media, required this.forceShow, this.openInBrowser = false, Key? key, required this.model, }) : super(key: key); final String content; final String spoilerText; final bool sensitive; final bool forceShow; final List media; final bool openInBrowser; final tl.PostModel model; @override State createState() => _PostBodyState(); } class _PostBodyState extends State { bool visible = false; String cwButtonText = "show".i18n(); Icon cwButtonIcon = const Icon(Icons.visibility); @override void initState() { visible = !widget.sensitive; super.initState(); } @override Widget build(BuildContext context) { return InkWell( onTap: () => showDialog( context: context, builder: (context) => FullPostView(originPostModel: widget.model), ), child: Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(0, 6, 0, 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Visibility( visible: widget.forceShow ? false : widget.sensitive, child: Column( children: [ SizedBox( width: double.maxFinite, child: SelectableText.rich( TextSpan( children: [ WidgetSpan( child: ElevatedButton.icon( onPressed: () { setState(() { visible = !visible; if (visible) { cwButtonIcon = const Icon(Icons.visibility_off); cwButtonText = "hide".i18n(); } else { cwButtonText = "show".i18n(); cwButtonIcon = const Icon(Icons.visibility); } }); }, icon: cwButtonIcon, label: Text(cwButtonText), ), ), const WidgetSpan( child: SizedBox( width: 8, )), TextSpan(text: widget.spoilerText), ], ), ), ), const SizedBox( height: 8, ) ], ), ), Visibility( visible: visible || widget.forceShow, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ PostTextRenderer(input: widget.content), MediaAttachments(models: widget.media), ], ), ), ], ), ), ); } } class PostActionBar extends StatefulWidget { const PostActionBar({required this.model, Key? key}) : super(key: key); final tl.PostModel model; @override State createState() => _PostActionBarState(); } class _PostActionBarState extends State { @override Widget build(BuildContext context) { return Wrap( alignment: WrapAlignment.spaceAround, children: [ IconButton( onPressed: () { showDialog( context: context, builder: ((context) => MakePost( inReplyTo: widget.model, ))); }, icon: const Icon(Icons.reply), tooltip: "reply".i18n(), ), InteractionButton( model: widget.model, type: interactions.InteractionType.reblog, ), InteractionButton( model: widget.model, type: interactions.InteractionType.favorite, ), IconButton( tooltip: "show-in-full".i18n(), onPressed: () { showDialog( context: context, builder: (context) => FullPostView( originPostModel: widget.model, ), ); }, icon: const Icon(Icons.open_in_full)), IconButton( tooltip: "post-options".i18n(), onPressed: () { popupPostOptions(context, widget.model); }, icon: const Icon(Icons.more_horiz), ), ], ); } }