chore: Improved performance for some widgets (#525)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-10-10 15:54:17 +02:00 committed by GitHub
parent 10bd34bb20
commit 07972ea5ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 589 additions and 545 deletions

View file

@ -72,54 +72,51 @@ class SettingsListTile extends StatelessWidget {
constraints: const BoxConstraints(
minHeight: 50,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
DefaultTextStyle.merge(
style: TextStyle(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
DefaultTextStyle.merge(
style: TextStyle(
color: contentColor ?? Theme.of(context).colorScheme.onSurface,
),
child: IconTheme(
data: IconThemeData(
color: contentColor ?? Theme.of(context).colorScheme.onSurface,
),
child: IconTheme(
data: IconThemeData(
color: contentColor ?? Theme.of(context).colorScheme.onSurface,
),
child: leadingWidget,
),
child: leadingWidget,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Material(
color: Colors.transparent,
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: contentColor),
child: label,
),
if (subLabel != null)
Material(
color: Colors.transparent,
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: contentColor),
child: label,
textStyle: Theme.of(context).textTheme.labelLarge?.copyWith(
color:
(contentColor ?? Theme.of(context).colorScheme.onSurface).withValues(alpha: 0.65),
),
child: subLabel,
),
if (subLabel != null)
Opacity(
opacity: 0.65,
child: Material(
color: Colors.transparent,
textStyle: Theme.of(context).textTheme.labelLarge?.copyWith(color: contentColor),
child: subLabel,
),
),
],
),
],
),
if (trailing != null)
ExcludeFocusTraversal(
excluding: onTap != null,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: trailing,
),
)
],
),
),
if (trailing != null)
ExcludeFocusTraversal(
excluding: onTap != null,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: trailing,
),
)
],
),
),
),

View file

@ -104,16 +104,30 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
final minHeight = 450.0.clamp(0, size.height).toDouble();
final maxHeight = size.height - 10;
final sideBarPadding = AdaptiveLayout.of(context).sideBarWidth;
final newColorScheme = dominantColor != null
? ColorScheme.fromSeed(
seedColor: dominantColor!,
brightness: Theme.brightnessOf(context),
dynamicSchemeVariant: ref.watch(clientSettingsProvider.select((value) => value.schemeVariant)),
)
: null;
final amoledBlack = ref.watch(clientSettingsProvider.select((value) => value.amoledBlack));
final amoledOverwrite = amoledBlack ? Colors.black : null;
return Theme(
data: Theme.of(context).copyWith(
colorScheme: dominantColor != null
? ColorScheme.fromSeed(
seedColor: dominantColor!,
brightness: Theme.brightnessOf(context),
dynamicSchemeVariant: ref.watch(clientSettingsProvider.select((value) => value.schemeVariant)),
)
: null,
),
data: Theme.of(context)
.copyWith(
colorScheme: newColorScheme,
)
.copyWith(
scaffoldBackgroundColor: amoledOverwrite,
cardColor: amoledOverwrite,
canvasColor: amoledOverwrite,
colorScheme: newColorScheme?.copyWith(
surface: amoledOverwrite,
surfaceContainerHighest: amoledOverwrite,
surfaceContainerLow: amoledOverwrite,
),
),
child: Builder(builder: (context) {
return PullToRefresh(
onRefresh: () async {

View file

@ -5,13 +5,14 @@ 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/screens/shared/flat_button.dart';
import 'package:fladder/screens/shared/media/banner_play_button.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/fladder_image.dart';
import 'package:fladder/util/focus_provider.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/ensure_visible.dart';
import 'package:fladder/widgets/shared/item_actions.dart';
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
@ -64,148 +65,151 @@ class _CarouselBannerState extends ConsumerState<CarouselBanner> {
itemExtent: itemExtent,
children: [
...widget.items.mapIndexed(
(index, item) => LayoutBuilder(builder: (context, constraints) {
final opacity = (constraints.maxWidth / maxExtent);
return 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
.withValues(alpha: 0.85),
Colors.transparent,
],
),
(index, item) => LayoutBuilder(
builder: (context, constraints) {
final opacity = (constraints.maxWidth / maxExtent);
return FocusButton(
onTap: () => widget.items[index].navigateTo(context),
onFocusChanged: (hover) {
context.ensureVisible();
},
onLongPress: AdaptiveLayout.inputDeviceOf(context) == 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.inputDeviceOf(context) == InputDevice.touch
? null
: (details) async {
Offset localPosition = details.globalPosition;
RelativeRect position = RelativeRect.fromLTRB(
localPosition.dx, 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(
children: [
FladderImage(image: item.bannerImage),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomLeft,
end: Alignment.topCenter,
colors: [
ThemesData.of(context)
.dark
.colorScheme
.primaryContainer
.withValues(alpha: opacity.clamp(0, 1)),
Colors.transparent,
],
),
),
],
),
),
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)),
),
),
),
FlatButton(
onTap: () => widget.items[index].navigateTo(context),
onLongPress: AdaptiveLayout.inputDeviceOf(context) == 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),
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),
),
);
},
onSecondaryTapDown: AdaptiveLayout.inputDeviceOf(context) == InputDevice.touch
? null
: (details) async {
Offset localPosition = details.globalPosition;
RelativeRect position = RelativeRect.fromLTRB(
localPosition.dx, 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),
);
},
),
ExcludeFocus(
child: BannerPlayButton(item: widget.items[index]),
),
IgnorePointer(
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white.withValues(alpha: 0.1),
width: 1.0,
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),
),
),
),
ExcludeFocus(
child: BannerPlayButton(item: widget.items[index]),
),
IgnorePointer(
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white.withValues(alpha: 0.1),
width: 1.0,
),
borderRadius: border,
),
),
),
],
),
],
);
}),
);
},
),
)
],
),
if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer)
AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: showControls ? 1 : 0,
child: IgnorePointer(
ignoring: !showControls,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton.filledTonal(
onPressed: () {
final currentPos = carouselController.position;
carouselController.animateTo(currentPos.pixels - itemExtent,
curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 250));
},
icon: const Icon(IconsaxPlusLinear.arrow_left_1),
),
IconButton.filledTonal(
onPressed: () {
final currentPos = carouselController.position;
carouselController.animateTo(currentPos.pixels + itemExtent,
curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 250));
},
icon: const Icon(IconsaxPlusLinear.arrow_right_3),
),
],
ExcludeFocus(
child: AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: showControls ? 1 : 0,
child: IgnorePointer(
ignoring: !showControls,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton.filledTonal(
onPressed: () {
final currentPos = carouselController.position;
carouselController.animateTo(currentPos.pixels - itemExtent,
curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 250));
},
icon: const Icon(IconsaxPlusLinear.arrow_left_1),
),
IconButton.filledTonal(
onPressed: () {
final currentPos = carouselController.position;
carouselController.animateTo(currentPos.pixels + itemExtent,
curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 250));
},
icon: const Icon(IconsaxPlusLinear.arrow_right_3),
),
],
),
),
),
),

