Skip to content

Instantly share code, notes, and snippets.

@lewismorgan
Created July 10, 2020 21:14
Show Gist options
  • Save lewismorgan/78af01df3d7f4c3e8e83c5f735580e98 to your computer and use it in GitHub Desktop.
Save lewismorgan/78af01df3d7f4c3e8e83c5f735580e98 to your computer and use it in GitHub Desktop.
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vytality/api/models/post_model.dart';
import 'package:vytality/presentation/bloc/post/post_bloc.dart';
import 'package:vytality/presentation/widgets/comments/comments.dart';
import 'package:vytality/presentation/widgets/common/user_avatar.dart';
import 'package:vytality/presentation/widgets/feed/user_action.dart';
/// The time it takes to expand a post
const Duration _kExpand = Duration(milliseconds: 350);
class Post extends StatefulWidget {
const Post({
Key key,
}) : super(key: key);
@override
_PostState createState() => _PostState();
}
class _PostState extends State<Post> {
PostBloc _bloc;
@override
void initState() {
super.initState();
_bloc = BlocProvider.of<PostBloc>(context);
}
void _onDetailed(BuildContext context, PostModel post) {
Navigator.of(context).pushNamed('/post', arguments: {"model": post});
}
void _onLike() {
// TODO: Display Reactions popovers
}
void _onComment() {
// TODO: Begin adding a new comment to the post
}
void _onShare(PostModel) {
// TODO: Display native Sharing dialog
// IDEAL: Direct links to the post, media and all
final state = _bloc.state;
if (state is PostItemState) {
final post = state.post;
} else if (state is PostInitial) {}
}
void _onOptions() {
// TODO: Add Post options
}
Widget _buildFromState(BuildContext context, PostItemState state) {
final textTheme = Theme.of(context).textTheme;
if (state.isLoading) {
return CircularProgressIndicator();
}
final post = state.post;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar/Name, Post Timestamp
_buildHeader(context, state),
// User Action Information
if (post.hasAction) ...[
Padding(
padding: const EdgeInsets.only(top: 12),
child: Center(child: UserActionActivity(action: post.action)),
),
] else ...[
SizedBox(height: 23),
],
// Image
if (post.hasMedia) ...[
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildMedia(post.mediaUrl),
),
],
// Post Body
Padding(
padding: const EdgeInsets.fromLTRB(15, 0, 8, 8),
child: _buildBodyText(textTheme, state),
),
// Read More / Collapse
if (state.hasExpandableInfo && state.isCollapsed) ...[
Padding(
padding: const EdgeInsets.only(top: 5, right: 25),
child: Align(
alignment: Alignment.topRight,
child: _buildSeeDetails(context, state),
),
),
],
// Action Bar
Padding(
padding: const EdgeInsets.only(top: 12),
child: _buildButtons(context, state),
),
// Reactions/Comments
if (post.hasReactions || post.hasComments) ...[
Container(
padding: const EdgeInsets.only(top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (post.hasReactions) ...[
_buildReactionsButton(textTheme, post),
] else ...[
Spacer(),
],
if (post.hasComments) ...[
_buildCommentsButton(textTheme, post),
],
],
),
),
if (!state.isCollapsed && state.hasExpandableInfo) ...[
if (post.hasReactions) ...[
Padding(
padding: const EdgeInsets.only(left: 20, top: 10),
child: _buildRecentReactions(post),
),
],
if (post.hasComments) ...[
CommentsView(commentCount: post.commentCount),
],
],
],
],
);
}
Row _buildRecentReactions(PostModel post) {
final textTheme = Theme.of(context).textTheme;
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Recent Reaction Avatars
Row(
children: List.generate(
post.recentReactors.length,
(index) => UserAvatar(
url: post.recentReactors[index].avatarUrl,
name: post.recentReactors[index].name,
),
),
),
// View all n reactions
// TODO: Display Extended Reactions dialog on View all reactions
Text(
'View all ${post.reactionCount} reactions',
style: textTheme.caption,
),
],
);
}
Widget _buildButton(
IconData icon, {
String label,
@required void Function() onTap,
bool flip = false,
PostItemState state,
}) {
var child;
if (flip) {
child = Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(pi),
child: Icon(icon, color: Colors.black, size: 32.0),
);
} else {
child = Icon(icon, color: Colors.black, size: 32.0);
}
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: label != null
? Row(
children: [
child,
SizedBox(width: 5),
Text(label),
],
)
: child,
);
}
Widget _buildButtons(BuildContext context, PostItemState state) {
return Material(
elevation: 0,
color: Colors.white,
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButton(CupertinoIcons.heart, label: 'Like', onTap: _onLike),
_buildButton(CupertinoIcons.conversation_bubble,
label: 'Comment', onTap: _onComment),
_buildButton(CupertinoIcons.reply,
label: 'Share', flip: true, onTap: _onShare),
],
),
),
);
}
Widget _buildBodyText(TextTheme textTheme, PostItemState state) {
String details;
if (state.hasExpandableInfo) {
details = state.visibleDetails;
} else {
details = state.post.details;
}
return AnimatedSwitcher(
duration: _kExpand,
child: Text(
details,
style: textTheme.bodyText2.copyWith(fontSize: 14.0),
),
);
}
Widget _buildSeeDetails(BuildContext context, PostItemState state) {
final textTheme = Theme.of(context).textTheme;
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => _onDetailed(context, state.post),
child: Text(
'See More',
style: textTheme.caption,
),
);
}
Widget _buildCommentsButton(TextTheme text, PostModel post) {
return GestureDetector(
onTap: () => _onDetailed(context, post),
child: Text('${post.commentCount} Comments', style: text.caption),
);
}
Widget _buildReactionsButton(TextTheme text, PostModel post) {
return GestureDetector(
onTap: () => _onDetailed(context, post),
child: Text('${post.reactionCount} Reactions', style: text.caption),
);
}
Widget _buildHeader(BuildContext context, PostItemState state) {
final post = state.post;
return Row(
children: [
UserAvatar(
radius: 25, url: post.author.avatarUrl, name: post.author.name),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post.author.name,
style: Theme.of(context).textTheme.headline6.copyWith(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
Text(
state.displayedTime,
style: Theme.of(context).textTheme.caption,
)
],
),
),
Spacer(),
_buildButton(CupertinoIcons.ellipsis, onTap: _onOptions),
],
);
}
Widget _buildMedia(String url) {
return Align(
alignment: Alignment.center,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
url,
width: 375,
height: 295,
fit: BoxFit.cover,
),
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<PostBloc, PostState>(
builder: (context, state) {
if (state is PostInitial) {
return CircularProgressIndicator();
}
if (state is PostItemState) {
return _buildFromState(context, state);
} else {
return Container();
}
},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment