diff --git a/lib/util/get_note_action.dart b/lib/util/get_note_action.dart index 3a2e7958..2791191e 100644 --- a/lib/util/get_note_action.dart +++ b/lib/util/get_note_action.dart @@ -11,8 +11,7 @@ import '../view/widget/emoji_picker.dart'; import '../view/widget/note_sheet.dart'; import 'future_with_dialog.dart'; -void Function()? getNoteAction( - WidgetRef ref, { +void Function(WidgetRef ref)? getNoteAction({ required Account account, required NoteActionType type, required Note note, @@ -25,10 +24,10 @@ void Function()? getNoteAction( } return switch (type) { NoteActionType.none => null, - NoteActionType.expand => () => ref.context.push( + NoteActionType.expand => (ref) => ref.context.push( '/$account/notes/${appearNote.id}', ), - NoteActionType.menu => () => showNoteSheet( + NoteActionType.menu => (ref) => showNoteSheet( context: ref.context, account: account, noteId: note.id, @@ -37,7 +36,7 @@ void Function()? getNoteAction( ), NoteActionType.reaction => !account.isGuest - ? () async { + ? (ref) async { final emoji = appearNote.reactionAcceptance == ReactionAcceptance.likeOnly ? '❤' diff --git a/lib/view/widget/deleted_note_widget.dart b/lib/view/widget/deleted_note_widget.dart new file mode 100644 index 00000000..048c00c4 --- /dev/null +++ b/lib/view/widget/deleted_note_widget.dart @@ -0,0 +1,42 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../../i18n/strings.g.dart'; + +class DeletedNoteWidget extends StatelessWidget { + const DeletedNoteWidget({ + super.key, + this.borderRadius = const BorderRadius.all(Radius.circular(16.0)), + }); + + final BorderRadiusGeometry? borderRadius; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: borderRadius, + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.center, + colors: [ + Colors.transparent, + Colors.transparent, + Color.fromRGBO(158, 158, 158, 0.1), + Color.fromRGBO(158, 158, 158, 0.1), + ], + stops: [0.0, 0.7, 0.7, 1.0], + tileMode: TileMode.repeated, + transform: GradientRotation(-pi / 4), + ), + ), + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(t.misskey.deletedNote), + ), + ), + ); + } +} diff --git a/lib/view/widget/deleted_renote_widget.dart b/lib/view/widget/deleted_renote_widget.dart new file mode 100644 index 00000000..88e239a0 --- /dev/null +++ b/lib/view/widget/deleted_renote_widget.dart @@ -0,0 +1,138 @@ +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' hide Clip; + +import '../../model/account.dart'; +import '../../model/general_settings.dart'; +import '../../provider/general_settings_notifier_provider.dart'; +import 'channel_color_bar_box.dart'; +import 'deleted_note_widget.dart'; +import 'note_sheet.dart'; +import 'renote_header.dart'; + +class DeletedRenoteWidget extends HookConsumerWidget { + const DeletedRenoteWidget({ + super.key, + required this.account, + required this.note, + this.backgroundColor, + this.borderRadius, + }); + + final Account account; + final Note note; + final Color? backgroundColor; + final BorderRadiusGeometry? borderRadius; + + void Function(BuildContext context)? _getNoteAction(NoteActionType type) { + if (note.id.isEmpty) { + return null; + } + return switch (type) { + NoteActionType.none => null, + NoteActionType.expand => (context) => context.push( + '/$account/notes/${note.id}', + ), + NoteActionType.menu => (context) => showNoteSheet( + context: context, + account: account, + noteId: note.id, + ), + NoteActionType.reaction => null, + }; + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final ( + verticalPadding, + horizontalPadding, + showAvatars, + tapAction, + doubleTapAction, + longPressAction, + noteBackgroundColor, + ) = ref.watch( + generalSettingsNotifierProvider.select( + (settings) => ( + settings.noteVerticalPadding, + settings.noteHorizontalPadding, + settings.showAvatarsInNote, + settings.noteTapAction, + settings.noteDoubleTapAction, + settings.noteLongPressAction, + switch (note.visibility) { + NoteVisibility.public => settings.publicNoteBackgroundColor, + NoteVisibility.home => settings.homeNoteBackgroundColor, + NoteVisibility.followers => settings.followersNoteBackgroundColor, + NoteVisibility.specified => settings.specifiedNoteBackgroundColor, + null => null, + }, + ), + ), + ); + final onTap = useMemoized(() => _getNoteAction(tapAction), [ + account, + tapAction, + note.id, + ]); + final onDoubleTap = useMemoized(() => _getNoteAction(doubleTapAction), [ + account, + doubleTapAction, + note.id, + ]); + final onLongPress = useMemoized(() => _getNoteAction(longPressAction), [ + account, + longPressAction, + note.id, + ]); + final backgroundColor = this.backgroundColor ?? noteBackgroundColor; + final theme = Theme.of(context); + + return Material( + color: backgroundColor ?? theme.colorScheme.surface, + clipBehavior: Clip.hardEdge, + borderRadius: borderRadius, + child: InkWell( + onTap: onTap != null ? () => onTap(context) : null, + onDoubleTap: onDoubleTap != null ? () => onDoubleTap(context) : null, + onLongPress: onLongPress != null ? () => onLongPress(context) : null, + child: Padding( + padding: EdgeInsetsDirectional.only( + start: 4.0, + top: verticalPadding, + end: horizontalPadding, + bottom: verticalPadding, + ), + child: Column( + children: [ + ChannelColorBarBox( + note: note, + child: Padding( + padding: EdgeInsetsDirectional.only( + start: horizontalPadding - 4.0, + ), + child: RenoteHeader( + account: account, + noteId: note.id, + onTap: () => context.push('/$account/notes/${note.id}'), + onLongPress: () => showNoteSheet( + context: context, + account: account, + noteId: note.id, + renote: true, + ), + ), + ), + ), + const SizedBox(height: 4.0), + const DeletedNoteWidget(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/view/widget/muted_note_widget.dart b/lib/view/widget/muted_note_widget.dart index ff724578..2b52f7f4 100644 --- a/lib/view/widget/muted_note_widget.dart +++ b/lib/view/widget/muted_note_widget.dart @@ -25,15 +25,26 @@ class MutedNoteWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final (verticalPadding, horizontalPadding) = ref.watch( + final (verticalPadding, horizontalPadding, noteBackgroundColor) = ref.watch( generalSettingsNotifierProvider.select( - (settings) => - (settings.noteVerticalPadding, settings.noteHorizontalPadding), + (settings) => ( + settings.noteVerticalPadding, + settings.noteHorizontalPadding, + switch (note.visibility) { + NoteVisibility.public => settings.publicNoteBackgroundColor, + NoteVisibility.home => settings.homeNoteBackgroundColor, + NoteVisibility.followers => settings.followersNoteBackgroundColor, + NoteVisibility.specified => settings.specifiedNoteBackgroundColor, + null => null, + }, + ), ), ); + final backgroundColor = this.backgroundColor ?? noteBackgroundColor; + final theme = Theme.of(context); return Material( - color: Theme.of(context).colorScheme.surface, + color: backgroundColor ?? theme.colorScheme.surface, clipBehavior: Clip.hardEdge, borderRadius: borderRadius, child: InkWell( @@ -51,9 +62,7 @@ class MutedNoteWidget extends ConsumerWidget { builder: (context, span) => Text.rich( t.aria.userSaysSomething(name: span), style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurface.withValues(alpha: 0.7), + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), diff --git a/lib/view/widget/note_detailed_widget.dart b/lib/view/widget/note_detailed_widget.dart index 3f2763b6..478d57ab 100644 --- a/lib/view/widget/note_detailed_widget.dart +++ b/lib/view/widget/note_detailed_widget.dart @@ -27,11 +27,14 @@ import 'acct_widget.dart'; import 'bot_badge.dart'; import 'channel_color_bar_box.dart'; import 'cw_button.dart'; +import 'deleted_note_widget.dart'; +import 'deleted_renote_widget.dart'; import 'instance_ticker_widget.dart'; import 'media_list.dart'; import 'mfm.dart'; import 'muted_note_widget.dart'; import 'note_footer.dart'; +import 'note_sheet.dart'; import 'note_simple_widget.dart'; import 'note_sub_widget.dart'; import 'note_visibility_icon.dart'; @@ -63,7 +66,11 @@ class NoteDetailedWidget extends HookConsumerWidget { } final appearNote = ref.watch(appearNoteProvider(account, noteId)); if (appearNote == null) { - return const SizedBox.shrink(); + return DeletedRenoteWidget( + account: account, + note: note, + backgroundColor: Colors.transparent, + ); } final muted = useState( ref.watch(checkWordMuteProvider(account, appearNote.id)) || @@ -76,6 +83,7 @@ class NoteDetailedWidget extends HookConsumerWidget { account: account, note: note, onTap: () => muted.value = false, + backgroundColor: Colors.transparent, ); } @@ -105,13 +113,9 @@ class NoteDetailedWidget extends HookConsumerWidget { ? ref.watch(conversationNotesProvider(account, appearNote.id)) : null; final isRenote = note.isRenote; - final theme = Theme.of(context); - final style = DefaultTextStyle.of(context).style; - - return InkWell( - onTap: tapAction != NoteActionType.expand + final onTap = useMemoized( + () => tapAction != NoteActionType.expand ? getNoteAction( - ref, account: account, type: tapAction, note: note, @@ -119,9 +123,11 @@ class NoteDetailedWidget extends HookConsumerWidget { disableHeader: true, ) : null, - onDoubleTap: doubleTapAction != NoteActionType.expand + [account, tapAction, noteId], + ); + final onDoubleTap = useMemoized( + () => doubleTapAction != NoteActionType.expand ? getNoteAction( - ref, account: account, type: doubleTapAction, note: note, @@ -129,9 +135,11 @@ class NoteDetailedWidget extends HookConsumerWidget { disableHeader: true, ) : null, - onLongPress: longPressAction != NoteActionType.expand + [account, doubleTapAction, noteId], + ); + final onLongPress = useMemoized( + () => longPressAction != NoteActionType.expand ? getNoteAction( - ref, account: account, type: longPressAction, note: note, @@ -139,6 +147,15 @@ class NoteDetailedWidget extends HookConsumerWidget { disableHeader: true, ) : null, + [account, longPressAction, noteId], + ); + final theme = Theme.of(context); + final style = DefaultTextStyle.of(context).style; + + return InkWell( + onTap: onTap != null ? () => onTap(ref) : null, + onDoubleTap: onDoubleTap != null ? () => onDoubleTap(ref) : null, + onLongPress: onLongPress != null ? () => onLongPress(ref) : null, child: Padding( padding: EdgeInsetsDirectional.only( start: 4.0, @@ -160,19 +177,24 @@ class NoteDetailedWidget extends HookConsumerWidget { ), child: Column( children: [ - for (final note in conversation.reversed) ...[ - ChannelColorBarBox( - note: appearNote.reply, - child: Padding( - padding: EdgeInsetsDirectional.only( - start: horizontalPadding - 4.0, - ), - child: NoteSubWidget( - account: account, - noteId: note.id, + if (conversation.isNotEmpty) + for (final note in conversation.reversed) ...[ + ChannelColorBarBox( + note: appearNote.reply, + child: Padding( + padding: EdgeInsetsDirectional.only( + start: horizontalPadding - 4.0, + ), + child: NoteSubWidget( + account: account, + noteId: note.id, + ), ), ), - ), + const SizedBox(height: 8.0), + ] + else ...[ + const DeletedNoteWidget(), const SizedBox(height: 8.0), ], ], @@ -186,7 +208,17 @@ class NoteDetailedWidget extends HookConsumerWidget { padding: EdgeInsetsDirectional.only( start: horizontalPadding - 4.0, ), - child: RenoteHeader(account: account, noteId: noteId), + child: RenoteHeader( + account: account, + noteId: noteId, + onLongPress: () => showNoteSheet( + context: context, + account: account, + noteId: noteId, + renote: true, + disableHeader: true, + ), + ), ), ), ChannelColorBarBox( diff --git a/lib/view/widget/note_sheet.dart b/lib/view/widget/note_sheet.dart index 5cd029a5..52ff3d5e 100644 --- a/lib/view/widget/note_sheet.dart +++ b/lib/view/widget/note_sheet.dart @@ -39,6 +39,7 @@ Future showNoteSheet({ required BuildContext context, required Account account, required String noteId, + bool renote = false, String? clipId, bool disableHeader = false, }) { @@ -47,6 +48,7 @@ Future showNoteSheet({ builder: (context) => NoteSheet( account: account, noteId: noteId, + renote: renote, clipId: clipId, disableHeader: disableHeader, ), @@ -60,12 +62,14 @@ class NoteSheet extends ConsumerWidget { super.key, required this.account, required this.noteId, + this.renote = false, this.clipId, this.disableHeader = false, }); final Account account; final String noteId; + final bool renote; final String? clipId; final bool disableHeader; @@ -73,9 +77,27 @@ class NoteSheet extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final note = ref.watch(noteProvider(account, noteId)); final appearNote = ref.watch(appearNoteProvider(account, noteId)); - if (note == null || appearNote == null) { + if (note == null) { return NoteFallbackWidget(account: account, noteId: noteId); } + if (renote) { + return _RenoteSheet( + account: account, + note: note, + disableHeader: disableHeader, + ); + } + if (appearNote == null) { + if (note.isRenote) { + return _RenoteSheet( + account: account, + note: note, + disableHeader: disableHeader, + ); + } else { + return NoteFallbackWidget(account: account, noteId: noteId); + } + } final serverUrl = ref.watch(serverUrlNotifierProvider(account.host)); final url = serverUrl.replace(pathSegments: ['notes', appearNote.id]); final i = ref.watch(iNotifierProvider(account)).valueOrNull; @@ -612,3 +634,62 @@ class NoteSheet extends ConsumerWidget { ); } } + +class _RenoteSheet extends ConsumerWidget { + const _RenoteSheet({ + required this.account, + required this.note, + required this.disableHeader, + }); + + final Account account; + final Note note; + final bool disableHeader; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final serverUrl = ref.watch(serverUrlNotifierProvider(account.host)); + final url = serverUrl.replace(pathSegments: ['notes', note.id]); + final theme = Theme.of(context); + + return ListView( + shrinkWrap: true, + children: [ + if (!disableHeader) + ListTile( + leading: const Icon(Icons.info_outline), + title: Text(t.misskey.renoteDetails), + onTap: () => context.push('/$account/notes/${note.id}'), + ), + ListTile( + leading: const Icon(Icons.link), + title: Text(t.misskey.copyLinkRenote), + onTap: () => copyToClipboard(context, url.toString()), + ), + if (account.username == note.user.username && note.user.host == null) + ListTile( + leading: const Icon(Icons.delete), + title: Text(t.misskey.unrenote), + onTap: () async { + await futureWithDialog( + context, + ref + .read(misskeyProvider(account)) + .notes + .delete(NotesDeleteRequest(noteId: note.id)) + .then( + (_) => ref + .read(notesNotifierProvider(account).notifier) + .remove(note.id), + ), + ); + if (!context.mounted) return; + context.pop(); + }, + iconColor: theme.colorScheme.error, + textColor: theme.colorScheme.error, + ), + ], + ); + } +} diff --git a/lib/view/widget/note_simple_widget.dart b/lib/view/widget/note_simple_widget.dart index ea6d9358..2d6f623a 100644 --- a/lib/view/widget/note_simple_widget.dart +++ b/lib/view/widget/note_simple_widget.dart @@ -9,6 +9,7 @@ import '../../provider/general_settings_notifier_provider.dart'; import '../../provider/note_provider.dart'; import '../../util/get_note_action.dart'; import 'cw_button.dart'; +import 'deleted_note_widget.dart'; import 'mfm.dart'; import 'note_header.dart'; import 'sub_note_content.dart'; @@ -36,7 +37,7 @@ class NoteSimpleWidget extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final note = this.note ?? ref.watch(noteProvider(account, noteId)); if (note == null) { - return const SizedBox.shrink(); + return DeletedNoteWidget(borderRadius: borderRadius); } final ( tapAction, @@ -58,38 +59,38 @@ class NoteSimpleWidget extends HookConsumerWidget { ), ); final showContent = useState(alwaysExpandCw); - - return InkWell( - onTap: useMemoized( - () => getNoteAction( - ref, - account: account, - type: tapAction, - note: note, - appearNote: note, - ), - [account, tapAction, note.id], + final onTap = useMemoized( + () => getNoteAction( + account: account, + type: tapAction, + note: note, + appearNote: note, ), - onDoubleTap: useMemoized( - () => getNoteAction( - ref, - account: account, - type: doubleTapAction, - note: note, - appearNote: note, - ), - [account, doubleTapAction, note.id], + [account, tapAction, noteId], + ); + final onDoubleTap = useMemoized( + () => getNoteAction( + account: account, + type: doubleTapAction, + note: note, + appearNote: note, ), - onLongPress: useMemoized( - () => getNoteAction( - ref, - account: account, - type: longPressAction, - note: note, - appearNote: note, - ), - [account, longPressAction, note.id], + [account, doubleTapAction, noteId], + ); + final onLongPress = useMemoized( + () => getNoteAction( + account: account, + type: longPressAction, + note: note, + appearNote: note, ), + [account, longPressAction, noteId], + ); + + return InkWell( + onTap: onTap != null ? () => onTap(ref) : null, + onDoubleTap: onDoubleTap != null ? () => onDoubleTap(ref) : null, + onLongPress: onLongPress != null ? () => onLongPress(ref) : null, borderRadius: borderRadius, child: Padding( padding: const EdgeInsets.all(4.0), diff --git a/lib/view/widget/note_sub_widget.dart b/lib/view/widget/note_sub_widget.dart index c57ce150..f8b6ffbf 100644 --- a/lib/view/widget/note_sub_widget.dart +++ b/lib/view/widget/note_sub_widget.dart @@ -12,6 +12,7 @@ import '../../provider/note_provider.dart'; import '../../util/get_note_action.dart'; import 'channel_color_bar_box.dart'; import 'cw_button.dart'; +import 'deleted_note_widget.dart'; import 'mfm.dart'; import 'note_header.dart'; import 'sub_note_content.dart'; @@ -37,7 +38,7 @@ class NoteSubWidget extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final note = ref.watch(noteProvider(account, noteId)); if (note == null) { - return const SizedBox.shrink(); + return const DeletedNoteWidget(); } final ( tapAction, @@ -62,39 +63,39 @@ class NoteSubWidget extends HookConsumerWidget { ? ref.watch(childrenNotesNotifierProvider(account, noteId)) : null; final showContent = useState(alwaysExpandCw); - final style = DefaultTextStyle.of(context).style; - - return InkWell( - onTap: useMemoized( - () => getNoteAction( - ref, - account: account, - type: tapAction, - note: note, - appearNote: note, - ), - [account, tapAction, note.id], + final onTap = useMemoized( + () => getNoteAction( + account: account, + type: tapAction, + note: note, + appearNote: note, ), - onDoubleTap: useMemoized( - () => getNoteAction( - ref, - account: account, - type: doubleTapAction, - note: note, - appearNote: note, - ), - [account, doubleTapAction, note.id], + [account, tapAction, noteId], + ); + final onDoubleTap = useMemoized( + () => getNoteAction( + account: account, + type: doubleTapAction, + note: note, + appearNote: note, ), - onLongPress: useMemoized( - () => getNoteAction( - ref, - account: account, - type: longPressAction, - note: note, - appearNote: note, - ), - [account, longPressAction, note.id], + [account, doubleTapAction, noteId], + ); + final onLongPress = useMemoized( + () => getNoteAction( + account: account, + type: longPressAction, + note: note, + appearNote: note, ), + [account, longPressAction, noteId], + ); + final style = DefaultTextStyle.of(context).style; + + return InkWell( + onTap: onTap != null ? () => onTap(ref) : null, + onDoubleTap: onDoubleTap != null ? () => onDoubleTap(ref) : null, + onLongPress: onLongPress != null ? () => onLongPress(ref) : null, child: Column( children: [ Row( diff --git a/lib/view/widget/note_widget.dart b/lib/view/widget/note_widget.dart index 06ffa471..78ab2227 100644 --- a/lib/view/widget/note_widget.dart +++ b/lib/view/widget/note_widget.dart @@ -22,6 +22,7 @@ import '../../util/extract_url.dart'; import '../../util/get_note_action.dart'; import 'channel_color_bar_box.dart'; import 'cw_button.dart'; +import 'deleted_renote_widget.dart'; import 'hard_muted_note_widget.dart'; import 'instance_ticker_widget.dart'; import 'media_list.dart'; @@ -29,6 +30,7 @@ import 'mfm.dart'; import 'muted_note_widget.dart'; import 'note_footer.dart'; import 'note_header.dart'; +import 'note_sheet.dart'; import 'note_simple_widget.dart'; import 'note_sub_widget.dart'; import 'note_summary.dart'; @@ -73,7 +75,15 @@ class NoteWidget extends HookConsumerWidget { final appearNote = this.note ?? ref.watch(appearNoteProvider(account, noteId)); if (appearNote == null) { - return HardMutedNoteWidget(borderRadius: borderRadius); + return Padding( + padding: margin, + child: DeletedRenoteWidget( + account: account, + note: note, + backgroundColor: this.backgroundColor, + borderRadius: borderRadius, + ), + ); } final hardMuted = ref.watch( checkWordMuteProvider(account, appearNote.id, hardMute: true), @@ -91,7 +101,6 @@ class NoteWidget extends HookConsumerWidget { account: account, note: appearNote, onTap: () => muted.value = false, - backgroundColor: Theme.of(context).colorScheme.surface, borderRadius: borderRadius, ), ); @@ -140,6 +149,36 @@ class NoteWidget extends HookConsumerWidget { (isMyRenote || isMyNote || appearNote.myReaction != null), ); final backgroundColor = this.backgroundColor ?? noteBackgroundColor; + final onTap = useMemoized( + () => getNoteAction( + account: account, + type: tapAction, + note: note, + appearNote: appearNote, + clipId: clipId, + ), + [account, tapAction, noteId, clipId], + ); + final onDoubleTap = useMemoized( + () => getNoteAction( + account: account, + type: doubleTapAction, + note: note, + appearNote: appearNote, + clipId: clipId, + ), + [account, doubleTapAction, noteId, clipId], + ); + final onLongPress = useMemoized( + () => getNoteAction( + account: account, + type: longPressAction, + note: note, + appearNote: appearNote, + clipId: clipId, + ), + [account, longPressAction, noteId, clipId], + ); final style = DefaultTextStyle.of(context).style; return Padding( @@ -149,39 +188,9 @@ class NoteWidget extends HookConsumerWidget { clipBehavior: Clip.hardEdge, borderRadius: borderRadius, child: InkWell( - onTap: useMemoized( - () => getNoteAction( - ref, - account: account, - type: tapAction, - note: note, - appearNote: appearNote, - clipId: clipId, - ), - [account, tapAction, noteId, clipId], - ), - onDoubleTap: useMemoized( - () => getNoteAction( - ref, - account: account, - type: doubleTapAction, - note: note, - appearNote: appearNote, - clipId: clipId, - ), - [account, doubleTapAction, noteId, clipId], - ), - onLongPress: useMemoized( - () => getNoteAction( - ref, - account: account, - type: longPressAction, - note: note, - appearNote: appearNote, - clipId: clipId, - ), - [account, longPressAction, noteId, clipId], - ), + onTap: onTap != null ? () => onTap(ref) : null, + onDoubleTap: onDoubleTap != null ? () => onDoubleTap(ref) : null, + onLongPress: onLongPress != null ? () => onLongPress(ref) : null, child: Padding( padding: EdgeInsetsDirectional.only( start: 4.0, @@ -231,6 +240,12 @@ class NoteWidget extends HookConsumerWidget { account: account, noteId: noteId, onTap: () => context.push('/$account/notes/$noteId'), + onLongPress: () => showNoteSheet( + context: context, + account: account, + noteId: noteId, + renote: true, + ), ), ), ), diff --git a/lib/view/widget/renote_header.dart b/lib/view/widget/renote_header.dart index ed6258b5..caab0229 100644 --- a/lib/view/widget/renote_header.dart +++ b/lib/view/widget/renote_header.dart @@ -22,11 +22,13 @@ class RenoteHeader extends HookConsumerWidget { required this.account, required this.noteId, this.onTap, + this.onLongPress, }); final Account account; final String noteId; final void Function()? onTap; + final void Function()? onLongPress; @override Widget build(BuildContext context, WidgetRef ref) { @@ -48,6 +50,7 @@ class RenoteHeader extends HookConsumerWidget { return InkWell( onTap: onTap, + onLongPress: onLongPress, child: DefaultTextStyle.merge( style: style, child: IconTheme.merge( diff --git a/lib/view/widget/timeline_note.dart b/lib/view/widget/timeline_note.dart index 1db79183..163d81b8 100644 --- a/lib/view/widget/timeline_note.dart +++ b/lib/view/widget/timeline_note.dart @@ -48,7 +48,7 @@ class TimelineNote extends HookConsumerWidget { final notifier = ref.watch( noteSubscriptionNotifierProvider(account).notifier, ); - if (note == null || appearNote == null) { + if (note == null) { return Padding( padding: margin, child: Material( @@ -58,6 +58,15 @@ class TimelineNote extends HookConsumerWidget { ), ); } + if (appearNote == null) { + return NoteWidget( + account: account, + noteId: noteId, + focusPostForm: focusPostForm, + margin: margin, + borderRadius: borderRadius, + ); + } if ((tabSettings.withFiles && appearNote.fileIds.isEmpty) || hide) { return HardMutedNoteWidget(borderRadius: borderRadius); } diff --git a/test/view/widget/note_widget_test.dart b/test/view/widget/note_widget_test.dart index e64bed76..32308d55 100644 --- a/test/view/widget/note_widget_test.dart +++ b/test/view/widget/note_widget_test.dart @@ -88,7 +88,7 @@ Future setupWidget( } void main() { - group('mute', () { + group('deleted', () { testWidgets('should not show a note if not stored', (tester) async { const account = Account(host: 'misskey.tld'); await setupWidget(tester, account: account, noteId: 'test'); @@ -96,11 +96,27 @@ void main() { expect(find.byType(Text), findsNothing); }); - testWidgets('should not show a note if the renote target is not stored', ( + testWidgets( + 'should show a placeholder if the renote target is not stored', + (tester) async { + const account = Account(host: 'misskey.tld'); + final note = dummyNote.copyWith(id: 'test', renoteId: 'renote'); + final container = await setupWidget( + tester, + account: account, + noteId: note.id, + ); + container.read(notesNotifierProvider(account).notifier).add(note); + await tester.pumpAndSettle(); + expect(find.text(t.misskey.deletedNote), findsOne); + }, + ); + + testWidgets('should show a placeholder if the reply target is not stored', ( tester, ) async { const account = Account(host: 'misskey.tld'); - final note = dummyNote.copyWith(id: 'test', renoteId: 'renote'); + final note = dummyNote.copyWith(id: 'test', replyId: 'reply'); final container = await setupWidget( tester, account: account, @@ -108,9 +124,11 @@ void main() { ); container.read(notesNotifierProvider(account).notifier).add(note); await tester.pumpAndSettle(); - expect(find.byType(Text), findsNothing); + expect(find.text(t.misskey.deletedNote), findsOne); }); + }); + group('mute', () { testWidgets('should not show a hard muted note', (tester) async { const account = Account(host: 'misskey.tld', username: 'testuser'); final note = dummyNote.copyWith(