feat: Add suggestions to server login page

This commit is contained in:
Anton Stubenbord
2023-06-02 16:37:03 +02:00
parent 31447087e1
commit d2b428c05b
7 changed files with 146 additions and 44 deletions

View File

@@ -244,7 +244,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
if (userAccountBox.containsKey(localUserId)) {
throw Exception("User with id $localUserId already exists!");
throw Exception("User already exists!");
}
final apiVersion = await _getApiVersion(sessionManager.client);
@@ -282,6 +282,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
),
);
});
final hostsBox = Hive.box<String>(HiveBoxes.hosts);
if (!hostsBox.values.contains(serverUrl)) {
await hostsBox.add(serverUrl);
}
return serverUser.id;
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
@@ -14,12 +16,13 @@ import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_cr
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
import 'package:paperless_mobile/features/users/view/widgets/user_account_list_tile.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'widgets/login_pages/server_login_page.dart';
import 'widgets/never_scrollable_scroll_behavior.dart';
class LoginPage extends StatefulWidget {
final void Function(
final FutureOr<void> Function(
BuildContext context,
String username,
String password,
@@ -131,13 +134,17 @@ class _LoginPageState extends State<LoginPage> {
);
}
final credentials = form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
widget.onSubmit(
context,
credentials.username!,
credentials.password!,
form[ServerAddressFormField.fkServerAddress],
clientCert,
);
try {
await widget.onSubmit(
context,
credentials.username!,
credentials.password!,
form[ServerAddressFormField.fkServerAddress],
clientCert,
);
} on Exception catch (error) {
showGenericError(context, error);
}
}
}
}

View File

@@ -1,16 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class ServerAddressFormField extends StatefulWidget {
static const String fkServerAddress = "serverAddress";
final void Function(String? address) onDone;
final void Function(String? address) onSubmit;
const ServerAddressFormField({
Key? key,
required this.onDone,
required this.onSubmit,
}) : super(key: key);
@override
@@ -24,21 +26,18 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
void initState() {
super.initState();
_textEditingController.addListener(() {
if (_textEditingController.text.isNotEmpty) {
setState(() {
_canClear = true;
});
}
setState(() {
_canClear = _textEditingController.text.isNotEmpty;
});
});
}
final _focusNode = FocusNode();
final _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return FormBuilderTextField(
key: const ValueKey('login-server-address'),
controller: _textEditingController,
return FormBuilderField<String>(
name: ServerAddressFormField.fkServerAddress,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
@@ -50,20 +49,60 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
}
return null;
},
decoration: InputDecoration(
hintText: "http://192.168.1.50:8000",
labelText: S.of(context)!.serverAddress,
suffixIcon: _canClear
? IconButton(
icon: const Icon(Icons.clear),
color: Theme.of(context).iconTheme.color,
onPressed: () {
_textEditingController.clear();
},
)
: null,
),
onSubmitted: (_) => _formatInput(),
builder: (field) {
return RawAutocomplete<String>(
focusNode: _focusNode,
textEditingController: _textEditingController,
optionsViewBuilder: (context, onSelected, options) {
return _AutocompleteOptions(
onSelected: onSelected,
options: options,
maxOptionsHeight: 200.0,
);
},
key: const ValueKey('login-server-address'),
optionsBuilder: (textEditingValue) {
return Hive.box<String>(HiveBoxes.hosts)
.values
.where((element) => element.contains(textEditingValue.text));
},
onSelected: (option) => _formatInput(),
fieldViewBuilder: (context, textEditingController, focusNode, onFieldSubmitted) {
return TextField(
controller: textEditingController,
focusNode: focusNode,
decoration: InputDecoration(
hintText: "http://192.168.1.50:8000",
labelText: S.of(context)!.serverAddress,
suffixIcon: _canClear
? IconButton(
icon: const Icon(Icons.clear),
color: Theme.of(context).iconTheme.color,
onPressed: () {
textEditingController.clear();
field.didChange(textEditingController.text);
widget.onSubmit(textEditingController.text);
},
)
: null,
),
autofocus: true,
onSubmitted: (_) {
onFieldSubmitted();
_formatInput();
},
keyboardType: TextInputType.url,
onChanged: (value) {
field.didChange(value);
},
onEditingComplete: () {
field.didChange(_textEditingController.text);
_focusNode.unfocus();
},
);
},
);
},
);
}
@@ -71,6 +110,59 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
String address = _textEditingController.text.trim();
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
_textEditingController.text = address;
widget.onDone(address);
widget.onSubmit(address);
}
}
/// Taken from [Autocomplete]
class _AutocompleteOptions extends StatelessWidget {
const _AutocompleteOptions({
required this.onSelected,
required this.options,
required this.maxOptionsHeight,
});
final AutocompleteOnSelected<String> onSelected;
final Iterable<String> options;
final double maxOptionsHeight;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: maxOptionsHeight),
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final option = options.elementAt(index);
return InkWell(
onTap: () {
onSelected(option);
},
child: Builder(builder: (BuildContext context) {
final bool highlight = AutocompleteHighlightedOption.of(context) == index;
if (highlight) {
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
Scrollable.ensureVisible(context, alignment: 0.5);
});
}
return Container(
color: highlight ? Theme.of(context).focusColor : null,
padding: const EdgeInsets.all(16.0),
child: Text(option),
);
}),
);
},
),
),
),
);
}
}

View File

@@ -48,7 +48,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
child: Column(
children: [
ServerAddressFormField(
onDone: (address) {
onSubmit: (address) {
_updateReachability(address);
},
).padded(),