refactor: format

This commit is contained in:
edde746
2025-11-16 23:57:32 +01:00
parent a6927f0edf
commit 9501831691
20 changed files with 244 additions and 282 deletions
+5 -3
View File
@@ -180,11 +180,13 @@ class StorageService {
}
Map<String, String> getLibraryFilters({String? sectionId}) {
final scopedKey =
sectionId != null ? 'library_filters_$sectionId' : _keyLibraryFilters;
final scopedKey = sectionId != null
? 'library_filters_$sectionId'
: _keyLibraryFilters;
// Prefer per-library filters when available
final jsonString = _prefs.getString(scopedKey) ??
final jsonString =
_prefs.getString(scopedKey) ??
// Legacy support: fall back to global filters if present
_prefs.getString(_keyLibraryFilters);
if (jsonString == null) return {};
+7 -12
View File
@@ -62,13 +62,13 @@ Future<void> playCollectionOrPlaylist({
// Set play queue in provider
final playbackState = context.read<PlaybackStateProvider>();
playbackState.setClient(client);
await playbackState.setPlaybackFromPlayQueue(
fetchedQueue,
ratingKey,
);
await playbackState.setPlaybackFromPlayQueue(fetchedQueue, ratingKey);
// Navigate to first item
await navigateToVideoPlayer(context, metadata: fetchedQueue.items!.first);
await navigateToVideoPlayer(
context,
metadata: fetchedQueue.items!.first,
);
return;
}
}
@@ -78,9 +78,7 @@ Future<void> playCollectionOrPlaylist({
playQueue.items!.isEmpty) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(t.messages.failedToCreatePlayQueueNoItems),
),
SnackBar(content: Text(t.messages.failedToCreatePlayQueueNoItems)),
);
}
return;
@@ -91,10 +89,7 @@ Future<void> playCollectionOrPlaylist({
// Set play queue in provider
final playbackState = context.read<PlaybackStateProvider>();
playbackState.setClient(client);
await playbackState.setPlaybackFromPlayQueue(
playQueue,
ratingKey,
);
await playbackState.setPlaybackFromPlayQueue(playQueue, ratingKey);
// Navigate to first item
await navigateToVideoPlayer(context, metadata: playQueue.items!.first);
+1 -4
View File
@@ -22,8 +22,5 @@ String mapDioErrorToMessage(DioException error, {required String context}) {
/// Generic fallback for unexpected errors.
String mapUnexpectedErrorToMessage(dynamic error, {required String context}) {
appLogger.e('Unexpected error in $context', error: error);
return t.errors.failedToLoad(
context: context,
error: error.toString(),
);
return t.errors.failedToLoad(context: context, error: error.toString());
}
+2 -1
View File
@@ -17,7 +17,8 @@ class GridSizeCalculator {
) {
final screenWidth = MediaQuery.of(context).size.width;
final isDesktop = screenWidth > desktopBreakpoint;
final isTablet = screenWidth > tabletBreakpoint && screenWidth <= desktopBreakpoint;
final isTablet =
screenWidth > tabletBreakpoint && screenWidth <= desktopBreakpoint;
switch (density) {
case LibraryDensity.comfortable:
+2 -1
View File
@@ -3,7 +3,8 @@ import 'dart:async';
/// Notifier for triggering refreshes of library tabs
/// Singleton pattern for global access
class LibraryRefreshNotifier {
static final LibraryRefreshNotifier _instance = LibraryRefreshNotifier._internal();
static final LibraryRefreshNotifier _instance =
LibraryRefreshNotifier._internal();
factory LibraryRefreshNotifier() => _instance;
+2 -8
View File
@@ -57,10 +57,7 @@ class ContentStateBuilder<T> extends StatelessWidget {
style: const TextStyle(color: Colors.white70),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: Text(t.common.retry),
),
ElevatedButton(onPressed: onRetry, child: Text(t.common.retry)),
],
),
);
@@ -74,10 +71,7 @@ class ContentStateBuilder<T> extends StatelessWidget {
children: [
Icon(emptyIcon, size: 64, color: Colors.grey),
const SizedBox(height: 16),
Text(
emptyMessage,
style: const TextStyle(color: Colors.white70),
),
Text(emptyMessage, style: const TextStyle(color: Colors.white70)),
],
),
);
+2 -5
View File
@@ -42,11 +42,8 @@ class EmptyStateWidget extends StatelessWidget {
message,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.6),
),
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
if (onAction != null && actionLabel != null) ...[
const SizedBox(height: 24),
+3 -7
View File
@@ -31,19 +31,15 @@ class ErrorStateWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(
icon,
size: 64,
color: Theme.of(context).colorScheme.error,
),
Icon(icon, size: 64, color: Theme.of(context).colorScheme.error),
const SizedBox(height: 16),
],
Text(
message,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.error,
),
color: Theme.of(context).colorScheme.error,
),
),
if (onRetry != null) ...[
const SizedBox(height: 24),
+3 -1
View File
@@ -113,7 +113,9 @@ class FolderTreeItem extends StatelessWidget {
item.year.toString(),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
],
+13 -16
View File
@@ -15,11 +15,7 @@ class FolderTreeView extends StatefulWidget {
final String libraryKey;
final void Function(String)? onRefresh;
const FolderTreeView({
super.key,
required this.libraryKey,
this.onRefresh,
});
const FolderTreeView({super.key, required this.libraryKey, this.onRefresh});
@override
State<FolderTreeView> createState() => _FolderTreeViewState();
@@ -109,7 +105,9 @@ class _FolderTreeViewState extends State<FolderTreeView> {
_loadingFolders.remove(folder.key);
});
appLogger.d('Loaded ${children.length} children for folder: ${folder.title}');
appLogger.d(
'Loaded ${children.length} children for folder: ${folder.title}',
);
} catch (e) {
if (!mounted) return;
@@ -148,10 +146,7 @@ class _FolderTreeViewState extends State<FolderTreeView> {
// For episodes, start playback directly
if (itemType == 'episode') {
final result = await navigateToVideoPlayer(
context,
metadata: item,
);
final result = await navigateToVideoPlayer(context, metadata: item);
if (result == true) {
widget.onRefresh?.call(item.ratingKey);
}
@@ -181,11 +176,15 @@ class _FolderTreeViewState extends State<FolderTreeView> {
// Folders typically don't have a specific type or might have special indicators
// Check for common folder indicators
return item.key.contains('/folder') ||
item.type.isEmpty ||
item.type.toLowerCase() == 'folder';
item.type.isEmpty ||
item.type.toLowerCase() == 'folder';
}
List<Widget> _buildTreeItems(List<PlexMetadata> items, int depth, [String parentPath = '']) {
List<Widget> _buildTreeItems(
List<PlexMetadata> items,
int depth, [
String parentPath = '',
]) {
final List<Widget> widgets = [];
for (int i = 0; i < items.length; i++) {
@@ -260,9 +259,7 @@ class _FolderTreeViewState extends State<FolderTreeView> {
return RefreshIndicator(
onRefresh: _loadRootFolders,
child: ListView(
children: _buildTreeItems(_rootFolders, 0),
),
child: ListView(children: _buildTreeItems(_rootFolders, 0)),
);
}
}
+8 -11
View File
@@ -45,10 +45,7 @@ class HubSection extends StatelessWidget {
: null,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
children: [
Icon(icon),
@@ -76,10 +73,10 @@ class HubSection extends StatelessWidget {
final cardWidth = screenWidth > 1600
? 220.0
: screenWidth > 1200
? 200.0
: screenWidth > 800
? 190.0
: 160.0;
? 200.0
: screenWidth > 800
? 190.0
: 160.0;
// MediaCard has 8px padding on all sides (16px total horizontally)
// So actual poster width is cardWidth - 16
@@ -125,9 +122,9 @@ class HubSection extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
t.messages.noItemsAvailable,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey,
),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.grey),
),
),
],
+166 -136
View File
@@ -31,7 +31,8 @@ class MediaContextMenu extends StatefulWidget {
final VoidCallback? onTap;
final Widget child;
final bool isInContinueWatching;
final String? collectionId; // The collection ID if displaying within a collection
final String?
collectionId; // The collection ID if displaying within a collection
const MediaContextMenu({
super.key,
@@ -65,7 +66,8 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
final itemType = isPlaylist ? 'playlist' : (metadata!.type.toLowerCase());
final isCollection = itemType == 'collection';
final isPartiallyWatched = !isPlaylist &&
final isPartiallyWatched =
!isPlaylist &&
metadata!.viewedLeafCount != null &&
metadata.leafCount != null &&
metadata.viewedLeafCount! > 0 &&
@@ -110,108 +112,104 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
} else {
// Regular menu items for other types
// Mark as Watched
if (!metadata!.isWatched || isPartiallyWatched) {
menuActions.add(
_MenuAction(
value: 'watch',
icon: Icons.check_circle_outline,
label: t.mediaMenu.markAsWatched,
),
);
}
// Mark as Watched
if (!metadata!.isWatched || isPartiallyWatched) {
menuActions.add(
_MenuAction(
value: 'watch',
icon: Icons.check_circle_outline,
label: t.mediaMenu.markAsWatched,
),
);
}
// Mark as Unwatched
if (metadata.isWatched || isPartiallyWatched) {
menuActions.add(
_MenuAction(
value: 'unwatch',
icon: Icons.remove_circle_outline,
label: t.mediaMenu.markAsUnwatched,
),
);
}
// Mark as Unwatched
if (metadata.isWatched || isPartiallyWatched) {
menuActions.add(
_MenuAction(
value: 'unwatch',
icon: Icons.remove_circle_outline,
label: t.mediaMenu.markAsUnwatched,
),
);
}
// Remove from Continue Watching (only in continue watching section)
if (widget.isInContinueWatching) {
menuActions.add(
_MenuAction(
value: 'remove_from_continue_watching',
icon: Icons.close,
label: t.mediaMenu.removeFromContinueWatching,
),
);
}
// Remove from Continue Watching (only in continue watching section)
if (widget.isInContinueWatching) {
menuActions.add(
_MenuAction(
value: 'remove_from_continue_watching',
icon: Icons.close,
label: t.mediaMenu.removeFromContinueWatching,
),
);
}
// Remove from Collection (only when viewing items within a collection)
if (widget.collectionId != null) {
menuActions.add(
_MenuAction(
value: 'remove_from_collection',
icon: Icons.delete_outline,
label: t.collections.removeFromCollection,
),
);
}
// Remove from Collection (only when viewing items within a collection)
if (widget.collectionId != null) {
menuActions.add(
_MenuAction(
value: 'remove_from_collection',
icon: Icons.delete_outline,
label: t.collections.removeFromCollection,
),
);
}
// Go to Series (for episodes and seasons)
if ((itemType == 'episode' || itemType == 'season') &&
metadata.grandparentTitle != null) {
menuActions.add(
_MenuAction(
value: 'series',
icon: Icons.tv,
label: t.mediaMenu.goToSeries,
),
);
}
// Go to Series (for episodes and seasons)
if ((itemType == 'episode' || itemType == 'season') &&
metadata.grandparentTitle != null) {
menuActions.add(
_MenuAction(
value: 'series',
icon: Icons.tv,
label: t.mediaMenu.goToSeries,
),
);
}
// Go to Season (for episodes)
if (itemType == 'episode' && metadata.parentTitle != null) {
menuActions.add(
_MenuAction(
value: 'season',
icon: Icons.playlist_play,
label: t.mediaMenu.goToSeason,
),
);
}
// Go to Season (for episodes)
if (itemType == 'episode' && metadata.parentTitle != null) {
menuActions.add(
_MenuAction(
value: 'season',
icon: Icons.playlist_play,
label: t.mediaMenu.goToSeason,
),
);
}
// Shuffle Play (for shows and seasons)
if (itemType == 'show' || itemType == 'season') {
menuActions.add(
_MenuAction(
value: 'shuffle_play',
icon: Icons.shuffle,
label: t.mediaMenu.shufflePlay,
),
);
}
// Shuffle Play (for shows and seasons)
if (itemType == 'show' || itemType == 'season') {
menuActions.add(
_MenuAction(
value: 'shuffle_play',
icon: Icons.shuffle,
label: t.mediaMenu.shufflePlay,
),
);
}
// File Info (for episodes and movies)
if (itemType == 'episode' || itemType == 'movie') {
menuActions.add(
_MenuAction(
value: 'fileinfo',
icon: Icons.info_outline,
label: t.mediaMenu.fileInfo,
),
);
}
// File Info (for episodes and movies)
if (itemType == 'episode' || itemType == 'movie') {
menuActions.add(
_MenuAction(
value: 'fileinfo',
icon: Icons.info_outline,
label: t.mediaMenu.fileInfo,
),
);
}
// Add to... (for episodes, movies, shows, and seasons)
if (itemType == 'episode' ||
itemType == 'movie' ||
itemType == 'show' ||
itemType == 'season') {
menuActions.add(
_MenuAction(
value: 'add_to',
icon: Icons.add,
label: t.common.addTo,
),
);
}
// Add to... (for episodes, movies, shows, and seasons)
if (itemType == 'episode' ||
itemType == 'movie' ||
itemType == 'show' ||
itemType == 'season') {
menuActions.add(
_MenuAction(value: 'add_to', icon: Icons.add, label: t.common.addTo),
);
}
} // End of regular menu items else block
String? selected;
@@ -478,10 +476,8 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => FileInfoBottomSheet(
fileInfo: fileInfo,
title: metadata.title,
),
builder: (context) =>
FileInfoBottomSheet(fileInfo: fileInfo, title: metadata.title),
);
} else if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -722,7 +718,9 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
// If still not found, try to extract from the key field
if (sectionId == null) {
final keyMatch = RegExp(r'/library/sections/(\d+)').firstMatch(metadata.key);
final keyMatch = RegExp(
r'/library/sections/(\d+)',
).firstMatch(metadata.key);
if (keyMatch != null) {
sectionId = int.tryParse(keyMatch.group(1)!);
appLogger.d(' - Extracted from key: $sectionId');
@@ -732,8 +730,12 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
// Last resort: try to get it from the item's parent (for episodes/seasons)
if (sectionId == null && metadata.grandparentRatingKey != null) {
try {
appLogger.d(' - Trying to get from parent: ${metadata.grandparentRatingKey}');
final parentMeta = await client.getMetadata(metadata.grandparentRatingKey!);
appLogger.d(
' - Trying to get from parent: ${metadata.grandparentRatingKey}',
);
final parentMeta = await client.getMetadata(
metadata.grandparentRatingKey!,
);
sectionId = parentMeta?.librarySectionID;
appLogger.d(' - Parent sectionId: $sectionId');
} catch (e) {
@@ -747,7 +749,9 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Unable to determine library section for this item'),
content: Text(
'Unable to determine library section for this item',
),
),
);
}
@@ -755,14 +759,17 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
}
// Load collections for this library section
final collections = await client.getLibraryCollections(sectionId.toString());
final collections = await client.getLibraryCollections(
sectionId.toString(),
);
if (!context.mounted) return;
// Show dialog to select collection or create new
final result = await showDialog<String>(
context: context,
builder: (context) => _CollectionSelectionDialog(collections: collections),
builder: (context) =>
_CollectionSelectionDialog(collections: collections),
);
if (result == null || !context.mounted) return;
@@ -778,7 +785,9 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
builder: (context) => _CreateCollectionDialog(),
);
if (collectionName == null || collectionName.isEmpty || !context.mounted) {
if (collectionName == null ||
collectionName.isEmpty ||
!context.mounted) {
return;
}
@@ -800,7 +809,9 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
break;
}
appLogger.d('Creating collection "$collectionName" with type $collectionType');
appLogger.d(
'Creating collection "$collectionName" with type $collectionType',
);
final newCollectionId = await client.createCollection(
sectionId: sectionId.toString(),
title: collectionName,
@@ -810,10 +821,14 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
if (context.mounted) {
if (newCollectionId != null) {
appLogger.d('Successfully created collection with ID: $newCollectionId');
appLogger.d(
'Successfully created collection with ID: $newCollectionId',
);
// Now add the item to the newly created collection
appLogger.d('Adding item to new collection $newCollectionId with URI: $itemUri');
appLogger.d(
'Adding item to new collection $newCollectionId with URI: $itemUri',
);
final addSuccess = await client.addToCollection(
collectionId: newCollectionId,
uri: itemUri,
@@ -828,15 +843,15 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
LibraryRefreshNotifier().notifyCollectionsChanged();
} else {
appLogger.e('Failed to add item to new collection');
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(t.collections.errorAddingToCollection)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(t.collections.errorAddingToCollection)),
);
}
} else {
appLogger.e('Failed to create collection - API returned null');
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(t.collections.errorAddingToCollection)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(t.collections.errorAddingToCollection)),
);
}
}
} else {
@@ -850,18 +865,18 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
if (context.mounted) {
if (success) {
appLogger.d('Successfully added item(s) to collection $result');
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(t.collections.addedToCollection)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(t.collections.addedToCollection)),
);
// Trigger refresh of collections tab
LibraryRefreshNotifier().notifyCollectionsChanged();
} else {
appLogger.e(
'Failed to add item(s) to collection $result - API returned false',
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(t.collections.errorAddingToCollection)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(t.collections.errorAddingToCollection)),
);
}
}
}
@@ -874,7 +889,9 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${t.collections.errorAddingToCollection}: ${e.toString()}'),
content: Text(
'${t.collections.errorAddingToCollection}: ${e.toString()}',
),
duration: const Duration(seconds: 5),
),
);
@@ -883,7 +900,10 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
}
/// Handle remove from collection action
Future<void> _handleRemoveFromCollection(BuildContext context, PlexMetadata metadata) async {
Future<void> _handleRemoveFromCollection(
BuildContext context,
PlexMetadata metadata,
) async {
final client = context.client;
if (client == null) return;
@@ -928,9 +948,7 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
if (context.mounted) {
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(t.collections.removedFromCollection),
),
SnackBar(content: Text(t.collections.removedFromCollection)),
);
// Trigger refresh of collections tab
LibraryRefreshNotifier().notifyCollectionsChanged();
@@ -938,9 +956,7 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
widget.onListRefresh?.call();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(t.collections.removeFromCollectionFailed),
),
SnackBar(content: Text(t.collections.removeFromCollectionFailed)),
);
}
}
@@ -959,7 +975,11 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
}
/// Handle play action for collections and playlists
Future<void> _handlePlay(BuildContext context, bool isCollection, bool isPlaylist) async {
Future<void> _handlePlay(
BuildContext context,
bool isCollection,
bool isPlaylist,
) async {
final client = context.client;
if (client == null) return;
@@ -972,7 +992,11 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
}
/// Handle shuffle action for collections and playlists
Future<void> _handleShuffle(BuildContext context, bool isCollection, bool isPlaylist) async {
Future<void> _handleShuffle(
BuildContext context,
bool isCollection,
bool isPlaylist,
) async {
final client = context.client;
if (client == null) return;
@@ -985,13 +1009,18 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
}
/// Handle delete action for collections and playlists
Future<void> _handleDelete(BuildContext context, bool isCollection, bool isPlaylist) async {
Future<void> _handleDelete(
BuildContext context,
bool isCollection,
bool isPlaylist,
) async {
final client = context.client;
if (client == null) return;
final itemTitle = widget.item.title;
final itemTypeLabel =
isCollection ? t.collections.collection : t.playlists.playlist;
final itemTypeLabel = isCollection
? t.collections.collection
: t.playlists.playlist;
// Show confirmation dialog
final confirmed = await showDialog<bool>(
@@ -1241,7 +1270,8 @@ class _CollectionSelectionDialog extends StatelessWidget {
/// Dialog to create a new collection
class _CreateCollectionDialog extends StatefulWidget {
@override
State<_CreateCollectionDialog> createState() => _CreateCollectionDialogState();
State<_CreateCollectionDialog> createState() =>
_CreateCollectionDialogState();
}
class _CreateCollectionDialogState extends State<_CreateCollectionDialog> {
+1 -4
View File
@@ -138,10 +138,7 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
: null,
leading: Radio<PlexSort>(value: sort, toggleable: false),
onTap: () {
_handleSortChange(
sort,
sort.isDefaultDescending,
);
_handleSortChange(sort, sort.isDefaultDescending);
},
);
},
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:plezy/services/settings_service.dart';
import '../../../i18n/strings.g.dart';
import 'base_video_control_sheet.dart';
/// Bottom sheet for adjusting audio sync offset
class AudioSyncSheet extends StatefulWidget {
@@ -14,23 +15,12 @@ class AudioSyncSheet extends StatefulWidget {
required this.initialOffset,
});
static BoxConstraints getBottomSheetConstraints(BuildContext context) {
final size = MediaQuery.of(context).size;
final isDesktop = size.width > 600;
return BoxConstraints(
maxWidth: isDesktop ? 700 : double.infinity,
maxHeight: isDesktop ? 400 : size.height * 0.75,
minHeight: isDesktop ? 300 : size.height * 0.5,
);
}
static void show(BuildContext context, Player player, int initialOffset) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.grey[900],
isScrollControlled: true,
constraints: getBottomSheetConstraints(context),
constraints: BaseVideoControlSheet.getBottomSheetConstraints(context),
builder: (context) =>
AudioSyncSheet(player: player, initialOffset: initialOffset),
);
@@ -49,7 +49,8 @@ class ChapterSheet extends StatelessWidget {
for (int i = 0; i < chapters.length; i++) {
final chapter = chapters[i];
final startMs = chapter.startTimeOffset ?? 0;
final endMs = chapter.endTimeOffset ??
final endMs =
chapter.endTimeOffset ??
(i < chapters.length - 1
? chapters[i + 1].startTimeOffset ?? 0
: double.maxFinite.toInt());
@@ -94,22 +95,16 @@ class ChapterSheet extends StatelessWidget {
);
}
return Image.network(
client.getThumbnailUrl(
chapter.thumb,
),
client.getThumbnailUrl(chapter.thumb),
width: 60,
height: 34,
fit: BoxFit.cover,
errorBuilder: (
context,
error,
stackTrace,
) =>
errorBuilder: (context, error, stackTrace) =>
const Icon(
Icons.image,
color: Colors.white54,
size: 34,
),
Icons.image,
color: Colors.white54,
size: 34,
),
);
},
),
@@ -118,9 +113,7 @@ class ChapterSheet extends StatelessWidget {
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
4,
),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.blue,
width: 2,
@@ -150,10 +143,7 @@ class ChapterSheet extends StatelessWidget {
),
),
trailing: isCurrentChapter
? const Icon(
Icons.play_circle_filled,
color: Colors.blue,
)
? const Icon(Icons.play_circle_filled, color: Colors.blue)
: null,
onTap: () {
player.seek(chapter.startTime);
@@ -36,8 +36,9 @@ class PlaybackSpeedSheet extends StatelessWidget {
final isSelected = (currentRate - speed).abs() < 0.01;
// Format speed label
final label =
speed == 1.0 ? 'Normal' : '${speed.toStringAsFixed(2)}x';
final label = speed == 1.0
? 'Normal'
: '${speed.toStringAsFixed(2)}x';
return ListTile(
title: Text(
@@ -67,15 +67,13 @@ class SubtitleTrackSheet extends StatelessWidget {
title: Text(
'Off',
style: TextStyle(
color:
isOffSelected ? Colors.blue : Colors.white,
color: isOffSelected
? Colors.blue
: Colors.white,
),
),
trailing: isOffSelected
? const Icon(
Icons.check,
color: Colors.blue,
)
? const Icon(Icons.check, color: Colors.blue)
: null,
onTap: () {
player.setSubtitleTrack(SubtitleTrack.no());
@@ -107,8 +105,7 @@ class SubtitleTrackSheet extends StatelessWidget {
codecName = 'SRT';
} else if (codecName == 'DVD_SUBTITLE') {
codecName = 'DVD';
} else if (codecName == 'ASS' ||
codecName == 'SSA') {
} else if (codecName == 'ASS' || codecName == 'SSA') {
codecName = codecName; // Keep as-is
} else if (codecName == 'WEBVTT') {
codecName = 'VTT';
@@ -116,8 +113,9 @@ class SubtitleTrackSheet extends StatelessWidget {
parts.add(codecName);
}
final label =
parts.isEmpty ? 'Track $index' : parts.join(' · ');
final label = parts.isEmpty
? 'Track $index'
: parts.join(' · ');
return ListTile(
title: Text(
@@ -45,9 +45,7 @@ class VersionSheet extends StatelessWidget {
return ListTile(
title: Text(
version.displayLabel,
style: TextStyle(
color: isSelected ? Colors.blue : Colors.white,
),
style: TextStyle(color: isSelected ? Colors.blue : Colors.white),
),
trailing: isSelected
? const Icon(Icons.check, color: Colors.blue)
@@ -6,6 +6,7 @@ import '../../../utils/platform_detector.dart';
import '../widgets/sync_offset_control.dart';
import '../widgets/sleep_timer_content.dart';
import '../../../i18n/strings.g.dart';
import 'base_video_control_sheet.dart';
enum _SettingsView { menu, speed, sleep, audioSync, subtitleSync, audioDevice }
@@ -39,21 +40,12 @@ class _SettingsMenuItem extends StatelessWidget {
);
return ListTile(
leading: Icon(
icon,
color: isHighlighted ? Colors.amber : Colors.white70,
),
title: Text(
title,
style: const TextStyle(color: Colors.white),
),
leading: Icon(icon, color: isHighlighted ? Colors.amber : Colors.white70),
title: Text(title, style: const TextStyle(color: Colors.white)),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (allowValueOverflow)
Flexible(child: valueWidget)
else
valueWidget,
if (allowValueOverflow) Flexible(child: valueWidget) else valueWidget,
const SizedBox(width: 8),
const Icon(Icons.chevron_right, color: Colors.white70),
],
@@ -76,17 +68,6 @@ class VideoSettingsSheet extends StatefulWidget {
required this.subtitleSyncOffset,
});
static BoxConstraints getBottomSheetConstraints(BuildContext context) {
final size = MediaQuery.of(context).size;
final isDesktop = size.width > 600;
return BoxConstraints(
maxWidth: isDesktop ? 700 : double.infinity,
maxHeight: isDesktop ? 400 : size.height * 0.75,
minHeight: isDesktop ? 300 : size.height * 0.5,
);
}
static Future<void> show(
BuildContext context,
Player player,
@@ -97,7 +78,7 @@ class VideoSettingsSheet extends StatefulWidget {
context: context,
backgroundColor: Colors.grey[900],
isScrollControlled: true,
constraints: getBottomSheetConstraints(context),
constraints: BaseVideoControlSheet.getBottomSheetConstraints(context),
builder: (context) => VideoSettingsSheet(
player: player,
audioSyncOffset: audioSyncOffset,
@@ -194,7 +175,6 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
}
}
Widget _buildHeader() {
final sleepTimer = SleepTimerService();
final isIconActive =
@@ -1624,5 +1624,4 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
}
}
}
}