mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 16:07:57 -06:00
feat: Add suggestions to server login page
This commit is contained in:
@@ -20,6 +20,7 @@ class HiveBoxes {
|
||||
static const localUserAccount = 'localUserAccount';
|
||||
static const localUserAppState = 'localUserAppState';
|
||||
static const localUserSettings = 'localUserSettings';
|
||||
static const hosts = 'hosts';
|
||||
}
|
||||
|
||||
class HiveTypeIds {
|
||||
|
||||
@@ -334,8 +334,7 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
|
||||
// TODO HACK to satisfy strictness
|
||||
suggestionsCallback: suggestionsCallback,
|
||||
itemBuilder: itemBuilder,
|
||||
transitionBuilder: (context, suggestionsBox, controller) =>
|
||||
suggestionsBox,
|
||||
transitionBuilder: (context, suggestionsBox, controller) => suggestionsBox,
|
||||
onSuggestionSelected: (T suggestion) {
|
||||
state.didChange(suggestion);
|
||||
onSuggestionSelected?.call(suggestion);
|
||||
@@ -357,8 +356,7 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
|
||||
keepSuggestionsOnLoading: keepSuggestionsOnLoading,
|
||||
autoFlipDirection: autoFlipDirection,
|
||||
suggestionsBoxController: suggestionsBoxController,
|
||||
keepSuggestionsOnSuggestionSelected:
|
||||
keepSuggestionsOnSuggestionSelected,
|
||||
keepSuggestionsOnSuggestionSelected: keepSuggestionsOnSuggestionSelected,
|
||||
hideKeyboard: hideKeyboard,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
@@ -369,15 +367,14 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
|
||||
FormBuilderTypeAheadState<T> createState() => FormBuilderTypeAheadState<T>();
|
||||
}
|
||||
|
||||
class FormBuilderTypeAheadState<T>
|
||||
extends FormBuilderFieldState<FormBuilderTypeAhead<T>, T> {
|
||||
class FormBuilderTypeAheadState<T> extends FormBuilderFieldState<FormBuilderTypeAhead<T>, T> {
|
||||
late TextEditingController _typeAheadController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_typeAheadController = widget.controller ??
|
||||
TextEditingController(text: _getTextString(initialValue));
|
||||
_typeAheadController =
|
||||
widget.controller ?? TextEditingController(text: _getTextString(initialValue));
|
||||
// _typeAheadController.addListener(_handleControllerChanged);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
||||
child: Column(
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
onDone: (address) {
|
||||
onSubmit: (address) {
|
||||
_updateReachability(address);
|
||||
},
|
||||
).padded(),
|
||||
|
||||
@@ -64,6 +64,7 @@ Future<void> _initHive() async {
|
||||
// await getApplicationDocumentsDirectory().then((value) => value.deleteSync(recursive: true));
|
||||
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||
await Hive.openBox<String>(HiveBoxes.hosts);
|
||||
final globalSettingsBox = await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
|
||||
|
||||
if (!globalSettingsBox.hasValue) {
|
||||
|
||||
Reference in New Issue
Block a user