mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-17 11:16:33 -07:00
Init repo
This commit is contained in:
commit
764b6034e3
566 changed files with 212335 additions and 0 deletions
306
lib/screens/shared/media/episode_posters.dart
Normal file
306
lib/screens/shared/media/episode_posters.dart
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
import 'package:fladder/screens/syncing/sync_button.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/disable_keypad_focus.dart';
|
||||
import 'package:fladder/util/fladder_image.dart';
|
||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/widgets/shared/clickable_text.dart';
|
||||
import 'package:fladder/widgets/shared/enum_selection.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:fladder/widgets/shared/status_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class EpisodePosters extends ConsumerStatefulWidget {
|
||||
final List<EpisodeModel> episodes;
|
||||
final String? label;
|
||||
final ValueChanged<EpisodeModel> playEpisode;
|
||||
final EdgeInsets contentPadding;
|
||||
final Function(VoidCallback action, EpisodeModel episodeModel)? onEpisodeTap;
|
||||
const EpisodePosters({
|
||||
this.label,
|
||||
required this.contentPadding,
|
||||
required this.playEpisode,
|
||||
required this.episodes,
|
||||
this.onEpisodeTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _EpisodePosterState();
|
||||
}
|
||||
|
||||
class _EpisodePosterState extends ConsumerState<EpisodePosters> {
|
||||
late int? selectedSeason = widget.episodes.nextUp?.season;
|
||||
|
||||
List<EpisodeModel> get episodes {
|
||||
if (selectedSeason == null) {
|
||||
return widget.episodes;
|
||||
} else {
|
||||
return widget.episodes.where((element) => element.season == selectedSeason).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final indexOfCurrent = (episodes.nextUp != null ? episodes.indexOf(episodes.nextUp!) : 0).clamp(0, episodes.length);
|
||||
final episodesBySeason = widget.episodes.episodesBySeason;
|
||||
final allPlayed = episodes.allPlayed;
|
||||
|
||||
return HorizontalList(
|
||||
label: widget.label,
|
||||
titleActions: [
|
||||
if (episodesBySeason.isNotEmpty && episodesBySeason.length > 1) ...{
|
||||
SizedBox(width: 12),
|
||||
EnumBox(
|
||||
current: selectedSeason != null ? "${context.localized.season(1)} $selectedSeason" : context.localized.all,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
child: Text(context.localized.all),
|
||||
onTap: () => setState(() => selectedSeason = null),
|
||||
),
|
||||
...episodesBySeason.entries.map(
|
||||
(e) => PopupMenuItem(
|
||||
child: Text("${context.localized.season(1)} ${e.key}"),
|
||||
onTap: () {
|
||||
setState(() => selectedSeason = e.key);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
},
|
||||
],
|
||||
height: AdaptiveLayout.poster(context).gridRatio,
|
||||
contentPadding: widget.contentPadding,
|
||||
startIndex: indexOfCurrent,
|
||||
items: episodes,
|
||||
itemBuilder: (context, index) {
|
||||
final episode = episodes[index];
|
||||
final isCurrentEpisode = index == indexOfCurrent;
|
||||
final syncedItem = ref.watch(syncProvider.notifier).getSyncedItem(episode);
|
||||
return EpisodePoster(
|
||||
episode: episode,
|
||||
blur: allPlayed ? false : indexOfCurrent < index,
|
||||
syncedItem: syncedItem,
|
||||
onTap: widget.onEpisodeTap != null
|
||||
? () {
|
||||
widget.onEpisodeTap?.call(
|
||||
() {
|
||||
episode.navigateTo(context);
|
||||
},
|
||||
episode,
|
||||
);
|
||||
}
|
||||
: () {
|
||||
episode.navigateTo(context);
|
||||
},
|
||||
onLongPress: () {
|
||||
showBottomSheetPill(
|
||||
context: context,
|
||||
item: episode,
|
||||
content: (context, scrollController) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: [
|
||||
...episode.generateActions(context, ref).listTileItems(context, useIcons: true),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
actions: episode.generateActions(context, ref),
|
||||
isCurrentEpisode: isCurrentEpisode,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EpisodePoster extends ConsumerWidget {
|
||||
final EpisodeModel episode;
|
||||
final SyncedItem? syncedItem;
|
||||
final bool showLabel;
|
||||
final Function()? onTap;
|
||||
final Function()? onLongPress;
|
||||
final bool blur;
|
||||
final List<ItemAction> actions;
|
||||
final bool isCurrentEpisode;
|
||||
|
||||
const EpisodePoster({
|
||||
super.key,
|
||||
required this.episode,
|
||||
this.syncedItem,
|
||||
this.showLabel = true,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.blur = false,
|
||||
required this.actions,
|
||||
required this.isCurrentEpisode,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget placeHolder = Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(Icons.local_movies_outlined),
|
||||
);
|
||||
final SyncedItem? iSyncedItem = syncedItem;
|
||||
bool episodeAvailable = episode.status == EpisodeStatus.available;
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.76,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Card(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
FladderImage(
|
||||
image: switch (episode.status) {
|
||||
EpisodeStatus.unaired || EpisodeStatus.missing => episode.parentImages?.primary,
|
||||
_ => episode.images?.primary
|
||||
},
|
||||
placeHolder: placeHolder,
|
||||
blurOnly:
|
||||
ref.watch(clientSettingsProvider.select((value) => value.blurUpcomingEpisodes)) ? blur : false,
|
||||
),
|
||||
if (!episodeAvailable)
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
elevation: 3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
episode.status.name,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (iSyncedItem != null)
|
||||
Consumer(builder: (context, ref, child) {
|
||||
final SyncStatus syncStatus =
|
||||
ref.watch(syncStatusesProvider(iSyncedItem)).value ?? SyncStatus.partially;
|
||||
return StatusCard(
|
||||
color: syncStatus.color,
|
||||
child: SyncButton(item: episode, syncedItem: syncedItem),
|
||||
);
|
||||
}),
|
||||
if (episode.userData.isFavourite)
|
||||
StatusCard(
|
||||
color: Colors.red,
|
||||
child: Icon(
|
||||
Icons.favorite_rounded,
|
||||
),
|
||||
),
|
||||
if (episode.userData.played)
|
||||
StatusCard(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
child: Icon(
|
||||
Icons.check_rounded,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if ((episode.userData.progress) > 0)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 6,
|
||||
backgroundColor: Colors.black.withOpacity(0.75),
|
||||
value: episode.userData.progress / 100,
|
||||
),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return FlatButton(
|
||||
onSecondaryTapDown: (details) {
|
||||
Offset localPosition = details.globalPosition;
|
||||
RelativeRect position = RelativeRect.fromLTRB(
|
||||
localPosition.dx - 260, localPosition.dy, localPosition.dx, localPosition.dy);
|
||||
|
||||
showMenu(context: context, position: position, items: actions.popupMenuItems(useIcons: true));
|
||||
},
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer && actions.isNotEmpty)
|
||||
DisableFocus(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: PopupMenuButton(
|
||||
tooltip: "Options",
|
||||
icon: Icon(
|
||||
Icons.more_vert,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(color: Colors.black.withOpacity(0.45), blurRadius: 8.0),
|
||||
const Shadow(color: Colors.black, blurRadius: 16.0),
|
||||
const Shadow(color: Colors.black, blurRadius: 32.0),
|
||||
const Shadow(color: Colors.black, blurRadius: 64.0),
|
||||
],
|
||||
),
|
||||
itemBuilder: (context) => actions.popupMenuItems(useIcons: true),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showLabel) ...{
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
if (isCurrentEpisode)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Container(
|
||||
height: 12,
|
||||
width: 12,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ClickableText(
|
||||
text: episode.episodeLabel(context),
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
}
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue