mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-01-10 20:29:32 +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(
|
return GridView.builder(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.all(16),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
childAspectRatio: 0.65,
|
||||||
|
crossAxisSpacing: 12,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
),
|
||||||
itemCount: response.results.length,
|
itemCount: response.results.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = response.results[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 {
|
class LibraryItemCard extends ConsumerWidget {
|
||||||
const LibraryItemListTile({
|
const LibraryItemCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
});
|
});
|
||||||
|
|
@ -110,7 +116,6 @@ class LibraryItemListTile extends ConsumerWidget {
|
||||||
|
|
||||||
// Extract book info
|
// Extract book info
|
||||||
String title = '';
|
String title = '';
|
||||||
String? subtitle;
|
|
||||||
String? authorName;
|
String? authorName;
|
||||||
|
|
||||||
// Use map to handle Media variants
|
// Use map to handle Media variants
|
||||||
|
|
@ -120,19 +125,16 @@ class LibraryItemListTile extends ConsumerWidget {
|
||||||
metadata.mapOrNull(
|
metadata.mapOrNull(
|
||||||
book: (m) {
|
book: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
if (m.authors.isNotEmpty) {
|
if (m.authors.isNotEmpty) {
|
||||||
authorName = m.authors.map((a) => a.name).join(', ');
|
authorName = m.authors.map((a) => a.name).join(', ');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookMinified: (m) {
|
bookMinified: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
authorName = m.authorName;
|
authorName = m.authorName;
|
||||||
},
|
},
|
||||||
bookExpanded: (m) {
|
bookExpanded: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
if (m.authors.isNotEmpty) {
|
if (m.authors.isNotEmpty) {
|
||||||
authorName = m.authors.map((a) => a.name).join(', ');
|
authorName = m.authors.map((a) => a.name).join(', ');
|
||||||
}
|
}
|
||||||
|
|
@ -144,19 +146,16 @@ class LibraryItemListTile extends ConsumerWidget {
|
||||||
metadata.mapOrNull(
|
metadata.mapOrNull(
|
||||||
book: (m) {
|
book: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
if (m.authors.isNotEmpty) {
|
if (m.authors.isNotEmpty) {
|
||||||
authorName = m.authors.map((a) => a.name).join(', ');
|
authorName = m.authors.map((a) => a.name).join(', ');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookMinified: (m) {
|
bookMinified: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
authorName = m.authorName;
|
authorName = m.authorName;
|
||||||
},
|
},
|
||||||
bookExpanded: (m) {
|
bookExpanded: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
if (m.authors.isNotEmpty) {
|
if (m.authors.isNotEmpty) {
|
||||||
authorName = m.authors.map((a) => a.name).join(', ');
|
authorName = m.authors.map((a) => a.name).join(', ');
|
||||||
}
|
}
|
||||||
|
|
@ -168,19 +167,16 @@ class LibraryItemListTile extends ConsumerWidget {
|
||||||
metadata.mapOrNull(
|
metadata.mapOrNull(
|
||||||
book: (m) {
|
book: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
if (m.authors.isNotEmpty) {
|
if (m.authors.isNotEmpty) {
|
||||||
authorName = m.authors.map((a) => a.name).join(', ');
|
authorName = m.authors.map((a) => a.name).join(', ');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookMinified: (m) {
|
bookMinified: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
authorName = m.authorName;
|
authorName = m.authorName;
|
||||||
},
|
},
|
||||||
bookExpanded: (m) {
|
bookExpanded: (m) {
|
||||||
title = m.title ?? 'Unknown';
|
title = m.title ?? 'Unknown';
|
||||||
subtitle = m.subtitle;
|
|
||||||
if (m.authors.isNotEmpty) {
|
if (m.authors.isNotEmpty) {
|
||||||
authorName = m.authors.map((a) => a.name).join(', ');
|
authorName = m.authors.map((a) => a.name).join(', ');
|
||||||
}
|
}
|
||||||
|
|
@ -193,72 +189,68 @@ class LibraryItemListTile extends ConsumerWidget {
|
||||||
? '${apiSettings.activeServer!.serverUrl}/api/items/${item.id}/cover'
|
? '${apiSettings.activeServer!.serverUrl}/api/items/${item.id}/cover'
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return Card(
|
return InkWell(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
onTap: () {
|
||||||
child: ListTile(
|
// Navigate to item detail page
|
||||||
leading: imageUrl != null
|
context.goNamed(
|
||||||
? ClipRRect(
|
Routes.libraryItem.name,
|
||||||
borderRadius: BorderRadius.circular(4),
|
pathParameters: {'itemId': item.id},
|
||||||
child: CachedNetworkImage(
|
);
|
||||||
imageUrl: imageUrl,
|
},
|
||||||
width: 48,
|
child: Column(
|
||||||
height: 48,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
fit: BoxFit.cover,
|
children: [
|
||||||
httpHeaders: {
|
// Book cover
|
||||||
if (apiSettings.activeUser?.authToken != null)
|
Expanded(
|
||||||
'Authorization':
|
child: ClipRRect(
|
||||||
'Bearer ${apiSettings.activeUser!.authToken}',
|
borderRadius: BorderRadius.circular(8),
|
||||||
},
|
child: imageUrl != null
|
||||||
placeholder: (context, url) => Container(
|
? CachedNetworkImage(
|
||||||
width: 48,
|
imageUrl: imageUrl,
|
||||||
height: 48,
|
fit: BoxFit.cover,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
httpHeaders: {
|
||||||
child: const Icon(Icons.book, size: 24),
|
if (apiSettings.activeUser?.authToken != null)
|
||||||
),
|
'Authorization':
|
||||||
errorWidget: (context, url, error) => Container(
|
'Bearer ${apiSettings.activeUser!.authToken}',
|
||||||
width: 48,
|
},
|
||||||
height: 48,
|
placeholder: (context, url) => Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
child: const Icon(Icons.book, size: 24),
|
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,
|
||||||
),
|
),
|
||||||
)
|
maxLines: 2,
|
||||||
: Container(
|
overflow: TextOverflow.ellipsis,
|
||||||
width: 48,
|
),
|
||||||
height: 48,
|
// Author name
|
||||||
decoration: BoxDecoration(
|
if (authorName != null)
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
Text(
|
||||||
borderRadius: BorderRadius.circular(4),
|
authorName!,
|
||||||
),
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
child: const Icon(Icons.book, size: 24),
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
title: Text(
|
maxLines: 1,
|
||||||
title,
|
overflow: TextOverflow.ellipsis,
|
||||||
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},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue