chore: format
This commit is contained in:
@@ -25,10 +25,7 @@ class EndpointFailoverManager {
|
||||
}
|
||||
|
||||
/// Replace the endpoint list and optionally set the active endpoint.
|
||||
void reset(
|
||||
List<String> urls, {
|
||||
String? currentBaseUrl,
|
||||
}) {
|
||||
void reset(List<String> urls, {String? currentBaseUrl}) {
|
||||
_setEndpoints(urls);
|
||||
if (currentBaseUrl != null) {
|
||||
final index = _endpoints.indexOf(currentBaseUrl);
|
||||
@@ -60,8 +57,8 @@ class EndpointFailoverInterceptor extends Interceptor {
|
||||
required Dio dio,
|
||||
required this.endpointManager,
|
||||
required Future<void> Function(String newBaseUrl) onEndpointSwitch,
|
||||
}) : _dio = dio,
|
||||
_onEndpointSwitch = onEndpointSwitch;
|
||||
}) : _dio = dio,
|
||||
_onEndpointSwitch = onEndpointSwitch;
|
||||
|
||||
final Dio _dio;
|
||||
final EndpointFailoverManager endpointManager;
|
||||
|
||||
@@ -27,7 +27,8 @@ class PlaybackStateProvider with ChangeNotifier {
|
||||
bool get isPlaylistActive => _playbackMode == PlaybackMode.playlist;
|
||||
|
||||
/// Whether any queue-based playback is active
|
||||
bool get isQueueActive => _queue.isNotEmpty && _playbackMode != PlaybackMode.none;
|
||||
bool get isQueueActive =>
|
||||
_queue.isNotEmpty && _playbackMode != PlaybackMode.none;
|
||||
|
||||
/// The context key (show/season/playlist ratingKey) for the current session
|
||||
String? get shuffleContextKey => _contextKey;
|
||||
|
||||
@@ -1130,9 +1130,7 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const PlaylistsScreen(),
|
||||
),
|
||||
MaterialPageRoute(builder: (context) => const PlaylistsScreen()),
|
||||
);
|
||||
},
|
||||
tooltip: t.playlists.title,
|
||||
|
||||
@@ -61,7 +61,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
appLogger.d('Loaded ${items.length} items for playlist: ${widget.playlist.title}');
|
||||
appLogger.d(
|
||||
'Loaded ${items.length} items for playlist: ${widget.playlist.title}',
|
||||
);
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to load playlist items', error: e);
|
||||
setState(() {
|
||||
@@ -96,14 +98,14 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
|
||||
if (mounted) {
|
||||
if (success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.deleted)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.deleted)));
|
||||
Navigator.pop(context); // Return to playlists screen
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorDeleting)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorDeleting)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,9 +126,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
if (movedItem.playlistItemID == null) {
|
||||
appLogger.e('Cannot reorder: item missing playlistItemID');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorReordering)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorReordering)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -142,16 +144,18 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
if (afterItem.playlistItemID == null) {
|
||||
appLogger.e('Cannot reorder: after item missing playlistItemID');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorReordering)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorReordering)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
afterPlaylistItemId = afterItem.playlistItemID!;
|
||||
}
|
||||
|
||||
appLogger.d('Reordering item from $oldIndex to $newIndex (after ID: $afterPlaylistItemId)');
|
||||
appLogger.d(
|
||||
'Reordering item from $oldIndex to $newIndex (after ID: $afterPlaylistItemId)',
|
||||
);
|
||||
|
||||
// Optimistically update UI
|
||||
setState(() {
|
||||
@@ -175,9 +179,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
_items.insert(oldIndex, item);
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorReordering)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorReordering)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,14 +193,16 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
if (item.playlistItemID == null) {
|
||||
appLogger.e('Cannot remove: item missing playlistItemID');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorRemoving)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorRemoving)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
appLogger.d('Removing item ${item.title} (playlistItemID: ${item.playlistItemID}) from playlist');
|
||||
appLogger.d(
|
||||
'Removing item ${item.title} (playlistItemID: ${item.playlistItemID}) from playlist',
|
||||
);
|
||||
|
||||
// Optimistically update UI
|
||||
setState(() {
|
||||
@@ -211,9 +217,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
|
||||
if (mounted) {
|
||||
if (success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.itemRemoved)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.itemRemoved)));
|
||||
} else {
|
||||
// Revert on failure
|
||||
appLogger.e('Failed to remove playlist item, reverting UI');
|
||||
@@ -221,9 +227,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
_items.insert(index, item);
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorRemoving)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorRemoving)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,9 +250,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
Future<void> _playPlaylist() async {
|
||||
if (_items.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.emptyPlaylist)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.emptyPlaylist)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -265,9 +271,9 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
Future<void> _shufflePlayPlaylist() async {
|
||||
if (_items.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.emptyPlaylist)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.emptyPlaylist)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -422,10 +428,7 @@ class _PlaylistDetailScreenState extends State<PlaylistDetailScreen>
|
||||
mainAxisSpacing: 0,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return MediaCard(
|
||||
item: _items[index],
|
||||
onRefresh: updateItem,
|
||||
);
|
||||
return MediaCard(item: _items[index], onRefresh: updateItem);
|
||||
}, childCount: _items.length),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -156,9 +156,8 @@ class _PlaylistsScreenState extends State<PlaylistsScreen> with Refreshable {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PlaylistDetailScreen(
|
||||
playlist: _playlists[index],
|
||||
),
|
||||
builder: (context) =>
|
||||
PlaylistDetailScreen(playlist: _playlists[index]),
|
||||
),
|
||||
).then((_) => _loadPlaylists()); // Refresh on return
|
||||
},
|
||||
@@ -275,14 +274,14 @@ class _PlaylistCard extends StatelessWidget {
|
||||
|
||||
if (context.mounted) {
|
||||
if (success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.deleted)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.deleted)));
|
||||
onDeleted();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorDeleting)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorDeleting)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,13 +369,12 @@ class _PlaylistCard extends StatelessWidget {
|
||||
Text(
|
||||
playlist.leafCount != null && playlist.leafCount! > 0
|
||||
? (playlist.leafCount == 1
|
||||
? t.playlists.oneItem
|
||||
: t.playlists.itemCount(count: playlist.leafCount!))
|
||||
? t.playlists.oneItem
|
||||
: t.playlists.itemCount(
|
||||
count: playlist.leafCount!,
|
||||
))
|
||||
: t.playlists.emptyPlaylist,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -393,11 +391,7 @@ class _PlaylistCard extends StatelessWidget {
|
||||
return Container(
|
||||
color: Colors.grey[850],
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.playlist_play,
|
||||
size: 48,
|
||||
color: Colors.grey,
|
||||
),
|
||||
child: Icon(Icons.playlist_play, size: 48, color: Colors.grey),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,9 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
|
||||
// Clear media controls when app goes to background or screen locks
|
||||
// (we don't support background playback)
|
||||
OsMediaControls.clear();
|
||||
appLogger.d('Media controls cleared due to app lifecycle state: $state');
|
||||
appLogger.d(
|
||||
'Media controls cleared due to app lifecycle state: $state',
|
||||
);
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
// Restore media controls when app is resumed
|
||||
@@ -303,9 +305,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
|
||||
widget.metadata.ratingKey,
|
||||
loopQueue: false, // Don't loop playlists by default
|
||||
);
|
||||
previous = playbackState.getPreviousEpisode(
|
||||
widget.metadata.ratingKey,
|
||||
);
|
||||
previous = playbackState.getPreviousEpisode(widget.metadata.ratingKey);
|
||||
}
|
||||
// Check if shuffle mode is active
|
||||
else if (playbackState.isShuffleActive) {
|
||||
|
||||
@@ -418,10 +418,7 @@ class PlexServer {
|
||||
final totalCandidates = candidates.length;
|
||||
appLogger.d(
|
||||
'Starting server connection discovery',
|
||||
error: {
|
||||
'preferred': preferredUri,
|
||||
'candidateCount': totalCandidates,
|
||||
},
|
||||
error: {'preferred': preferredUri, 'candidateCount': totalCandidates},
|
||||
);
|
||||
|
||||
_ConnectionCandidate? firstCandidate;
|
||||
@@ -466,13 +463,11 @@ class PlexServer {
|
||||
);
|
||||
|
||||
for (final candidate in candidates) {
|
||||
PlexClient
|
||||
.testConnectionWithLatency(
|
||||
PlexClient.testConnectionWithLatency(
|
||||
candidate.url,
|
||||
accessToken,
|
||||
timeout: raceTimeout,
|
||||
)
|
||||
.then((result) {
|
||||
).then((result) {
|
||||
completedTests++;
|
||||
|
||||
if (result.success && !completer.isCompleted) {
|
||||
@@ -545,7 +540,7 @@ class PlexServer {
|
||||
if (bestCandidate != null) {
|
||||
final upgradedCandidate =
|
||||
await _upgradeCandidateToHttpsIfPossible(bestCandidate) ??
|
||||
bestCandidate;
|
||||
bestCandidate;
|
||||
|
||||
final bestConnection = _updateConnectionUrl(
|
||||
upgradedCandidate.connection,
|
||||
@@ -596,8 +591,7 @@ class PlexServer {
|
||||
if (uri == url) {
|
||||
final isHttps = uri.startsWith('https://');
|
||||
final parsedHost = Uri.tryParse(uri)?.host ?? '';
|
||||
final isPlexDirect =
|
||||
parsedHost.toLowerCase().contains('plex.direct');
|
||||
final isPlexDirect = parsedHost.toLowerCase().contains('plex.direct');
|
||||
return _ConnectionCandidate(connection, uri, isPlexDirect, isHttps);
|
||||
}
|
||||
}
|
||||
@@ -644,18 +638,14 @@ class PlexServer {
|
||||
return;
|
||||
}
|
||||
seen.add(url);
|
||||
bucketFor(connection, isHttps).add(
|
||||
_ConnectionCandidate(connection, url, isPlexDirectUri, isHttps),
|
||||
);
|
||||
bucketFor(
|
||||
connection,
|
||||
isHttps,
|
||||
).add(_ConnectionCandidate(connection, url, isPlexDirectUri, isHttps));
|
||||
}
|
||||
|
||||
for (final connection in connections) {
|
||||
addCandidate(
|
||||
connection,
|
||||
connection.httpDirectUrl,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
addCandidate(connection, connection.httpDirectUrl, false, false);
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -789,8 +779,9 @@ class PlexServer {
|
||||
current.uri.contains('.plex.direct'),
|
||||
current.uri.startsWith('https://'),
|
||||
);
|
||||
final upgradedCandidate =
|
||||
await _upgradeCandidateToHttpsIfPossible(candidate);
|
||||
final upgradedCandidate = await _upgradeCandidateToHttpsIfPossible(
|
||||
candidate,
|
||||
);
|
||||
if (upgradedCandidate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class ServerConnectionResult {
|
||||
/// Implements fast-first connection with background optimization
|
||||
class ServerConnectionService {
|
||||
static StreamSubscription<List<ConnectivityResult>>?
|
||||
_connectivitySubscription;
|
||||
_connectivitySubscription;
|
||||
static Future<void>? _activeOptimization;
|
||||
static PlexServer? _activeServer;
|
||||
static PlexClient? _activeClient;
|
||||
@@ -51,28 +51,31 @@ class ServerConnectionService {
|
||||
}) async {
|
||||
final storage = await StorageService.getInstance();
|
||||
|
||||
final connectionStream =
|
||||
server.findBestWorkingConnection().asBroadcastStream();
|
||||
final connectionStream = server
|
||||
.findBestWorkingConnection()
|
||||
.asBroadcastStream();
|
||||
PlexClient? client;
|
||||
|
||||
final optimizationSubscription = connectionStream.skip(1).listen(
|
||||
(connection) async {
|
||||
await _handleOptimizedConnection(
|
||||
connection: connection,
|
||||
storage: storage,
|
||||
server: server,
|
||||
client: client,
|
||||
reason: 'initial_latency_sweep',
|
||||
final optimizationSubscription = connectionStream
|
||||
.skip(1)
|
||||
.listen(
|
||||
(connection) async {
|
||||
await _handleOptimizedConnection(
|
||||
connection: connection,
|
||||
storage: storage,
|
||||
server: server,
|
||||
client: client,
|
||||
reason: 'initial_latency_sweep',
|
||||
);
|
||||
},
|
||||
onError: (error, stackTrace) {
|
||||
appLogger.w(
|
||||
'Background connection optimization error',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: (error, stackTrace) {
|
||||
appLogger.w(
|
||||
'Background connection optimization error',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final connection = await connectionStream.first;
|
||||
@@ -132,9 +135,7 @@ class ServerConnectionService {
|
||||
await optimizationSubscription.cancel();
|
||||
appLogger.w('Server identity verification failed', error: e);
|
||||
await storage.clearCredentials();
|
||||
return ServerConnectionResult(
|
||||
error: 'Server is not accessible: $e',
|
||||
);
|
||||
return ServerConnectionResult(error: 'Server is not accessible: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,10 +150,7 @@ class ServerConnectionService {
|
||||
_activeClient = client;
|
||||
_startConnectivityMonitoring(server);
|
||||
|
||||
return ServerConnectionResult(
|
||||
client: client,
|
||||
userProfile: userProfile,
|
||||
);
|
||||
return ServerConnectionResult(client: client, userProfile: userProfile);
|
||||
} on StateError catch (e, stackTrace) {
|
||||
await optimizationSubscription.cancel();
|
||||
appLogger.e(
|
||||
@@ -205,8 +203,9 @@ class ServerConnectionService {
|
||||
final connectivity = Connectivity();
|
||||
_connectivitySubscription = connectivity.onConnectivityChanged.listen(
|
||||
(results) {
|
||||
final status =
|
||||
results.isNotEmpty ? results.first : ConnectivityResult.none;
|
||||
final status = results.isNotEmpty
|
||||
? results.first
|
||||
: ConnectivityResult.none;
|
||||
if (status == ConnectivityResult.none) {
|
||||
appLogger.w(
|
||||
'Connectivity lost, pausing optimization until network returns',
|
||||
@@ -250,13 +249,14 @@ class ServerConnectionService {
|
||||
return;
|
||||
}
|
||||
|
||||
_activeOptimization = _runOptimization(
|
||||
server: _activeServer!,
|
||||
client: _activeClient,
|
||||
reason: reason,
|
||||
).whenComplete(() {
|
||||
_activeOptimization = null;
|
||||
});
|
||||
_activeOptimization =
|
||||
_runOptimization(
|
||||
server: _activeServer!,
|
||||
client: _activeClient,
|
||||
reason: reason,
|
||||
).whenComplete(() {
|
||||
_activeOptimization = null;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _runOptimization({
|
||||
|
||||
@@ -2,10 +2,10 @@ class LogRedactionManager {
|
||||
static final Set<String> _tokens = <String>{};
|
||||
static final Set<String> _urls = <String>{};
|
||||
static final Set<String> _customValues = <String>{};
|
||||
static final RegExp _ipv4Pattern =
|
||||
RegExp(r'\b(\d{1,3})([.-])(\d{1,3})\2(\d{1,3})\2(\d{1,3})\b');
|
||||
static final RegExp _ipv4HostPattern =
|
||||
RegExp(r'^\d{1,3}([.-]\d{1,3}){3}$');
|
||||
static final RegExp _ipv4Pattern = RegExp(
|
||||
r'\b(\d{1,3})([.-])(\d{1,3})\2(\d{1,3})\2(\d{1,3})\b',
|
||||
);
|
||||
static final RegExp _ipv4HostPattern = RegExp(r'^\d{1,3}([.-]\d{1,3}){3}$');
|
||||
|
||||
/// Register a server access token or Plex.tv token for redaction.
|
||||
static void registerToken(String? token) {
|
||||
@@ -129,8 +129,9 @@ class LogRedactionManager {
|
||||
? (url.length / 2).ceil()
|
||||
: startPreviewLength;
|
||||
final remainingForEnd = url.length - startLength;
|
||||
final endLength =
|
||||
remainingForEnd <= endPreviewLength ? remainingForEnd : endPreviewLength;
|
||||
final endLength = remainingForEnd <= endPreviewLength
|
||||
? remainingForEnd
|
||||
: endPreviewLength;
|
||||
|
||||
final start = url.substring(0, startLength);
|
||||
if (endLength <= 0) {
|
||||
|
||||
@@ -146,7 +146,10 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
}
|
||||
|
||||
// Add to Playlist (for episodes, movies, shows, and seasons)
|
||||
if (itemType == 'episode' || itemType == 'movie' || itemType == 'show' || itemType == 'season') {
|
||||
if (itemType == 'episode' ||
|
||||
itemType == 'movie' ||
|
||||
itemType == 'show' ||
|
||||
itemType == 'season') {
|
||||
menuActions.add(
|
||||
_MenuAction(
|
||||
value: 'add_to_playlist',
|
||||
@@ -445,9 +448,7 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
// Show dialog to select playlist or create new
|
||||
final result = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => _PlaylistSelectionDialog(
|
||||
playlists: playlists,
|
||||
),
|
||||
builder: (context) => _PlaylistSelectionDialog(playlists: playlists),
|
||||
);
|
||||
|
||||
if (result == null || !context.mounted) return;
|
||||
@@ -469,7 +470,9 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
}
|
||||
|
||||
// Create playlist with the item(s)
|
||||
appLogger.d('Creating playlist "$playlistName" with URI length: ${itemUri.length}');
|
||||
appLogger.d(
|
||||
'Creating playlist "$playlistName" with URI length: ${itemUri.length}',
|
||||
);
|
||||
final newPlaylist = await client.createPlaylist(
|
||||
title: playlistName,
|
||||
uri: itemUri,
|
||||
@@ -478,14 +481,14 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
if (context.mounted) {
|
||||
if (newPlaylist != null) {
|
||||
appLogger.d('Successfully created playlist: ${newPlaylist.title}');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.created)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.created)));
|
||||
} else {
|
||||
appLogger.e('Failed to create playlist - API returned null');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorCreating)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorCreating)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -499,19 +502,25 @@ class _MediaContextMenuState extends State<MediaContextMenu> {
|
||||
if (context.mounted) {
|
||||
if (success) {
|
||||
appLogger.d('Successfully added item(s) to playlist $result');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.itemAdded)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.itemAdded)));
|
||||
} else {
|
||||
appLogger.e('Failed to add item(s) to playlist $result - API returned false');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(t.playlists.errorAdding)),
|
||||
appLogger.e(
|
||||
'Failed to add item(s) to playlist $result - API returned false',
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(t.playlists.errorAdding)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
appLogger.e('Error in add to playlist flow', error: e, stackTrace: stackTrace);
|
||||
appLogger.e(
|
||||
'Error in add to playlist flow',
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -568,9 +577,11 @@ class _PlaylistSelectionDialog extends StatelessWidget {
|
||||
: const Icon(Icons.playlist_play),
|
||||
title: Text(playlist.title),
|
||||
subtitle: playlist.leafCount != null
|
||||
? Text(playlist.leafCount == 1
|
||||
? t.playlists.oneItem
|
||||
: t.playlists.itemCount(count: playlist.leafCount!))
|
||||
? Text(
|
||||
playlist.leafCount == 1
|
||||
? t.playlists.oneItem
|
||||
: t.playlists.itemCount(count: playlist.leafCount!),
|
||||
)
|
||||
: null,
|
||||
onTap: playlist.smart
|
||||
? null // Disable smart playlists
|
||||
|
||||
@@ -39,10 +39,7 @@ class PlaylistItemCard extends StatelessWidget {
|
||||
index: index,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: Icon(
|
||||
Icons.drag_indicator,
|
||||
color: Colors.grey,
|
||||
),
|
||||
child: Icon(Icons.drag_indicator, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -73,10 +70,7 @@ class PlaylistItemCard extends StatelessWidget {
|
||||
// Subtitle (episode info or type)
|
||||
Text(
|
||||
_buildSubtitle(),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey[400]),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -104,10 +98,7 @@ class PlaylistItemCard extends StatelessWidget {
|
||||
if (item.duration != null)
|
||||
Text(
|
||||
_formatDuration(item.duration!),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey[400]),
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Reference in New Issue
Block a user