mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-09 07:28:14 -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
|
|
@ -9,10 +9,12 @@ class MediaHeader extends ConsumerWidget {
|
|||
final String name;
|
||||
final ImageData? logo;
|
||||
final Function()? onTap;
|
||||
final Alignment alignment;
|
||||
const MediaHeader({
|
||||
required this.name,
|
||||
required this.logo,
|
||||
this.onTap,
|
||||
this.alignment = Alignment.bottomCenter,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -48,7 +50,7 @@ class MediaHeader extends ConsumerWidget {
|
|||
? FladderImage(
|
||||
image: logo,
|
||||
disableBlur: true,
|
||||
alignment: Alignment.bottomCenter,
|
||||
alignment: alignment,
|
||||
imageErrorBuilder: (context, object, stack) => textWidget,
|
||||
placeHolder: const SizedBox(height: 0),
|
||||
fit: BoxFit.contain,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/arguments_provider.dart';
|
||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||
import 'package:fladder/theme.dart';
|
||||
import 'package:fladder/widgets/shared/ensure_visible.dart';
|
||||
|
||||
class MediaPlayButton extends ConsumerWidget {
|
||||
final ItemBaseModel? item;
|
||||
final Function()? onPressed;
|
||||
final Function()? onLongPressed;
|
||||
final VoidCallback? onPressed;
|
||||
final VoidCallback? onLongPressed;
|
||||
|
||||
const MediaPlayButton({
|
||||
required this.item,
|
||||
this.onPressed,
|
||||
|
|
@ -19,66 +23,110 @@ class MediaPlayButton extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final resume = (item?.progress ?? 0) > 0;
|
||||
Widget buttonBuilder(bool resume, ButtonStyle? style, Color? textColor) {
|
||||
return ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPressed,
|
||||
style: style,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
item?.playButtonLabel(context) ?? "",
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
final progress = (item?.progress ?? 0) / 100.0;
|
||||
final radius = FladderTheme.defaultShape.borderRadius;
|
||||
|
||||
Widget buttonTitle(Color contentColor) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
item?.playButtonLabel(context) ?? "",
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.clip,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: contentColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
IconsaxPlusBold.play,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
IconsaxPlusBold.play,
|
||||
color: contentColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return AnimatedFadeSize(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: onPressed != null
|
||||
? Stack(
|
||||
children: [
|
||||
buttonBuilder(resume, null, null),
|
||||
IgnorePointer(
|
||||
child: ClipRect(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
widthFactor: (item?.progress ?? 0) / 100,
|
||||
child: buttonBuilder(
|
||||
resume,
|
||||
ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary),
|
||||
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary),
|
||||
iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary),
|
||||
child: onPressed == null
|
||||
? const SizedBox.shrink(key: ValueKey('empty'))
|
||||
: TextButton(
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPressed,
|
||||
autofocus: ref.read(argumentsStateProvider).htpcMode,
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onFocusChange: (value) {
|
||||
if (value) {
|
||||
context.ensureVisible(
|
||||
alignment: 1.0,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Progress background
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 8.0,
|
||||
offset: const Offset(0, 2),
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
)
|
||||
],
|
||||
borderRadius: radius,
|
||||
),
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Button content
|
||||
buttonTitle(Theme.of(context).colorScheme.primary),
|
||||
Positioned.fill(
|
||||
child: ClipRect(
|
||||
clipper: _ProgressClipper(
|
||||
progress,
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: radius,
|
||||
),
|
||||
child: buttonTitle(Theme.of(context).colorScheme.onPrimary),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: Container(
|
||||
key: UniqueKey(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProgressClipper extends CustomClipper<Rect> {
|
||||
final double progress;
|
||||
_ProgressClipper(this.progress);
|
||||
|
||||
@override
|
||||
Rect getClip(Size size) {
|
||||
final w = (progress.clamp(0.0, 1.0) * size.width);
|
||||
return Rect.fromLTWH(0, 0, w, size.height);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(covariant _ProgressClipper old) => old.progress != progress;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -7,14 +6,14 @@ import 'package:iconsax_plus/iconsax_plus.dart';
|
|||
import 'package:fladder/models/book_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
import 'package:fladder/screens/shared/media/components/poster_placeholder.dart';
|
||||
import 'package:fladder/theme.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/disable_keypad_focus.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
import 'package:fladder/util/focus_provider.dart';
|
||||
import 'package:fladder/util/humanize_duration.dart';
|
||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
|
@ -26,7 +25,6 @@ import 'package:fladder/widgets/shared/status_card.dart';
|
|||
|
||||
class PosterImage extends ConsumerStatefulWidget {
|
||||
final ItemBaseModel poster;
|
||||
final bool heroTag;
|
||||
final bool? selected;
|
||||
final ValueChanged<bool>? playVideo;
|
||||
final bool inlineTitle;
|
||||
|
|
@ -36,9 +34,11 @@ class PosterImage extends ConsumerStatefulWidget {
|
|||
final Function(ItemBaseModel newItem)? onItemUpdated;
|
||||
final Function(ItemBaseModel oldItem)? onItemRemoved;
|
||||
final Function(Function() action, ItemBaseModel item)? onPressed;
|
||||
final bool primaryPosters;
|
||||
final Function(bool focus)? onFocusChanged;
|
||||
|
||||
const PosterImage({
|
||||
required this.poster,
|
||||
this.heroTag = false,
|
||||
this.selected,
|
||||
this.playVideo,
|
||||
this.inlineTitle = false,
|
||||
|
|
@ -48,6 +48,8 @@ class PosterImage extends ConsumerStatefulWidget {
|
|||
this.otherActions = const [],
|
||||
this.onPressed,
|
||||
this.onUserDataChanged,
|
||||
this.primaryPosters = false,
|
||||
this.onFocusChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -56,15 +58,8 @@ class PosterImage extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _PosterImageState extends ConsumerState<PosterImage> {
|
||||
late String currentTag = widget.heroTag == true ? widget.poster.id : UniqueKey().toString();
|
||||
bool hover = false;
|
||||
|
||||
final tag = UniqueKey();
|
||||
void pressedWidget(BuildContext context) async {
|
||||
if (widget.heroTag == false) {
|
||||
setState(() {
|
||||
currentTag = widget.poster.id;
|
||||
});
|
||||
}
|
||||
if (widget.onPressed != null) {
|
||||
widget.onPressed?.call(() async {
|
||||
await navigateToDetails();
|
||||
|
|
@ -78,7 +73,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
|||
}
|
||||
|
||||
Future<void> navigateToDetails() async {
|
||||
await widget.poster.navigateTo(context, ref: ref);
|
||||
await widget.poster.navigateTo(context, ref: ref, tag: tag);
|
||||
}
|
||||
|
||||
final posterRadius = FladderTheme.smallShape.borderRadius;
|
||||
|
|
@ -87,302 +82,268 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
|||
Widget build(BuildContext context) {
|
||||
final poster = widget.poster;
|
||||
final padding = const EdgeInsets.all(5);
|
||||
|
||||
return Hero(
|
||||
tag: currentTag,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (event) => setState(() => hover = true),
|
||||
onExit: (event) => setState(() => hover = false),
|
||||
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,
|
||||
tag: tag,
|
||||
child: Card(
|
||||
elevation: 6,
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
FladderImage(
|
||||
image: widget.poster.getPosters?.primary ?? widget.poster.getPosters?.backDrop?.lastOrNull,
|
||||
placeHolder: PosterPlaceholder(item: widget.poster),
|
||||
),
|
||||
if (poster.userData.progress > 0 && widget.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((widget.poster as BookModel).currentPage),
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
borderRadius: posterRadius,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
FladderImage(
|
||||
image: widget.primaryPosters
|
||||
? widget.poster.images?.primary
|
||||
: widget.poster.getPosters?.primary ?? widget.poster.getPosters?.backDrop?.lastOrNull,
|
||||
placeHolder: PosterPlaceholder(item: widget.poster),
|
||||
),
|
||||
if (poster.userData.progress > 0 && widget.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((widget.poster as BookModel).currentPage),
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.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(
|
||||
widget.poster.name,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onPrimary, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.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,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
if (widget.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) &&
|
||||
widget.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),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: Text(
|
||||
widget.poster.name,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onPrimary, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.inlineTitle)
|
||||
IgnorePointer(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
widget.poster.title.maxLength(limitTo: 25),
|
||||
style:
|
||||
Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.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) &&
|
||||
widget.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 (widget.inlineTitle)
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
widget.poster.title.maxLength(limitTo: 25),
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
if ((widget.poster.unPlayedItemCount != null && widget.poster is SeriesModel)
|
||||
|| (widget.poster is MovieModel && !widget.poster.unWatched))
|
||||
IgnorePointer(
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: StatusCard(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
useFittedBox: widget.poster.unPlayedItemCount != 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: widget.poster.unPlayedItemCount != 0
|
||||
? Container(
|
||||
constraints: const BoxConstraints(minWidth: 16),
|
||||
child: Text(
|
||||
widget.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 (widget.poster.overview.runTime != null &&
|
||||
((widget.poster is PhotoModel) &&
|
||||
(widget.poster as PhotoModel).internalType == FladderItemType.video)) ...{
|
||||
Align(
|
||||
),
|
||||
if ((widget.poster.unPlayedItemCount != null && widget.poster is SeriesModel) ||
|
||||
(widget.poster is MovieModel && !widget.poster.unWatched))
|
||||
IgnorePointer(
|
||||
child: 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(
|
||||
widget.poster.overview.runTime.humanizeSmall ?? "",
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
child: StatusCard(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
useFittedBox: widget.poster.unPlayedItemCount != 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: widget.poster.unPlayedItemCount != 0
|
||||
? Container(
|
||||
constraints: const BoxConstraints(minWidth: 16),
|
||||
child: Text(
|
||||
widget.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,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.poster.overview.runTime != null &&
|
||||
((widget.poster is PhotoModel) &&
|
||||
(widget.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(
|
||||
widget.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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
//Desktop overlay
|
||||
if (AdaptiveLayout.of(context).inputDevice != InputDevice.touch &&
|
||||
widget.poster.type != FladderItemType.person)
|
||||
AnimatedOpacity(
|
||||
opacity: hover ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 125),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
//Hover color overlay
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
||||
borderRadius: posterRadius,
|
||||
)),
|
||||
//Poster Button
|
||||
Focus(
|
||||
onFocusChange: (value) => setState(() => hover = value),
|
||||
child: FlatButton(
|
||||
onTap: () => pressedWidget(context),
|
||||
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: widget.poster
|
||||
.generateActions(
|
||||
context,
|
||||
ref,
|
||||
exclude: widget.excludeActions,
|
||||
otherActions: widget.otherActions,
|
||||
onUserDataChanged: widget.onUserDataChanged,
|
||||
onDeleteSuccesFully: widget.onItemRemoved,
|
||||
onItemUpdated: widget.onItemUpdated,
|
||||
)
|
||||
.popupMenuItems(useIcons: true),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
//Play Button
|
||||
if (widget.poster.playAble)
|
||||
DisableFocus(
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: IconButton.filledTonal(
|
||||
onPressed: () => widget.playVideo?.call(false),
|
||||
icon: const Icon(
|
||||
IconsaxPlusBold.play,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DisableFocus(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PopupMenuButton(
|
||||
tooltip: "Options",
|
||||
icon: const Icon(
|
||||
Icons.more_vert,
|
||||
color: Colors.white,
|
||||
),
|
||||
itemBuilder: (context) => widget.poster
|
||||
.generateActions(
|
||||
context,
|
||||
ref,
|
||||
exclude: widget.excludeActions,
|
||||
otherActions: widget.otherActions,
|
||||
onUserDataChanged: widget.onUserDataChanged,
|
||||
onDeleteSuccesFully: widget.onItemRemoved,
|
||||
onItemUpdated: widget.onItemUpdated,
|
||||
)
|
||||
.popupMenuItems(useIcons: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
},
|
||||
FocusButton(
|
||||
onTap: () => pressedWidget(context),
|
||||
onFocusChanged: widget.onFocusChanged,
|
||||
onLongPress: () {
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: widget.poster,
|
||||
content: (scrollContext, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: widget.poster
|
||||
.generateActions(
|
||||
context,
|
||||
ref,
|
||||
exclude: widget.excludeActions,
|
||||
otherActions: widget.otherActions,
|
||||
onUserDataChanged: widget.onUserDataChanged,
|
||||
onDeleteSuccesFully: widget.onItemRemoved,
|
||||
onItemUpdated: widget.onItemUpdated,
|
||||
)
|
||||
.listTileItems(scrollContext, useIcons: true),
|
||||
),
|
||||
)
|
||||
else
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => pressedWidget(context),
|
||||
onLongPress: () {
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: widget.poster,
|
||||
content: (scrollContext, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: widget.poster
|
||||
);
|
||||
},
|
||||
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: widget.poster
|
||||
.generateActions(
|
||||
context,
|
||||
ref,
|
||||
exclude: widget.excludeActions,
|
||||
otherActions: widget.otherActions,
|
||||
onUserDataChanged: widget.onUserDataChanged,
|
||||
onDeleteSuccesFully: widget.onItemRemoved,
|
||||
onItemUpdated: widget.onItemUpdated,
|
||||
)
|
||||
.popupMenuItems(useIcons: true),
|
||||
);
|
||||
},
|
||||
overlays: [
|
||||
//Poster Button
|
||||
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) ...[
|
||||
// Play Button
|
||||
if (widget.poster.playAble)
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: IconButton.filledTonal(
|
||||
onPressed: () => widget.playVideo?.call(false),
|
||||
icon: const Icon(
|
||||
IconsaxPlusBold.play,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PopupMenuButton(
|
||||
tooltip: "Options",
|
||||
icon: const Icon(
|
||||
Icons.more_vert,
|
||||
color: Colors.white,
|
||||
),
|
||||
itemBuilder: (context) => widget.poster
|
||||
.generateActions(
|
||||
context,
|
||||
ref,
|
||||
|
|
@ -392,14 +353,15 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
|||
onDeleteSuccesFully: widget.onItemRemoved,
|
||||
onItemUpdated: widget.onItemUpdated,
|
||||
)
|
||||
.listTileItems(scrollContext, useIcons: true),
|
||||
.popupMenuItems(useIcons: true),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue