mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-15 10:15:58 -07:00
feat: Android TV support (#503)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
7ab8c015b9
commit
c299492d6d
168 changed files with 12019 additions and 3073 deletions
|
|
@ -352,49 +352,27 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
|||
child: Tooltip(
|
||||
message: librarySearchResults.nestedCurrentItem?.type.label(context) ??
|
||||
context.localized.library(1),
|
||||
child: InkWell(
|
||||
onTapUp: (details) async {
|
||||
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) {
|
||||
double left = details.globalPosition.dx;
|
||||
double top = details.globalPosition.dy;
|
||||
await showMenu(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(left, top, 40, 100),
|
||||
items: <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
child: Text(librarySearchResults.nestedCurrentItem?.type.label(context) ??
|
||||
context.localized.library(0))),
|
||||
itemCountWidget.toPopupMenuItem(useIcons: true),
|
||||
refreshAction.toPopupMenuItem(useIcons: true),
|
||||
itemViewAction.toPopupMenuItem(useIcons: true),
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
await showBottomSheetPill(
|
||||
context: context,
|
||||
content: (context, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: [
|
||||
itemCountWidget.toListItem(context, useIcons: true),
|
||||
refreshAction.toListItem(context, useIcons: true),
|
||||
itemViewAction.toListItem(context, useIcons: true),
|
||||
if (librarySearchResults.views.hasEnabled == true)
|
||||
showSavedFiltersDialogue.toPopupMenuItem(useIcons: true),
|
||||
if (itemActions.isNotEmpty) ItemActionDivider().toPopupMenuItem(),
|
||||
...itemActions.popupMenuItems(useIcons: true),
|
||||
showSavedFiltersDialogue.toListItem(context, useIcons: true),
|
||||
if (itemActions.isNotEmpty) ItemActionDivider().toListItem(context),
|
||||
...itemActions.listTileItems(context, useIcons: true),
|
||||
],
|
||||
elevation: 8.0,
|
||||
);
|
||||
} else {
|
||||
await showBottomSheetPill(
|
||||
context: context,
|
||||
content: (context, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: [
|
||||
itemCountWidget.toListItem(context, useIcons: true),
|
||||
refreshAction.toListItem(context, useIcons: true),
|
||||
itemViewAction.toListItem(context, useIcons: true),
|
||||
if (librarySearchResults.views.hasEnabled == true)
|
||||
showSavedFiltersDialogue.toPopupMenuItem(useIcons: true),
|
||||
if (itemActions.isNotEmpty) ItemActionDivider().toListItem(context),
|
||||
...itemActions.listTileItems(context, useIcons: true),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Icon(
|
||||
isFavorite
|
||||
? librarySearchResults.nestedCurrentItem?.type.selectedicon
|
||||
|
|
|
|||
|
|
@ -149,16 +149,19 @@ class _LibraryFilterChipsState extends ConsumerState<LibraryFilterChips> {
|
|||
),
|
||||
];
|
||||
|
||||
return Row(
|
||||
spacing: 4,
|
||||
children: chips.mapIndexed(
|
||||
(index, element) {
|
||||
final position = index == 0
|
||||
? PositionContext.first
|
||||
: (index == chips.length - 1 ? PositionContext.last : PositionContext.middle);
|
||||
return PositionProvider(position: position, child: element);
|
||||
},
|
||||
).toList(),
|
||||
return FocusTraversalGroup(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: chips.mapIndexed(
|
||||
(index, element) {
|
||||
final position = index == 0
|
||||
? PositionContext.first
|
||||
: (index == chips.length - 1 ? PositionContext.last : PositionContext.middle);
|
||||
return PositionProvider(position: position, child: element);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,11 +23,14 @@ import 'package:fladder/routes/auto_router.gr.dart';
|
|||
import 'package:fladder/screens/shared/media/poster_list_item.dart';
|
||||
import 'package:fladder/screens/shared/media/poster_widget.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/focus_provider.dart';
|
||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/refresh_state.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:fladder/util/theme_extensions.dart';
|
||||
import 'package:fladder/widgets/shared/ensure_visible.dart';
|
||||
import 'package:fladder/widgets/shared/grid_focus_traveler.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
|
||||
final libraryViewTypeProvider = StateProvider<LibraryViewTypes>((ref) {
|
||||
|
|
@ -63,12 +66,12 @@ class LibraryViews extends ConsumerWidget {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
sliver: SliverAnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: _getWidget(ref, context),
|
||||
child: _getWidget(context, ref),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getWidget(WidgetRef ref, BuildContext context) {
|
||||
Widget _getWidget(BuildContext context, WidgetRef ref) {
|
||||
final selected = ref.watch(librarySearchProvider(key!).select((value) => value.selectedPosters));
|
||||
final posterSizeMultiplier = ref.watch(clientSettingsProvider.select((value) => value.posterSize));
|
||||
final libraryProvider = ref.read(librarySearchProvider(key!).notifier);
|
||||
|
|
@ -111,21 +114,24 @@ class LibraryViews extends ConsumerWidget {
|
|||
switch (ref.watch(libraryViewTypeProvider)) {
|
||||
case LibraryViewTypes.grid:
|
||||
Widget createGrid(List<ItemBaseModel> items) {
|
||||
return SliverGrid.builder(
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
final cellWidth = (width / posterSize).floorToDouble();
|
||||
final crossAxisCount = ((width / cellWidth).floor()).clamp(2, 10);
|
||||
return GridFocusTraveler(
|
||||
itemCount: items.length,
|
||||
crossAxisCount: crossAxisCount,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: posterSize.clamp(2, double.maxFinite).toInt(),
|
||||
mainAxisSpacing: (8 * decimal) + 8,
|
||||
crossAxisSpacing: (8 * decimal) + 8,
|
||||
crossAxisCount: crossAxisCount,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
childAspectRatio: items.getMostCommonType.aspectRatio,
|
||||
),
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
itemBuilder: (other, selectedIndex, index) {
|
||||
final item = items[index];
|
||||
return PosterWidget(
|
||||
key: Key(item.id),
|
||||
poster: item,
|
||||
maxLines: 2,
|
||||
heroTag: true,
|
||||
subTitle: item.subTitle(sortingOptions),
|
||||
excludeActions: excludeActions,
|
||||
otherActions: otherActions(item),
|
||||
|
|
@ -134,6 +140,11 @@ class LibraryViews extends ConsumerWidget {
|
|||
onItemRemoved: (oldItem) => libraryProvider.removeFromPosters([oldItem.id]),
|
||||
onItemUpdated: (newItem) => libraryProvider.updateItem(newItem),
|
||||
onPressed: (action, item) async => onItemPressed(action, key, item, ref, context),
|
||||
onFocusChanged: (focus) {
|
||||
if (focus) {
|
||||
other.ensureVisible();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -165,16 +176,19 @@ class LibraryViews extends ConsumerWidget {
|
|||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final poster = items[index];
|
||||
return PosterListItem(
|
||||
poster: poster,
|
||||
selected: selected.contains(poster),
|
||||
excludeActions: excludeActions,
|
||||
otherActions: otherActions(poster),
|
||||
subTitle: poster.subTitle(sortingOptions),
|
||||
onUserDataChanged: (id, newData) => libraryProvider.updateUserData(id, newData),
|
||||
onItemRemoved: (oldItem) => libraryProvider.removeFromPosters([oldItem.id]),
|
||||
onItemUpdated: (newItem) => libraryProvider.updateItem(newItem),
|
||||
onPressed: (action, item) async => onItemPressed(action, key, item, ref, context),
|
||||
return FocusProvider(
|
||||
autoFocus: index == 0,
|
||||
child: PosterListItem(
|
||||
poster: poster,
|
||||
selected: selected.contains(poster),
|
||||
excludeActions: excludeActions,
|
||||
otherActions: otherActions(poster),
|
||||
subTitle: poster.subTitle(sortingOptions),
|
||||
onUserDataChanged: (id, newData) => libraryProvider.updateUserData(id, newData),
|
||||
onItemRemoved: (oldItem) => libraryProvider.removeFromPosters([oldItem.id]),
|
||||
onItemUpdated: (newItem) => libraryProvider.updateItem(newItem),
|
||||
onPressed: (action, item) async => onItemPressed(action, key, item, ref, context),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -228,7 +242,6 @@ class LibraryViews extends ConsumerWidget {
|
|||
aspectRatio: item.primaryRatio,
|
||||
selected: selected.contains(item),
|
||||
inlineTitle: true,
|
||||
heroTag: true,
|
||||
subTitle: item.subTitle(sortingOptions),
|
||||
excludeActions: excludeActions,
|
||||
otherActions: otherActions(group[index]),
|
||||
|
|
@ -257,7 +270,6 @@ class LibraryViews extends ConsumerWidget {
|
|||
aspectRatio: item.primaryRatio,
|
||||
selected: selected.contains(item),
|
||||
inlineTitle: true,
|
||||
heroTag: true,
|
||||
excludeActions: excludeActions,
|
||||
otherActions: otherActions(item),
|
||||
subTitle: item.subTitle(sortingOptions),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:page_transition/page_transition.dart';
|
|||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/library_search_provider.dart';
|
||||
import 'package:fladder/screens/shared/outlined_text_field.dart';
|
||||
import 'package:fladder/theme.dart';
|
||||
import 'package:fladder/util/debouncer.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
|
|
@ -87,7 +88,7 @@ class _SearchBarState extends ConsumerState<SuggestionSearchBar> {
|
|||
),
|
||||
child: child,
|
||||
),
|
||||
builder: (context, controller, focusNode) => TextField(
|
||||
builder: (context, controller, focusNode) => OutlinedTextField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
onSubmitted: (value) {
|
||||
|
|
@ -99,6 +100,7 @@ class _SearchBarState extends ConsumerState<SuggestionSearchBar> {
|
|||
isEmpty = value.isEmpty;
|
||||
});
|
||||
},
|
||||
placeHolder: widget.title ?? "${context.localized.search}...",
|
||||
decoration: InputDecoration(
|
||||
hintText: widget.title ?? "${context.localized.search}...",
|
||||
prefixIcon: const Icon(IconsaxPlusLinear.search_normal),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue