237 lines
7.1 KiB
Dart
237 lines
7.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../models/plex_metadata.dart';
|
|
import '../providers/settings_provider.dart';
|
|
import '../utils/app_logger.dart';
|
|
import '../widgets/media_card.dart';
|
|
import '../widgets/desktop_app_bar.dart';
|
|
import '../i18n/strings.g.dart';
|
|
import '../utils/grid_size_calculator.dart';
|
|
import '../utils/dialogs.dart';
|
|
import '../utils/provider_extensions.dart';
|
|
import 'base_media_list_detail_screen.dart';
|
|
|
|
/// Screen to display the contents of a collection
|
|
class CollectionDetailScreen extends StatefulWidget {
|
|
final PlexMetadata collection;
|
|
|
|
const CollectionDetailScreen({super.key, required this.collection});
|
|
|
|
@override
|
|
State<CollectionDetailScreen> createState() => _CollectionDetailScreenState();
|
|
}
|
|
|
|
class _CollectionDetailScreenState
|
|
extends BaseMediaListDetailScreen<CollectionDetailScreen> {
|
|
@override
|
|
PlexMetadata get mediaItem => widget.collection;
|
|
|
|
@override
|
|
String get title => widget.collection.title;
|
|
|
|
@override
|
|
String get emptyMessage => t.collections.empty;
|
|
|
|
@override
|
|
Future<void> loadItems() async {
|
|
if (mounted) {
|
|
setState(() {
|
|
isLoading = true;
|
|
errorMessage = null;
|
|
});
|
|
}
|
|
|
|
try {
|
|
final client = this.client;
|
|
final newItems = await client.getCollectionItems(widget.collection.ratingKey);
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
items = newItems;
|
|
isLoading = false;
|
|
});
|
|
}
|
|
|
|
appLogger.d(
|
|
'Loaded ${newItems.length} items for collection: ${widget.collection.title}',
|
|
);
|
|
} catch (e) {
|
|
appLogger.e('Failed to load collection items', error: e);
|
|
if (mounted) {
|
|
setState(() {
|
|
errorMessage = t.collections.failedToLoadItems(error: e.toString());
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _deleteCollection() async {
|
|
// Get library section ID from the collection or its items
|
|
int? sectionId = widget.collection.librarySectionID;
|
|
|
|
// If collection doesn't have it, try to get it from loaded items
|
|
if (sectionId == null && items.isNotEmpty) {
|
|
sectionId = items.first.librarySectionID;
|
|
}
|
|
|
|
if (sectionId == null) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(t.collections.unknownLibrarySection)),
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Show confirmation dialog
|
|
final confirmed = await showDeleteConfirmation(
|
|
context,
|
|
title: t.collections.deleteCollection,
|
|
message: t.collections.deleteConfirm(title: widget.collection.title),
|
|
);
|
|
|
|
if (confirmed != true) return;
|
|
if (!mounted) return;
|
|
|
|
try {
|
|
final clientProvider = context.plexClient;
|
|
final client = clientProvider.client;
|
|
if (client == null) return;
|
|
|
|
final success = await client.deleteCollection(
|
|
sectionId.toString(),
|
|
widget.collection.ratingKey,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
if (mounted) {
|
|
if (success) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(t.collections.deleted)),
|
|
);
|
|
Navigator.pop(context, true); // Return true to indicate refresh needed
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(t.collections.deleteFailed)),
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
appLogger.e('Failed to delete collection', error: e);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
t.collections.deleteFailedWithError(error: e.toString()),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: CustomScrollView(
|
|
slivers: [
|
|
CustomAppBar(
|
|
title: Text(widget.collection.title),
|
|
pinned: true,
|
|
actions: [
|
|
// Play button
|
|
if (items.isNotEmpty)
|
|
IconButton(
|
|
icon: const Icon(Icons.play_arrow),
|
|
tooltip: t.discover.play,
|
|
onPressed: playItems,
|
|
),
|
|
// Shuffle button
|
|
if (items.isNotEmpty)
|
|
IconButton(
|
|
icon: const Icon(Icons.shuffle),
|
|
tooltip: t.common.shuffle,
|
|
onPressed: shufflePlayItems,
|
|
),
|
|
// Delete button
|
|
IconButton(
|
|
icon: const Icon(Icons.delete),
|
|
tooltip: t.common.delete,
|
|
onPressed: _deleteCollection,
|
|
color: Colors.red,
|
|
),
|
|
],
|
|
),
|
|
if (errorMessage != null)
|
|
SliverFillRemaining(
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.error_outline,
|
|
size: 48,
|
|
color: Colors.red,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(errorMessage!),
|
|
const SizedBox(height: 16),
|
|
ElevatedButton(
|
|
onPressed: loadItems,
|
|
child: Text(t.common.retry),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
else if (items.isEmpty && isLoading)
|
|
const SliverFillRemaining(
|
|
child: Center(child: CircularProgressIndicator()),
|
|
)
|
|
else if (items.isEmpty)
|
|
SliverFillRemaining(
|
|
child: Center(
|
|
child: Text(t.collections.noItems),
|
|
),
|
|
)
|
|
else
|
|
SliverPadding(
|
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
|
|
sliver: Consumer<SettingsProvider>(
|
|
builder: (context, settingsProvider, child) {
|
|
return SliverGrid(
|
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: GridSizeCalculator.getMaxCrossAxisExtent(
|
|
context,
|
|
settingsProvider.libraryDensity,
|
|
),
|
|
childAspectRatio: 2 / 3.3,
|
|
crossAxisSpacing: 0,
|
|
mainAxisSpacing: 0,
|
|
),
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
final item = items[index];
|
|
return MediaCard(
|
|
key: Key(item.ratingKey),
|
|
item: item,
|
|
onRefresh: updateItem,
|
|
collectionId: widget.collection.ratingKey,
|
|
onListRefresh: loadItems,
|
|
);
|
|
},
|
|
childCount: items.length,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|