show story progress and reset last read when resetting progress.

This commit is contained in:
Stefan Hardegger
2025-10-30 13:44:54 +01:00
parent a3bc83db8a
commit 0e1ed7c92e
8 changed files with 62 additions and 19 deletions

View File

@@ -595,6 +595,7 @@ public class StoryController {
// Reading progress fields // Reading progress fields
dto.setIsRead(story.getIsRead()); dto.setIsRead(story.getIsRead());
dto.setReadingPosition(story.getReadingPosition()); dto.setReadingPosition(story.getReadingPosition());
dto.setReadingProgressPercentage(calculateReadingProgressPercentage(story));
dto.setLastReadAt(story.getLastReadAt()); dto.setLastReadAt(story.getLastReadAt());
if (story.getAuthor() != null) { if (story.getAuthor() != null) {
@@ -614,6 +615,27 @@ public class StoryController {
return dto; return dto;
} }
private Integer calculateReadingProgressPercentage(Story story) {
if (story.getReadingPosition() == null || story.getReadingPosition() == 0) {
return 0;
}
// Determine the total content length
int totalLength = 0;
if (story.getContentPlain() != null && !story.getContentPlain().isEmpty()) {
totalLength = story.getContentPlain().length();
} else if (story.getContentHtml() != null && !story.getContentHtml().isEmpty()) {
totalLength = story.getContentHtml().length();
}
if (totalLength == 0) {
return 0;
}
// Calculate percentage and round to nearest integer
return Math.round((float) story.getReadingPosition() * 100 / totalLength);
}
private StoryReadingDto convertToReadingDto(Story story) { private StoryReadingDto convertToReadingDto(Story story) {
StoryReadingDto dto = new StoryReadingDto(); StoryReadingDto dto = new StoryReadingDto();
dto.setId(story.getId()); dto.setId(story.getId());
@@ -632,6 +654,7 @@ public class StoryController {
// Reading progress fields // Reading progress fields
dto.setIsRead(story.getIsRead()); dto.setIsRead(story.getIsRead());
dto.setReadingPosition(story.getReadingPosition()); dto.setReadingPosition(story.getReadingPosition());
dto.setReadingProgressPercentage(calculateReadingProgressPercentage(story));
dto.setLastReadAt(story.getLastReadAt()); dto.setLastReadAt(story.getLastReadAt());
if (story.getAuthor() != null) { if (story.getAuthor() != null) {

View File

@@ -31,6 +31,7 @@ public class StoryDto {
// Reading progress fields // Reading progress fields
private Boolean isRead; private Boolean isRead;
private Integer readingPosition; private Integer readingPosition;
private Integer readingProgressPercentage; // Pre-calculated percentage (0-100)
private LocalDateTime lastReadAt; private LocalDateTime lastReadAt;
// Related entities as simple references // Related entities as simple references
@@ -147,6 +148,14 @@ public class StoryDto {
this.readingPosition = readingPosition; this.readingPosition = readingPosition;
} }
public Integer getReadingProgressPercentage() {
return readingProgressPercentage;
}
public void setReadingProgressPercentage(Integer readingProgressPercentage) {
this.readingProgressPercentage = readingProgressPercentage;
}
public LocalDateTime getLastReadAt() { public LocalDateTime getLastReadAt() {
return lastReadAt; return lastReadAt;
} }

View File

@@ -25,6 +25,7 @@ public class StoryReadingDto {
// Reading progress fields // Reading progress fields
private Boolean isRead; private Boolean isRead;
private Integer readingPosition; private Integer readingPosition;
private Integer readingProgressPercentage; // Pre-calculated percentage (0-100)
private LocalDateTime lastReadAt; private LocalDateTime lastReadAt;
// Related entities as simple references // Related entities as simple references
@@ -136,6 +137,14 @@ public class StoryReadingDto {
this.readingPosition = readingPosition; this.readingPosition = readingPosition;
} }
public Integer getReadingProgressPercentage() {
return readingProgressPercentage;
}
public void setReadingProgressPercentage(Integer readingProgressPercentage) {
this.readingProgressPercentage = readingProgressPercentage;
}
public LocalDateTime getLastReadAt() { public LocalDateTime getLastReadAt() {
return lastReadAt; return lastReadAt;
} }

View File

@@ -287,10 +287,17 @@ public class Story {
/** /**
* Updates the reading progress and timestamp * Updates the reading progress and timestamp
* When position is 0 or null, resets lastReadAt to null so the story won't appear in "last read" sorting
*/ */
public void updateReadingProgress(Integer position) { public void updateReadingProgress(Integer position) {
this.readingPosition = position; this.readingPosition = position;
this.lastReadAt = LocalDateTime.now(); // Only update lastReadAt if there's actual reading progress
// Reset to null when position is 0 or null to remove from "last read" sorting
if (position == null || position == 0) {
this.lastReadAt = null;
} else {
this.lastReadAt = LocalDateTime.now();
}
} }
/** /**

View File

@@ -85,7 +85,8 @@ class StoryServiceTest {
Story result = storyService.updateReadingProgress(testId, position); Story result = storyService.updateReadingProgress(testId, position);
assertEquals(0, result.getReadingPosition()); assertEquals(0, result.getReadingPosition());
assertNotNull(result.getLastReadAt()); // When position is 0, lastReadAt should be reset to null so the story doesn't appear in "last read" sorting
assertNull(result.getLastReadAt());
verify(storyRepository).save(testStory); verify(storyRepository).save(testStory);
} }
@@ -111,7 +112,8 @@ class StoryServiceTest {
Story result = storyService.updateReadingProgress(testId, position); Story result = storyService.updateReadingProgress(testId, position);
assertNull(result.getReadingPosition()); assertNull(result.getReadingPosition());
assertNotNull(result.getLastReadAt()); // When position is null, lastReadAt should be reset to null so the story doesn't appear in "last read" sorting
assertNull(result.getLastReadAt());
verify(storyRepository).save(testStory); verify(storyRepository).save(testStory);
} }

View File

@@ -72,16 +72,8 @@ export default function StoryCard({
return new Date(dateString).toLocaleDateString(); return new Date(dateString).toLocaleDateString();
}; };
const calculateReadingPercentage = (story: Story): number => { // Use the pre-calculated percentage from the backend
if (!story.readingPosition) return 0; const readingPercentage = story.readingProgressPercentage || 0;
const totalLength = story.contentPlain?.length || story.contentHtml?.length || 0;
if (totalLength === 0) return 0;
return Math.round((story.readingPosition / totalLength) * 100);
};
const readingPercentage = calculateReadingPercentage(story);
if (viewMode === 'list') { if (viewMode === 'list') {
return ( return (

View File

@@ -16,6 +16,7 @@ export interface Story {
tags: Tag[]; tags: Tag[];
tagNames?: string[] | null; // Used in search results tagNames?: string[] | null; // Used in search results
readingPosition?: number; readingPosition?: number;
readingProgressPercentage?: number; // Pre-calculated percentage (0-100) from backend
lastReadAt?: string; lastReadAt?: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;

File diff suppressed because one or more lines are too long