Skip to content

Commit

Permalink
feat: show all reactions in ReactionUsersSheet (#446)
Browse files Browse the repository at this point in the history
* feat: show all reactions in ReactionUsersSheet

* test: add test for NoteFooter

* refactor: split buttons in NoteFooter into widgets

* feat: show reacted users on long press add or remove reaction button
  • Loading branch information
poppingmoon authored Oct 25, 2024
1 parent 8fbfe5e commit 15e19dc
Show file tree
Hide file tree
Showing 7 changed files with 1,920 additions and 514 deletions.
1,017 changes: 586 additions & 431 deletions lib/view/widget/note_footer.dart

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions lib/view/widget/reaction_button.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:misskey_dart/misskey_dart.dart';
import 'package:misskey_dart/misskey_dart.dart' hide Clip;

import '../../extension/text_style_extension.dart';
import '../../i18n/strings.g.dart';
Expand Down Expand Up @@ -136,8 +136,9 @@ class ReactionButton extends ConsumerWidget {
builder: (context) => ReactionUsersSheet(
account: account,
noteId: note.id,
reaction: emoji,
initialReaction: emoji,
),
clipBehavior: Clip.antiAlias,
isScrollControlled: true,
)
: null,
Expand Down
192 changes: 143 additions & 49 deletions lib/view/widget/reaction_users_sheet.dart
Original file line number Diff line number Diff line change
@@ -1,98 +1,192 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:misskey_dart/misskey_dart.dart';

import '../../extension/text_style_extension.dart';
import '../../i18n/strings.g.dart';
import '../../model/account.dart';
import '../../provider/api/reactions_notifier_provider.dart';
import '../../provider/misskey_colors_provider.dart';
import '../../provider/note_provider.dart';
import 'emoji_sheet.dart';
import 'emoji_widget.dart';
import 'paginated_list_view.dart';
import 'user_preview.dart';
import 'user_sheet.dart';

class ReactionUsersSheet extends ConsumerWidget {
class ReactionUsersSheet extends HookConsumerWidget {
const ReactionUsersSheet({
required this.account,
required this.noteId,
required this.reaction,
this.initialReaction,
});

final Account account;
final String noteId;
final String reaction;
final String? initialReaction;

@override
Widget build(BuildContext context, WidgetRef ref) {
final note = ref.watch(noteProvider(account, noteId));
final reactions =
ref.watch(reactionsNotifierProvider(account, noteId, reaction));
final likeOnly = ref.watch(
noteProvider(account, noteId).select(
(note) => note?.reactionAcceptance == ReactionAcceptance.likeOnly,
),
);
final emojis = ref.watch(
noteProvider(account, noteId)
.select((note) => {...?note?.emojis, ...?note?.reactionEmojis}),
);
final reactions = ref.watch(
noteProvider(account, noteId).select(
(note) => note?.reactions.entries
.sortedBy<num>((e) => -e.value)
.map((e) => e.key),
),
);
final reaction = useState(initialReaction);
final colors =
ref.watch(misskeyColorsProvider(Theme.of(context).brightness));
final style = DefaultTextStyle.of(context).style;

return DraggableScrollableSheet(
minChildSize: 0.5,
maxChildSize: 0.8,
expand: false,
builder: (context, scrollController) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
const SizedBox(height: 12.0),
if (!likeOnly) ...[
SizedBox(
height: style.lineHeight * 1.8,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
EmojiWidget(
account: account,
emoji: reaction,
style: DefaultTextStyle.of(context)
.style
.apply(fontSizeFactor: 1.5),
emojis: {...?note?.emojis, ...?note?.reactionEmojis},
onTap: () => showModalBottomSheet<void>(
context: context,
builder: (context) => EmojiSheet(
account: account,
emoji: reaction,
targetNote: note,
const SizedBox(width: 12.0),
...?reactions?.map(
(emoji) => Padding(
padding: const EdgeInsets.all(2.0),
child: ElevatedButton(
onPressed: () => reaction.value = emoji,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 4.0,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: emoji == reaction.value
? colors.accent.withOpacity(0.5)
: colors.buttonBg,
elevation: 0.0,
),
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: style.lineHeight),
child: EmojiWidget(
style: style,
account: account,
emoji: emoji,
emojis: emojis,
),
),
),
),
),
if (reaction.startsWith(':')) Text(reaction),
const SizedBox(width: 12.0),
],
),
),
),
Expanded(
child: PaginatedListView(
controller: scrollController,
paginationState: reactions,
itemBuilder: (context, reaction) => UserPreview(
const Divider(),
],
if (reaction.value case final reaction?) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: EmojiWidget(
account: account,
user: reaction.user,
avatarSize: 50.0,
onTap: () =>
context.push('/$account/users/${reaction.user.id}'),
onLongPress: () => showUserSheet(
emoji: reaction,
style: style.apply(fontSizeFactor: 1.5),
emojis: emojis,
onTap: () => showModalBottomSheet<void>(
context: context,
account: account,
userId: reaction.user.id,
builder: (context) => EmojiSheet(
account: account,
emoji: reaction,
targetNote: ref.read(noteProvider(account, noteId)),
),
),
),
onRefresh: () => ref.refresh(
reactionsNotifierProvider(account, noteId, reaction).future,
),
if (reaction.startsWith(':')) ...[
const SizedBox(height: 2.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(reaction.replaceFirst('@.', '')),
),
],
const SizedBox(height: 4.0),
Expanded(
child: _ReactionUsers(
account: account,
noteId: noteId,
reaction: reaction,
controller: scrollController,
),
loadMore: (skipError) => ref
.read(
reactionsNotifierProvider(account, noteId, reaction)
.notifier,
)
.loadMore(skipError: skipError),
noItemsLabel: t.misskey.noUsers,
),
),
],
],
),
);
}
}

class _ReactionUsers extends ConsumerWidget {
const _ReactionUsers({
required this.account,
required this.noteId,
required this.reaction,
this.controller,
});

final Account account;
final String noteId;
final String reaction;
final ScrollController? controller;

@override
Widget build(BuildContext context, WidgetRef ref) {
final reactions =
ref.watch(reactionsNotifierProvider(account, noteId, reaction));

return PaginatedListView(
controller: controller,
paginationState: reactions,
itemBuilder: (context, reaction) => UserPreview(
account: account,
user: reaction.user,
avatarSize: 50.0,
onTap: () => context.push('/$account/users/${reaction.user.id}'),
onLongPress: () => showUserSheet(
context: context,
account: account,
userId: reaction.user.id,
),
),
onRefresh: () => ref.refresh(
reactionsNotifierProvider(account, noteId, reaction).future,
),
loadMore: (skipError) => ref
.read(
reactionsNotifierProvider(account, noteId, reaction).notifier,
)
.loadMore(skipError: skipError),
noItemsLabel: t.misskey.noUsers,
);
}
}
Loading

0 comments on commit 15e19dc

Please sign in to comment.