Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions app/lib/data/sample_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:papyrus/models/reading_session.dart';
import 'package:papyrus/models/series.dart';
import 'package:papyrus/models/shelf.dart';
import 'package:papyrus/models/tag.dart';
import 'package:papyrus/providers/enums/library_reading_status.dart';

/// Comprehensive sample data for development and testing.
class SampleData {
Expand Down Expand Up @@ -40,7 +41,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1401432508i/4099.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.inProgress,
readingStatus: LibraryReadingStatus.inProgress,
currentPage: 264,
currentPosition: 0.75,
isFavorite: true,
Expand All @@ -64,7 +65,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1436202607i/3735293.jpg',
fileFormat: BookFormat.pdf,
readingStatus: ReadingStatus.completed,
readingStatus: LibraryReadingStatus.completed,
currentPage: 464,
currentPosition: 1.0,
isFavorite: true,
Expand All @@ -88,7 +89,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1555447414i/44767458.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.inProgress,
readingStatus: LibraryReadingStatus.inProgress,
currentPage: 310,
currentPosition: 0.45,
seriesId: 'series-1',
Expand All @@ -112,7 +113,7 @@ class SampleData {
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1657781256i/61439040.jpg',
isPhysical: true,
physicalLocation: 'Bookshelf A, Row 2',
readingStatus: ReadingStatus.completed,
readingStatus: LibraryReadingStatus.completed,
currentPage: 328,
currentPosition: 1.0,
rating: 4,
Expand All @@ -137,7 +138,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1348027904i/85009.jpg',
fileFormat: BookFormat.pdf,
readingStatus: ReadingStatus.inProgress,
readingStatus: LibraryReadingStatus.inProgress,
currentPage: 83,
currentPosition: 0.2,
addedAt: _daysAgo(45),
Expand All @@ -158,7 +159,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1546071216i/5907.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.notStarted,
readingStatus: LibraryReadingStatus.unread,
currentPosition: 0.0,
isFavorite: true,
seriesId: 'series-2',
Expand All @@ -180,7 +181,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1386925632i/44936.jpg',
fileFormat: BookFormat.mobi,
readingStatus: ReadingStatus.inProgress,
readingStatus: LibraryReadingStatus.inProgress,
currentPage: 269,
currentPosition: 0.6,
addedAt: _daysAgo(60),
Expand All @@ -202,7 +203,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1703329310i/23692271.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.inProgress,
readingStatus: LibraryReadingStatus.inProgress,
currentPage: 394,
currentPosition: 0.85,
isFavorite: true,
Expand All @@ -225,7 +226,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1554437249i/6088007.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.notStarted,
readingStatus: LibraryReadingStatus.unread,
currentPosition: 0.0,
addedAt: _daysAgo(14),
),
Expand All @@ -244,7 +245,7 @@ class SampleData {
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1655988385i/40121378.jpg',
isPhysical: true,
physicalLocation: 'Bookshelf B, Row 1',
readingStatus: ReadingStatus.completed,
readingStatus: LibraryReadingStatus.completed,
currentPage: 320,
currentPosition: 1.0,
isFavorite: true,
Expand All @@ -269,7 +270,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1344692678i/11724436.jpg',
fileFormat: BookFormat.pdf,
readingStatus: ReadingStatus.inProgress,
readingStatus: LibraryReadingStatus.inProgress,
currentPage: 144,
currentPosition: 0.3,
addedAt: _daysAgo(90),
Expand All @@ -290,7 +291,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1417900846i/29579.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.notStarted,
readingStatus: LibraryReadingStatus.unread,
currentPosition: 0.0,
seriesId: 'series-3',
seriesNumber: 1,
Expand All @@ -310,7 +311,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1472680751i/44492285.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.notStarted,
readingStatus: LibraryReadingStatus.unread,
currentPosition: 0.0,
seriesId: 'series-1',
seriesNumber: 2,
Expand All @@ -330,7 +331,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1564063404i/44492286.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.notStarted,
readingStatus: LibraryReadingStatus.unread,
currentPosition: 0.0,
seriesId: 'series-1',
seriesNumber: 3,
Expand All @@ -350,7 +351,7 @@ class SampleData {
coverUrl:
'https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1546071217i/15241.jpg',
fileFormat: BookFormat.epub,
readingStatus: ReadingStatus.notStarted,
readingStatus: LibraryReadingStatus.unread,
currentPosition: 0.0,
seriesId: 'series-2',
seriesNumber: 2,
Expand Down
51 changes: 6 additions & 45 deletions app/lib/models/book.dart
Original file line number Diff line number Diff line change
@@ -1,38 +1,5 @@
/// Reading status of a book.
enum ReadingStatus { notStarted, inProgress, completed, paused, abandoned }

/// Extension to get display labels for reading status.
extension ReadingStatusExtension on ReadingStatus {
String get label {
switch (this) {
case ReadingStatus.notStarted:
return 'Not started';
case ReadingStatus.inProgress:
return 'Reading';
case ReadingStatus.completed:
return 'Completed';
case ReadingStatus.paused:
return 'Paused';
case ReadingStatus.abandoned:
return 'Abandoned';
}
}

String get shortLabel {
switch (this) {
case ReadingStatus.notStarted:
return 'Not started';
case ReadingStatus.inProgress:
return 'Reading';
case ReadingStatus.completed:
return 'Finished';
case ReadingStatus.paused:
return 'Paused';
case ReadingStatus.abandoned:
return 'DNF';
}
}
}
import 'package:flutter/material.dart';
import 'package:papyrus/providers/enums/library_reading_status.dart';

/// Format of the book file.
enum BookFormat { epub, pdf, mobi, azw3, txt, cbr, cbz }
Expand Down Expand Up @@ -90,7 +57,7 @@ class Book {
final DateTime? lentAt;

// Reading state
final ReadingStatus readingStatus;
final LibraryReadingStatus readingStatus;
final int? currentPage;
final double currentPosition; // 0.0 to 1.0
final String? currentCfi; // EPUB CFI position
Expand Down Expand Up @@ -135,7 +102,7 @@ class Book {
this.physicalLocation,
this.lentTo,
this.lentAt,
this.readingStatus = ReadingStatus.notStarted,
this.readingStatus = LibraryReadingStatus.unread,
this.currentPage,
this.currentPosition = 0.0,
this.currentCfi,
Expand Down Expand Up @@ -169,12 +136,6 @@ class Book {
/// Progress as a percentage string.
String get progressLabel => '$progressPercent%';

/// Whether the book is currently being read.
bool get isReading => readingStatus == ReadingStatus.inProgress;

/// Whether the book has been finished.
bool get isFinished => readingStatus == ReadingStatus.completed;

/// Whether the book has any progress.
bool get hasProgress => currentPosition > 0;

Expand Down Expand Up @@ -230,7 +191,7 @@ class Book {
bool clearLentTo = false,
DateTime? lentAt,
bool clearLentAt = false,
ReadingStatus? readingStatus,
LibraryReadingStatus? readingStatus,
int? currentPage,
double? currentPosition,
String? currentCfi,
Expand Down Expand Up @@ -358,7 +319,7 @@ class Book {
physicalLocation: json['physical_location'] as String?,
lentTo: json['lent_to'] as String?,
lentAt: json['lent_at'] != null ? DateTime.parse(json['lent_at'] as String) : null,
readingStatus: ReadingStatus.values.byName(json['reading_status'] as String? ?? 'notStarted'),
readingStatus: LibraryReadingStatus.values.byName(json['reading_status'] as String? ?? 'notStarted'),
currentPage: json['current_page'] as int?,
currentPosition: (json['current_position'] as num?)?.toDouble() ?? 0.0,
currentCfi: json['current_cfi'] as String?,
Expand Down
84 changes: 84 additions & 0 deletions app/lib/models/library_filter_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import 'package:papyrus/data/data_store.dart';
import 'package:papyrus/utils/book_language.dart';

class LibraryFilterOption<T> {
final T value;
final String label;

const LibraryFilterOption({required this.value, required this.label});
}

class LibraryFilterOptions {
final List<LibraryFilterOption<String>> authors;
final List<LibraryFilterOption<String>> languages;
final List<LibraryFilterOption<String>> formats;
final List<LibraryFilterOption<String>> topics;
final List<LibraryFilterOption<String>> shelves;
final List<LibraryFilterOption<String>> publishers;
final List<LibraryFilterOption<String>> series;

const LibraryFilterOptions({
required this.authors,
required this.languages,
required this.formats,
required this.topics,
required this.shelves,
required this.publishers,
required this.series,
});

factory LibraryFilterOptions.fromDataStore(DataStore dataStore) {
final authors = <String, String>{};
final languages = <String, String>{};
final formats = <String, String>{};
final publishers = <String, String>{};
final series = <String, String>{};

for (final book in dataStore.books) {
for (final author in [book.author, ...book.coAuthors]) {
_addNormalized(authors, author);
}

final language = book.language;
final normalizedLanguage = normalizeBookLanguage(language);
if (language != null && normalizedLanguage != null) {
languages.putIfAbsent(normalizedLanguage, () => bookLanguageLabel(language));
}

_addNormalized(formats, book.formatLabel);
_addNormalized(publishers, book.publisher);
_addNormalized(series, book.seriesName);
}

return LibraryFilterOptions(
authors: _stringOptions(authors),
languages: _stringOptions(languages),
formats: _stringOptions(formats),
topics: _sortedOptions(dataStore.tags.map((topic) => LibraryFilterOption(value: topic.id, label: topic.name))),
shelves: _sortedOptions(
dataStore.shelves.map((shelf) => LibraryFilterOption(value: shelf.id, label: shelf.name)),
),
publishers: _stringOptions(publishers),
series: _stringOptions(series),
);
}

static void _addNormalized(Map<String, String> labelsByValue, String? value) {
final label = value?.trim();
if (label == null || label.isEmpty) {
return;
}

labelsByValue.putIfAbsent(label.toLowerCase(), () => label);
}

static List<LibraryFilterOption<String>> _stringOptions(Map<String, String> labelsByValue) {
return _sortedOptions(
labelsByValue.entries.map((entry) => LibraryFilterOption(value: entry.key, label: entry.value)),
);
}

static List<LibraryFilterOption<String>> _sortedOptions(Iterable<LibraryFilterOption<String>> options) {
return options.toList()..sort((a, b) => a.label.toLowerCase().compareTo(b.label.toLowerCase()));
}
}
Loading
Loading