Improve keyboard experience

This commit is contained in:
shilangyu 2021-04-11 17:19:44 +02:00
parent 9fa198c3bb
commit 59d93fe697
7 changed files with 163 additions and 88 deletions

View File

@ -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(

View File

@ -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(

View File

@ -66,6 +66,9 @@ class CreatePostPage extends HookWidget {
final pictrsDeleteToken = useState<PictrsUploadFile?>(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<int>(
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<int>(
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(),

View File

@ -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),

View File

@ -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),
)
],

View File

@ -86,6 +86,7 @@ class WriteMessagePage extends HookWidget {
TextField(
controller: bodyController,
keyboardType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences,
maxLines: null,
minLines: 5,
autofocus: true,

View File

@ -97,6 +97,8 @@ class WriteComment extends HookWidget {
children: [
TextField(
controller: controller,
keyboardType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences,
autofocus: true,
minLines: 5,
maxLines: null,