mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-16 10:46:00 -07:00
feat: Android TV support (#503)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
7ab8c015b9
commit
c299492d6d
168 changed files with 12019 additions and 3073 deletions
|
|
@ -5,6 +5,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/screens/details_screens/components/label_title_item.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/widgets/shared/enum_selection.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
|
||||
class MediaStreamInformation extends ConsumerWidget {
|
||||
final MediaStreamsModel mediaStream;
|
||||
|
|
@ -30,15 +32,13 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
label: Text(context.localized.version),
|
||||
current: mediaStream.currentVersionStream?.name ?? "",
|
||||
itemBuilder: (context) => mediaStream.versionStreams
|
||||
.map((e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: textWidget(
|
||||
.map((e) => ItemActionButton(
|
||||
selected: mediaStream.currentVersionStream == e,
|
||||
label: textWidget(
|
||||
context,
|
||||
selected: mediaStream.currentVersionStream == e,
|
||||
label: e.name,
|
||||
),
|
||||
onTap: () => onVersionIndexChanged?.call(e.index),
|
||||
action: () => onVersionIndexChanged?.call(e.index),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
|
|
@ -48,10 +48,8 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
current: (mediaStream.videoStreams.first).prettyName,
|
||||
itemBuilder: (context) => mediaStream.videoStreams
|
||||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: Text(e.prettyName),
|
||||
(e) => ItemActionButton(
|
||||
label: Text(e.prettyName),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
@ -62,12 +60,13 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
current: mediaStream.currentAudioStream?.displayTitle ?? "",
|
||||
itemBuilder: (context) => [AudioStreamModel.no(), ...mediaStream.audioStreams]
|
||||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: textWidget(context,
|
||||
selected: mediaStream.currentAudioStream?.index == e.index, label: e.displayTitle),
|
||||
onTap: () => onAudioIndexChanged?.call(e.index),
|
||||
(e) => ItemActionButton(
|
||||
selected: mediaStream.currentAudioStream?.index == e.index,
|
||||
label: textWidget(
|
||||
context,
|
||||
label: e.displayTitle,
|
||||
),
|
||||
action: () => onAudioIndexChanged?.call(e.index),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
@ -78,12 +77,13 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
current: mediaStream.currentSubStream?.displayTitle ?? "",
|
||||
itemBuilder: (context) => [SubStreamModel.no(), ...mediaStream.subStreams]
|
||||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: textWidget(context,
|
||||
selected: mediaStream.currentSubStream?.index == e.index, label: e.displayTitle),
|
||||
onTap: () => onSubIndexChanged?.call(e.index),
|
||||
(e) => ItemActionButton(
|
||||
selected: mediaStream.currentSubStream?.index == e.index,
|
||||
label: textWidget(
|
||||
context,
|
||||
label: e.displayTitle,
|
||||
),
|
||||
action: () => onSubIndexChanged?.call(e.index),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
@ -92,22 +92,12 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget textWidget(BuildContext context, {required bool selected, required String label}) {
|
||||
return Container(
|
||||
height: kMinInteractiveDimension,
|
||||
width: double.maxFinite,
|
||||
color: selected ? Theme.of(context).colorScheme.primary : null,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: selected ? Theme.of(context).colorScheme.onPrimary : null,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Widget textWidget(BuildContext context, {required String label}) {
|
||||
return Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +105,7 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
class _StreamOptionSelect<T> extends StatelessWidget {
|
||||
final Text label;
|
||||
final String current;
|
||||
final List<PopupMenuEntry<T>> Function(BuildContext context) itemBuilder;
|
||||
final List<ItemAction> Function(BuildContext context) itemBuilder;
|
||||
const _StreamOptionSelect({
|
||||
required this.label,
|
||||
required this.current,
|
||||
|
|
@ -124,47 +114,14 @@ class _StreamOptionSelect<T> extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = Theme.of(context).textTheme.titleMedium;
|
||||
const padding = EdgeInsets.all(6);
|
||||
final itemList = itemBuilder(context);
|
||||
return LabelTitleItem(
|
||||
title: label,
|
||||
content: Flexible(
|
||||
child: PopupMenuButton(
|
||||
tooltip: '',
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
enabled: itemList.length > 1,
|
||||
itemBuilder: itemBuilder,
|
||||
enableFeedback: false,
|
||||
menuPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
padding: padding,
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Material(
|
||||
textStyle: textStyle?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: itemList.length > 1 ? Theme.of(context).colorScheme.primary : null),
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
current,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if (itemList.length > 1)
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: LabelTitleItem(
|
||||
title: label,
|
||||
content: Flexible(
|
||||
child: EnumBox(
|
||||
current: current,
|
||||
itemBuilder: itemBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ class OverviewHeader extends ConsumerWidget {
|
|||
final EdgeInsets? padding;
|
||||
final String? subTitle;
|
||||
final String? originalTitle;
|
||||
final Alignment logoAlignment;
|
||||
final Function()? onTitleClicked;
|
||||
final int? productionYear;
|
||||
final String? summary;
|
||||
final Duration? runTime;
|
||||
final String? officialRating;
|
||||
final double? communityRating;
|
||||
|
|
@ -32,8 +34,10 @@ class OverviewHeader extends ConsumerWidget {
|
|||
this.padding,
|
||||
this.subTitle,
|
||||
this.originalTitle,
|
||||
this.logoAlignment = Alignment.bottomCenter,
|
||||
this.onTitleClicked,
|
||||
this.productionYear,
|
||||
this.summary,
|
||||
this.runTime,
|
||||
this.officialRating,
|
||||
this.communityRating,
|
||||
|
|
@ -68,83 +72,101 @@ class OverviewHeader extends ConsumerWidget {
|
|||
crossAxisAlignment: crossAlignment,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MediaHeader(
|
||||
name: name,
|
||||
logo: image?.logo,
|
||||
onTap: onTitleClicked,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: crossAlignment,
|
||||
children: [
|
||||
if (subTitle != null)
|
||||
Flexible(
|
||||
child: SelectableText(
|
||||
subTitle ?? "",
|
||||
textAlign: TextAlign.center,
|
||||
style: mainStyle,
|
||||
),
|
||||
),
|
||||
if (name.toLowerCase() != originalTitle?.toLowerCase() && originalTitle != null)
|
||||
SelectableText(
|
||||
originalTitle.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: subStyle,
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 4)),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: crossAlignment,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
direction: Axis.horizontal,
|
||||
alignment: WrapAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (officialRating != null)
|
||||
ChipButton(
|
||||
label: officialRating.toString(),
|
||||
),
|
||||
if (productionYear != null)
|
||||
SelectableText(
|
||||
productionYear.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: subStyle,
|
||||
),
|
||||
if (runTime != null && (runTime?.inSeconds ?? 0) > 1)
|
||||
SelectableText(
|
||||
runTime.humanize.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: subStyle,
|
||||
),
|
||||
if (communityRating != null)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.star_rate_rounded,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
communityRating?.toStringAsFixed(2) ?? "",
|
||||
style: subStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
].addInBetween(CircleAvatar(
|
||||
radius: 3,
|
||||
backgroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
)),
|
||||
Flexible(
|
||||
child: ExcludeFocus(
|
||||
child: MediaHeader(
|
||||
name: name,
|
||||
logo: image?.logo,
|
||||
onTap: onTitleClicked,
|
||||
alignment: logoAlignment,
|
||||
),
|
||||
if (genres.isNotEmpty)
|
||||
Genres(
|
||||
genres: genres.take(6).toList(),
|
||||
),
|
||||
),
|
||||
ExcludeFocus(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: crossAlignment,
|
||||
children: [
|
||||
if (subTitle != null)
|
||||
Flexible(
|
||||
child: SelectableText(
|
||||
subTitle ?? "",
|
||||
textAlign: TextAlign.center,
|
||||
style: mainStyle,
|
||||
),
|
||||
),
|
||||
if (name.toLowerCase() != originalTitle?.toLowerCase() && originalTitle != null)
|
||||
SelectableText(
|
||||
originalTitle.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: subStyle,
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 4)),
|
||||
),
|
||||
),
|
||||
ExcludeFocus(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: crossAlignment,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
direction: Axis.horizontal,
|
||||
alignment: WrapAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (officialRating != null)
|
||||
ChipButton(
|
||||
label: officialRating.toString(),
|
||||
),
|
||||
if (productionYear != null)
|
||||
SelectableText(
|
||||
productionYear.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: subStyle,
|
||||
),
|
||||
if (runTime != null && (runTime?.inSeconds ?? 0) > 1)
|
||||
SelectableText(
|
||||
runTime.humanize.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: subStyle,
|
||||
),
|
||||
if (communityRating != null)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.star_rate_rounded,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
communityRating?.toStringAsFixed(2) ?? "",
|
||||
style: subStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
].addInBetween(CircleAvatar(
|
||||
radius: 3,
|
||||
backgroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
)),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 10)),
|
||||
if (summary?.isNotEmpty == true)
|
||||
Flexible(
|
||||
child: Text(
|
||||
summary ?? "",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
if (genres.isNotEmpty)
|
||||
Genres(
|
||||
genres: genres.take(6).toList(),
|
||||
),
|
||||
].addInBetween(const SizedBox(height: 10)),
|
||||
),
|
||||
),
|
||||
if (centerButtons != null) centerButtons!,
|
||||
].addInBetween(const SizedBox(height: 21)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue