diff --git a/lib/pages/add_account.dart b/lib/pages/add_account.dart index 0f35693..83039a7 100644 --- a/lib/pages/add_account.dart +++ b/lib/pages/add_account.dart @@ -24,6 +24,7 @@ class AddAccountPage extends HookWidget { final usernameController = useListenable(useTextEditingController()); final passwordController = useListenable(useTextEditingController()); + final passwordFocusNode = useFocusNode(); final accountsStore = useAccountsStore(); final loading = useDelayedLoading(); @@ -54,6 +55,13 @@ class AddAccountPage extends HookWidget { loading.cancel(); } + final handleSubmit = + usernameController.text.isEmpty || passwordController.text.isEmpty + ? null + : loading.pending + ? () {} + : handleOnAdd; + return Scaffold( appBar: AppBar( leading: const CloseButton(), @@ -105,10 +113,11 @@ class AddAccountPage extends HookWidget { }, ), ), - // TODO: add support for password managers TextField( autofocus: true, controller: usernameController, + autofillHints: const [AutofillHints.email, AutofillHints.username], + onSubmitted: (_) => passwordFocusNode.requestFocus(), decoration: InputDecoration(labelText: L10n.of(context)!.email_or_username), ), @@ -116,15 +125,14 @@ class AddAccountPage extends HookWidget { TextField( controller: passwordController, obscureText: true, + focusNode: passwordFocusNode, + onSubmitted: (_) => handleSubmit?.call(), + autofillHints: const [AutofillHints.password], + keyboardType: TextInputType.visiblePassword, decoration: InputDecoration(labelText: L10n.of(context)!.password), ), ElevatedButton( - onPressed: usernameController.text.isEmpty || - passwordController.text.isEmpty - ? null - : loading.pending - ? () {} - : handleOnAdd, + onPressed: handleSubmit, child: !loading.loading ? const Text('Sign in') : SizedBox( diff --git a/lib/pages/add_instance.dart b/lib/pages/add_instance.dart index 89a8c74..16e71b1 100644 --- a/lib/pages/add_instance.dart +++ b/lib/pages/add_instance.dart @@ -47,7 +47,9 @@ class AddInstancePage extends HookWidget { instanceController.removeListener(debounce); }; }, []); + final inst = normalizeInstanceHost(instanceController.text); + handleOnAdd() async { try { await accountsStore.addInstance(inst, assumeValid: true); @@ -59,6 +61,8 @@ class AddInstancePage extends HookWidget { } } + final handleAdd = isSite.value == true ? handleOnAdd : null; + return Scaffold( appBar: AppBar( leading: const CloseButton(), @@ -97,6 +101,9 @@ class AddInstancePage extends HookWidget { child: TextField( autofocus: true, controller: instanceController, + autofillHints: const [AutofillHints.url], + keyboardType: TextInputType.url, + onSubmitted: (_) => handleAdd?.call(), autocorrect: false, decoration: const InputDecoration(labelText: 'instance url'), ), @@ -108,7 +115,7 @@ class AddInstancePage extends HookWidget { child: SizedBox( height: 40, child: ElevatedButton( - onPressed: isSite.value == true ? handleOnAdd : null, + onPressed: handleAdd, child: !debounce.loading ? const Text('Add') : SizedBox( diff --git a/lib/pages/create_post.dart b/lib/pages/create_post.dart index 73e40d1..48a9ab8 100644 --- a/lib/pages/create_post.dart +++ b/lib/pages/create_post.dart @@ -66,6 +66,9 @@ class CreatePostPage extends HookWidget { final pictrsDeleteToken = useState(null); final loggedInAction = useLoggedInAction(selectedInstance.value); + final titleFocusNode = useFocusNode(); + final bodyFocusNode = useFocusNode(); + final allCommunitiesSnap = useMemoFuture( () => LemmyApiV3(selectedInstance.value) .run(ListCommunities( @@ -155,77 +158,6 @@ class CreatePostPage extends HookWidget { } } - // TODO: use lazy autocomplete - final communitiesDropdown = InputDecorator( - decoration: const InputDecoration( - contentPadding: EdgeInsets.symmetric(vertical: 1, horizontal: 20), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)))), - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: selectedCommunity.value?.community.id, - hint: Text(L10n.of(context)!.community), - onChanged: (communityId) => selectedCommunity.value = - allCommunitiesSnap.data - ?.firstWhere((e) => e.community.id == communityId), - items: communitiesList(), - ), - ), - ); - - final url = Row(children: [ - Expanded( - child: TextField( - enabled: pictrsDeleteToken.value == null, - controller: urlController, - decoration: InputDecoration( - labelText: L10n.of(context)!.url, - suffixIcon: const Icon(Icons.link), - ), - ), - ), - const SizedBox(width: 5), - IconButton( - icon: imageUploadLoading.value - ? const CircularProgressIndicator() - : Icon(pictrsDeleteToken.value == null - ? Icons.add_photo_alternate - : Icons.close), - onPressed: pictrsDeleteToken.value == null - ? loggedInAction(uploadPicture) - : () => removePicture(pictrsDeleteToken.value!), - tooltip: - pictrsDeleteToken.value == null ? 'Add picture' : 'Delete picture', - ) - ]); - - final title = TextField( - controller: titleController, - minLines: 1, - maxLines: 2, - decoration: InputDecoration(labelText: L10n.of(context)!.title), - ); - - final body = IndexedStack( - index: showFancy.value ? 1 : 0, - children: [ - TextField( - controller: bodyController, - keyboardType: TextInputType.multiline, - maxLines: null, - minLines: 5, - decoration: InputDecoration(labelText: L10n.of(context)!.body), - ), - Padding( - padding: const EdgeInsets.all(16), - child: MarkdownText( - bodyController.text, - instanceHost: selectedInstance.value, - ), - ), - ], - ); - handleSubmit(Jwt token) async { if (selectedCommunity.value == null || titleController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( @@ -256,6 +188,88 @@ class CreatePostPage extends HookWidget { delayed.cancel(); } + // TODO: use lazy autocomplete + final communitiesDropdown = InputDecorator( + decoration: const InputDecoration( + contentPadding: EdgeInsets.symmetric(vertical: 1, horizontal: 20), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)))), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: selectedCommunity.value?.community.id, + hint: Text(L10n.of(context)!.community), + onChanged: (communityId) => selectedCommunity.value = + allCommunitiesSnap.data + ?.firstWhere((e) => e.community.id == communityId), + items: communitiesList(), + ), + ), + ); + + final url = Row(children: [ + Expanded( + child: TextField( + enabled: pictrsDeleteToken.value == null, + controller: urlController, + autofillHints: const [AutofillHints.url], + keyboardType: TextInputType.url, + onSubmitted: (_) => titleFocusNode.requestFocus(), + decoration: InputDecoration( + labelText: L10n.of(context)!.url, + suffixIcon: const Icon(Icons.link), + ), + ), + ), + const SizedBox(width: 5), + IconButton( + icon: imageUploadLoading.value + ? const CircularProgressIndicator() + : Icon(pictrsDeleteToken.value == null + ? Icons.add_photo_alternate + : Icons.close), + onPressed: pictrsDeleteToken.value == null + ? loggedInAction(uploadPicture) + : () => removePicture(pictrsDeleteToken.value!), + tooltip: + pictrsDeleteToken.value == null ? 'Add picture' : 'Delete picture', + ) + ]); + + final title = TextField( + controller: titleController, + focusNode: titleFocusNode, + keyboardType: TextInputType.text, + textCapitalization: TextCapitalization.sentences, + onSubmitted: (_) => bodyFocusNode.requestFocus(), + minLines: 1, + maxLines: 2, + decoration: InputDecoration(labelText: L10n.of(context)!.title), + ); + + final body = IndexedStack( + index: showFancy.value ? 1 : 0, + children: [ + TextField( + controller: bodyController, + focusNode: bodyFocusNode, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + onSubmitted: (_) => + delayed.pending ? () {} : loggedInAction(handleSubmit), + maxLines: null, + minLines: 5, + decoration: InputDecoration(labelText: L10n.of(context)!.body), + ), + Padding( + padding: const EdgeInsets.all(16), + child: MarkdownText( + bodyController.text, + instanceHost: selectedInstance.value, + ), + ), + ], + ); + return Scaffold( appBar: AppBar( leading: const CloseButton(), diff --git a/lib/pages/manage_account.dart b/lib/pages/manage_account.dart index eda538a..d6d35f8 100644 --- a/lib/pages/manage_account.dart +++ b/lib/pages/manage_account.dart @@ -91,6 +91,13 @@ class _ManageAccount extends HookWidget { final deleteAccountPasswordController = useTextEditingController(); + final bioFocusNode = useFocusNode(); + final emailFocusNode = useFocusNode(); + final matrixUserFocusNode = useFocusNode(); + final newPasswordFocusNode = useFocusNode(); + final verifyPasswordFocusNode = useFocusNode(); + final oldPasswordFocusNode = useFocusNode(); + final token = accountsStore.tokenFor(user.instanceHost, user.person.name)!; handleSubmit() async { @@ -155,6 +162,8 @@ class _ManageAccount extends HookWidget { const SizedBox(height: 10), TextField( controller: deleteAccountPasswordController, + autofillHints: const [AutofillHints.password], + keyboardType: TextInputType.visiblePassword, obscureText: true, decoration: InputDecoration(hintText: L10n.of(context)!.password), @@ -219,37 +228,64 @@ class _ManageAccount extends HookWidget { ), const SizedBox(height: 8), Text(L10n.of(context)!.display_name, style: theme.textTheme.headline6), - TextField(controller: displayNameController), + TextField( + controller: displayNameController, + onSubmitted: (_) => bioFocusNode.requestFocus(), + ), const SizedBox(height: 8), Text(L10n.of(context)!.bio, style: theme.textTheme.headline6), TextField( controller: bioController, + focusNode: bioFocusNode, + textCapitalization: TextCapitalization.sentences, + onSubmitted: (_) => emailFocusNode.requestFocus(), minLines: 4, maxLines: 10, ), const SizedBox(height: 8), Text(L10n.of(context)!.email, style: theme.textTheme.headline6), - TextField(controller: emailController), + 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(controller: matrixUserController), + 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), diff --git a/lib/pages/search_tab.dart b/lib/pages/search_tab.dart index c975b90..a0b02be 100644 --- a/lib/pages/search_tab.dart +++ b/lib/pages/search_tab.dart @@ -31,6 +31,16 @@ class SearchTab extends HookWidget { ); } + handleSearch() => searchInputController.text.isNotEmpty + ? goTo( + context, + (context) => SearchResultsPage( + instanceHost: instanceHost.value!, + query: searchInputController.text, + ), + ) + : null; + return Scaffold( appBar: AppBar(), body: ListView( @@ -38,7 +48,9 @@ class SearchTab extends HookWidget { children: [ TextField( controller: searchInputController, + keyboardType: TextInputType.text, textAlign: TextAlign.center, + onSubmitted: (_) => handleSearch(), decoration: InputDecoration(hintText: L10n.of(context)!.search), ), const SizedBox(height: 5), @@ -60,12 +72,7 @@ class SearchTab extends HookWidget { ), if (searchInputController.text.isNotEmpty) ElevatedButton( - onPressed: () => goTo( - context, - (c) => SearchResultsPage( - instanceHost: instanceHost.value!, - query: searchInputController.text, - )), + onPressed: handleSearch, child: Text(L10n.of(context)!.search), ) ], diff --git a/lib/pages/write_message.dart b/lib/pages/write_message.dart index 106a419..1f1be79 100644 --- a/lib/pages/write_message.dart +++ b/lib/pages/write_message.dart @@ -86,6 +86,7 @@ class WriteMessagePage extends HookWidget { TextField( controller: bodyController, keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, maxLines: null, minLines: 5, autofocus: true, diff --git a/lib/widgets/write_comment.dart b/lib/widgets/write_comment.dart index 51d2e86..1669b92 100644 --- a/lib/widgets/write_comment.dart +++ b/lib/widgets/write_comment.dart @@ -97,6 +97,8 @@ class WriteComment extends HookWidget { children: [ TextField( controller: controller, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, autofocus: true, minLines: 5, maxLines: null,