mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
chore: Cleanup and performance improvements for posters
This commit is contained in:
parent
9e5537089b
commit
b28a409757
7 changed files with 437 additions and 456 deletions
|
|
@ -18,6 +18,7 @@ class FlatButton extends ConsumerWidget {
|
||||||
final double elevation;
|
final double elevation;
|
||||||
final bool showFeedback;
|
final bool showFeedback;
|
||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
|
final List<Widget> overlays;
|
||||||
const FlatButton({
|
const FlatButton({
|
||||||
this.child,
|
this.child,
|
||||||
this.onFocusChange,
|
this.onFocusChange,
|
||||||
|
|
@ -32,6 +33,7 @@ class FlatButton extends ConsumerWidget {
|
||||||
this.elevation = 0,
|
this.elevation = 0,
|
||||||
this.showFeedback = true,
|
this.showFeedback = true,
|
||||||
this.clipBehavior = Clip.none,
|
this.clipBehavior = Clip.none,
|
||||||
|
this.overlays = const [],
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -67,6 +69,7 @@ class FlatButton extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
...overlays,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ class _CarouselBannerState extends ConsumerState<CarouselBanner> {
|
||||||
final opacity = (constraints.maxWidth / maxExtent);
|
final opacity = (constraints.maxWidth / maxExtent);
|
||||||
return FocusButton(
|
return FocusButton(
|
||||||
onTap: () => widget.items[index].navigateTo(context),
|
onTap: () => widget.items[index].navigateTo(context),
|
||||||
|
borderRadius: border,
|
||||||
onFocusChanged: (hover) {
|
onFocusChanged: (hover) {
|
||||||
context.ensureVisible();
|
context.ensureVisible();
|
||||||
},
|
},
|
||||||
|
|
@ -155,9 +156,6 @@ class _CarouselBannerState extends ConsumerState<CarouselBanner> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ExcludeFocus(
|
|
||||||
child: BannerPlayButton(item: widget.items[index]),
|
|
||||||
),
|
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -171,6 +169,11 @@ class _CarouselBannerState extends ConsumerState<CarouselBanner> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
overlays: [
|
||||||
|
ExcludeFocus(
|
||||||
|
child: BannerPlayButton(item: widget.items[index]),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// poster_image.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
@ -54,7 +55,7 @@ class PosterImage extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final posterRadius = FladderTheme.smallShape.borderRadius;
|
final radius = FladderTheme.smallShape.borderRadius;
|
||||||
final padding = const EdgeInsets.all(5);
|
final padding = const EdgeInsets.all(5);
|
||||||
final myKey = key ?? UniqueKey();
|
final myKey = key ?? UniqueKey();
|
||||||
|
|
||||||
|
|
@ -74,240 +75,188 @@ class PosterImage extends ConsumerWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFocusChanged: onFocusChanged,
|
onFocusChanged: onFocusChanged,
|
||||||
onLongPress: () {
|
onLongPress: () => _showBottomSheet(context, ref),
|
||||||
showBottomSheetPill(
|
onSecondaryTapDown: (details) => _showContextMenu(context, ref, details.globalPosition),
|
||||||
context: context,
|
child: Container(
|
||||||
item: poster,
|
color: Theme.of(context).cardColor,
|
||||||
content: (scrollContext, scrollController) => ListView(
|
child: FladderImage(
|
||||||
shrinkWrap: true,
|
image: primaryPosters
|
||||||
controller: scrollController,
|
? poster.images?.primary
|
||||||
children: poster
|
: poster.getPosters?.primary ?? poster.getPosters?.backDrop?.lastOrNull,
|
||||||
.generateActions(
|
placeHolder: PosterPlaceholder(item: poster),
|
||||||
context,
|
|
||||||
ref,
|
|
||||||
exclude: excludeActions,
|
|
||||||
otherActions: otherActions,
|
|
||||||
onUserDataChanged: onUserDataChanged,
|
|
||||||
onDeleteSuccesFully: onItemRemoved,
|
|
||||||
onItemUpdated: onItemUpdated,
|
|
||||||
)
|
|
||||||
.listTileItems(scrollContext, useIcons: true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onSecondaryTapDown: (details) async {
|
|
||||||
Offset localPosition = details.globalPosition;
|
|
||||||
RelativeRect position =
|
|
||||||
RelativeRect.fromLTRB(localPosition.dx, localPosition.dy, localPosition.dx, localPosition.dy);
|
|
||||||
await showMenu(
|
|
||||||
context: context,
|
|
||||||
position: position,
|
|
||||||
items: poster
|
|
||||||
.generateActions(
|
|
||||||
context,
|
|
||||||
ref,
|
|
||||||
exclude: excludeActions,
|
|
||||||
otherActions: otherActions,
|
|
||||||
onUserDataChanged: onUserDataChanged,
|
|
||||||
onDeleteSuccesFully: onItemRemoved,
|
|
||||||
onItemUpdated: onItemUpdated,
|
|
||||||
)
|
|
||||||
.popupMenuItems(useIcons: true),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Card(
|
|
||||||
elevation: 6,
|
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
side: BorderSide(
|
|
||||||
width: 1.0,
|
|
||||||
color: Colors.white.withValues(alpha: 0.10),
|
|
||||||
),
|
|
||||||
borderRadius: posterRadius,
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
FladderImage(
|
|
||||||
image: primaryPosters
|
|
||||||
? poster.images?.primary
|
|
||||||
: poster.getPosters?.primary ?? poster.getPosters?.backDrop?.lastOrNull,
|
|
||||||
placeHolder: PosterPlaceholder(item: poster),
|
|
||||||
),
|
|
||||||
if (poster.userData.progress > 0 && poster.type == FladderItemType.book)
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: padding,
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(5.5),
|
|
||||||
child: Text(
|
|
||||||
context.localized.page((poster as BookModel).currentPage),
|
|
||||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (selected == true)
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withValues(alpha: 0.15),
|
|
||||||
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
|
||||||
borderRadius: posterRadius,
|
|
||||||
),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
width: double.infinity,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
child: Text(
|
|
||||||
poster.name,
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium
|
|
||||||
?.copyWith(color: Theme.of(context).colorScheme.onPrimary, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (poster.userData.isFavourite)
|
|
||||||
const Row(
|
|
||||||
children: [
|
|
||||||
StatusCard(
|
|
||||||
color: Colors.red,
|
|
||||||
child: Icon(
|
|
||||||
IconsaxPlusBold.heart,
|
|
||||||
size: 21,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if ((poster.userData.progress > 0 && poster.userData.progress < 100) &&
|
|
||||||
poster.type != FladderItemType.book) ...{
|
|
||||||
const SizedBox(
|
|
||||||
height: 4,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 3).copyWith(bottom: 3).add(padding),
|
|
||||||
child: Card(
|
|
||||||
color: Colors.transparent,
|
|
||||||
elevation: 3,
|
|
||||||
shadowColor: Colors.transparent,
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
minHeight: 7.5,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.5),
|
|
||||||
value: poster.userData.progress / 100,
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (inlineTitle)
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Text(
|
|
||||||
poster.title.maxLength(limitTo: 25),
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if ((poster.unPlayedItemCount != null && poster is SeriesModel) ||
|
|
||||||
(poster.playAble && !poster.unWatched && poster is! PhotoAlbumModel))
|
|
||||||
IgnorePointer(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.topRight,
|
|
||||||
child: StatusCard(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
useFittedBox: poster.unPlayedItemCount != 0,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
child: poster.unPlayedItemCount != 0
|
|
||||||
? Container(
|
|
||||||
constraints: const BoxConstraints(minWidth: 16),
|
|
||||||
child: Text(
|
|
||||||
poster.userData.unPlayedItemCount.toString(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
overflow: TextOverflow.visible,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
Icons.check_rounded,
|
|
||||||
size: 20,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (poster.overview.runTime != null &&
|
|
||||||
((poster is PhotoModel) && (poster as PhotoModel).internalType == FladderItemType.video)) ...{
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.topRight,
|
|
||||||
child: Padding(
|
|
||||||
padding: padding,
|
|
||||||
child: Card(
|
|
||||||
elevation: 5,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
poster.overview.runTime.humanizeSmall ?? "",
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
Icon(
|
|
||||||
Icons.play_arrow_rounded,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
overlays: [
|
overlays: [
|
||||||
//Poster Button
|
if (poster.userData.progress > 0 && poster.type == FladderItemType.book)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(5.5),
|
||||||
|
child: Text(
|
||||||
|
context.localized.page((poster as BookModel).currentPage),
|
||||||
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (selected == true)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
|
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
||||||
|
borderRadius: radius,
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
child: Text(
|
||||||
|
poster.name,
|
||||||
|
maxLines: 2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium
|
||||||
|
?.copyWith(color: Theme.of(context).colorScheme.onPrimary, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (poster.userData.isFavourite)
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
StatusCard(
|
||||||
|
color: Colors.red,
|
||||||
|
child: Icon(
|
||||||
|
IconsaxPlusBold.heart,
|
||||||
|
size: 21,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if ((poster.userData.progress > 0 && poster.userData.progress < 100) &&
|
||||||
|
poster.type != FladderItemType.book) ...{
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 3).copyWith(bottom: 3).add(padding),
|
||||||
|
child: Card(
|
||||||
|
color: Colors.transparent,
|
||||||
|
elevation: 3,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
minHeight: 7.5,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.5),
|
||||||
|
value: poster.userData.progress / 100,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (inlineTitle)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Text(
|
||||||
|
poster.title.maxLength(limitTo: 25),
|
||||||
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (poster is! PhotoAlbumModel && (poster.unPlayedItemCount != null && poster is SeriesModel) ||
|
||||||
|
(poster.playAble && !poster.unWatched))
|
||||||
|
IgnorePointer(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: StatusCard(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
useFittedBox: poster.unPlayedItemCount != 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: poster.unPlayedItemCount != 0
|
||||||
|
? Container(
|
||||||
|
constraints: const BoxConstraints(minWidth: 16),
|
||||||
|
child: Text(
|
||||||
|
poster.userData.unPlayedItemCount.toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.check_rounded,
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (poster.overview.runTime != null &&
|
||||||
|
((poster is PhotoModel) && (poster as PhotoModel).internalType == FladderItemType.video)) ...{
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Card(
|
||||||
|
elevation: 5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
poster.overview.runTime.humanizeSmall ?? "",
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 2),
|
||||||
|
Icon(
|
||||||
|
Icons.play_arrow_rounded,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
focusedOverlays: [
|
||||||
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) ...[
|
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) ...[
|
||||||
// Play Button
|
// Play Button
|
||||||
if (poster.playAble)
|
if (poster.playAble)
|
||||||
|
|
@ -352,4 +301,45 @@ class PosterImage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showBottomSheet(BuildContext context, WidgetRef ref) {
|
||||||
|
showBottomSheetPill(
|
||||||
|
context: context,
|
||||||
|
item: poster,
|
||||||
|
content: (scrollContext, scrollController) => ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
controller: scrollController,
|
||||||
|
children: poster
|
||||||
|
.generateActions(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
exclude: excludeActions,
|
||||||
|
otherActions: otherActions,
|
||||||
|
onUserDataChanged: onUserDataChanged,
|
||||||
|
onDeleteSuccesFully: onItemRemoved,
|
||||||
|
onItemUpdated: onItemUpdated,
|
||||||
|
)
|
||||||
|
.listTileItems(scrollContext, useIcons: true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showContextMenu(BuildContext context, WidgetRef ref, Offset globalPos) async {
|
||||||
|
final position = RelativeRect.fromLTRB(globalPos.dx, globalPos.dy, globalPos.dx, globalPos.dy);
|
||||||
|
await showMenu(
|
||||||
|
context: context,
|
||||||
|
position: position,
|
||||||
|
items: poster
|
||||||
|
.generateActions(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
exclude: excludeActions,
|
||||||
|
otherActions: otherActions,
|
||||||
|
onUserDataChanged: onUserDataChanged,
|
||||||
|
onDeleteSuccesFully: onItemRemoved,
|
||||||
|
onItemUpdated: onItemUpdated,
|
||||||
|
)
|
||||||
|
.popupMenuItems(useIcons: true),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,9 +112,7 @@ class _EpisodePosterState extends ConsumerState<EpisodePosters> {
|
||||||
return ListView(
|
return ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
children: [
|
children: episode.generateActions(context, ref).listTileItems(context, useIcons: true).toList(),
|
||||||
...episode.generateActions(context, ref).listTileItems(context, useIcons: true),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -165,116 +163,109 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Card(
|
child: FocusButton(
|
||||||
child: Stack(
|
onTap: onTap,
|
||||||
fit: StackFit.expand,
|
onLongPress: onLongPress,
|
||||||
children: [
|
onSecondaryTapDown: (details) async {
|
||||||
FocusButton(
|
Offset localPosition = details.globalPosition;
|
||||||
onTap: onTap,
|
RelativeRect position =
|
||||||
onLongPress: onLongPress,
|
RelativeRect.fromLTRB(localPosition.dx, localPosition.dy, localPosition.dx, localPosition.dy);
|
||||||
onSecondaryTapDown: (details) async {
|
await showMenu(context: context, position: position, items: actions.popupMenuItems(useIcons: true));
|
||||||
Offset localPosition = details.globalPosition;
|
},
|
||||||
RelativeRect position =
|
child: Hero(
|
||||||
RelativeRect.fromLTRB(localPosition.dx, localPosition.dy, localPosition.dx, localPosition.dy);
|
tag: heroTag ?? UniqueKey(),
|
||||||
|
child: FladderImage(
|
||||||
await showMenu(
|
image: !episodeAvailable ? episode.parentImages?.primary : episode.images?.primary,
|
||||||
context: context, position: position, items: actions.popupMenuItems(useIcons: true));
|
placeHolder: placeHolder,
|
||||||
},
|
blurOnly: !episodeAvailable
|
||||||
child: Hero(
|
? true
|
||||||
tag: heroTag ?? UniqueKey(),
|
: ref.watch(clientSettingsProvider.select((value) => value.blurUpcomingEpisodes))
|
||||||
child: FladderImage(
|
? blur
|
||||||
image: !episodeAvailable ? episode.parentImages?.primary : episode.images?.primary,
|
: false,
|
||||||
placeHolder: placeHolder,
|
decodeHeight: 250,
|
||||||
blurOnly: !episodeAvailable
|
),
|
||||||
? true
|
),
|
||||||
: ref.watch(clientSettingsProvider.select((value) => value.blurUpcomingEpisodes))
|
overlays: [
|
||||||
? blur
|
if (!episodeAvailable)
|
||||||
: false,
|
Align(
|
||||||
decodeHeight: 250,
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Card(
|
||||||
|
color: episode.status.color,
|
||||||
|
elevation: 3,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
episode.status.label(context),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
overlays: [
|
),
|
||||||
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer && actions.isNotEmpty)
|
Align(
|
||||||
ExcludeFocus(
|
alignment: Alignment.topRight,
|
||||||
child: Align(
|
child: Row(
|
||||||
alignment: Alignment.bottomRight,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
child: PopupMenuButton(
|
children: [
|
||||||
tooltip: context.localized.options,
|
switch (syncedDetails) {
|
||||||
icon: const Icon(
|
AsyncValue<SyncedItem?>(:final value) => Builder(
|
||||||
Icons.more_vert,
|
builder: (context) {
|
||||||
color: Colors.white,
|
if (value == null) {
|
||||||
),
|
return const SizedBox.shrink();
|
||||||
itemBuilder: (context) => actions.popupMenuItems(useIcons: true),
|
}
|
||||||
),
|
return StatusCard(
|
||||||
|
child: SyncButton(item: episode, syncedItem: value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
if (episode.userData.isFavourite)
|
||||||
|
const StatusCard(
|
||||||
|
color: Colors.red,
|
||||||
|
child: Icon(
|
||||||
|
Icons.favorite_rounded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (episode.userData.played)
|
||||||
|
StatusCard(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.check_rounded,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!episodeAvailable)
|
),
|
||||||
Align(
|
if ((episode.userData.progress) > 0)
|
||||||
alignment: Alignment.bottomLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Card(
|
|
||||||
color: episode.status.color,
|
|
||||||
elevation: 3,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
episode.status.label(context),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Row(
|
child: LinearProgressIndicator(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
minHeight: 6,
|
||||||
children: [
|
backgroundColor: Colors.black.withValues(alpha: 0.75),
|
||||||
switch (syncedDetails) {
|
value: episode.userData.progress / 100,
|
||||||
AsyncValue<SyncedItem?>(:final value) => Builder(
|
|
||||||
builder: (context) {
|
|
||||||
if (value == null) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return StatusCard(
|
|
||||||
child: SyncButton(item: episode, syncedItem: value),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
if (episode.userData.isFavourite)
|
|
||||||
const StatusCard(
|
|
||||||
color: Colors.red,
|
|
||||||
child: Icon(
|
|
||||||
Icons.favorite_rounded,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (episode.userData.played)
|
|
||||||
StatusCard(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
child: const Icon(
|
|
||||||
Icons.check_rounded,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if ((episode.userData.progress) > 0)
|
],
|
||||||
Align(
|
focusedOverlays: [
|
||||||
alignment: Alignment.bottomCenter,
|
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer && actions.isNotEmpty)
|
||||||
child: LinearProgressIndicator(
|
ExcludeFocus(
|
||||||
minHeight: 6,
|
child: Align(
|
||||||
backgroundColor: Colors.black.withValues(alpha: 0.75),
|
alignment: Alignment.bottomRight,
|
||||||
value: episode.userData.progress / 100,
|
child: PopupMenuButton(
|
||||||
|
tooltip: context.localized.options,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.more_vert,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
itemBuilder: (context) => actions.popupMenuItems(useIcons: true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showLabel) ...{
|
if (showLabel) ...{
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||||
import 'package:fladder/models/items/item_shared_models.dart';
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
import 'package:fladder/screens/details_screens/person_detail_screen.dart';
|
import 'package:fladder/screens/details_screens/person_detail_screen.dart';
|
||||||
|
import 'package:fladder/theme.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.dart';
|
||||||
import 'package:fladder/util/focus_provider.dart';
|
import 'package:fladder/util/focus_provider.dart';
|
||||||
|
|
@ -26,10 +26,11 @@ class PeopleRow extends ConsumerWidget {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 75,
|
height: 75,
|
||||||
width: 75,
|
width: 75,
|
||||||
child: Card(
|
child: Container(
|
||||||
elevation: 5,
|
decoration: BoxDecoration(
|
||||||
shadowColor: Colors.transparent,
|
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||||
color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.50),
|
color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.50),
|
||||||
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
name.getInitials(),
|
name.getInitials(),
|
||||||
|
|
@ -55,26 +56,24 @@ class PeopleRow extends ConsumerWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: OpenContainer(
|
child: Container(
|
||||||
closedColor: Colors.transparent,
|
decoration: BoxDecoration(
|
||||||
closedElevation: 5,
|
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||||
openElevation: 0,
|
color: Theme.of(context).cardTheme.color?.withValues(alpha: 0.1),
|
||||||
closedShape: const RoundedRectangleBorder(),
|
),
|
||||||
transitionType: ContainerTransitionType.fadeThrough,
|
child: FocusButton(
|
||||||
openColor: Colors.transparent,
|
onTap: () => Navigator.of(context).push(
|
||||||
tappable: false,
|
MaterialPageRoute(
|
||||||
closedBuilder: (context, action) => Card(
|
builder: (context) => PersonDetailScreen(
|
||||||
child: FocusButton(
|
person: person,
|
||||||
onTap: () => action(),
|
),
|
||||||
child: FladderImage(
|
|
||||||
image: person.image,
|
|
||||||
placeHolder: placeHolder(person.name),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
child: FladderImage(
|
||||||
openBuilder: (context, action) => PersonDetailScreen(
|
image: person.image,
|
||||||
person: person,
|
placeHolder: placeHolder(person.name),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -80,20 +80,42 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Card(
|
child: Hero(
|
||||||
child: Stack(
|
tag: myKey,
|
||||||
children: [
|
child: FocusButton(
|
||||||
Positioned.fill(
|
child: FladderImage(
|
||||||
child: Hero(
|
image: season.getPosters?.primary ??
|
||||||
tag: myKey,
|
season.parentImages?.backDrop?.firstOrNull ??
|
||||||
child: FladderImage(
|
season.parentImages?.primary,
|
||||||
image: season.getPosters?.primary ??
|
placeHolder: placeHolder(season.name),
|
||||||
season.parentImages?.backDrop?.firstOrNull ??
|
),
|
||||||
season.parentImages?.primary,
|
onSecondaryTapDown: (details) async {
|
||||||
placeHolder: placeHolder(season.name),
|
Offset localPosition = details.globalPosition;
|
||||||
),
|
RelativeRect position =
|
||||||
),
|
RelativeRect.fromLTRB(localPosition.dx, localPosition.dy, localPosition.dx, localPosition.dy);
|
||||||
),
|
await showMenu(
|
||||||
|
context: context,
|
||||||
|
position: position,
|
||||||
|
items: season.generateActions(context, ref).popupMenuItems(useIcons: true));
|
||||||
|
},
|
||||||
|
onTap: () async {
|
||||||
|
await season.navigateTo(context, ref: ref, tag: myKey);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.refreshData();
|
||||||
|
},
|
||||||
|
onLongPress: AdaptiveLayout.inputDeviceOf(context) == InputDevice.touch
|
||||||
|
? () {
|
||||||
|
showBottomSheetPill(
|
||||||
|
context: context,
|
||||||
|
content: (context, scrollController) => ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
controller: scrollController,
|
||||||
|
children: season.generateActions(context, ref).listTileItems(context, useIcons: true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
overlays: [
|
||||||
if (season.images?.primary == null)
|
if (season.images?.primary == null)
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
|
|
@ -141,50 +163,19 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
],
|
||||||
child: FocusButton(
|
focusedOverlays: [
|
||||||
onSecondaryTapDown: (details) async {
|
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer)
|
||||||
Offset localPosition = details.globalPosition;
|
ExcludeFocus(
|
||||||
RelativeRect position = RelativeRect.fromLTRB(
|
child: Align(
|
||||||
localPosition.dx, localPosition.dy, localPosition.dx, localPosition.dy);
|
alignment: Alignment.bottomRight,
|
||||||
await showMenu(
|
child: PopupMenuButton(
|
||||||
context: context,
|
tooltip: context.localized.options,
|
||||||
position: position,
|
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||||
items: season.generateActions(context, ref).popupMenuItems(useIcons: true));
|
itemBuilder: (context) => season.generateActions(context, ref).popupMenuItems(useIcons: true),
|
||||||
},
|
),
|
||||||
onTap: () async {
|
),
|
||||||
await season.navigateTo(context, ref: ref, tag: myKey);
|
|
||||||
if (!context.mounted) return;
|
|
||||||
context.refreshData();
|
|
||||||
},
|
|
||||||
onLongPress: AdaptiveLayout.inputDeviceOf(context) == InputDevice.touch
|
|
||||||
? () {
|
|
||||||
showBottomSheetPill(
|
|
||||||
context: context,
|
|
||||||
content: (context, scrollController) => ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
controller: scrollController,
|
|
||||||
children: season.generateActions(context, ref).listTileItems(context, useIcons: true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
overlays: [
|
|
||||||
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer)
|
|
||||||
ExcludeFocus(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
child: PopupMenuButton(
|
|
||||||
tooltip: context.localized.options,
|
|
||||||
icon: const Icon(Icons.more_vert, color: Colors.white),
|
|
||||||
itemBuilder: (context) =>
|
|
||||||
season.generateActions(context, ref).popupMenuItems(useIcons: true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class FocusButton extends StatefulWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final bool autoFocus;
|
final bool autoFocus;
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
|
final List<Widget> focusedOverlays;
|
||||||
final List<Widget> overlays;
|
final List<Widget> overlays;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
final Function()? onLongPress;
|
final Function()? onLongPress;
|
||||||
|
|
@ -58,6 +59,7 @@ class FocusButton extends StatefulWidget {
|
||||||
this.child,
|
this.child,
|
||||||
this.autoFocus = false,
|
this.autoFocus = false,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
|
this.focusedOverlays = const [],
|
||||||
this.overlays = const [],
|
this.overlays = const [],
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
|
@ -74,7 +76,7 @@ class FocusButton extends StatefulWidget {
|
||||||
|
|
||||||
class FocusButtonState extends State<FocusButton> {
|
class FocusButtonState extends State<FocusButton> {
|
||||||
late FocusNode focusNode = widget.focusNode ?? FocusNode();
|
late FocusNode focusNode = widget.focusNode ?? FocusNode();
|
||||||
ValueNotifier<bool> onHover = ValueNotifier<bool>(false);
|
ValueNotifier<bool> onHover = ValueNotifier(false);
|
||||||
Timer? _longPressTimer;
|
Timer? _longPressTimer;
|
||||||
bool _longPressTriggered = false;
|
bool _longPressTriggered = false;
|
||||||
bool _keyDownActive = false;
|
bool _keyDownActive = false;
|
||||||
|
|
@ -153,47 +155,49 @@ class FocusButtonState extends State<FocusButton> {
|
||||||
},
|
},
|
||||||
onKeyEvent: _handleKey,
|
onKeyEvent: _handleKey,
|
||||||
child: ExcludeFocus(
|
child: ExcludeFocus(
|
||||||
child: Stack(
|
child: ValueListenableBuilder(
|
||||||
children: [
|
valueListenable: onHover,
|
||||||
FlatButton(
|
builder: (context, value, child) {
|
||||||
onTap: widget.onTap,
|
return AnimatedContainer(
|
||||||
onSecondaryTapDown: widget.onSecondaryTapDown,
|
duration: const Duration(milliseconds: 200),
|
||||||
onLongPress: widget.onLongPress,
|
curve: Curves.easeInOut,
|
||||||
child: Container(
|
clipBehavior: Clip.hardEdge,
|
||||||
clipBehavior: Clip.hardEdge,
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
borderRadius: widget.borderRadius ?? FladderTheme.smallShape.borderRadius,
|
||||||
borderRadius: widget.borderRadius ?? FladderTheme.smallShape.borderRadius,
|
|
||||||
),
|
|
||||||
child: widget.child,
|
|
||||||
),
|
),
|
||||||
),
|
foregroundDecoration: BoxDecoration(
|
||||||
Positioned.fill(
|
borderRadius: widget.borderRadius ?? FladderTheme.smallShape.borderRadius,
|
||||||
child: ValueListenableBuilder(
|
color: widget.darkOverlay
|
||||||
valueListenable: onHover,
|
? Theme.of(context).colorScheme.primaryFixedDim.withValues(alpha: value ? 0.10 : 0.0)
|
||||||
builder: (context, value, child) => AnimatedOpacity(
|
: null,
|
||||||
opacity: value ? 1 : 0,
|
border: Border.all(
|
||||||
duration: const Duration(milliseconds: 125),
|
width: value ? 3.5 : 2,
|
||||||
child: Stack(
|
color: value ? Theme.of(context).colorScheme.primary : Colors.white.withAlpha(15),
|
||||||
children: [
|
),
|
||||||
IgnorePointer(
|
),
|
||||||
child: Container(
|
child: FlatButton(
|
||||||
decoration: BoxDecoration(
|
onTap: widget.onTap,
|
||||||
color: Theme.of(context)
|
onSecondaryTapDown: widget.onSecondaryTapDown,
|
||||||
.colorScheme
|
onLongPress: widget.onLongPress,
|
||||||
.surfaceContainerLowest
|
child: Stack(
|
||||||
.withValues(alpha: widget.darkOverlay ? 0.35 : 0),
|
children: [
|
||||||
border: Border.all(width: 3, color: Theme.of(context).colorScheme.onPrimaryContainer),
|
if (widget.child != null) widget.child!,
|
||||||
borderRadius: widget.borderRadius ?? FladderTheme.smallShape.borderRadius,
|
],
|
||||||
),
|
),
|
||||||
),
|
overlays: [
|
||||||
|
if (widget.overlays.isNotEmpty) ...widget.overlays,
|
||||||
|
if (widget.focusedOverlays.isNotEmpty)
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: value ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
child: Stack(
|
||||||
|
children: [...widget.focusedOverlays],
|
||||||
),
|
),
|
||||||
...widget.overlays,
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue