diff --git a/lib/pages/create_post/create_post.dart b/lib/pages/create_post/create_post.dart index 8a74e8e..af64f45 100644 --- a/lib/pages/create_post/create_post.dart +++ b/lib/pages/create_post/create_post.dart @@ -31,6 +31,7 @@ class CreatePostPage extends HookWidget { ); final titleFocusNode = useFocusNode(); + final editorFocusNode = useFocusNode(); handleSubmit(Jwt token) async { if (formKey.currentState!.validate()) { @@ -63,6 +64,7 @@ class CreatePostPage extends HookWidget { labelText: L10n.of(context).body, instanceHost: store.instanceHost, fancy: store.showFancy, + focusNode: editorFocusNode, ), ); @@ -146,6 +148,7 @@ class CreatePostPage extends HookWidget { children: [ const Spacer(), EditorToolbar( + editorFocusNode: editorFocusNode, controller: bodyController, instanceHost: context.read().instanceHost, ), diff --git a/lib/pages/manage_account.dart b/lib/pages/manage_account.dart index a3408f1..84047c3 100644 --- a/lib/pages/manage_account.dart +++ b/lib/pages/manage_account.dart @@ -234,150 +234,169 @@ class _ManageAccount extends HookWidget { } } - return ListView( - padding: const EdgeInsets.symmetric(horizontal: 15), + return Stack( children: [ - _ImagePicker( - user: user, - name: L10n.of(context).avatar, - initialUrl: avatar.value, - onChange: (value) => avatar.value = value, - informAcceptedRef: informAcceptedAvatarRef, + ListView( + padding: const EdgeInsets.symmetric(horizontal: 15), + children: [ + _ImagePicker( + user: user, + name: L10n.of(context).avatar, + initialUrl: avatar.value, + onChange: (value) => avatar.value = value, + informAcceptedRef: informAcceptedAvatarRef, + ), + const SizedBox(height: 8), + _ImagePicker( + user: user, + name: L10n.of(context).banner, + initialUrl: banner.value, + onChange: (value) => banner.value = value, + informAcceptedRef: informAcceptedBannerRef, + ), + const SizedBox(height: 8), + Text(L10n.of(context).display_name, + style: theme.textTheme.headline6), + TextField( + controller: displayNameController, + onSubmitted: (_) => bioFocusNode.requestFocus(), + ), + const SizedBox(height: 8), + Text(L10n.of(context).bio, style: theme.textTheme.headline6), + Editor( + controller: bioController, + focusNode: bioFocusNode, + onSubmitted: (_) => emailFocusNode.requestFocus(), + instanceHost: user.instanceHost, + maxLines: 10, + ), + const SizedBox(height: 8), + Text(L10n.of(context).email, style: theme.textTheme.headline6), + TextField( + focusNode: emailFocusNode, + controller: emailController, + autofillHints: const [AutofillHints.email], + keyboardType: TextInputType.emailAddress, + onSubmitted: (_) => matrixUserFocusNode.requestFocus(), + ), + const SizedBox(height: 8), + Text(L10n.of(context).matrix_user, + style: theme.textTheme.headline6), + TextField( + focusNode: matrixUserFocusNode, + controller: matrixUserController, + onSubmitted: (_) => newPasswordFocusNode.requestFocus(), + ), + const SizedBox(height: 8), + // Text(L10n.of(context)!.new_password, style: theme.textTheme.headline6), + // TextField( + // focusNode: newPasswordFocusNode, + // controller: newPasswordController, + // autofillHints: const [AutofillHints.newPassword], + // keyboardType: TextInputType.visiblePassword, + // obscureText: true, + // onSubmitted: (_) => verifyPasswordFocusNode.requestFocus(), + // ), + // const SizedBox(height: 8), + // Text(L10n.of(context)!.verify_password, + // style: theme.textTheme.headline6), + // TextField( + // focusNode: verifyPasswordFocusNode, + // controller: newPasswordVerifyController, + // autofillHints: const [AutofillHints.newPassword], + // keyboardType: TextInputType.visiblePassword, + // obscureText: true, + // onSubmitted: (_) => oldPasswordFocusNode.requestFocus(), + // ), + // const SizedBox(height: 8), + // Text(L10n.of(context)!.old_password, style: theme.textTheme.headline6), + // TextField( + // focusNode: oldPasswordFocusNode, + // controller: oldPasswordController, + // autofillHints: const [AutofillHints.password], + // keyboardType: TextInputType.visiblePassword, + // obscureText: true, + // ), + // const SizedBox(height: 8), + SwitchListTile.adaptive( + value: showNsfw.value, + onChanged: (checked) { + showNsfw.value = checked; + }, + title: Text(L10n.of(context).show_nsfw), + dense: true, + ), + const SizedBox(height: 8), + SwitchListTile.adaptive( + value: botAccount.value, + onChanged: (checked) { + botAccount.value = checked; + }, + title: Text(L10n.of(context).bot_account), + dense: true, + ), + const SizedBox(height: 8), + SwitchListTile.adaptive( + value: showBotAccounts.value, + onChanged: (checked) { + showBotAccounts.value = checked; + }, + title: Text(L10n.of(context).show_bot_accounts), + dense: true, + ), + const SizedBox(height: 8), + SwitchListTile.adaptive( + value: showReadPosts.value, + onChanged: (checked) { + showReadPosts.value = checked; + }, + title: Text(L10n.of(context).show_read_posts), + dense: true, + ), + const SizedBox(height: 8), + SwitchListTile.adaptive( + value: sendNotificationsToEmail.value, + onChanged: (checked) { + sendNotificationsToEmail.value = checked; + }, + title: Text(L10n.of(context).send_notifications_to_email), + dense: true, + ), + const SizedBox(height: 8), + ElevatedButton( + onPressed: saveDelayedLoading.loading ? null : handleSubmit, + child: saveDelayedLoading.loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator.adaptive(), + ) + : Text(L10n.of(context).save), + ), + const SizedBox(height: 8), + ElevatedButton( + onPressed: deleteAccountDialog, + style: ElevatedButton.styleFrom( + primary: Colors.red, + ), + child: Text(L10n.of(context).delete_account.toUpperCase()), + ), + const BottomSafe(), + ], ), - const SizedBox(height: 8), - _ImagePicker( - user: user, - name: L10n.of(context).banner, - initialUrl: banner.value, - onChange: (value) => banner.value = value, - informAcceptedRef: informAcceptedBannerRef, - ), - const SizedBox(height: 8), - Text(L10n.of(context).display_name, style: theme.textTheme.headline6), - TextField( - controller: displayNameController, - onSubmitted: (_) => bioFocusNode.requestFocus(), - ), - const SizedBox(height: 8), - Text(L10n.of(context).bio, style: theme.textTheme.headline6), - Editor( - controller: bioController, - focusNode: bioFocusNode, - onSubmitted: (_) => emailFocusNode.requestFocus(), - instanceHost: user.instanceHost, - maxLines: 10, - ), - const SizedBox(height: 8), - Text(L10n.of(context).email, style: theme.textTheme.headline6), - TextField( - focusNode: emailFocusNode, - controller: emailController, - autofillHints: const [AutofillHints.email], - keyboardType: TextInputType.emailAddress, - onSubmitted: (_) => matrixUserFocusNode.requestFocus(), - ), - const SizedBox(height: 8), - Text(L10n.of(context).matrix_user, style: theme.textTheme.headline6), - TextField( - focusNode: matrixUserFocusNode, - controller: matrixUserController, - onSubmitted: (_) => newPasswordFocusNode.requestFocus(), - ), - const SizedBox(height: 8), - // Text(L10n.of(context)!.new_password, style: theme.textTheme.headline6), - // TextField( - // focusNode: newPasswordFocusNode, - // controller: newPasswordController, - // autofillHints: const [AutofillHints.newPassword], - // keyboardType: TextInputType.visiblePassword, - // obscureText: true, - // onSubmitted: (_) => verifyPasswordFocusNode.requestFocus(), - // ), - // const SizedBox(height: 8), - // Text(L10n.of(context)!.verify_password, - // style: theme.textTheme.headline6), - // TextField( - // focusNode: verifyPasswordFocusNode, - // controller: newPasswordVerifyController, - // autofillHints: const [AutofillHints.newPassword], - // keyboardType: TextInputType.visiblePassword, - // obscureText: true, - // onSubmitted: (_) => oldPasswordFocusNode.requestFocus(), - // ), - // const SizedBox(height: 8), - // Text(L10n.of(context)!.old_password, style: theme.textTheme.headline6), - // TextField( - // focusNode: oldPasswordFocusNode, - // controller: oldPasswordController, - // autofillHints: const [AutofillHints.password], - // keyboardType: TextInputType.visiblePassword, - // obscureText: true, - // ), - // const SizedBox(height: 8), - SwitchListTile.adaptive( - value: showNsfw.value, - onChanged: (checked) { - showNsfw.value = checked; - }, - title: Text(L10n.of(context).show_nsfw), - dense: true, - ), - const SizedBox(height: 8), - SwitchListTile.adaptive( - value: botAccount.value, - onChanged: (checked) { - botAccount.value = checked; - }, - title: Text(L10n.of(context).bot_account), - dense: true, - ), - const SizedBox(height: 8), - SwitchListTile.adaptive( - value: showBotAccounts.value, - onChanged: (checked) { - showBotAccounts.value = checked; - }, - title: Text(L10n.of(context).show_bot_accounts), - dense: true, - ), - const SizedBox(height: 8), - SwitchListTile.adaptive( - value: showReadPosts.value, - onChanged: (checked) { - showReadPosts.value = checked; - }, - title: Text(L10n.of(context).show_read_posts), - dense: true, - ), - const SizedBox(height: 8), - SwitchListTile.adaptive( - value: sendNotificationsToEmail.value, - onChanged: (checked) { - sendNotificationsToEmail.value = checked; - }, - title: Text(L10n.of(context).send_notifications_to_email), - dense: true, - ), - const SizedBox(height: 8), - ElevatedButton( - onPressed: saveDelayedLoading.loading ? null : handleSubmit, - child: saveDelayedLoading.loading - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator.adaptive(), - ) - : Text(L10n.of(context).save), - ), - const SizedBox(height: 8), - ElevatedButton( - onPressed: deleteAccountDialog, - style: ElevatedButton.styleFrom( - primary: Colors.red, + SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Spacer(), + EditorToolbar( + editorFocusNode: bioFocusNode, + controller: bioController, + instanceHost: user.instanceHost, + ), + ], ), - child: Text(L10n.of(context).delete_account.toUpperCase()), ), - const BottomSafe(), ], ); } diff --git a/lib/widgets/editor/editor.dart b/lib/widgets/editor/editor.dart index be4b99e..39ad656 100644 --- a/lib/widgets/editor/editor.dart +++ b/lib/widgets/editor/editor.dart @@ -52,18 +52,22 @@ class Editor extends HookWidget { ); } - return TextField( - controller: actualController, - focusNode: focusNode, - autofocus: autofocus, - keyboardType: TextInputType.multiline, - textCapitalization: TextCapitalization.sentences, - onChanged: onChanged, - onSubmitted: onSubmitted, - maxLines: maxLines, - minLines: minLines, - decoration: InputDecoration(labelText: labelText), - inputFormatters: [MarkdownFormatter()], + return FocusScope( + child: Focus( + focusNode: focusNode, + child: TextField( + controller: actualController, + autofocus: autofocus, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + onChanged: onChanged, + onSubmitted: onSubmitted, + maxLines: maxLines, + minLines: minLines, + decoration: InputDecoration(labelText: labelText), + inputFormatters: [MarkdownFormatter()], + ), + ), ); } } diff --git a/lib/widgets/editor/editor_toolbar.dart b/lib/widgets/editor/editor_toolbar.dart index 28f6d58..d29bb2e 100644 --- a/lib/widgets/editor/editor_toolbar.dart +++ b/lib/widgets/editor/editor_toolbar.dart @@ -39,37 +39,50 @@ enum HeaderLevel { final int value; } -class EditorToolbar extends StatelessWidget { +class EditorToolbar extends HookWidget { final TextEditingController controller; final String instanceHost; final EditorToolbarStore store; + final FocusNode editorFocusNode; static const _height = 50.0; EditorToolbar({ required this.controller, required this.instanceHost, + required this.editorFocusNode, }) : store = EditorToolbarStore(instanceHost); @override Widget build(BuildContext context) { + final visible = useState(editorFocusNode.hasFocus); + useEffect(() { + editorFocusNode.addListener(() { + visible.value = editorFocusNode.hasFocus; + }); + + return; + }, [editorFocusNode]); + return MobxProvider( create: (context) => store, child: AsyncStoreListener( asyncStore: store.imageUploadState, - child: Container( - height: _height, - width: double.infinity, - color: Theme.of(context).cardColor, - child: Material( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: _ToolbarBody( - controller: controller, - instanceHost: instanceHost, - ), - ), - ), - ), + child: visible.value + ? Container( + height: _height, + width: double.infinity, + color: Theme.of(context).cardColor, + child: Material( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: _ToolbarBody( + controller: controller, + instanceHost: instanceHost, + ), + ), + ), + ) + : const SizedBox.shrink(), ), ); } diff --git a/lib/widgets/write_comment.dart b/lib/widgets/write_comment.dart index 25d29f3..5456f2c 100644 --- a/lib/widgets/write_comment.dart +++ b/lib/widgets/write_comment.dart @@ -36,6 +36,7 @@ class WriteComment extends HookWidget { final showFancy = useState(false); final delayed = useDelayedLoading(); final loggedInAction = useLoggedInAction(post.instanceHost); + final editorFocusNode = useFocusNode(); final preview = () { final body = () { @@ -117,6 +118,7 @@ class WriteComment extends HookWidget { controller: controller, autofocus: true, fancy: showFancy.value, + focusNode: editorFocusNode, ), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -141,6 +143,7 @@ class WriteComment extends HookWidget { children: [ const Spacer(), EditorToolbar( + editorFocusNode: editorFocusNode, controller: controller, instanceHost: post.instanceHost, ),