mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-24 03:49:30 +00:00
feat: convert filtered items to grid view and fix navigation
- Change from ListView to GridView with 3 columns for better book browsing - Redesign LibraryItemCard as grid cards with cover images and metadata - Fix navigation to use Routes.libraryItem instead of Routes.you - Use correct path parameter 'itemId' for book detail navigation - Remove subtitle from cards to simplify grid layout - Books now open properly when tapped, same as home page
This commit is contained in:
parent
9e0f25f0e2
commit
cf0778e263
1 changed files with 72 additions and 80 deletions
|
|
@ -73,12 +73,18 @@ class FilteredLibraryItemsPage extends HookConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: 0.65,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 16,
|
||||
),
|
||||
itemCount: response.results.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = response.results[index];
|
||||
return LibraryItemListTile(item: item);
|
||||
return LibraryItemCard(item: item);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
@ -95,8 +101,8 @@ class FilteredLibraryItemsPage extends HookConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class LibraryItemListTile extends ConsumerWidget {
|
||||
const LibraryItemListTile({
|
||||
class LibraryItemCard extends ConsumerWidget {
|
||||
const LibraryItemCard({
|
||||
super.key,
|
||||
required this.item,
|
||||
});
|
||||
|
|
@ -110,7 +116,6 @@ class LibraryItemListTile extends ConsumerWidget {
|
|||
|
||||
// Extract book info
|
||||
String title = '';
|
||||
String? subtitle;
|
||||
String? authorName;
|
||||
|
||||
// Use map to handle Media variants
|
||||
|
|
@ -120,19 +125,16 @@ class LibraryItemListTile extends ConsumerWidget {
|
|||
metadata.mapOrNull(
|
||||
book: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
if (m.authors.isNotEmpty) {
|
||||
authorName = m.authors.map((a) => a.name).join(', ');
|
||||
}
|
||||
},
|
||||
bookMinified: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
authorName = m.authorName;
|
||||
},
|
||||
bookExpanded: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
if (m.authors.isNotEmpty) {
|
||||
authorName = m.authors.map((a) => a.name).join(', ');
|
||||
}
|
||||
|
|
@ -144,19 +146,16 @@ class LibraryItemListTile extends ConsumerWidget {
|
|||
metadata.mapOrNull(
|
||||
book: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
if (m.authors.isNotEmpty) {
|
||||
authorName = m.authors.map((a) => a.name).join(', ');
|
||||
}
|
||||
},
|
||||
bookMinified: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
authorName = m.authorName;
|
||||
},
|
||||
bookExpanded: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
if (m.authors.isNotEmpty) {
|
||||
authorName = m.authors.map((a) => a.name).join(', ');
|
||||
}
|
||||
|
|
@ -168,19 +167,16 @@ class LibraryItemListTile extends ConsumerWidget {
|
|||
metadata.mapOrNull(
|
||||
book: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
if (m.authors.isNotEmpty) {
|
||||
authorName = m.authors.map((a) => a.name).join(', ');
|
||||
}
|
||||
},
|
||||
bookMinified: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
authorName = m.authorName;
|
||||
},
|
||||
bookExpanded: (m) {
|
||||
title = m.title ?? 'Unknown';
|
||||
subtitle = m.subtitle;
|
||||
if (m.authors.isNotEmpty) {
|
||||
authorName = m.authors.map((a) => a.name).join(', ');
|
||||
}
|
||||
|
|
@ -193,72 +189,68 @@ class LibraryItemListTile extends ConsumerWidget {
|
|||
? '${apiSettings.activeServer!.serverUrl}/api/items/${item.id}/cover'
|
||||
: null;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: imageUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
width: 48,
|
||||
height: 48,
|
||||
fit: BoxFit.cover,
|
||||
httpHeaders: {
|
||||
if (apiSettings.activeUser?.authToken != null)
|
||||
'Authorization':
|
||||
'Bearer ${apiSettings.activeUser!.authToken}',
|
||||
},
|
||||
placeholder: (context, url) => Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(Icons.book, size: 24),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(Icons.book, size: 24),
|
||||
),
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
// Navigate to item detail page
|
||||
context.goNamed(
|
||||
Routes.libraryItem.name,
|
||||
pathParameters: {'itemId': item.id},
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Book cover
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: imageUrl != null
|
||||
? CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
httpHeaders: {
|
||||
if (apiSettings.activeUser?.authToken != null)
|
||||
'Authorization':
|
||||
'Bearer ${apiSettings.activeUser!.authToken}',
|
||||
},
|
||||
placeholder: (context, url) => Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(Icons.book, size: 48),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: const Icon(Icons.book, size: 48),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Book title
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Icon(Icons.book, size: 24),
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (authorName != null) Text(authorName!),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle!,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// Navigate to item detail page
|
||||
context.goNamed(
|
||||
Routes.you.name,
|
||||
pathParameters: {'libraryItemId': item.id},
|
||||
);
|
||||
},
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
// Author name
|
||||
if (authorName != null)
|
||||
Text(
|
||||
authorName!,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue