Files
PinePods-nix/PinePods-0.8.2/mobile/lib/ui/widgets/offline_episode_tile.dart
2026-03-03 10:57:43 -05:00

162 lines
4.7 KiB
Dart

// lib/ui/widgets/offline_episode_tile.dart
import 'package:flutter/material.dart';
import 'package:pinepods_mobile/entities/episode.dart';
import 'package:pinepods_mobile/ui/widgets/tile_image.dart';
import 'package:pinepods_mobile/l10n/L.dart';
import 'package:intl/intl.dart' show DateFormat;
/// A custom episode tile specifically for offline downloaded episodes.
/// This bypasses the legacy PlayControl system and uses a custom play callback.
class OfflineEpisodeTile extends StatelessWidget {
final Episode episode;
final VoidCallback? onPlayPressed;
final VoidCallback? onTap;
const OfflineEpisodeTile({
super.key,
required this.episode,
this.onPlayPressed,
this.onTap,
});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
child: ListTile(
onTap: onTap,
leading: Stack(
alignment: Alignment.bottomLeft,
children: [
Opacity(
opacity: episode.played ? 0.5 : 1.0,
child: TileImage(
url: episode.thumbImageUrl ?? episode.imageUrl!,
size: 56.0,
highlight: episode.highlight,
),
),
// Progress indicator
SizedBox(
height: 5.0,
width: 56.0 * (episode.percentagePlayed / 100),
child: Container(
color: Theme.of(context).primaryColor,
),
),
],
),
title: Opacity(
opacity: episode.played ? 0.5 : 1.0,
child: Text(
episode.title!,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: textTheme.bodyMedium,
),
),
subtitle: Opacity(
opacity: episode.played ? 0.5 : 1.0,
child: _EpisodeSubtitle(episode),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Offline indicator
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.green[100],
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.offline_pin,
size: 12,
color: Colors.green[700],
),
const SizedBox(width: 4),
Text(
'Offline',
style: TextStyle(
fontSize: 10,
color: Colors.green[700],
fontWeight: FontWeight.w500,
),
),
],
),
),
const SizedBox(width: 8),
// Custom play button that bypasses legacy audio system
SizedBox(
width: 48,
height: 48,
child: IconButton(
onPressed: onPlayPressed,
icon: Icon(
Icons.play_arrow,
color: Theme.of(context).primaryColor,
),
tooltip: L.of(context)?.play_button_label ?? 'Play',
),
),
],
),
),
);
}
}
class _EpisodeSubtitle extends StatelessWidget {
final Episode episode;
final String date;
final Duration length;
_EpisodeSubtitle(this.episode)
: date = episode.publicationDate == null
? ''
: DateFormat(episode.publicationDate!.year == DateTime.now().year ? 'd MMM' : 'd MMM yyyy')
.format(episode.publicationDate!),
length = Duration(seconds: episode.duration);
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
var timeRemaining = episode.timeRemaining;
String title;
if (length.inSeconds > 0) {
if (length.inSeconds < 60) {
title = '$date${length.inSeconds} sec';
} else {
title = '$date${length.inMinutes} min';
}
} else {
title = date;
}
if (timeRemaining.inSeconds > 0) {
if (timeRemaining.inSeconds < 60) {
title = '$title / ${timeRemaining.inSeconds} sec left';
} else {
title = '$title / ${timeRemaining.inMinutes} min left';
}
}
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(
title,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: textTheme.bodySmall,
),
);
}
}