mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-15 02:05:58 -07:00
feature: Details screen rework (#190)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
473e817e0f
commit
d2138da785
21 changed files with 462 additions and 394 deletions
|
|
@ -61,9 +61,11 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final padding = EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width / 25);
|
||||
final padding = EdgeInsets.symmetric(horizontal: MediaQuery.sizeOf(context).width / 25);
|
||||
final backGroundColor = Theme.of(context).colorScheme.surface.withOpacity(0.8);
|
||||
final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state));
|
||||
final minHeight = 450.0.clamp(0, MediaQuery.sizeOf(context).height).toDouble();
|
||||
final maxHeight = MediaQuery.sizeOf(context).height - 10;
|
||||
return PullToRefresh(
|
||||
onRefresh: () async {
|
||||
await widget.onRefresh?.call();
|
||||
|
|
@ -90,18 +92,54 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
|||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 10,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: maxHeight,
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: FladderImage(
|
||||
image: backgroundImage,
|
||||
blurOnly: true,
|
||||
),
|
||||
),
|
||||
if (backgroundImage != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ShaderMask(
|
||||
shaderCallback: (bounds) => LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.white.withOpacity(0),
|
||||
],
|
||||
).createShader(bounds),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: double.infinity,
|
||||
minHeight: minHeight - 20,
|
||||
maxHeight: maxHeight.clamp(minHeight, 2500) - 20,
|
||||
),
|
||||
child: FadeInImage(
|
||||
placeholder: backgroundImage!.imageProvider,
|
||||
placeholderColor: Colors.transparent,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
placeholderFit: BoxFit.cover,
|
||||
excludeFromSemantics: true,
|
||||
filterQuality: FilterQuality.high,
|
||||
placeholderFilterQuality: FilterQuality.low,
|
||||
image: backgroundImage!.imageProvider,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
width: double.infinity,
|
||||
height: maxHeight + 10,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
|
|
@ -117,8 +155,8 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
|||
),
|
||||
),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
color: widget.backgroundColor,
|
||||
),
|
||||
Padding(
|
||||
|
|
@ -128,8 +166,8 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
|||
top: MediaQuery.of(context).padding.top + 50),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height,
|
||||
maxWidth: MediaQuery.of(context).size.width,
|
||||
minHeight: MediaQuery.sizeOf(context).height,
|
||||
maxWidth: MediaQuery.sizeOf(context).width,
|
||||
),
|
||||
child: widget.content(padding),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class FlatButton extends ConsumerWidget {
|
|||
final BorderRadius? borderRadiusGeometry;
|
||||
final Color? splashColor;
|
||||
final double elevation;
|
||||
final bool showFeedback;
|
||||
final Clip clipBehavior;
|
||||
const FlatButton({
|
||||
this.child,
|
||||
|
|
@ -23,6 +24,7 @@ class FlatButton extends ConsumerWidget {
|
|||
this.borderRadiusGeometry,
|
||||
this.splashColor,
|
||||
this.elevation = 0,
|
||||
this.showFeedback = true,
|
||||
this.clipBehavior = Clip.none,
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -46,6 +48,7 @@ class FlatButton extends ConsumerWidget {
|
|||
onSecondaryTapDown: onSecondaryTapDown,
|
||||
borderRadius: borderRadiusGeometry ?? BorderRadius.circular(10),
|
||||
splashColor: splashColor ?? Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
||||
hoverColor: showFeedback ? null : Colors.transparent,
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
|
|
@ -8,8 +12,6 @@ import 'package:fladder/util/localization_helper.dart';
|
|||
import 'package:fladder/widgets/shared/horizontal_list.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ChapterRow extends ConsumerWidget {
|
||||
final List<Chapter> chapters;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
|
||||
class ChipButton extends ConsumerWidget {
|
||||
final String label;
|
||||
final Function()? onPressed;
|
||||
|
|
@ -8,19 +11,20 @@ class ChipButton extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.75),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide.none,
|
||||
return Card(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.15),
|
||||
shadowColor: Colors.transparent,
|
||||
child: FlatButton(
|
||||
onTap: onPressed,
|
||||
// ),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,66 @@
|
|||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
|
||||
class MediaHeader extends ConsumerWidget {
|
||||
final String name;
|
||||
final ImageData? logo;
|
||||
final Function()? onTap;
|
||||
const MediaHeader({
|
||||
required this.name,
|
||||
required this.logo,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final maxWidth =
|
||||
switch (AdaptiveLayout.layoutOf(context)) { LayoutState.desktop || LayoutState.tablet => 0.55, _ => 1 };
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Material(
|
||||
elevation: 30,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(150)),
|
||||
shadowColor: Colors.black.withOpacity(0.35),
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.2,
|
||||
maxWidth: MediaQuery.sizeOf(context).width * maxWidth,
|
||||
),
|
||||
child: FladderImage(
|
||||
image: logo,
|
||||
enableBlur: true,
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) => Container(
|
||||
color: Colors.red,
|
||||
width: 512,
|
||||
height: 512,
|
||||
child: child,
|
||||
),
|
||||
placeHolder: const SizedBox(height: 0),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
final maxSize = 700.0;
|
||||
final textWidget = Container(
|
||||
height: 512,
|
||||
alignment: Alignment.center,
|
||||
child: SelectableText(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
fontSize: 55,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Center(
|
||||
child: Material(
|
||||
elevation: 30,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(150)),
|
||||
shadowColor: Colors.black.withOpacity(0.3),
|
||||
color: Colors.transparent,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: (MediaQuery.sizeOf(context).height * 0.275).clamp(0, maxSize),
|
||||
maxWidth: MediaQuery.sizeOf(context).width.clamp(0, maxSize),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
logo != null
|
||||
? FladderImage(
|
||||
image: logo,
|
||||
enableBlur: true,
|
||||
alignment: Alignment.bottomCenter,
|
||||
imageErrorBuilder: (context, object, stack) => textWidget,
|
||||
placeHolder: const SizedBox(height: 0),
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
: textWidget,
|
||||
if (onTap != null)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class MediaPlayButton extends ConsumerWidget {
|
||||
final ItemBaseModel? item;
|
||||
|
|
@ -33,8 +35,8 @@ class MediaPlayButton extends ConsumerWidget {
|
|||
child: Text(
|
||||
item?.playButtonLabel(context) ?? "",
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/screens/details_screens/components/media_stream_information.dart';
|
||||
|
|
@ -5,9 +10,7 @@ import 'package:fladder/screens/shared/media/episode_posters.dart';
|
|||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/sticky_header_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
||||
class NextUpEpisode extends ConsumerWidget {
|
||||
final EpisodeModel nextEpisode;
|
||||
|
|
@ -17,6 +20,7 @@ class NextUpEpisode extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final alreadyPlayed = nextEpisode.userData.played;
|
||||
final episodeSummary = nextEpisode.overview.summary.maxLength(limitTo: 250);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -53,7 +57,7 @@ class NextUpEpisode extends ConsumerWidget {
|
|||
const SizedBox(height: 16),
|
||||
if (nextEpisode.overview.summary.isNotEmpty)
|
||||
HtmlWidget(
|
||||
nextEpisode.overview.summary,
|
||||
episodeSummary,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
|
|
@ -88,7 +92,7 @@ class NextUpEpisode extends ConsumerWidget {
|
|||
mediaStreams: nextEpisode.mediaStreams.copyWith(defaultSubStreamIndex: index))),
|
||||
),
|
||||
if (nextEpisode.overview.summary.isNotEmpty)
|
||||
HtmlWidget(nextEpisode.overview.summary, textStyle: Theme.of(context).textTheme.titleMedium),
|
||||
HtmlWidget(episodeSummary, textStyle: Theme.of(context).textTheme.titleMedium),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ class Genres extends StatelessWidget {
|
|||
return Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
runAlignment: WrapAlignment.center,
|
||||
alignment: WrapAlignment.center,
|
||||
children: genres
|
||||
.map(
|
||||
(genre) => ChipButton(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as customtab;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' as urllauncher;
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/sticky_header_text.dart';
|
||||
|
||||
class ExternalUrlsRow extends ConsumerWidget {
|
||||
final List<ExternalUrls>? urls;
|
||||
const ExternalUrlsRow({
|
||||
|
|
@ -15,16 +19,28 @@ class ExternalUrlsRow extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Wrap(
|
||||
children: urls
|
||||
?.map(
|
||||
(url) => TextButton(
|
||||
onPressed: () => launchUrl(context, url.url),
|
||||
child: Text(url.name),
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
StickyHeaderText(
|
||||
label: context.localized.external,
|
||||
),
|
||||
Transform.translate(
|
||||
offset: const Offset(-12, 0),
|
||||
child: Wrap(
|
||||
children: urls
|
||||
?.map(
|
||||
(url) => TextButton(
|
||||
onPressed: () => launchUrl(context, url.url),
|
||||
child: Text(url.name),
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue