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/makepost.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 '../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, }) : super(key: key); final tl.PostModel model; final bool reblogVisible; final bool hideSensitive; @override State createState() => _PostState(); } class _PostState extends State { @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ DisplayName(account: widget.model.account), PostBody( sensitive: widget.model.sensitive, content: widget.model.content, spoilerText: widget.model.spoilerText, media: widget.model.attachments, forceShow: !widget.hideSensitive, ), PostActionBar(model: widget.model), RebloggedBy( account: widget.model.rebloggedBy, reblogVisible: widget.reblogVisible, ), ], ); } } class DisplayName extends StatelessWidget { const DisplayName({ required this.account, this.isReblog = false, Key? key, }) : super(key: key); final AccountModel account; final bool isReblog; @override Widget build(BuildContext context) { TextStyle? usernameStyle; if (isReblog) { usernameStyle = Theme.of(context).textTheme.bodyMedium; } else { usernameStyle = Theme.of(context).textTheme.displaySmall; } return Row( children: [ ProfilePic(url: account.avatar), const SizedBox( width: 8, ), Flexible( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( account.displayName, style: usernameStyle, ), Text( 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, Key? key, }) : super(key: key); final String content; final String spoilerText; final bool sensitive; final bool forceShow; final List media; @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 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: [ SelectableText.rich( TextSpan( children: [ WidgetSpan( child: OutlinedButton.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 Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Expanded( flex: 20, child: IconButton( onPressed: () { showDialog( context: context, builder: ((context) => MakePost( inReplyTo: widget.model, ))); }, icon: const Icon(Icons.reply), tooltip: "reply".i18n(), ), ), Expanded( flex: 20, child: InteractionButton( model: widget.model, type: interactions.InteractionType.reblog, ), ), Expanded( flex: 20, child: InteractionButton( model: widget.model, type: interactions.InteractionType.favorite, ), ), Expanded( flex: 20, child: IconButton( tooltip: "post-options".i18n(), onPressed: () { popupPostOptions(context, widget.model); }, icon: const Icon(Icons.more_horiz), ), ) ], ); } }