diff --git a/lib/providers/dashboard_provider.dart b/lib/providers/dashboard_provider.dart index 2d78a63..512c837 100644 --- a/lib/providers/dashboard_provider.dart +++ b/lib/providers/dashboard_provider.dart @@ -33,19 +33,21 @@ class DashboardNotifier extends StateNotifier { ImageType.banner, }.toList(); + final fieldsToFetch = [ + ItemFields.parentid, + ItemFields.mediastreams, + ItemFields.mediasources, + ItemFields.candelete, + ItemFields.candownload, + ItemFields.primaryimageaspectratio, + ItemFields.overview, + ItemFields.genres, + ]; + if (viewTypes.containsAny([CollectionType.movies, CollectionType.tvshows])) { final resumeVideoResponse = await api.usersUserIdItemsResumeGet( enableImageTypes: imagesToFetch, - fields: [ - ItemFields.parentid, - ItemFields.mediastreams, - ItemFields.mediasources, - ItemFields.candelete, - ItemFields.candownload, - ItemFields.primaryimageaspectratio, - ItemFields.overview, - ItemFields.genres, - ], + fields: fieldsToFetch, mediaTypes: [MediaType.video], enableTotalRecordCount: false, ); @@ -58,16 +60,7 @@ class DashboardNotifier extends StateNotifier { if (viewTypes.contains(CollectionType.music)) { final resumeAudioResponse = await api.usersUserIdItemsResumeGet( enableImageTypes: imagesToFetch, - fields: [ - ItemFields.parentid, - ItemFields.mediastreams, - ItemFields.mediasources, - ItemFields.candelete, - ItemFields.candownload, - ItemFields.primaryimageaspectratio, - ItemFields.overview, - ItemFields.genres, - ], + fields: fieldsToFetch, mediaTypes: [MediaType.audio], enableTotalRecordCount: false, ); @@ -80,16 +73,7 @@ class DashboardNotifier extends StateNotifier { if (viewTypes.contains(CollectionType.books)) { final resumeBookResponse = await api.usersUserIdItemsResumeGet( enableImageTypes: imagesToFetch, - fields: [ - ItemFields.parentid, - ItemFields.mediastreams, - ItemFields.mediasources, - ItemFields.candelete, - ItemFields.candownload, - ItemFields.primaryimageaspectratio, - ItemFields.overview, - ItemFields.genres, - ], + fields: fieldsToFetch, mediaTypes: [MediaType.book], enableTotalRecordCount: false, ); @@ -102,16 +86,7 @@ class DashboardNotifier extends StateNotifier { final nextResponse = await api.showsNextUpGet( nextUpDateCutoff: DateTime.now().subtract( ref.read(clientSettingsProvider.select((value) => value.nextUpDateCutoff ?? const Duration(days: 28)))), - fields: [ - ItemFields.parentid, - ItemFields.mediastreams, - ItemFields.mediasources, - ItemFields.candelete, - ItemFields.candownload, - ItemFields.primaryimageaspectratio, - ItemFields.overview, - ItemFields.genres, - ], + fields: fieldsToFetch, ); final next = nextResponse.body?.items diff --git a/lib/screens/dashboard/dashboard_screen.dart b/lib/screens/dashboard/dashboard_screen.dart index eb4d002..768130c 100644 --- a/lib/screens/dashboard/dashboard_screen.dart +++ b/lib/screens/dashboard/dashboard_screen.dart @@ -105,7 +105,16 @@ class _DashboardScreenState extends ConsumerState { valueListenable: selectedPoster, builder: (_, value, __) { return BackgroundImage( - items: value != null ? [value] : [...homeCarouselItems, ...dashboardData.nextUp, ...allResume], + images: (value != null + ? [value] + : [ + ...homeCarouselItems, + ...dashboardData.nextUp, + ...allResume, + ]) + .map((e) => e.images) + .nonNulls + .toList(), ); }, ), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 75dbfdd..18a3a25 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -14,7 +14,7 @@ import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/util/input_handler.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/string_extensions.dart'; -import 'package:fladder/widgets/keyboard/custom_keyboard.dart'; +import 'package:fladder/widgets/keyboard/slide_in_keyboard.dart'; import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart'; import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart'; import 'package:fladder/widgets/navigation_scaffold/navigation_scaffold.dart'; diff --git a/lib/screens/login/login_screen.dart b/lib/screens/login/login_screen.dart index 45837d4..8ad974e 100644 --- a/lib/screens/login/login_screen.dart +++ b/lib/screens/login/login_screen.dart @@ -13,7 +13,7 @@ import 'package:fladder/screens/login/login_user_grid.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/screens/shared/fladder_logo.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; -import 'package:fladder/widgets/keyboard/custom_keyboard.dart'; +import 'package:fladder/widgets/keyboard/slide_in_keyboard.dart'; import 'package:fladder/widgets/navigation_scaffold/components/fladder_app_bar.dart'; @RoutePage() diff --git a/lib/screens/shared/media/components/poster_image.dart b/lib/screens/shared/media/components/poster_image.dart index 2d6b540..70a741e 100644 --- a/lib/screens/shared/media/components/poster_image.dart +++ b/lib/screens/shared/media/components/poster_image.dart @@ -251,7 +251,7 @@ class _PosterImageState extends ConsumerState { ), ), if ((widget.poster.unPlayedItemCount != null && widget.poster is SeriesModel) || - (widget.poster.playAble && !widget.poster.unWatched)) + (widget.poster.playAble && !widget.poster.unWatched && widget.poster is! PhotoAlbumModel)) IgnorePointer( child: Align( alignment: Alignment.topRight, diff --git a/lib/screens/shared/outlined_text_field.dart b/lib/screens/shared/outlined_text_field.dart index 2e3d3c7..518b73b 100644 --- a/lib/screens/shared/outlined_text_field.dart +++ b/lib/screens/shared/outlined_text_field.dart @@ -11,7 +11,7 @@ import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/theme.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/focus_provider.dart'; -import 'package:fladder/widgets/keyboard/custom_keyboard.dart'; +import 'package:fladder/widgets/keyboard/slide_in_keyboard.dart'; import 'package:fladder/widgets/shared/ensure_visible.dart'; class OutlinedTextField extends ConsumerStatefulWidget { @@ -71,6 +71,7 @@ class OutlinedTextField extends ConsumerStatefulWidget { } class _OutlinedTextFieldState extends ConsumerState { + late final controller = widget.controller ?? TextEditingController(); late final FocusNode _textFocus = widget.focusNode ?? FocusNode(); late final FocusNode _wrapperFocus = FocusNode() ..addListener(() { @@ -130,7 +131,7 @@ class _OutlinedTextFieldState extends ConsumerState { ref.watch(clientSettingsProvider.select((value) => !value.useSystemIME)); final textField = TextField( - controller: widget.controller, + controller: controller, onChanged: widget.onChanged, focusNode: _textFocus, onTap: widget.onTap, @@ -199,26 +200,28 @@ class _OutlinedTextFieldState extends ConsumerState { ignoring: widget.enabled == false, child: KeyboardListener( focusNode: _wrapperFocus, - onKeyEvent: (KeyEvent event) { + onKeyEvent: (KeyEvent event) async { if (keyboardFocus || AdaptiveLayout.inputDeviceOf(context) != InputDevice.dPad) return; if (event is KeyDownEvent && acceptKeys.contains(event.logicalKey)) { if (_textFocus.hasFocus) { _wrapperFocus.requestFocus(); } else if (_wrapperFocus.hasFocus) { if (useCustomKeyboard) { - CustomKeyboard.of(context).openKeyboard( - textField, - onClosed: () { - setState(() { - keyboardFocus = false; - }); - _wrapperFocus.requestFocus(); + await openKeyboard( + context, + controller, + inputType: widget.keyboardType, + inputAction: widget.textInputAction, + searchQuery: widget.searchQuery, + onChanged: () { + widget.onChanged?.call(controller.text); }, - query: widget.searchQuery, ); + widget.onSubmitted?.call(controller.text); setState(() { - keyboardFocus = true; + keyboardFocus = false; }); + _wrapperFocus.requestFocus(); } else { _textFocus.requestFocus(); } diff --git a/lib/util/adaptive_layout/adaptive_layout.dart b/lib/util/adaptive_layout/adaptive_layout.dart index 8962d1d..b073506 100644 --- a/lib/util/adaptive_layout/adaptive_layout.dart +++ b/lib/util/adaptive_layout/adaptive_layout.dart @@ -12,6 +12,7 @@ import 'package:fladder/util/debug_banner.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/poster_defaults.dart'; import 'package:fladder/util/resolution_checker.dart'; +import 'package:fladder/widgets/keyboard/slide_in_keyboard.dart'; enum InputDevice { touch, @@ -209,32 +210,40 @@ class _AdaptiveLayoutBuilderState extends ConsumerState { final mediaQuery = MediaQuery.of(context); - return MediaQuery( - data: mediaQuery.copyWith( - padding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null, - viewPadding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null, - ), - child: AdaptiveLayout( - data: currentLayout.copyWith( - viewSize: selectedViewSize, - layoutMode: selectedLayoutMode, - inputDevice: input, - platform: currentPlatform, - isDesktop: isDesktop, - controller: scrollControllers, - posterDefaults: posterDefaults, - ), - child: Builder( - builder: (context) => isDesktop - ? ResolutionChecker( - child: - widget.adaptiveLayout == null ? DebugBanner(child: widget.child(context)) : widget.child(context), - ) - : widget.adaptiveLayout == null - ? DebugBanner(child: widget.child(context)) - : widget.child(context), - ), - ), + return ValueListenableBuilder( + valueListenable: isKeyboardOpen, + builder: (context, value, child) { + return MediaQuery( + data: mediaQuery.copyWith( + padding: (isDesktop || kIsWeb + ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) + : mediaQuery.padding), + viewPadding: isDesktop || kIsWeb ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) : null, + ), + child: AdaptiveLayout( + data: currentLayout.copyWith( + viewSize: selectedViewSize, + layoutMode: selectedLayoutMode, + inputDevice: input, + platform: currentPlatform, + isDesktop: isDesktop, + controller: scrollControllers, + posterDefaults: posterDefaults, + ), + child: Builder( + builder: (context) => isDesktop + ? ResolutionChecker( + child: widget.adaptiveLayout == null + ? DebugBanner(child: widget.child(context)) + : widget.child(context), + ) + : widget.adaptiveLayout == null + ? DebugBanner(child: widget.child(context)) + : widget.child(context), + ), + ), + ); + }, ); } } diff --git a/lib/util/focus_provider.dart b/lib/util/focus_provider.dart index d2daf22..15322ef 100644 --- a/lib/util/focus_provider.dart +++ b/lib/util/focus_provider.dart @@ -141,7 +141,6 @@ class FocusButtonState extends State { cursor: SystemMouseCursors.click, onEnter: (event) => onHover.value = true, onExit: (event) => onHover.value = false, - hitTestBehavior: HitTestBehavior.translucent, child: Focus( focusNode: focusNode, autofocus: widget.autoFocus, @@ -160,7 +159,13 @@ class FocusButtonState extends State { onTap: widget.onTap, onSecondaryTapDown: widget.onSecondaryTapDown, onLongPress: widget.onLongPress, - child: widget.child, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: widget.borderRadius ?? FladderTheme.smallShape.borderRadius, + ), + child: widget.child, + ), ), Positioned.fill( child: ValueListenableBuilder( diff --git a/lib/widgets/keyboard/custom_keyboard.dart b/lib/widgets/keyboard/slide_in_keyboard.dart similarity index 62% rename from lib/widgets/keyboard/custom_keyboard.dart rename to lib/widgets/keyboard/slide_in_keyboard.dart index d581949..93ad621 100644 --- a/lib/widgets/keyboard/custom_keyboard.dart +++ b/lib/widgets/keyboard/slide_in_keyboard.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:iconsax_plus/iconsax_plus.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart'; @@ -9,128 +10,124 @@ import 'package:fladder/util/focus_provider.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/widgets/keyboard/alpha_numeric_keyboard.dart'; -class CustomKeyboard extends InheritedWidget { - final CustomKeyboardState state; +ValueNotifier isKeyboardOpen = ValueNotifier(false); - const CustomKeyboard({ - required this.state, - required super.child, +double keyboardWidthFactor = 0.2; + +class CustomKeyboardWrapper extends StatelessWidget { + final Widget child; + const CustomKeyboardWrapper({ + required this.child, super.key, }); - static CustomKeyboardState of(BuildContext context) { - final inherited = context.dependOnInheritedWidgetOfExactType(); - assert(inherited != null, 'No CustomKeyboard found in context'); - return inherited!.state; - } - - @override - bool updateShouldNotify(CustomKeyboard oldWidget) => state != oldWidget.state; -} - -class CustomKeyboardWrapper extends StatefulWidget { - final Widget child; - - const CustomKeyboardWrapper({super.key, required this.child}); - - @override - CustomKeyboardState createState() => CustomKeyboardState(); -} - -class CustomKeyboardState extends State { - bool _isOpen = false; - TextEditingController? _controller; - TextField? _textField; - - bool get isOpen => _isOpen; - - VoidCallback? onCloseCall; - - FutureOr> Function(String query)? searchQuery; - - void openKeyboard( - TextField textField, { - VoidCallback? onClosed, - FutureOr> Function(String query)? query, - }) { - onCloseCall = onClosed; - - searchQuery = query; - - setState(() { - _isOpen = true; - _textField = textField; - _controller = textField.controller; - }); - _controller?.addListener(() { - _textField?.onChanged?.call(_controller?.text ?? ""); - }); - } - - void closeKeyboard() { - setState(() { - _isOpen = false; - }); - onCloseCall?.call(); - onCloseCall = null; - if (_controller != null) { - _textField?.onSubmitted?.call(_controller?.text ?? ""); - _textField?.onEditingComplete?.call(); - _controller?.removeListener(() { - _textField?.onChanged?.call(_controller?.text ?? ""); - }); - } - } - - @override - void dispose() { - super.dispose(); - _controller?.dispose(); - } - @override Widget build(BuildContext context) { - final mq = MediaQuery.of(context); - return BackButtonListener( - onBackButtonPressed: () async { - if (!context.mounted) return false; - if (_isOpen && context.mounted) { - closeKeyboard(); - return true; - } else { - return false; - } - }, - child: CustomKeyboard( - state: this, + return Container( + color: Theme.of(context).colorScheme.surface, + child: ValueListenableBuilder( + valueListenable: isKeyboardOpen, + builder: (context, value, _) { + return AnimatedFractionallySizedBox( + duration: const Duration(milliseconds: 175), + widthFactor: value ? 1.0 - keyboardWidthFactor : 1.0, + heightFactor: 1.0, + alignment: Alignment.centerRight, + child: child, + ); + }, + ), + ); + } +} + +Future openKeyboard( + BuildContext context, + TextEditingController controller, { + TextInputType? inputType, + TextInputAction? inputAction, + FutureOr> Function(String query)? searchQuery, + VoidCallback? onChanged, +}) async { + isKeyboardOpen.value = true; + await showGeneralDialog( + context: context, + transitionDuration: const Duration(milliseconds: 175), + barrierDismissible: true, + barrierColor: Colors.transparent, + barrierLabel: 'Custom keyboard', + useRootNavigator: true, + fullscreenDialog: true, + transitionBuilder: (context, animation, secondaryAnimation, child) { + return SlideTransition( + position: Tween(begin: const Offset(-1, 0), end: const Offset(0, 0)).animate( + animation, + ), + child: child, + ); + }, + pageBuilder: (context, animation1, animation2) { + return Align( + alignment: Alignment.centerLeft, + child: _SlideInKeyboard( + controller: controller, + onChanged: onChanged ?? () {}, + onClose: () { + context.router.pop(); + isKeyboardOpen.value = false; + return null; + }, + inputType: inputType, + inputAction: inputAction, + searchQuery: searchQuery, + ), + ); + }, + ); + isKeyboardOpen.value = false; + return null; +} + +class _SlideInKeyboard extends StatefulWidget { + final TextEditingController controller; + final Function() onChanged; + final Function() onClose; + final TextInputType? inputType; + final TextInputAction? inputAction; + final FutureOr> Function(String query)? searchQuery; + const _SlideInKeyboard({ + required this.controller, + required this.onChanged, + required this.onClose, + this.inputType, + this.inputAction, + this.searchQuery, + }); + + @override + State<_SlideInKeyboard> createState() => __SlideInKeyboardState(); +} + +class __SlideInKeyboardState extends State<_SlideInKeyboard> { + @override + Widget build(BuildContext context) { + final padding = MediaQuery.paddingOf(context); + final width = MediaQuery.sizeOf(context).width * keyboardWidthFactor; + return FractionallySizedBox( + widthFactor: keyboardWidthFactor, + heightFactor: 1.0, + child: Padding( + padding: padding.copyWith(left: (padding.left - width).clamp(0, padding.left)), child: Container( + height: double.infinity, color: Theme.of(context).colorScheme.surface, - alignment: Alignment.center, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - AnimatedSize( - duration: const Duration(milliseconds: 125), - child: _isOpen - ? SizedBox( - width: mq.size.width * 0.20, - height: double.infinity, - child: Material( - color: Theme.of(context).colorScheme.surface, - child: _CustomKeyboardView( - controller: _controller!, - keyboardType: _textField?.keyboardType, - keyboardActionType: _textField?.textInputAction, - onClose: closeKeyboard, - onChanged: () => _textField?.onChanged?.call(_controller?.text ?? ""), - searchQuery: searchQuery, - ), - ), - ) - : const SizedBox.shrink(), - ), - Expanded(child: widget.child), - ], + child: _CustomKeyboardView( + controller: widget.controller, + onChanged: widget.onChanged, + onClose: widget.onClose, + keyboardType: widget.inputType, + keyboardActionType: widget.inputAction, + searchQuery: widget.searchQuery, ), ), ), @@ -187,7 +184,7 @@ class _CustomKeyboardViewState extends State<_CustomKeyboardView> { node: scope, autofocus: true, child: Padding( - padding: const EdgeInsets.all(12.0).add(MediaQuery.paddingOf(context)), + padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 16, @@ -301,7 +298,10 @@ class _SearchResults extends StatelessWidget { spacing: 8, children: [ const Icon(IconsaxPlusLinear.search_status_1), - Text(context.localized.noResults), + Text( + context.localized.noResults, + style: Theme.of(context).textTheme.titleLarge, + ), ], ), ), diff --git a/lib/widgets/navigation_scaffold/components/background_image.dart b/lib/widgets/navigation_scaffold/components/background_image.dart index 793b154..b872515 100644 --- a/lib/widgets/navigation_scaffold/components/background_image.dart +++ b/lib/widgets/navigation_scaffold/components/background_image.dart @@ -31,7 +31,7 @@ class _BackgroundImageState extends ConsumerState { @override void didUpdateWidget(covariant BackgroundImage oldWidget) { super.didUpdateWidget(oldWidget); - if (!oldWidget.items.equals(widget.items)) { + if (!oldWidget.items.equals(widget.items) || !oldWidget.images.equals(widget.images)) { updateItems(); } } @@ -46,7 +46,7 @@ class _BackgroundImageState extends ConsumerState { ImageData? newImage; if (widget.images.isNotEmpty) { - newImage = widget.images.shuffled().firstOrNull?.primary; + newImage = widget.images.shuffled().firstOrNull?.randomBackDrop; } else if (widget.items.isNotEmpty) { final randomItem = widget.items.shuffled().firstOrNull; final itemId = switch (randomItem?.type) {