View file

@ -20,6 +20,7 @@ class NextUpEpisode extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final alreadyPlayed = nextEpisode.userData.played;
final episodeSummary = nextEpisode.overview.summary.maxLength(limitTo: 250);
final style = Theme.of(context).textTheme.titleMedium;
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
@ -27,11 +28,10 @@ class NextUpEpisode extends ConsumerWidget {
StickyHeaderText(
label: alreadyPlayed ? context.localized.reWatch : context.localized.nextUp,
),
Opacity(
opacity: 0.75,
child: SelectableText(
nextEpisode.seasonEpisodeLabelFull(context),
style: Theme.of(context).textTheme.titleMedium,
SelectableText(
nextEpisode.seasonEpisodeLabelFull(context),
style: style?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.75),
),
),
SelectableText(

View file

@ -84,205 +84,269 @@ class _PosterImageState extends ConsumerState<PosterImage> {
return Hero(
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: 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),
),
);
},
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),
);
},
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,
),
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,
),
),
),
),
),
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 (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,
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(2),
padding: const EdgeInsets.all(5.5),
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),
),
),
)
],
),
),
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.playAble && !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,
),
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,
),
)
: 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(
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,
),
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),
),
const SizedBox(width: 2),
Icon(
Icons.play_arrow_rounded,
color: Theme.of(context).colorScheme.onSurface,
),
)
],
),
),
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),
),
),
),
)
},
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
if ((widget.poster.unPlayedItemCount != null && widget.poster is SeriesModel) ||
(widget.poster.playAble && !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(
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,
),
],
),
),
),
),
)
},
],
),
),
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,
@ -292,76 +356,13 @@ class _PosterImageState extends ConsumerState<PosterImage> {
onDeleteSuccesFully: widget.onItemRemoved,
onItemUpdated: widget.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: 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,
exclude: widget.excludeActions,
otherActions: widget.otherActions,
onUserDataChanged: widget.onUserDataChanged,
onDeleteSuccesFully: widget.onItemRemoved,
onItemUpdated: widget.onItemUpdated,
)
.popupMenuItems(useIcons: true),
),
],
),
.popupMenuItems(useIcons: true),
),
],
],
),
),
],
),
],
),
);
}

View file

