-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #128 from RA341/url-autofill
feat: added autocomplete options to url input
- Loading branch information
Showing
5 changed files
with
201 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/scheduler.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:jellyflix/providers/url_autocomplete_provider.dart'; | ||
|
||
class CustomAutocompleteOptions<T extends Object> extends ConsumerWidget { | ||
const CustomAutocompleteOptions({ | ||
super.key, | ||
required this.displayStringForOption, | ||
required this.onSelected, | ||
required this.openDirection, | ||
required this.options, | ||
required this.maxOptionsHeight, | ||
required this.maxOptionsWidth, | ||
}); | ||
|
||
final AutocompleteOptionToString<T> displayStringForOption; | ||
|
||
final AutocompleteOnSelected<T> onSelected; | ||
final OptionsViewOpenDirection openDirection; | ||
|
||
final Iterable<T> options; | ||
final double maxOptionsHeight; | ||
final double maxOptionsWidth; | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final AlignmentDirectional optionsAlignment = switch (openDirection) { | ||
OptionsViewOpenDirection.up => AlignmentDirectional.bottomStart, | ||
OptionsViewOpenDirection.down => AlignmentDirectional.topStart, | ||
}; | ||
// taken from flutter\lib\src\material\autocomplete.dart and slightly modified | ||
return Align( | ||
alignment: optionsAlignment, | ||
child: Material( | ||
elevation: 4.0, | ||
child: ConstrainedBox( | ||
constraints: BoxConstraints( | ||
maxHeight: maxOptionsHeight, | ||
maxWidth: maxOptionsWidth, | ||
), | ||
child: ListView.builder( | ||
padding: EdgeInsets.zero, | ||
shrinkWrap: true, | ||
itemCount: options.length, | ||
itemBuilder: (BuildContext context, int index) { | ||
final T option = options.elementAt(index); | ||
return InkWell( | ||
onTap: () { | ||
onSelected(option); | ||
}, | ||
child: Builder(builder: (BuildContext context) { | ||
final bool highlight = | ||
AutocompleteHighlightedOption.of(context) == index; | ||
if (highlight) { | ||
// wrapped in a future to update the options after building | ||
Future( | ||
() => ref.read(selectedOptionProvider.notifier).state = | ||
option as String, | ||
); | ||
SchedulerBinding.instance.addPostFrameCallback( | ||
(Duration timeStamp) { | ||
Scrollable.ensureVisible(context, alignment: 0.5); | ||
}, debugLabel: 'AutocompleteOptions.ensureVisible'); | ||
} | ||
|
||
return Container( | ||
color: highlight ? Theme.of(context).focusColor : null, | ||
padding: const EdgeInsets.all(16.0), | ||
// do not show shortcut hint on mobile platforms | ||
child: highlight && !(Platform.isAndroid || Platform.isIOS) | ||
? Row( | ||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
children: [ | ||
Text(displayStringForOption(option)), | ||
const SizedBox(width: 20), | ||
const Text('Enter to fill') | ||
], | ||
) | ||
: Text(displayStringForOption(option)), | ||
); | ||
}), | ||
); | ||
}, | ||
), | ||
), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:jellyflix/components/custom_autocomplete_options.dart'; | ||
import 'package:jellyflix/providers/auth_provider.dart'; | ||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||
import 'package:jellyflix/providers/url_autocomplete_provider.dart'; | ||
|
||
// Courtesy of sevenrats for the shortcuts | ||
|
||
class UrlFieldInput extends ConsumerWidget { | ||
const UrlFieldInput({super.key, required this.serverAddress}); | ||
|
||
final TextEditingController serverAddress; | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final savedAddress = ref.read(allProfilesProvider); | ||
|
||
// Needed to get width of the URL text field | ||
// so we can assign that to the autocomplete width | ||
final urlTextFieldKey = GlobalKey(); | ||
|
||
// Create a custom FocusNode to listen for key events | ||
final focusNode = FocusNode(); | ||
|
||
// Attach the key listener for Tab key press | ||
focusNode.onKeyEvent = (FocusNode node, KeyEvent event) { | ||
if (event is KeyDownEvent && | ||
event.logicalKey == LogicalKeyboardKey.enter) { | ||
serverAddress.text = ref.read(selectedOptionProvider); | ||
return KeyEventResult.handled; | ||
} | ||
return KeyEventResult.ignored; | ||
}; | ||
|
||
return RawAutocomplete<String>( | ||
focusNode: focusNode, | ||
textEditingController: serverAddress, | ||
optionsBuilder: (TextEditingValue textEditingValue) { | ||
final result = savedAddress.valueOrNull | ||
?.where( | ||
(element) => | ||
element.serverAdress! | ||
.contains(textEditingValue.text.toLowerCase()) && | ||
// ensure that the option is not already filled | ||
element.serverAdress! != textEditingValue.text.toLowerCase(), | ||
) | ||
.map((e) => e.serverAdress!) | ||
.toSet(); // remove duplicates | ||
|
||
final options = result == null || result.isEmpty | ||
? ['http://', 'https://'].where((e) => | ||
e.contains(textEditingValue.text.toLowerCase()) && | ||
// ensure that the option is not already filled | ||
e != textEditingValue.text.toLowerCase()) | ||
: result; | ||
|
||
// clear options on change | ||
ref.invalidate(selectedOptionProvider); | ||
|
||
return options; | ||
}, | ||
onSelected: (option) => serverAddress.text = option, | ||
optionsViewOpenDirection: OptionsViewOpenDirection.down, | ||
fieldViewBuilder: (context, controller, focusNode, _) { | ||
return TextField( | ||
key: urlTextFieldKey, | ||
focusNode: focusNode, | ||
controller: controller, | ||
decoration: InputDecoration( | ||
border: const OutlineInputBorder(), | ||
labelText: AppLocalizations.of(context)!.serverAddress, | ||
hintText: 'http://', | ||
), | ||
); | ||
}, | ||
optionsViewBuilder: ( | ||
BuildContext context, | ||
void Function(String) onSelected, | ||
Iterable<String> options, | ||
) { | ||
RenderBox? renderBox; | ||
if (urlTextFieldKey.currentContext?.findRenderObject() != null) { | ||
renderBox = | ||
urlTextFieldKey.currentContext!.findRenderObject() as RenderBox; | ||
} | ||
|
||
return CustomAutocompleteOptions( | ||
displayStringForOption: RawAutocomplete.defaultStringForOption, | ||
onSelected: onSelected, | ||
options: options, | ||
openDirection: OptionsViewOpenDirection.down, | ||
maxOptionsHeight: 150, | ||
maxOptionsWidth: renderBox?.size.width ?? 300, | ||
); | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
|
||
final selectedOptionProvider = StateProvider<String>((ref) { | ||
return ''; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters