lemmur-app-android/lib/widgets/user_profile.dart

433 lines
15 KiB
Dart
Raw Normal View History

2020-08-31 21:04:17 +02:00
import 'package:cached_network_image/cached_network_image.dart';
2020-08-31 12:05:45 +02:00
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart';
2021-01-24 20:01:55 +01:00
import 'package:lemmy_api_client/v2.dart';
2020-08-31 12:05:45 +02:00
import 'package:timeago/timeago.dart' as timeago;
2020-09-30 23:53:20 +02:00
import '../hooks/stores.dart';
2021-01-02 01:13:13 +01:00
import '../util/extensions/api.dart';
import '../util/goto.dart';
2020-08-31 15:43:09 +02:00
import '../util/intl.dart';
import '../util/text_color.dart';
2020-09-01 21:59:00 +02:00
import 'badge.dart';
2020-09-30 21:47:14 +02:00
import 'fullscreenable_image.dart';
2020-09-30 23:43:21 +02:00
import 'markdown_text.dart';
import 'sortable_infinite_list.dart';
2020-08-31 15:43:09 +02:00
2020-09-30 19:05:00 +02:00
/// Shared widget of UserPage and ProfileTab
2020-08-31 12:05:45 +02:00
class UserProfile extends HookWidget {
2021-01-24 20:01:55 +01:00
final Future<FullUserView> _userDetails;
final String instanceHost;
2020-08-31 12:05:45 +02:00
UserProfile({@required int userId, @required this.instanceHost})
2021-01-24 20:01:55 +01:00
: _userDetails = LemmyApiV2(instanceHost).run(GetUserDetails(
userId: userId, savedOnly: false, sort: SortType.active));
2020-08-31 12:05:45 +02:00
2021-01-26 22:18:53 +01:00
UserProfile.fromFullUserView(FullUserView fullUserView)
: _userDetails = Future.value(fullUserView),
instanceHost = fullUserView.instanceHost;
2020-09-08 21:08:37 +02:00
2020-08-31 12:05:45 +02:00
@override
Widget build(BuildContext context) {
2020-09-16 23:22:04 +02:00
final theme = Theme.of(context);
2020-09-30 23:43:21 +02:00
final userDetailsSnap = useFuture(_userDetails, preserveState: false);
2020-08-31 12:05:45 +02:00
2020-09-30 23:43:21 +02:00
if (!userDetailsSnap.hasData) {
2020-09-30 21:47:14 +02:00
return const Center(child: CircularProgressIndicator());
2020-10-02 20:07:13 +02:00
} else if (userDetailsSnap.hasError) {
return Center(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
2021-01-03 19:43:39 +01:00
const Icon(Icons.error),
2020-10-02 20:07:13 +02:00
Padding(
padding: const EdgeInsets.all(8),
child: Text('ERROR: ${userDetailsSnap.error}'),
)
]),
);
2020-09-30 21:47:14 +02:00
}
2021-01-24 20:01:55 +01:00
final userView = userDetailsSnap.data.userView;
2020-09-30 21:47:14 +02:00
return DefaultTabController(
length: 3,
child: NestedScrollView(
headerSliverBuilder: (_, __) => [
SliverAppBar(
pinned: true,
2020-10-02 20:07:13 +02:00
expandedHeight: 265,
2020-09-30 21:47:14 +02:00
toolbarHeight: 0,
forceElevated: true,
elevation: 0,
backgroundColor: theme.cardColor,
brightness: theme.brightness,
iconTheme: theme.iconTheme,
flexibleSpace:
FlexibleSpaceBar(background: _UserOverview(userView)),
bottom: TabBar(
labelColor: theme.textTheme.bodyText1.color,
2021-01-03 18:21:56 +01:00
tabs: const [
2020-09-30 21:47:14 +02:00
Tab(text: 'Posts'),
Tab(text: 'Comments'),
Tab(text: 'About'),
],
),
2020-08-31 21:04:17 +02:00
),
2020-09-30 21:47:14 +02:00
],
body: TabBarView(children: [
2020-09-30 23:43:21 +02:00
// TODO: first batch is already fetched on render
// TODO: comment and post come from the same endpoint, could be shared
InfinitePostList(
2021-01-24 20:01:55 +01:00
fetcher: (page, batchSize, sort) => LemmyApiV2(instanceHost)
.run(GetUserDetails(
userId: userView.user.id,
2020-09-30 23:43:21 +02:00
savedOnly: false,
sort: SortType.active,
page: page,
limit: batchSize,
2021-01-24 20:01:55 +01:00
))
2020-09-30 23:43:21 +02:00
.then((val) => val.posts),
),
InfiniteCommentList(
2021-01-24 20:01:55 +01:00
fetcher: (page, batchSize, sort) => LemmyApiV2(instanceHost)
.run(GetUserDetails(
userId: userView.user.id,
2020-09-30 23:43:21 +02:00
savedOnly: false,
sort: SortType.active,
page: page,
limit: batchSize,
2021-01-24 20:01:55 +01:00
))
2020-09-30 23:43:21 +02:00
.then((val) => val.comments),
),
_AboutTab(userDetailsSnap.data),
2020-09-30 21:47:14 +02:00
]),
),
);
}
}
2020-09-30 23:43:21 +02:00
/// Content in the sliver flexible space
2020-10-02 20:07:13 +02:00
/// Renders general info about the given user.
/// Such as his nickname, no. of posts, no. of posts,
/// banner, avatar etc.
2020-09-30 21:47:14 +02:00
class _UserOverview extends HookWidget {
2021-01-24 20:01:55 +01:00
final UserViewSafe userView;
2020-09-30 21:47:14 +02:00
const _UserOverview(this.userView);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorOnTopOfAccentColor =
textColorBasedOnBackground(theme.accentColor);
2020-08-31 21:04:17 +02:00
2020-09-30 21:47:14 +02:00
return Stack(
children: [
2021-01-24 20:01:55 +01:00
if (userView.user.banner != null)
2020-09-30 21:47:14 +02:00
// TODO: for some reason doesnt react to presses
FullscreenableImage(
2021-01-24 20:01:55 +01:00
url: userView.user.banner,
2020-09-30 21:47:14 +02:00
child: CachedNetworkImage(
2021-01-24 20:01:55 +01:00
imageUrl: userView.user.banner,
2021-01-03 19:43:39 +01:00
errorWidget: (_, __, ___) => const SizedBox.shrink(),
2020-08-31 21:04:17 +02:00
),
2020-09-30 21:47:14 +02:00
)
else
Container(
2020-09-30 21:47:14 +02:00
width: double.infinity,
2020-10-01 15:55:22 +02:00
height: 200,
color: theme.accentColor,
2020-09-30 21:47:14 +02:00
),
Container(
height: 200,
2021-01-03 19:43:39 +01:00
decoration: const BoxDecoration(
2020-09-30 21:47:14 +02:00
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.black26,
Colors.transparent,
],
),
),
2020-09-30 21:47:14 +02:00
),
SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 60),
child: SizedBox(
width: double.infinity,
height: double.infinity,
child: Container(
decoration: BoxDecoration(
2021-01-03 19:43:39 +01:00
borderRadius: const BorderRadius.only(
2020-09-30 21:47:14 +02:00
topRight: Radius.circular(40),
topLeft: Radius.circular(40),
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
color: theme.scaffoldBackgroundColor,
2020-08-31 12:05:45 +02:00
),
),
),
),
2020-09-30 21:47:14 +02:00
),
SafeArea(
child: Column(
children: [
2021-01-24 20:01:55 +01:00
if (userView.user.avatar != null)
2020-09-30 21:47:14 +02:00
SizedBox(
width: 80,
height: 80,
child: Container(
// clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
2021-01-03 19:43:39 +01:00
boxShadow: const [
2020-09-30 21:47:14 +02:00
BoxShadow(blurRadius: 6, color: Colors.black54)
],
2021-01-03 19:43:39 +01:00
borderRadius: const BorderRadius.all(Radius.circular(15)),
2020-09-30 21:47:14 +02:00
border: Border.all(color: Colors.white, width: 3),
),
child: ClipRRect(
2021-01-03 19:43:39 +01:00
borderRadius: const BorderRadius.all(Radius.circular(12)),
2020-09-30 21:47:14 +02:00
child: FullscreenableImage(
2021-01-24 20:01:55 +01:00
url: userView.user.avatar,
2020-08-31 21:04:17 +02:00
child: CachedNetworkImage(
2021-01-24 20:01:55 +01:00
imageUrl: userView.user.avatar,
2021-01-03 19:43:39 +01:00
errorWidget: (_, __, ___) => const SizedBox.shrink(),
2020-08-31 21:04:17 +02:00
),
),
2020-08-31 12:05:45 +02:00
),
),
2020-09-30 21:47:14 +02:00
),
Padding(
2021-01-24 20:01:55 +01:00
padding: userView.user.avatar != null
2021-01-03 18:03:59 +01:00
? const EdgeInsets.only(top: 8)
2020-09-30 21:47:14 +02:00
: const EdgeInsets.only(top: 70),
child: Padding(
2021-01-24 20:01:55 +01:00
padding: EdgeInsets.only(
top: userView.user.avatar == null ? 10 : 0),
2020-09-30 21:47:14 +02:00
child: Text(
2021-01-27 21:11:36 +01:00
userView.user.displayName,
2020-09-30 21:47:14 +02:00
style: theme.textTheme.headline6,
2020-08-31 12:05:45 +02:00
),
),
2020-09-30 21:47:14 +02:00
),
Padding(
2021-01-03 18:03:59 +01:00
padding: const EdgeInsets.only(top: 4),
2020-09-30 21:47:14 +02:00
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
2021-01-24 20:01:55 +01:00
'@${userView.user.name}@',
2020-09-30 21:47:14 +02:00
style: theme.textTheme.caption,
),
InkWell(
onTap: () => goToInstance(
context, userView.user.originInstanceHost),
2020-09-30 21:47:14 +02:00
child: Text(
userView.user.originInstanceHost,
style: theme.textTheme.caption,
),
2020-09-30 21:47:14 +02:00
)
],
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
),
Padding(
padding: const EdgeInsets.only(top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Badge(
child: Row(
children: [
Icon(
2021-01-27 20:50:39 +01:00
Icons.article,
2020-09-30 21:47:14 +02:00
size: 15,
color: colorOnTopOfAccentColor,
),
Padding(
2021-01-03 18:03:59 +01:00
padding: const EdgeInsets.only(left: 4),
2020-09-30 21:47:14 +02:00
child: Text(
2021-01-24 20:01:55 +01:00
'${compactNumber(userView.counts.postCount)}'
' Post${pluralS(userView.counts.postCount)}',
2020-09-30 21:47:14 +02:00
style: TextStyle(color: colorOnTopOfAccentColor),
),
),
],
),
),
Padding(
2021-01-03 18:03:59 +01:00
padding: const EdgeInsets.only(left: 16),
2020-09-30 21:47:14 +02:00
child: Badge(
2020-09-01 21:59:00 +02:00
child: Row(
children: [
Icon(
2020-09-30 21:47:14 +02:00
Icons.comment,
2020-09-01 21:59:00 +02:00
size: 15,
color: colorOnTopOfAccentColor,
2020-09-01 21:59:00 +02:00
),
Padding(
2021-01-03 18:03:59 +01:00
padding: const EdgeInsets.only(left: 4),
child: Text(
2021-01-24 20:01:55 +01:00
'${compactNumber(userView.counts.commentCount)}'
2021-01-26 23:53:40 +01:00
''' Comment${pluralS(userView.counts.commentCount)}''',
style:
TextStyle(color: colorOnTopOfAccentColor),
),
2020-09-01 21:59:00 +02:00
),
],
),
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
),
],
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
),
Padding(
padding: const EdgeInsets.only(top: 15),
child: Text(
2021-01-24 20:01:55 +01:00
'Joined ${timeago.format(userView.user.published)}',
2020-09-30 21:47:14 +02:00
style: theme.textTheme.bodyText1,
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
),
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
2021-01-03 19:43:39 +01:00
const Icon(
2020-09-30 21:47:14 +02:00
Icons.cake,
size: 13,
),
Padding(
2021-01-03 18:03:59 +01:00
padding: const EdgeInsets.only(left: 4),
2020-09-30 21:47:14 +02:00
child: Text(
2021-01-24 20:01:55 +01:00
DateFormat('MMM dd, yyyy')
.format(userView.user.published),
2020-09-30 21:47:14 +02:00
style: theme.textTheme.bodyText1,
),
2020-09-30 21:47:14 +02:00
),
],
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
),
// Expanded(child: tabs())
],
2020-08-31 12:05:45 +02:00
),
2020-09-30 21:47:14 +02:00
),
],
2020-08-31 12:05:45 +02:00
);
}
2020-08-31 21:04:17 +02:00
}
2020-09-30 21:47:14 +02:00
class _AboutTab extends HookWidget {
2021-01-24 20:01:55 +01:00
final FullUserView userDetails;
2020-09-30 21:47:14 +02:00
2020-09-30 23:43:21 +02:00
const _AboutTab(this.userDetails);
2020-09-30 21:47:14 +02:00
@override
Widget build(BuildContext context) {
2020-09-30 23:43:21 +02:00
final theme = Theme.of(context);
2021-01-24 20:01:55 +01:00
final instanceHost = userDetails.userView.user.instanceHost;
2020-09-30 23:43:21 +02:00
2020-09-30 23:53:20 +02:00
final accStore = useAccountsStore();
final isOwnedAccount = accStore.loggedInInstances.contains(instanceHost) &&
2021-01-24 20:01:55 +01:00
accStore
.usernamesFor(instanceHost)
.contains(userDetails.userView.user.name);
2020-09-30 23:53:20 +02:00
2020-09-30 23:43:21 +02:00
const wallPadding = EdgeInsets.symmetric(horizontal: 15);
final divider = Padding(
padding: EdgeInsets.symmetric(
horizontal: wallPadding.horizontal / 2, vertical: 10),
2021-01-03 19:43:39 +01:00
child: const Divider(),
2020-09-30 23:43:21 +02:00
);
2020-09-30 21:47:14 +02:00
2020-10-02 20:07:13 +02:00
communityTile(String name, String icon, int id) => ListTile(
dense: true,
onTap: () => goToCommunity.byId(context, instanceHost, id),
2020-10-02 20:07:13 +02:00
title: Text('!$name'),
leading: icon != null
? CachedNetworkImage(
height: 40,
width: 40,
imageUrl: icon,
2021-01-03 19:43:39 +01:00
errorWidget: (_, __, ___) =>
const SizedBox(width: 40, height: 40),
2020-10-02 20:07:13 +02:00
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
fit: BoxFit.cover,
image: imageProvider,
),
),
))
2021-01-03 19:43:39 +01:00
: const SizedBox(width: 40),
2020-10-02 20:07:13 +02:00
);
2020-09-30 23:43:21 +02:00
return ListView(
2021-01-03 19:43:39 +01:00
padding: const EdgeInsets.symmetric(vertical: 20),
2020-09-30 23:43:21 +02:00
children: [
2020-09-30 23:53:20 +02:00
if (isOwnedAccount)
2020-10-02 20:07:13 +02:00
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
2021-01-03 19:43:39 +01:00
children: const [
2020-10-02 20:07:13 +02:00
Icon(Icons.edit),
SizedBox(width: 10),
Text('edit profile'),
],
2020-09-30 23:53:20 +02:00
),
2020-10-02 20:07:13 +02:00
onTap: () {}, // TODO: go to account editing
2020-09-30 23:53:20 +02:00
),
2021-01-24 20:01:55 +01:00
if (userDetails.userView.user.bio != null) ...[
2020-09-30 23:43:21 +02:00
Padding(
padding: wallPadding,
2021-01-24 20:01:55 +01:00
child: MarkdownText(userDetails.userView.user.bio,
instanceHost: instanceHost)),
2020-09-30 23:43:21 +02:00
divider,
],
if (userDetails.moderates.isNotEmpty) ...[
2020-10-02 20:07:13 +02:00
ListTile(
title: Center(
child: Text(
'Moderates:',
style: theme.textTheme.headline6.copyWith(fontSize: 18),
),
2020-09-30 23:43:21 +02:00
),
2020-10-02 20:07:13 +02:00
),
for (final comm
in userDetails.moderates
2021-01-24 20:01:55 +01:00
..sort((a, b) => a.community.name.compareTo(b.community.name)))
2020-10-02 20:07:13 +02:00
communityTile(
2021-01-24 20:01:55 +01:00
comm.community.name, comm.community.icon, comm.community.id),
2020-09-30 23:43:21 +02:00
divider
],
2020-10-02 20:07:13 +02:00
ListTile(
title: Center(
child: Text(
'Subscribed:',
style: theme.textTheme.headline6.copyWith(fontSize: 18),
),
),
2020-09-30 23:43:21 +02:00
),
2020-10-02 20:07:13 +02:00
if (userDetails.follows.isNotEmpty)
for (final comm
in userDetails.follows
2021-01-24 20:01:55 +01:00
..sort((a, b) => a.community.name.compareTo(b.community.name)))
2020-10-02 20:07:13 +02:00
communityTile(
2021-01-24 20:01:55 +01:00
comm.community.name, comm.community.icon, comm.community.id)
2020-09-30 23:43:21 +02:00
else
2021-01-03 19:43:39 +01:00
const Padding(
padding: EdgeInsets.only(top: 8),
2020-09-30 23:43:21 +02:00
child: Center(
child: Text(
'this user does not subscribe to any community',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
)
],
);
2020-09-30 21:47:14 +02:00
}
}