@ -8,6 +8,7 @@ class PosterPlaceholder extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.75);
return Stack(
alignment: Alignment.center,
children: [
@ -15,7 +16,10 @@ class PosterPlaceholder extends StatelessWidget {
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Opacity(opacity: 0.5, child: Icon(item.type.icon)),
child: Icon(
item.type.icon,
color: color.withValues(alpha: 0.5),
),
),
),
Padding(
@ -34,15 +38,14 @@ class PosterPlaceholder extends StatelessWidget {
softWrap: true,
),
if (item.label(context) != null) ...[
Opacity(
opacity: 0.75,
child: Text(
item.label(context)!,
maxLines: 2,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall,
softWrap: true,
),
Text(
item.label(context)!,
maxLines: 2,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: color.withValues(alpha: 0.75),
),
softWrap: true,
),
],
],

View file

@ -38,6 +38,9 @@ class EpisodeDetailsList extends ConsumerWidget {
((AdaptiveLayout.poster(context).gridRatio * 2) *
ref.watch(clientSettingsProvider.select((value) => value.posterSize)));
final decimals = size - size.toInt();
final textStyle = Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.65),
);
return AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: switch (viewType) {
@ -73,20 +76,14 @@ class EpisodeDetailsList extends ConsumerWidget {
Row(
mainAxisSize: MainAxisSize.min,
children: [
Opacity(
opacity: 0.65,
child: SelectableText(
episode.seasonEpisodeLabel(context),
style: Theme.of(context).textTheme.titleMedium,
),
SelectableText(
episode.seasonEpisodeLabel(context),
style: textStyle,
),
if (episode.overview.runTime != null)
Opacity(
opacity: 0.65,
child: SelectableText(
" - ${episode.overview.runTime!.humanize!}",
style: Theme.of(context).textTheme.titleMedium,
),
SelectableText(
" - ${episode.overview.runTime!.humanize!}",
style: textStyle,
),
],
),

View file

@ -149,13 +149,10 @@ class PosterListItem extends ConsumerWidget {
overflow: TextOverflow.ellipsis,
),
if ((poster.subText ?? poster.subTextShort(context))?.isNotEmpty == true)
Opacity(
opacity: 0.45,
child: Text(
poster.subText ?? poster.subTextShort(context) ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
poster.subText ?? poster.subTextShort(context) ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
children: [

View file

@ -96,10 +96,7 @@ class PosterWidget extends ConsumerWidget {
children: [
if (subTitle != null) ...[
Flexible(
child: Opacity(
opacity: opacity,
child: subTitle!,
),
child: subTitle!,
),
],
if (poster.subText?.isNotEmpty ?? false)

View file

@ -200,7 +200,7 @@ class _OutlinedTextFieldState extends ConsumerState<OutlinedTextField> {
child: KeyboardListener(
focusNode: _wrapperFocus,
onKeyEvent: (KeyEvent event) {
if (keyboardFocus) return;
if (keyboardFocus || AdaptiveLayout.inputDeviceOf(context) != InputDevice.dPad) return;
if (event is KeyDownEvent && acceptKeys.contains(event.logicalKey)) {
if (_textFocus.hasFocus) {
_wrapperFocus.requestFocus();

View file

@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/account_model.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/theme.dart';
import 'package:fladder/util/string_extensions.dart';
class UserIcon extends ConsumerWidget {
@ -18,7 +17,7 @@ class UserIcon extends ConsumerWidget {
const UserIcon({
this.size = const Size(50, 50),
this.labelStyle,
this.cornerRadius = 5,
this.cornerRadius = 16,
this.onTap,
this.onLongPress,
required this.user,
@ -46,25 +45,23 @@ class UserIcon extends ConsumerWidget {
tag: Key(user?.id ?? "empty-user-avatar"),
child: AspectRatio(
aspectRatio: 1,
child: Card(
elevation: 0,
surfaceTintColor: Colors.transparent,
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(cornerRadius),
),
clipBehavior: Clip.hardEdge,
child: SizedBox.fromSize(
size: size,
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: [
ClipRRect(
borderRadius: FladderTheme.smallShape.borderRadius,
child: CachedNetworkImage(
imageUrl: user?.avatar ?? "",
progressIndicatorBuilder: (context, url, progress) => placeHolder(),
errorWidget: (context, url, error) => placeHolder(),
memCacheHeight: 128,
fit: BoxFit.cover,
),
CachedNetworkImage(
imageUrl: user?.avatar ?? "",
progressIndicatorBuilder: (context, url, progress) => placeHolder(),
errorWidget: (context, url, error) => placeHolder(),
memCacheHeight: 128,
fit: BoxFit.cover,
),
FlatButton(
onTap: onTap,