133 lines
4.0 KiB
Dart
133 lines
4.0 KiB
Dart
|
|
import 'dart:ui' show ImageFilter;
|
||
|
|
|
||
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
|
||
|
|
import '../../providers/emby_headers_provider.dart';
|
||
|
|
import '../../providers/shell_backdrop_provider.dart';
|
||
|
|
|
||
|
|
|
||
|
|
class ShellBackdropLayer extends ConsumerWidget {
|
||
|
|
final bool active;
|
||
|
|
|
||
|
|
const ShellBackdropLayer({super.key, this.active = false});
|
||
|
|
|
||
|
|
|
||
|
|
static const double _blurSigma = 32;
|
||
|
|
|
||
|
|
|
||
|
|
static const Duration _crossfade = Duration(milliseconds: 350);
|
||
|
|
|
||
|
|
|
||
|
|
static const LinearGradient _immersiveDarkFallback = LinearGradient(
|
||
|
|
begin: Alignment.topCenter,
|
||
|
|
end: Alignment.bottomCenter,
|
||
|
|
stops: [0.0, 0.4, 1.0],
|
||
|
|
colors: [Color(0xFF1A1A1A), Color(0xFF0F0F0F), Color(0xFF000000)],
|
||
|
|
);
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
||
|
|
final backdrop = active ? ref.watch(shellBackdropProvider) : null;
|
||
|
|
final imageHeaders = ref.watch(embyImageHeadersProvider).value;
|
||
|
|
final scheme = Theme.of(context).colorScheme;
|
||
|
|
|
||
|
|
final Widget child;
|
||
|
|
if (backdrop != null) {
|
||
|
|
child = _BackdropImage(
|
||
|
|
key: ValueKey<String>(backdrop.primaryImageUrl),
|
||
|
|
state: backdrop,
|
||
|
|
imageHeaders: imageHeaders,
|
||
|
|
);
|
||
|
|
} else if (active) {
|
||
|
|
child = const DecoratedBox(
|
||
|
|
key: ValueKey<String>('__immersive_dark_fallback__'),
|
||
|
|
decoration: BoxDecoration(gradient: _immersiveDarkFallback),
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
child = ColoredBox(
|
||
|
|
key: ValueKey<String>('__surface__${scheme.brightness.name}'),
|
||
|
|
color: scheme.surface,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return AnimatedSwitcher(
|
||
|
|
duration: _crossfade,
|
||
|
|
switchInCurve: Curves.easeOut,
|
||
|
|
switchOutCurve: Curves.easeOut,
|
||
|
|
layoutBuilder: (currentChild, previousChildren) {
|
||
|
|
final seen = <Key?>{if (currentChild != null) currentChild.key};
|
||
|
|
final filtered = previousChildren.where((child) {
|
||
|
|
if (seen.contains(child.key)) return false;
|
||
|
|
seen.add(child.key);
|
||
|
|
return true;
|
||
|
|
}).toList();
|
||
|
|
return Stack(
|
||
|
|
fit: StackFit.expand,
|
||
|
|
children: [...filtered, if (currentChild != null) currentChild],
|
||
|
|
);
|
||
|
|
},
|
||
|
|
child: child,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class _BackdropImage extends StatelessWidget {
|
||
|
|
final ShellBackdropState state;
|
||
|
|
final Map<String, String>? imageHeaders;
|
||
|
|
|
||
|
|
const _BackdropImage({
|
||
|
|
super.key,
|
||
|
|
required this.state,
|
||
|
|
required this.imageHeaders,
|
||
|
|
});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final fallbackUrl = state.fallbackImageUrls.isNotEmpty
|
||
|
|
? state.fallbackImageUrls.first
|
||
|
|
: state.primaryImageUrl;
|
||
|
|
final surface = Theme.of(context).colorScheme.surface;
|
||
|
|
|
||
|
|
return Stack(
|
||
|
|
fit: StackFit.expand,
|
||
|
|
children: [
|
||
|
|
ImageFiltered(
|
||
|
|
imageFilter: ImageFilter.blur(
|
||
|
|
sigmaX: ShellBackdropLayer._blurSigma,
|
||
|
|
sigmaY: ShellBackdropLayer._blurSigma,
|
||
|
|
tileMode: TileMode.clamp,
|
||
|
|
),
|
||
|
|
child: CachedNetworkImage(
|
||
|
|
imageUrl: state.primaryImageUrl,
|
||
|
|
httpHeaders: imageHeaders,
|
||
|
|
fit: BoxFit.cover,
|
||
|
|
memCacheWidth: 800,
|
||
|
|
fadeInDuration: const Duration(milliseconds: 200),
|
||
|
|
placeholder: (_, _) => ColoredBox(color: surface),
|
||
|
|
errorWidget: (_, _, _) => CachedNetworkImage(
|
||
|
|
imageUrl: fallbackUrl,
|
||
|
|
httpHeaders: imageHeaders,
|
||
|
|
fit: BoxFit.cover,
|
||
|
|
memCacheWidth: 800,
|
||
|
|
placeholder: (_, _) => ColoredBox(color: surface),
|
||
|
|
errorWidget: (_, _, _) => ColoredBox(color: surface),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const DecoratedBox(
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
gradient: LinearGradient(
|
||
|
|
begin: Alignment.topCenter,
|
||
|
|
end: Alignment.bottomCenter,
|
||
|
|
stops: [0.0, 0.4, 1.0],
|
||
|
|
colors: [Color(0x33000000), Color(0x59000000), Color(0xB3000000)],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|