Merge pull request #326 from micahmo/feature/episode-release-date
Add episode release date to episode listing within season
This commit is contained in:
@@ -39,6 +39,7 @@ import 'utils/orientation_helper.dart';
|
||||
import 'utils/language_codes.dart';
|
||||
import 'i18n/strings.g.dart';
|
||||
import 'focus/input_mode_tracker.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
// Workaround for Flutter bug #177992: iPadOS 26.1+ misinterprets fake touch events
|
||||
// at (0,0) as barrier taps, causing modals to dismiss immediately.
|
||||
@@ -68,6 +69,9 @@ void main() async {
|
||||
// Initialize localization with saved locale
|
||||
LocaleSettings.setLocale(savedLocale);
|
||||
|
||||
// Needed for formatting dates in different locales
|
||||
await initializeDateFormatting(savedLocale.languageCode, null);
|
||||
|
||||
// Configure image cache for large libraries
|
||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 200 << 20; // 200MB
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ class PlexMetadata with MultiServerFields {
|
||||
final double? rating;
|
||||
final double? audienceRating;
|
||||
final int? year;
|
||||
final String? originallyAvailableAt; // Full release date (YYYY-MM-DD)
|
||||
final String? thumb;
|
||||
final String? art;
|
||||
final int? duration;
|
||||
@@ -120,6 +121,7 @@ class PlexMetadata with MultiServerFields {
|
||||
this.rating,
|
||||
this.audienceRating,
|
||||
this.year,
|
||||
this.originallyAvailableAt,
|
||||
this.thumb,
|
||||
this.art,
|
||||
this.duration,
|
||||
@@ -165,6 +167,7 @@ class PlexMetadata with MultiServerFields {
|
||||
double? rating,
|
||||
double? audienceRating,
|
||||
int? year,
|
||||
String? originallyAvailableAt,
|
||||
String? thumb,
|
||||
String? art,
|
||||
int? duration,
|
||||
@@ -208,6 +211,7 @@ class PlexMetadata with MultiServerFields {
|
||||
rating: rating ?? this.rating,
|
||||
audienceRating: audienceRating ?? this.audienceRating,
|
||||
year: year ?? this.year,
|
||||
originallyAvailableAt: originallyAvailableAt ?? this.originallyAvailableAt,
|
||||
thumb: thumb ?? this.thumb,
|
||||
art: art ?? this.art,
|
||||
duration: duration ?? this.duration,
|
||||
|
||||
@@ -18,6 +18,7 @@ PlexMetadata _$PlexMetadataFromJson(Map<String, dynamic> json) => PlexMetadata(
|
||||
rating: (json['rating'] as num?)?.toDouble(),
|
||||
audienceRating: (json['audienceRating'] as num?)?.toDouble(),
|
||||
year: (json['year'] as num?)?.toInt(),
|
||||
originallyAvailableAt: json['originallyAvailableAt'] as String?,
|
||||
thumb: json['thumb'] as String?,
|
||||
art: json['art'] as String?,
|
||||
duration: (json['duration'] as num?)?.toInt(),
|
||||
@@ -60,6 +61,7 @@ Map<String, dynamic> _$PlexMetadataToJson(PlexMetadata instance) => <String, dyn
|
||||
'rating': instance.rating,
|
||||
'audienceRating': instance.audienceRating,
|
||||
'year': instance.year,
|
||||
'originallyAvailableAt': instance.originallyAvailableAt,
|
||||
'thumb': instance.thumb,
|
||||
'art': instance.art,
|
||||
'duration': instance.duration,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/widgets/app_icon.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -224,6 +225,8 @@ class _EpisodeCard extends StatelessWidget {
|
||||
});
|
||||
|
||||
Widget _buildEpisodeMetaRow(BuildContext context) {
|
||||
final isMobile = PlatformDetector.isMobile(context);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (episode.duration != null)
|
||||
@@ -231,6 +234,19 @@ class _EpisodeCard extends StatelessWidget {
|
||||
formatDurationTimestamp(Duration(milliseconds: episode.duration!)),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: tokens(context).textMuted, fontSize: 12),
|
||||
),
|
||||
if (!isMobile && episode.originallyAvailableAt != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Text(
|
||||
'•',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: tokens(context).textMuted, fontSize: 12),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatFullDate(episode.originallyAvailableAt!),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: tokens(context).textMuted, fontSize: 12),
|
||||
),
|
||||
],
|
||||
// Hide watch status when offline (not tracked)
|
||||
if (!isOffline && episode.duration != null && episode.isWatched) ...[
|
||||
Padding(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:duration/duration.dart';
|
||||
import 'package:duration/locale.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
|
||||
/// Formats a number with a minimum number of digits using leading zeros.
|
||||
@@ -183,3 +184,19 @@ DurationLocale _getDurationLocale() {
|
||||
String toBulletedString(List<String> parts) {
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
/// Takes a date string in the format "YYYY-MM-DD" and returns a localized full date string
|
||||
/// If there is any error, `dateString` is returned as is
|
||||
String formatFullDate(String dateString) {
|
||||
try {
|
||||
// Parse the date
|
||||
final date = DateTime.parse(dateString);
|
||||
|
||||
// Create a DateFormat with the full date pattern for the current locale
|
||||
final formatter = DateFormat.yMMMMd(LocaleSettings.currentLocale.languageCode);
|
||||
|
||||
return formatter.format(date);
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user