mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-08 23:18:16 -07:00
feature: Improved banners, made banner settings easier to understand. (#71)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
11e0e106d3
commit
476bdc130e
29 changed files with 916 additions and 666 deletions
51
lib/screens/shared/media/banner_play_button.dart
Normal file
51
lib/screens/shared/media/banner_play_button.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:square_progress_indicator/square_progress_indicator.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/util/item_base_model/play_item_helpers.dart';
|
||||
|
||||
class BannerPlayButton extends ConsumerWidget {
|
||||
final ItemBaseModel item;
|
||||
const BannerPlayButton({
|
||||
super.key,
|
||||
required this.item,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Opacity(
|
||||
opacity: 0.9,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: SquareProgressIndicator(
|
||||
value: item.userData.progress / 100,
|
||||
borderRadius: 12,
|
||||
strokeCap: StrokeCap.round,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => item.play(context, ref),
|
||||
icon: const Icon(
|
||||
IconsaxBold.play,
|
||||
size: 30,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@ import 'package:collection/collection.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/screens/shared/media/banner_play_button.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/themes_data.dart';
|
||||
import 'package:fladder/widgets/shared/fladder_carousel.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
|
|
@ -34,107 +36,119 @@ class _CarouselBannerState extends ConsumerState<CarouselBanner> {
|
|||
constraints: BoxConstraints(maxHeight: widget.maxHeight),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxExtent = (constraints.maxHeight * 2.1).clamp(250.0, MediaQuery.sizeOf(context).shortestSide * 0.75);
|
||||
final maxExtent = (constraints.maxHeight * 2.1).clamp(
|
||||
250.0,
|
||||
(MediaQuery.sizeOf(context).shortestSide * 0.75).clamp(251.0, double.maxFinite),
|
||||
);
|
||||
final border = BorderRadius.circular(18);
|
||||
return FladderCarousel(
|
||||
elevation: 3,
|
||||
shrinkExtent: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: border),
|
||||
onTap: (index) => widget.items[index].navigateTo(context),
|
||||
onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
|
||||
? null
|
||||
: (index) {
|
||||
final poster = widget.items[index];
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: poster,
|
||||
content: (scrollContext, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSecondaryTap: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
||||
? null
|
||||
: (details) async {
|
||||
Offset localPosition = details.$2.globalPosition;
|
||||
RelativeRect position = RelativeRect.fromLTRB(
|
||||
localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy);
|
||||
final poster = widget.items[details.$1];
|
||||
|
||||
await showMenu(
|
||||
context: context,
|
||||
position: position,
|
||||
items: poster.generateActions(context, ref).popupMenuItems(useIcons: true),
|
||||
);
|
||||
},
|
||||
itemExtent: maxExtent,
|
||||
itemPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 4).copyWith(top: AdaptiveLayout.of(context).isDesktop ? 6 : 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
itemExtent: widget.items.length == 1 ? MediaQuery.sizeOf(context).width : maxExtent,
|
||||
children: [
|
||||
...widget.items.mapIndexed(
|
||||
(index, e) => LayoutBuilder(builder: (context, constraints) {
|
||||
(index, item) => LayoutBuilder(builder: (context, constraints) {
|
||||
final opacity = (constraints.maxWidth / maxExtent);
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
FladderImage(image: e.bannerImage),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
opacity: opacity.clamp(0, 1),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.primaryContainer.withOpacity(0.75),
|
||||
Colors.transparent,
|
||||
],
|
||||
return GestureDetector(
|
||||
onTap: () => widget.items[index].navigateTo(context),
|
||||
onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
|
||||
? null
|
||||
: () {
|
||||
final poster = widget.items[index];
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: poster,
|
||||
content: (scrollContext, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children:
|
||||
poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSecondaryTapDown: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
||||
? null
|
||||
: (details) async {
|
||||
Offset localPosition = details.globalPosition;
|
||||
RelativeRect position = RelativeRect.fromLTRB(
|
||||
localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy);
|
||||
final poster = widget.items[index];
|
||||
|
||||
await showMenu(
|
||||
context: context,
|
||||
position: position,
|
||||
items: poster.generateActions(context, ref).popupMenuItems(useIcons: true),
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
FladderImage(image: item.bannerImage),
|
||||
Opacity(
|
||||
opacity: opacity.clamp(0, 1),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
ThemesData.of(context).dark.colorScheme.primaryContainer.withOpacity(0.85),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
e.title,
|
||||
maxLines: 2,
|
||||
softWrap: e.title.length > 25,
|
||||
textWidthBasis: TextWidthBasis.parent,
|
||||
overflow: TextOverflow.fade,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Colors.white),
|
||||
),
|
||||
if (e.label(context) != null)
|
||||
Text(
|
||||
e.label(context)!,
|
||||
maxLines: 2,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 4)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
width: 1.0,
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0).copyWith(right: constraints.maxWidth * 0.2),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title,
|
||||
maxLines: 2,
|
||||
softWrap: item.title.length > 25,
|
||||
overflow: TextOverflow.fade,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Colors.white),
|
||||
),
|
||||
if (item.label(context) != null || item.subText != null)
|
||||
Text(
|
||||
item.label(context) ?? item.subText ?? "",
|
||||
maxLines: 2,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 4)),
|
||||
),
|
||||
borderRadius: border),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
BannerPlayButton(item: widget.items[index]),
|
||||
IgnorePointer(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
width: 1.0,
|
||||
),
|
||||
borderRadius: border),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/screens/shared/media/banner_play_button.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/themes_data.dart';
|
||||
import 'package:fladder/widgets/shared/fladder_carousel.dart';
|
||||
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
|
||||
|
|
@ -84,269 +84,211 @@ class _MediaBannerState extends ConsumerState<MediaBanner> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final overlayColor = ThemesData.of(context).dark.colorScheme.onSecondary;
|
||||
final overlayColor = ThemesData.of(context).dark.colorScheme.primaryContainer;
|
||||
final shadows = [
|
||||
BoxShadow(blurRadius: 12, spreadRadius: 8, color: overlayColor),
|
||||
];
|
||||
final currentItem = widget.items[currentPage.clamp(0, widget.items.length - 1)];
|
||||
final actions = currentItem.generateActions(context, ref);
|
||||
final double dragOpacity = (1 - dragOffset.abs()).clamp(0, 1);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: widget.maxHeight),
|
||||
child: Card(
|
||||
elevation: 16,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
surfaceTintColor: overlayColor,
|
||||
color: overlayColor,
|
||||
child: GestureDetector(
|
||||
onTap: () => currentItem.navigateTo(context),
|
||||
onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
||||
? () async {
|
||||
interacting = true;
|
||||
final poster = currentItem;
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: poster,
|
||||
content: (scrollContext, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true),
|
||||
),
|
||||
);
|
||||
interacting = false;
|
||||
timer.reset();
|
||||
}
|
||||
: null,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: widget.maxHeight),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
surfaceTintColor: overlayColor,
|
||||
color: overlayColor,
|
||||
child: MouseRegion(
|
||||
onEnter: (event) => setState(() => showControls = true),
|
||||
onHover: (event) => timer.reset(),
|
||||
onExit: (event) => setState(() => showControls = false),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Dismissible(
|
||||
key: const Key("Dismissable"),
|
||||
direction: DismissDirection.horizontal,
|
||||
onUpdate: (details) {
|
||||
setState(() {
|
||||
dragOffset = details.progress * 4;
|
||||
});
|
||||
},
|
||||
confirmDismiss: (direction) async {
|
||||
if (direction == DismissDirection.startToEnd) {
|
||||
previousSlide();
|
||||
} else {
|
||||
nextSlide();
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => currentItem.navigateTo(context),
|
||||
onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
||||
? () async {
|
||||
interacting = true;
|
||||
final poster = currentItem;
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: poster,
|
||||
content: (scrollContext, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children:
|
||||
poster.generateActions(context, ref).listTileItems(scrollContext, useIcons: true),
|
||||
),
|
||||
);
|
||||
interacting = false;
|
||||
timer.reset();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
opacity: dragOpacity.abs(),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
child: Container(
|
||||
key: Key(currentItem.id),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
foregroundDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.10), strokeAlign: BorderSide.strokeAlignInside),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
overlayColor.withOpacity(1),
|
||||
overlayColor.withOpacity(0.75),
|
||||
overlayColor.withOpacity(0.45),
|
||||
overlayColor.withOpacity(0.15),
|
||||
overlayColor.withOpacity(0),
|
||||
overlayColor.withOpacity(0),
|
||||
overlayColor.withOpacity(0.1),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: FladderImage(
|
||||
fit: BoxFit.cover,
|
||||
image: currentItem.bannerImage,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
: null,
|
||||
onSecondaryTapDown: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
||||
? null
|
||||
: (details) async {
|
||||
Offset localPosition = details.globalPosition;
|
||||
RelativeRect position = RelativeRect.fromLTRB(
|
||||
localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy);
|
||||
final poster = currentItem;
|
||||
|
||||
await showMenu(
|
||||
context: context,
|
||||
position: position,
|
||||
items: poster.generateActions(context, ref).popupMenuItems(useIcons: true),
|
||||
);
|
||||
},
|
||||
child: MouseRegion(
|
||||
onEnter: (event) => setState(() => showControls = true),
|
||||
onHover: (event) => timer.reset(),
|
||||
onExit: (event) => setState(() => showControls = false),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: IgnorePointer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem.title,
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
shadows: shadows,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (currentItem.label(context) != null && currentItem is! MovieModel)
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem.label(context)!,
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
shadows: shadows,
|
||||
color: Colors.white.withOpacity(0.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (currentItem.overview.summary.isNotEmpty &&
|
||||
AdaptiveLayout.layoutOf(context) != LayoutState.phone)
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem.overview.summary,
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
shadows: shadows,
|
||||
color: Colors.white.withOpacity(0.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 6)),
|
||||
Dismissible(
|
||||
key: const Key("Dismissable"),
|
||||
direction: DismissDirection.horizontal,
|
||||
onUpdate: (details) {
|
||||
setState(() {
|
||||
dragOffset = details.progress * 4;
|
||||
});
|
||||
},
|
||||
confirmDismiss: (direction) async {
|
||||
if (direction == DismissDirection.startToEnd) {
|
||||
previousSlide();
|
||||
} else {
|
||||
nextSlide();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
opacity: dragOpacity.abs(),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
child: Container(
|
||||
key: Key(currentItem.id),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
foregroundDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.10), strokeAlign: BorderSide.strokeAlignInside),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
overlayColor.withOpacity(0.85),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: FladderImage(
|
||||
fit: BoxFit.cover,
|
||||
image: currentItem.bannerImage,
|
||||
),
|
||||
),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: AnimatedOpacity(
|
||||
opacity: showControls ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => nextSlide(),
|
||||
icon: const Icon(IconsaxOutline.arrow_right_3),
|
||||
)
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: IgnorePointer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem.title,
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
shadows: shadows,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (currentItem.label(context) != null || currentItem.subText != null)
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem.label(context) ?? currentItem.subText ?? "",
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
shadows: shadows,
|
||||
color: Colors.white.withOpacity(0.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 6)),
|
||||
),
|
||||
),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: AnimatedOpacity(
|
||||
opacity: showControls ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => nextSlide(),
|
||||
icon: const Icon(IconsaxOutline.arrow_right_3),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
BannerPlayButton(item: currentItem),
|
||||
],
|
||||
),
|
||||
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Card(
|
||||
child: PopupMenuButton(
|
||||
onOpened: () => interacting = true,
|
||||
onCanceled: () {
|
||||
interacting = false;
|
||||
timer.reset();
|
||||
},
|
||||
itemBuilder: (context) => actions.popupMenuItems(useIcons: true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onHorizontalDragUpdate: (details) {
|
||||
final delta = (details.primaryDelta ?? 0) / 20;
|
||||
slidePosition += delta;
|
||||
if (slidePosition > 1) {
|
||||
nextSlide();
|
||||
slidePosition = 0;
|
||||
} else if (slidePosition < -1) {
|
||||
previousSlide();
|
||||
slidePosition = 0;
|
||||
}
|
||||
},
|
||||
onHorizontalDragStart: (details) {
|
||||
slidePosition = 0;
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: widget.items.mapIndexed((index, e) {
|
||||
return Tooltip(
|
||||
message: '${e.name}\n${e.detailedName}',
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTapUp: currentPage == index
|
||||
? null
|
||||
: (details) {
|
||||
animateToTarget(index);
|
||||
timer.reset();
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
color: Colors.red.withOpacity(0),
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
width: currentItem == e ? 22 : 6,
|
||||
height: currentItem == e ? 10 : 6,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: currentItem == e
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.primary.withOpacity(0.25),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
if (widget.items.length > 1)
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.35,
|
||||
child: FladderSlider(
|
||||
value: currentPage.toDouble(),
|
||||
min: 0,
|
||||
animation: const Duration(milliseconds: 250),
|
||||
thumbWidth: 24,
|
||||
activeTrackColor: Theme.of(context).colorScheme.surfaceDim,
|
||||
inactiveTrackColor: Theme.of(context).colorScheme.surfaceDim,
|
||||
max: widget.items.length.toDouble() - 1,
|
||||
onChanged: (value) => setState(() => currentPage = value.toInt()),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
else
|
||||
const SizedBox(height: 24)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -368,3 +310,28 @@ class _MediaBannerState extends ConsumerState<MediaBanner> {
|
|||
updateItem(currentPage + step);
|
||||
}
|
||||
}
|
||||
|
||||
class RoundedTrackShape extends RoundedRectSliderTrackShape {
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset,
|
||||
{required RenderBox parentBox,
|
||||
required SliderThemeData sliderTheme,
|
||||
required Animation<double> enableAnimation,
|
||||
required TextDirection textDirection,
|
||||
required Offset thumbCenter,
|
||||
Offset? secondaryOffset,
|
||||
bool isDiscrete = false,
|
||||
bool isEnabled = false,
|
||||
double additionalActiveTrackHeight = 0}) {
|
||||
super.paint(context, offset,
|
||||
parentBox: parentBox,
|
||||
sliderTheme: sliderTheme,
|
||||
enableAnimation: enableAnimation,
|
||||
textDirection: textDirection,
|
||||
thumbCenter: thumbCenter,
|
||||
secondaryOffset: secondaryOffset,
|
||||
isDiscrete: isDiscrete,
|
||||
isEnabled: isEnabled,
|
||||
additionalActiveTrackHeight: additionalActiveTrackHeight);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue