154 lines
4.6 KiB
Dart
154 lines
4.6 KiB
Dart
|
|
import 'dart:io' show Platform;
|
||
|
|
import 'dart:ui' show ImageFilter;
|
||
|
|
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
|
||
|
|
import 'detail_backdrop.dart';
|
||
|
|
|
||
|
|
|
||
|
|
class DetailImmersiveScaffold extends StatelessWidget {
|
||
|
|
final ScrollController scrollController;
|
||
|
|
final ValueNotifier<double> scrollProgress;
|
||
|
|
final String? backdropUrl;
|
||
|
|
final List<String> fallbackUrls;
|
||
|
|
final String embyBaseUrl;
|
||
|
|
final Map<String, String>? imageHeaders;
|
||
|
|
final Widget child;
|
||
|
|
final bool compact;
|
||
|
|
final double horizontalPadding;
|
||
|
|
|
||
|
|
const DetailImmersiveScaffold({
|
||
|
|
super.key,
|
||
|
|
required this.scrollController,
|
||
|
|
required this.scrollProgress,
|
||
|
|
required this.child,
|
||
|
|
this.backdropUrl,
|
||
|
|
this.fallbackUrls = const <String>[],
|
||
|
|
this.embyBaseUrl = '',
|
||
|
|
this.imageHeaders,
|
||
|
|
this.compact = false,
|
||
|
|
this.horizontalPadding = 32,
|
||
|
|
});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final screenSize = MediaQuery.sizeOf(context);
|
||
|
|
final screenHeight = screenSize.height;
|
||
|
|
final screenWidth = screenSize.width;
|
||
|
|
final bannerHeight = compact
|
||
|
|
? screenHeight * 0.30
|
||
|
|
: (screenWidth * 9.0 / 16.0).clamp(280.0, screenHeight * 0.6);
|
||
|
|
final backdrop = backdropUrl;
|
||
|
|
|
||
|
|
return Stack(
|
||
|
|
children: [
|
||
|
|
if (backdrop != null)
|
||
|
|
DetailBackdrop(
|
||
|
|
scrollProgress: scrollProgress,
|
||
|
|
backdropUrl: backdrop,
|
||
|
|
fallbackUrls: fallbackUrls,
|
||
|
|
embyBaseUrl: embyBaseUrl,
|
||
|
|
imageHeaders: imageHeaders,
|
||
|
|
),
|
||
|
|
SingleChildScrollView(
|
||
|
|
controller: scrollController,
|
||
|
|
child: Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
SizedBox(height: bannerHeight - 80),
|
||
|
|
Stack(
|
||
|
|
children: [
|
||
|
|
Positioned.fill(child: _FrostedPanelVeil()),
|
||
|
|
Padding(
|
||
|
|
padding: EdgeInsets.fromLTRB(
|
||
|
|
horizontalPadding,
|
||
|
|
64,
|
||
|
|
horizontalPadding,
|
||
|
|
48,
|
||
|
|
),
|
||
|
|
child: Align(
|
||
|
|
alignment: Alignment.topCenter,
|
||
|
|
child: ConstrainedBox(
|
||
|
|
constraints: const BoxConstraints(maxWidth: 1200),
|
||
|
|
child: child,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class _FrostedPanelVeil extends StatelessWidget {
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
if (Platform.isAndroid) {
|
||
|
|
return IgnorePointer(
|
||
|
|
child: ShaderMask(
|
||
|
|
shaderCallback: (rect) {
|
||
|
|
final ratio = (140.0 / rect.height).clamp(0.02, 0.5);
|
||
|
|
return LinearGradient(
|
||
|
|
begin: Alignment.topCenter,
|
||
|
|
end: Alignment.bottomCenter,
|
||
|
|
colors: const [Colors.transparent, Colors.black],
|
||
|
|
stops: [0.0, ratio],
|
||
|
|
).createShader(rect);
|
||
|
|
},
|
||
|
|
blendMode: BlendMode.dstIn,
|
||
|
|
child: DecoratedBox(
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
gradient: LinearGradient(
|
||
|
|
begin: Alignment.topCenter,
|
||
|
|
end: Alignment.bottomCenter,
|
||
|
|
stops: const [0.0, 0.12, 1.0],
|
||
|
|
colors: [
|
||
|
|
const Color(0xFF0a0c10).withValues(alpha: 0.0),
|
||
|
|
const Color(0xFF0a0c10).withValues(alpha: 0.88),
|
||
|
|
const Color(0xFF0a0c10).withValues(alpha: 0.96),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return IgnorePointer(
|
||
|
|
child: ShaderMask(
|
||
|
|
shaderCallback: (rect) {
|
||
|
|
final ratio = (140.0 / rect.height).clamp(0.02, 0.5);
|
||
|
|
return LinearGradient(
|
||
|
|
begin: Alignment.topCenter,
|
||
|
|
end: Alignment.bottomCenter,
|
||
|
|
colors: const [Colors.transparent, Colors.black],
|
||
|
|
stops: [0.0, ratio],
|
||
|
|
).createShader(rect);
|
||
|
|
},
|
||
|
|
blendMode: BlendMode.dstIn,
|
||
|
|
child: BackdropFilter(
|
||
|
|
filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40),
|
||
|
|
child: DecoratedBox(
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
gradient: LinearGradient(
|
||
|
|
begin: Alignment.topCenter,
|
||
|
|
end: Alignment.bottomCenter,
|
||
|
|
stops: const [0.0, 0.20, 1.0],
|
||
|
|
colors: [
|
||
|
|
const Color(0xFF0a0c10).withValues(alpha: 0.0),
|
||
|
|
const Color(0xFF0a0c10).withValues(alpha: 0.12),
|
||
|
|
const Color(0xFF0a0c10).withValues(alpha: 0.20),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|