From 5220d071be2006f172ed5b88c99c9631786c9e85 Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:43:30 +0900 Subject: [PATCH 1/5] fix: adjust padding of keyboard buttons --- lib/view/widget/mfm_keyboard.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/view/widget/mfm_keyboard.dart b/lib/view/widget/mfm_keyboard.dart index 7400d062e..37f6baf59 100644 --- a/lib/view/widget/mfm_keyboard.dart +++ b/lib/view/widget/mfm_keyboard.dart @@ -179,12 +179,6 @@ class MfmKeyboard extends HookConsumerWidget { ...emojis.map( (emoji) => TextButton( key: ValueKey(emoji), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 8.0, - ), - ), onPressed: () => controller.replace(query.length + 1, emoji), child: EmojiWidget(account: account, emoji: emoji), @@ -479,7 +473,7 @@ class MfmKeyboard extends HookConsumerWidget { child: TextButtonTheme( data: TextButtonThemeData( style: TextButton.styleFrom( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), minimumSize: Size.zero, foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer, ), From 7eb706963aadee078ab9dd6c3d758b5f15a6fdee Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:46:49 +0900 Subject: [PATCH 2/5] fix: show post form with stack --- lib/view/page/timelines_page.dart | 94 +++++++++++++++++++------------ 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/lib/view/page/timelines_page.dart b/lib/view/page/timelines_page.dart index 72ded1c3d..aa810952a 100644 --- a/lib/view/page/timelines_page.dart +++ b/lib/view/page/timelines_page.dart @@ -179,21 +179,59 @@ class TimelinesPage extends HookConsumerWidget { ], ), ) - : TabBarView( - controller: controller, - physics: enableHorizontalSwipe - ? null - : const NeverScrollableScrollPhysics(), - children: List.generate( - numTabs, - (index) => TimelineWidget( - tabIndex: index, - focusPostForm: () { - showPostForm.value = true; - postFormFocusNode.requestFocus(); - }, + : Stack( + alignment: Alignment.bottomCenter, + children: [ + TabBarView( + controller: controller, + physics: enableHorizontalSwipe + ? null + : const NeverScrollableScrollPhysics(), + children: List.generate( + numTabs, + (index) => TimelineWidget( + tabIndex: index, + focusPostForm: () { + showPostForm.value = true; + postFormFocusNode.requestFocus(); + }, + ), + ), ), - ), + if (tabSettings != null && showPostForm.value) + Material( + clipBehavior: Clip.hardEdge, + color: colors.panel.withOpacity(0.5), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 16.0, + sigmaY: 16.0, + ), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: colors.divider.withOpacity(0.1), + width: 2.0, + ), + ), + ), + child: SingleChildScrollView( + child: PostForm( + account: tabSettings.account, + focusNode: postFormFocusNode, + onHide: () => showPostForm.value = false, + onExpand: (account) => + context.push('/$account/post'), + showPostButton: true, + showKeyboard: true, + maxLines: 6, + ), + ), + ), + ), + ), + ], ), ), drawer: TimelineDrawer(controller: controller), @@ -206,24 +244,8 @@ class TimelinesPage extends HookConsumerWidget { child: TimelineTabBar(controller: controller), ) : null, - floatingActionButton: tabSettings != null && showPostForm.value - ? Material( - clipBehavior: Clip.hardEdge, - color: colors.panel.withOpacity(0.5), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 16.0, sigmaY: 16.0), - child: PostForm( - account: tabSettings.account, - focusNode: postFormFocusNode, - onHide: () => showPostForm.value = false, - onExpand: (account) => context.push('/$account/post'), - showPostButton: true, - showKeyboard: true, - maxLines: 6, - ), - ), - ) - : Row( + floatingActionButton: tabSettings == null || !showPostForm.value + ? Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (!isLargeScreen) @@ -355,10 +377,10 @@ class TimelinesPage extends HookConsumerWidget { ), ), ], - ), - floatingActionButtonLocation: showPostForm.value - ? FloatingActionButtonLocation.centerDocked - : FloatingActionButtonLocation.centerFloat, + ) + : null, + floatingActionButtonLocation: + FloatingActionButtonLocation.centerFloat, ), ), ], From e61e8876bb7ca746b05472028b94727239c8de4c Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:49:03 +0900 Subject: [PATCH 3/5] fix: show small thumbnail of attaches --- lib/view/page/timelines_page.dart | 1 + lib/view/widget/post_form.dart | 1 + lib/view/widget/post_form_attaches.dart | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/view/page/timelines_page.dart b/lib/view/page/timelines_page.dart index aa810952a..93e0e0122 100644 --- a/lib/view/page/timelines_page.dart +++ b/lib/view/page/timelines_page.dart @@ -226,6 +226,7 @@ class TimelinesPage extends HookConsumerWidget { showPostButton: true, showKeyboard: true, maxLines: 6, + thumbnailSize: 100.0, ), ), ), diff --git a/lib/view/widget/post_form.dart b/lib/view/widget/post_form.dart index 2e5cd9308..07dc5413e 100644 --- a/lib/view/widget/post_form.dart +++ b/lib/view/widget/post_form.dart @@ -953,6 +953,7 @@ class PostForm extends HookConsumerWidget { child: PostFormAttaches( account: account.value, noteId: noteId, + maxCrossAxisExtent: thumbnailSize, ), ), ], diff --git a/lib/view/widget/post_form_attaches.dart b/lib/view/widget/post_form_attaches.dart index 4f1b091ed..72baffc74 100644 --- a/lib/view/widget/post_form_attaches.dart +++ b/lib/view/widget/post_form_attaches.dart @@ -29,11 +29,13 @@ class PostFormAttaches extends ConsumerWidget { required this.account, this.noteId, this.gallery = false, + this.maxCrossAxisExtent = 200.0, }); final Account account; final String? noteId; final bool gallery; + final double maxCrossAxisExtent; Future _editFile(WidgetRef ref, int index) async { final file = ref.read( @@ -145,7 +147,7 @@ class PostFormAttaches extends ConsumerWidget { return ReorderableGridView.extent( shrinkWrap: true, - maxCrossAxisExtent: 200, + maxCrossAxisExtent: maxCrossAxisExtent, physics: const NeverScrollableScrollPhysics(), onReorder: (oldIndex, newIndex) => ref .read( From 19ff67cc65d34c93e185e86686d87535762138ed Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:49:58 +0900 Subject: [PATCH 4/5] fix: remove padding around post form buttons --- lib/view/widget/post_form.dart | 1145 ++++++++++++++++---------------- 1 file changed, 582 insertions(+), 563 deletions(-) diff --git a/lib/view/widget/post_form.dart b/lib/view/widget/post_form.dart index 07dc5413e..a3897fec3 100644 --- a/lib/view/widget/post_form.dart +++ b/lib/view/widget/post_form.dart @@ -63,6 +63,7 @@ class PostForm extends HookConsumerWidget { this.showPostButton = false, this.showKeyboard = false, this.maxLines, + this.thumbnailSize = 200.0, }); final Account account; @@ -77,6 +78,7 @@ class PostForm extends HookConsumerWidget { final bool showPostButton; final bool showKeyboard; final int? maxLines; + final double thumbnailSize; Future _post( WidgetRef ref, @@ -340,653 +342,670 @@ class PostForm extends HookConsumerWidget { } }), }, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - IconButton( - tooltip: t.misskey.switchAccount, - onPressed: noteId == null - ? () async { - final destination = - await _switchAccount(ref, account.value); - if (destination != null) { - account.value = destination; - onAccountChanged?.call(destination); - } - } - : null, - icon: i != null - ? UserAvatar( - account: account.value, - user: i, - size: 32.0, - ) - : const Icon(Icons.person), - ), - const Spacer(), - IconButton( - onPressed: canChangeVisibility - ? () async { - final candidates = NoteVisibility.values.where( - (visibility) => - (visibility != NoteVisibility.public || - i == null || - !i.isSilenced) && - (visibility.priority >= - NoteVisibility.min( - reply?.visibility ?? - NoteVisibility.public, - renote?.visibility ?? - NoteVisibility.public, - ).priority), - ); - final result = - await showModalBottomSheet( - context: context, - builder: (context) => NoteVisibilitySheet( - visibilities: candidates, - ), - ); - if (result != null) { - ref - .read( - postNotifierProvider(account.value).notifier, - ) - .setVisibility(result); - } - } - : null, - icon: NoteVisibilityIcon(visibility: request.visibility), - ), - IconButton( - tooltip: request.localOnly ?? false - ? t.misskey.visibility_.disableFederation - : null, - onPressed: canChangeLocalOnly - ? () => ref - .read(postNotifierProvider(account.value).notifier) - .setLocalOnly(!(request.localOnly ?? false)) - : null, - color: request.localOnly ?? false - ? Theme.of(context).colorScheme.error - : null, - disabledColor: request.localOnly ?? false - ? Theme.of(context).colorScheme.error.withOpacity(0.5) - : null, - icon: request.localOnly ?? false - ? const Icon(Icons.rocket_outlined) - : const Icon(Icons.rocket), - ), - IconButton( - onPressed: noteId == null - ? () async { - final result = await showModalBottomSheet< - (ReactionAcceptance?,)>( - context: context, - builder: (context) => ListView( - shrinkWrap: true, - children: [ - ListTile( - title: Text( - t.misskey.reactionAcceptance, - ), - ), - const Divider(height: 0.0), - ...[ - null, - ...ReactionAcceptance.values, - ].map( - (acceptance) => ListTile( - leading: ReactionAcceptanceIcon( - acceptance: acceptance, - ), - title: ReactionAcceptanceWidget( - acceptance: acceptance, - ), - onTap: () => context.pop((acceptance,)), - ), - ), - ], - ), - ); - if (result != null) { - ref - .read( - postNotifierProvider(account.value).notifier, - ) - .setReactionAcceptance(result.$1); - } - } - : null, - icon: ReactionAcceptanceIcon( - acceptance: request.reactionAcceptance, - ), - ), - if (showPostButton) - ElevatedButton( - style: ElevatedButton.styleFrom( - padding: EdgeInsets.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - foregroundColor: colors.fgOnAccent, - disabledForegroundColor: - colors.fgOnAccent.withOpacity(0.5), - backgroundColor: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - ), - onPressed: canPost ? () => _post(ref, account.value) : null, - child: Ink( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - colors.buttonGradateA - .withOpacity(canPost ? 1.0 : 0.5), - colors.buttonGradateB - .withOpacity(canPost ? 1.0 : 0.5), - ], - ), - borderRadius: BorderRadius.circular(8.0), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 16.0, - ), - child: Row( - children: [ - Text(buttonText), - const SizedBox(width: 4.0), - Icon(buttonIcon), - ], - ), - ), - ), - ), - ], - ), + child: IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - if (request case NotesCreateRequest(:final replyId?)) - InkWell( - onTap: () => context.push('/${account.value}/notes/$replyId'), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), child: Row( children: [ IconButton( + tooltip: t.misskey.switchAccount, onPressed: noteId == null - ? () => ref - .read(postNotifierProvider(account.value).notifier) - .setReply(null) + ? () async { + final destination = + await _switchAccount(ref, account.value); + if (destination != null) { + account.value = destination; + onAccountChanged?.call(destination); + } + } : null, - icon: const Icon(Icons.close), - ), - const Icon(Icons.reply), - const SizedBox(width: 4.0), - Text('${t.misskey.reply}: '), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4.0, - ), - child: reply != null + icon: i != null ? UserAvatar( account: account.value, - user: reply.user, - onTap: () => context.push( - '/$account/users/${reply.userId}', - ), + user: i, + size: 32.0, ) - : null, + : const Icon(Icons.person), ), - Expanded( - child: NoteSummary( - account: account.value, - noteId: replyId, - ), + const Spacer(), + IconButton( + onPressed: canChangeVisibility + ? () async { + final candidates = NoteVisibility.values.where( + (visibility) => + (visibility != NoteVisibility.public || + i == null || + !i.isSilenced) && + (visibility.priority >= + NoteVisibility.min( + reply?.visibility ?? + NoteVisibility.public, + renote?.visibility ?? + NoteVisibility.public, + ).priority), + ); + final result = + await showModalBottomSheet( + context: context, + builder: (context) => NoteVisibilitySheet( + visibilities: candidates, + ), + ); + if (result != null) { + ref + .read( + postNotifierProvider(account.value) + .notifier, + ) + .setVisibility(result); + } + } + : null, + icon: NoteVisibilityIcon(visibility: request.visibility), ), - ], - ), - ), - if (request case NotesCreateRequest(:final renoteId?)) - InkWell( - onTap: () => context.push('/${account.value}/notes/$renoteId'), - child: Row( - children: [ IconButton( - onPressed: noteId == null + tooltip: request.localOnly ?? false + ? t.misskey.visibility_.disableFederation + : null, + onPressed: canChangeLocalOnly ? () => ref .read(postNotifierProvider(account.value).notifier) - .setRenote(null) + .setLocalOnly(!(request.localOnly ?? false)) : null, - icon: const Icon(Icons.close), - ), - const Icon(Icons.repeat_rounded), - const SizedBox(width: 4.0), - Text('${t.misskey.renote}: '), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: renote != null - ? UserAvatar( - account: account.value, - user: renote.user, - onTap: () => context.push( - '/$account/users/${renote.userId}', - ), - ) + color: request.localOnly ?? false + ? Theme.of(context).colorScheme.error : null, + disabledColor: request.localOnly ?? false + ? Theme.of(context).colorScheme.error.withOpacity(0.5) + : null, + icon: request.localOnly ?? false + ? const Icon(Icons.rocket_outlined) + : const Icon(Icons.rocket), ), - Expanded( - child: NoteSummary( - account: account.value, - noteId: renoteId, + IconButton( + onPressed: noteId == null + ? () async { + final result = await showModalBottomSheet< + (ReactionAcceptance?,)>( + context: context, + builder: (context) => ListView( + shrinkWrap: true, + children: [ + ListTile( + title: Text( + t.misskey.reactionAcceptance, + ), + ), + const Divider(height: 0.0), + ...[ + null, + ...ReactionAcceptance.values, + ].map( + (acceptance) => ListTile( + leading: ReactionAcceptanceIcon( + acceptance: acceptance, + ), + title: ReactionAcceptanceWidget( + acceptance: acceptance, + ), + onTap: () => context.pop((acceptance,)), + ), + ), + ], + ), + ); + if (result != null) { + ref + .read( + postNotifierProvider(account.value) + .notifier, + ) + .setReactionAcceptance(result.$1); + } + } + : null, + icon: ReactionAcceptanceIcon( + acceptance: request.reactionAcceptance, ), ), + if (showPostButton) ...[ + const SizedBox(width: 4.0), + ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + foregroundColor: colors.fgOnAccent, + disabledForegroundColor: + colors.fgOnAccent.withOpacity(0.5), + backgroundColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + onPressed: + canPost ? () => _post(ref, account.value) : null, + child: Ink( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + colors.buttonGradateA + .withOpacity(canPost ? 1.0 : 0.5), + colors.buttonGradateB + .withOpacity(canPost ? 1.0 : 0.5), + ], + ), + borderRadius: BorderRadius.circular(8.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 16.0, + ), + child: Row( + children: [ + Text(buttonText), + const SizedBox(width: 4.0), + Icon(buttonIcon), + ], + ), + ), + ), + ), + ], ], ), ), - if (request case NotesCreateRequest(:final channelId?)) - InkWell( - onTap: () => - context.push('/${account.value}/channels/$channelId'), - child: DecoratedBox( - decoration: BoxDecoration( - border: Border( - left: BorderSide( - color: channel?.toColor() ?? Colors.transparent, - width: 4, - ), - ), - ), + if (request case NotesCreateRequest(:final replyId?)) + InkWell( + onTap: () => context.push('/${account.value}/notes/$replyId'), child: Row( children: [ IconButton( - onPressed: canChangeChannel + onPressed: noteId == null ? () => ref .read( postNotifierProvider(account.value).notifier, ) - .setChannel(null) + .setReply(null) : null, icon: const Icon(Icons.close), ), - const Icon(Icons.tv), + const Icon(Icons.reply), const SizedBox(width: 4.0), - Text('${t.misskey.channel}: '), - if (channel != null) Expanded(child: Text(channel.name)), + Text('${t.misskey.reply}: '), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, + ), + child: reply != null + ? UserAvatar( + account: account.value, + user: reply.user, + onTap: () => context.push( + '/$account/users/${reply.userId}', + ), + ) + : null, + ), + Expanded( + child: NoteSummary( + account: account.value, + noteId: replyId, + ), + ), ], ), ), - ), - if (request.visibility == NoteVisibility.specified) ...[ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Wrap( - spacing: 4.0, - runSpacing: 4.0, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text(t.misskey.recipient), - ...visibleUsers.value.map( - (user) => MentionWidget( - account: account.value, - username: user.username, - host: user.host ?? account.value.host, - onDeleted: noteId == null - ? () { - ref - .read( - postNotifierProvider(account.value) - .notifier, - ) - .removeVisibleUser(user.id); - visibleUsers.value = visibleUsers.value - .where((e) => e.id != user.id) - .toList(); - } - : null, - ), - ), - if (noteId == null) + if (request case NotesCreateRequest(:final renoteId?)) + InkWell( + onTap: () => context.push('/${account.value}/notes/$renoteId'), + child: Row( + children: [ IconButton( - onPressed: () async { - final user = await selectUser(context, account.value); - if (user != null && - !(request.visibleUserIds?.contains(user.id) ?? - false)) { - visibleUsers.value = [...visibleUsers.value, user]; - ref + onPressed: noteId == null + ? () => ref .read( postNotifierProvider(account.value).notifier, ) - .addVisibleUser(user); - } - }, - icon: const Icon(Icons.add), + .setRenote(null) + : null, + icon: const Icon(Icons.close), ), - ], + const Icon(Icons.repeat_rounded), + const SizedBox(width: 4.0), + Text('${t.misskey.renote}: '), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: renote != null + ? UserAvatar( + account: account.value, + user: renote.user, + onTap: () => context.push( + '/$account/users/${renote.userId}', + ), + ) + : null, + ), + Expanded( + child: NoteSummary( + account: account.value, + noteId: renoteId, + ), + ), + ], + ), ), - ), - if (notSpecifiedMentions.isNotEmpty) - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), + if (request case NotesCreateRequest(:final channelId?)) + InkWell( + onTap: () => + context.push('/${account.value}/channels/$channelId'), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: channel?.toColor() ?? Colors.transparent, + width: 4, + ), + ), + ), child: Row( children: [ - Expanded( - child: Text(t.misskey.notSpecifiedMentionWarning), + IconButton( + onPressed: canChangeChannel + ? () => ref + .read( + postNotifierProvider(account.value).notifier, + ) + .setChannel(null) + : null, + icon: const Icon(Icons.close), ), - if (noteId == null) - TextButton( - onPressed: () async { - final users = await futureWithDialog( - context, - Future.wait( - notSpecifiedMentions.map( - (node) => ref.read( - userNotifierProvider( - account.value, - username: node.username, - host: node.host, - ).future, - ), - ), - ), - ); - if (users != null) { - visibleUsers.value = [ - ...visibleUsers.value, - ...users, - ]; - ref - .read( - postNotifierProvider(account.value) - .notifier, - ) - .addVisibleUsers(users); - } - }, - child: Text(t.misskey.add), - ), + const Icon(Icons.tv), + const SizedBox(width: 4.0), + Text('${t.misskey.channel}: '), + if (channel != null) Expanded(child: Text(channel.name)), ], ), ), ), - ], - if (hasMentionToRemote && (request.localOnly ?? false)) - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( + if (request.visibility == NoteVisibility.specified) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Wrap( + spacing: 4.0, + runSpacing: 4.0, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, children: [ - Expanded( - child: Text(t.aria.mentionToRemoteWarning), + Text(t.misskey.recipient), + ...visibleUsers.value.map( + (user) => MentionWidget( + account: account.value, + username: user.username, + host: user.host ?? account.value.host, + onDeleted: noteId == null + ? () { + ref + .read( + postNotifierProvider(account.value) + .notifier, + ) + .removeVisibleUser(user.id); + visibleUsers.value = visibleUsers.value + .where((e) => e.id != user.id) + .toList(); + } + : null, + ), ), - if (request.channelId == null && - !(reply?.localOnly ?? false) && - !(renote?.localOnly ?? false)) - TextButton( - onPressed: noteId == null - ? () => ref + if (noteId == null) + IconButton( + onPressed: () async { + final user = await selectUser(context, account.value); + if (user != null && + !(request.visibleUserIds?.contains(user.id) ?? + false)) { + visibleUsers.value = [...visibleUsers.value, user]; + ref .read( postNotifierProvider(account.value).notifier, ) - .setLocalOnly(false) - : null, - child: Text(t.aria.enableFederation), + .addVisibleUser(user); + } + }, + icon: const Icon(Icons.add), ), ], ), ), - ), - if (useCw.value) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Shortcuts( - shortcuts: disablingTextShortcuts, - child: TextField( - controller: cwController, - focusNode: cwFocusNode, - decoration: InputDecoration( - hintText: t.misskey.annotation, - ), - autofocus: true, - textInputAction: TextInputAction.next, - maxLength: (request.cw?.length ?? 0) > 80 ? 100 : null, - maxLengthEnforcement: MaxLengthEnforcement.none, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: TextField( - controller: controller, - focusNode: focusNode, - decoration: InputDecoration(hintText: placeholder), - autofocus: true, - minLines: 1, - maxLines: maxLines, - maxLength: (request.text?.length ?? 0) > 2900 ? 3000 : null, - maxLengthEnforcement: MaxLengthEnforcement.none, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, + if (notSpecifiedMentions.isNotEmpty) + Card( + child: Padding( + padding: const EdgeInsets.all(8.0), child: Row( children: [ - IconButton( - tooltip: t.misskey.attachFile, - onPressed: () async { - final files = - await showModalBottomSheet>( - context: context, - builder: (context) => FilePickerSheet( - account: account.value, - allowMultiple: true, - ), - clipBehavior: Clip.hardEdge, - ); - if (files != null) { - ref - .read( - attachesNotifierProvider( - account.value, - noteId: noteId, - ).notifier, - ) - .addAll(files); - } - }, - icon: const Icon(Icons.add_photo_alternate), + Expanded( + child: Text(t.misskey.notSpecifiedMentionWarning), ), - IconButton( - tooltip: t.misskey.poll, - onPressed: () => ref - .read( - postNotifierProvider( - account.value, - noteId: noteId, - ).notifier, - ) - .togglePoll(), - icon: Icon( - Icons.bar_chart, - color: request.poll != null - ? Theme.of(context).colorScheme.primary - : null, + if (noteId == null) + TextButton( + onPressed: () async { + final users = await futureWithDialog( + context, + Future.wait( + notSpecifiedMentions.map( + (node) => ref.read( + userNotifierProvider( + account.value, + username: node.username, + host: node.host, + ).future, + ), + ), + ), + ); + if (users != null) { + visibleUsers.value = [ + ...visibleUsers.value, + ...users, + ]; + ref + .read( + postNotifierProvider(account.value) + .notifier, + ) + .addVisibleUsers(users); + } + }, + child: Text(t.misskey.add), ), - ), - IconButton( - tooltip: t.misskey.useCw, - onPressed: () { - if (useCw.value) { - ref - .read( - postNotifierProvider( - account.value, - noteId: noteId, - ).notifier, - ) - .setCw(null); - useCw.value = false; - } else { - ref + ], + ), + ), + ), + ], + if (hasMentionToRemote && (request.localOnly ?? false)) + Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Text(t.aria.mentionToRemoteWarning), + ), + if (request.channelId == null && + !(reply?.localOnly ?? false) && + !(renote?.localOnly ?? false)) + TextButton( + onPressed: noteId == null + ? () => ref .read( - postNotifierProvider( - account.value, - noteId: noteId, - ).notifier, + postNotifierProvider(account.value) + .notifier, ) - .setCw(cwController.text); - useCw.value = true; - } - }, - icon: Icon( - useCw.value - ? Icons.visibility_off - : Icons.visibility, - ), - ), - IconButton( - tooltip: t.misskey.mention, - onPressed: () async { - final user = await selectUser( - context, - account.value, - localOnly: request.localOnly ?? false, - ); - if (user != null) { - controller.insert(user.acct); - } - }, - icon: const Icon(Icons.alternate_email), - ), - // TODO: Preserve hashtags. - // IconButton( - // tooltip: t.misskey.hashtags, - // onPressed: () {}, - // icon: const Icon(Icons.tag), - // ), - IconButton( - tooltip: t.misskey.emoji, - onPressed: () => pickEmoji( - ref, - account.value, - post: true, - onTapEmoji: (emoji) => controller.insert( - emoji.replaceFirst('@.', ''), - ), - ), - icon: const Icon(Icons.mood), - ), - IconButton( - tooltip: t.misskey.channel, - onPressed: canChangeChannel - ? () async { - final result = - await showDialog( - context: context, - builder: (context) => ChannelsPage( - account: account.value, - onChannelTap: (channel) => - context.pop(channel), - initialIndex: 2, - ), - ); - if (!context.mounted) return; - if (result != null) { - ref - .read( - postNotifierProvider(account.value) - .notifier, - ) - .setChannel(result.id); - } - } + .setLocalOnly(false) : null, - icon: const Icon(Icons.tv), + child: Text(t.aria.enableFederation), ), - ], - ), + ], ), ), - if (onHide case final onHide?) - IconButton( - tooltip: t.misskey.hide, - onPressed: onHide, - icon: const Icon(Icons.keyboard_hide), - ), - if (onExpand case final onExpand?) - IconButton( - onPressed: () => onExpand(account.value), - icon: const Icon(Icons.open_in_full), - ), - ], - ), - ), - if (attaches.isNotEmpty) ...[ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Align( - alignment: Alignment.centerRight, - child: Text( - '${attaches.length}/16', - style: TextStyle( - color: attaches.length > 16 - ? Theme.of(context).colorScheme.error - : null, + ), + if (useCw.value) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Shortcuts( + shortcuts: disablingTextShortcuts, + child: TextField( + controller: cwController, + focusNode: cwFocusNode, + decoration: InputDecoration( + hintText: t.misskey.annotation, + ), + autofocus: true, + textInputAction: TextInputAction.next, + maxLength: (request.cw?.length ?? 0) > 80 ? 100 : null, + maxLengthEnforcement: MaxLengthEnforcement.none, ), ), ), - ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: PostFormAttaches( - account: account.value, - noteId: noteId, - maxCrossAxisExtent: thumbnailSize, + child: TextField( + controller: controller, + focusNode: focusNode, + decoration: InputDecoration(hintText: placeholder), + autofocus: true, + minLines: 1, + maxLines: maxLines, + maxLength: (request.text?.length ?? 0) > 2900 ? 3000 : null, + maxLengthEnforcement: MaxLengthEnforcement.none, ), ), - ], - if (request.poll != null) Padding( padding: const EdgeInsets.all(8.0), - child: PollEditor(account: account.value, noteId: noteId), + child: Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + IconButton( + tooltip: t.misskey.attachFile, + onPressed: () async { + final files = + await showModalBottomSheet>( + context: context, + builder: (context) => FilePickerSheet( + account: account.value, + allowMultiple: true, + ), + clipBehavior: Clip.hardEdge, + ); + if (files != null) { + ref + .read( + attachesNotifierProvider( + account.value, + noteId: noteId, + ).notifier, + ) + .addAll(files); + } + }, + icon: const Icon(Icons.add_photo_alternate), + ), + IconButton( + tooltip: t.misskey.poll, + onPressed: () => ref + .read( + postNotifierProvider( + account.value, + noteId: noteId, + ).notifier, + ) + .togglePoll(), + icon: Icon( + Icons.bar_chart, + color: request.poll != null + ? Theme.of(context).colorScheme.primary + : null, + ), + ), + IconButton( + tooltip: t.misskey.useCw, + onPressed: () { + if (useCw.value) { + ref + .read( + postNotifierProvider( + account.value, + noteId: noteId, + ).notifier, + ) + .setCw(null); + useCw.value = false; + } else { + ref + .read( + postNotifierProvider( + account.value, + noteId: noteId, + ).notifier, + ) + .setCw(cwController.text); + useCw.value = true; + } + }, + icon: Icon( + useCw.value + ? Icons.visibility_off + : Icons.visibility, + ), + ), + IconButton( + tooltip: t.misskey.mention, + onPressed: () async { + final user = await selectUser( + context, + account.value, + localOnly: request.localOnly ?? false, + ); + if (user != null) { + controller.insert(user.acct); + } + }, + icon: const Icon(Icons.alternate_email), + ), + // TODO: Preserve hashtags. + // IconButton( + // tooltip: t.misskey.hashtags, + // onPressed: () {}, + // icon: const Icon(Icons.tag), + // ), + IconButton( + tooltip: t.misskey.emoji, + onPressed: () => pickEmoji( + ref, + account.value, + post: true, + onTapEmoji: (emoji) => controller.insert( + emoji.replaceFirst('@.', ''), + ), + ), + icon: const Icon(Icons.mood), + ), + IconButton( + tooltip: t.misskey.channel, + onPressed: canChangeChannel + ? () async { + final result = + await showDialog( + context: context, + builder: (context) => ChannelsPage( + account: account.value, + onChannelTap: (channel) => + context.pop(channel), + initialIndex: 2, + ), + ); + if (!context.mounted) return; + if (result != null) { + ref + .read( + postNotifierProvider(account.value) + .notifier, + ) + .setChannel(result.id); + } + } + : null, + icon: const Icon(Icons.tv), + ), + ], + ), + ), + ), + if (onHide case final onHide?) + IconButton( + tooltip: t.misskey.hide, + onPressed: onHide, + icon: const Icon(Icons.keyboard_hide), + ), + if (onExpand case final onExpand?) + IconButton( + onPressed: () => onExpand(account.value), + icon: const Icon(Icons.open_in_full), + ), + ], + ), ), - if (showKeyboard) ...[ - Visibility( - visible: isCwFocused.value, - maintainState: true, - child: TextFieldTapRegion( - onTapOutside: (_) => primaryFocus?.unfocus(), - child: MfmKeyboard( - account: account.value, - controller: cwController, + if (attaches.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Align( + alignment: Alignment.centerRight, + child: Text( + '${attaches.length}/16', + style: TextStyle( + color: attaches.length > 16 + ? Theme.of(context).colorScheme.error + : null, + ), + ), ), ), - ), - Visibility( - visible: isFocused.value, - maintainState: true, - child: TextFieldTapRegion( - onTapOutside: (_) => primaryFocus?.unfocus(), - child: MfmKeyboard( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: PostFormAttaches( account: account.value, - controller: controller, + noteId: noteId, + maxCrossAxisExtent: thumbnailSize, ), ), - ), + ], + if (request.poll != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: PollEditor(account: account.value, noteId: noteId), + ), + if (showKeyboard) ...[ + Visibility( + visible: isCwFocused.value, + maintainState: true, + child: TextFieldTapRegion( + onTapOutside: (_) => primaryFocus?.unfocus(), + child: MfmKeyboard( + account: account.value, + controller: cwController, + ), + ), + ), + Visibility( + visible: isFocused.value, + maintainState: true, + child: TextFieldTapRegion( + onTapOutside: (_) => primaryFocus?.unfocus(), + child: MfmKeyboard( + account: account.value, + controller: controller, + ), + ), + ), + ], ], - ], + ), ), ); } From 3588ed3f8b9f8df100099a909b22d0e75ff6d732 Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:57:52 +0900 Subject: [PATCH 5/5] fix: reset keyboard on switch account --- lib/extension/scroll_controller_extension.dart | 5 ++--- lib/view/page/timelines_page.dart | 10 ++++++---- lib/view/widget/mfm_keyboard.dart | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/extension/scroll_controller_extension.dart b/lib/extension/scroll_controller_extension.dart index c23f3b507..8ba943b37 100644 --- a/lib/extension/scroll_controller_extension.dart +++ b/lib/extension/scroll_controller_extension.dart @@ -2,10 +2,9 @@ import 'package:flutter/material.dart'; extension ScrollControllerExtension on ScrollController { Future scrollToTop() async { + if (!hasClients) return; final extentBefore = position.extentBefore; - if (extentBefore == 0.0) { - return; - } + if (extentBefore == 0.0) return; if (extentBefore < 10000.0) { await animateTo( position.minScrollExtent, diff --git a/lib/view/page/timelines_page.dart b/lib/view/page/timelines_page.dart index 93e0e0122..5224da8f0 100644 --- a/lib/view/page/timelines_page.dart +++ b/lib/view/page/timelines_page.dart @@ -67,7 +67,7 @@ class TimelinesPage extends HookConsumerWidget { final showPostForm = useState(false); useEffect( () { - controller.addListener(() { + void callback() { final previousIndex = tabIndex.value; final nextIndex = controller.index; if (previousIndex == nextIndex) { @@ -110,10 +110,12 @@ class TimelinesPage extends HookConsumerWidget { } } tabIndex.value = nextIndex; - }); - return; + } + + controller.addListener(callback); + return () => controller.removeListener(callback); }, - [], + [tabs], ); final isLargeScreen = MediaQuery.sizeOf(context).width > 1200.0; final scaffoldKey = useMemoized(() => GlobalKey()); diff --git a/lib/view/widget/mfm_keyboard.dart b/lib/view/widget/mfm_keyboard.dart index 37f6baf59..2ead6500f 100644 --- a/lib/view/widget/mfm_keyboard.dart +++ b/lib/view/widget/mfm_keyboard.dart @@ -463,7 +463,7 @@ class MfmKeyboard extends HookConsumerWidget { }); return; }, - [], + [account, controller], ); return Container(