Display and correct calculation of reading progress of a story
This commit is contained in:
@@ -620,11 +620,9 @@ public class StoryController {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the total content length
|
// ALWAYS use contentHtml for consistency (frontend uses contentHtml for position tracking)
|
||||||
int totalLength = 0;
|
int totalLength = 0;
|
||||||
if (story.getContentPlain() != null && !story.getContentPlain().isEmpty()) {
|
if (story.getContentHtml() != null && !story.getContentHtml().isEmpty()) {
|
||||||
totalLength = story.getContentPlain().length();
|
|
||||||
} else if (story.getContentHtml() != null && !story.getContentHtml().isEmpty()) {
|
|
||||||
totalLength = story.getContentHtml().length();
|
totalLength = story.getContentHtml().length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,7 +631,8 @@ public class StoryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate percentage and round to nearest integer
|
// Calculate percentage and round to nearest integer
|
||||||
return Math.round((float) story.getReadingPosition() * 100 / totalLength);
|
int percentage = Math.round((float) story.getReadingPosition() * 100 / totalLength);
|
||||||
|
return Math.min(100, percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StoryReadingDto convertToReadingDto(Story story) {
|
private StoryReadingDto convertToReadingDto(Story story) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class StorySearchDto {
|
|||||||
// Reading status
|
// Reading status
|
||||||
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;
|
||||||
|
|
||||||
// Author info
|
// Author info
|
||||||
@@ -132,7 +133,15 @@ public class StorySearchDto {
|
|||||||
public void setReadingPosition(Integer readingPosition) {
|
public void setReadingPosition(Integer readingPosition) {
|
||||||
this.readingPosition = readingPosition;
|
this.readingPosition = readingPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getReadingProgressPercentage() {
|
||||||
|
return readingProgressPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadingProgressPercentage(Integer readingProgressPercentage) {
|
||||||
|
this.readingProgressPercentage = readingProgressPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
public UUID getAuthorId() {
|
public UUID getAuthorId() {
|
||||||
return authorId;
|
return authorId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ public class SolrService {
|
|||||||
doc.addField("volume", story.getVolume());
|
doc.addField("volume", story.getVolume());
|
||||||
doc.addField("isRead", story.getIsRead());
|
doc.addField("isRead", story.getIsRead());
|
||||||
doc.addField("readingPosition", story.getReadingPosition());
|
doc.addField("readingPosition", story.getReadingPosition());
|
||||||
|
doc.addField("readingProgressPercentage", calculateReadingProgressPercentage(story));
|
||||||
|
|
||||||
if (story.getLastReadAt() != null) {
|
if (story.getLastReadAt() != null) {
|
||||||
doc.addField("lastReadAt", formatDateTime(story.getLastReadAt()));
|
doc.addField("lastReadAt", formatDateTime(story.getLastReadAt()));
|
||||||
@@ -544,6 +545,26 @@ public class SolrService {
|
|||||||
return dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "Z";
|
return dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "Z";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer calculateReadingProgressPercentage(Story story) {
|
||||||
|
if (story.getReadingPosition() == null || story.getReadingPosition() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALWAYS use contentHtml for consistency (frontend uses contentHtml for position tracking)
|
||||||
|
int totalLength = 0;
|
||||||
|
if (story.getContentHtml() != null && !story.getContentHtml().isEmpty()) {
|
||||||
|
totalLength = story.getContentHtml().length();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalLength == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate percentage and round to nearest integer
|
||||||
|
int percentage = Math.round((float) story.getReadingPosition() * 100 / totalLength);
|
||||||
|
return Math.min(100, percentage);
|
||||||
|
}
|
||||||
|
|
||||||
// ===============================
|
// ===============================
|
||||||
// UTILITY METHODS
|
// UTILITY METHODS
|
||||||
// ===============================
|
// ===============================
|
||||||
@@ -1039,6 +1060,7 @@ public class SolrService {
|
|||||||
story.setVolume((Integer) doc.getFieldValue("volume"));
|
story.setVolume((Integer) doc.getFieldValue("volume"));
|
||||||
story.setIsRead((Boolean) doc.getFieldValue("isRead"));
|
story.setIsRead((Boolean) doc.getFieldValue("isRead"));
|
||||||
story.setReadingPosition((Integer) doc.getFieldValue("readingPosition"));
|
story.setReadingPosition((Integer) doc.getFieldValue("readingPosition"));
|
||||||
|
story.setReadingProgressPercentage((Integer) doc.getFieldValue("readingProgressPercentage"));
|
||||||
|
|
||||||
// Handle dates
|
// Handle dates
|
||||||
story.setLastReadAt(parseDateTimeFromSolr(doc.getFieldValue("lastReadAt")));
|
story.setLastReadAt(parseDateTimeFromSolr(doc.getFieldValue("lastReadAt")));
|
||||||
|
|||||||
@@ -95,20 +95,20 @@ export default function StoryReadingPage() {
|
|||||||
// Convert scroll position to approximate character position in the content
|
// Convert scroll position to approximate character position in the content
|
||||||
const getCharacterPositionFromScroll = useCallback((): number => {
|
const getCharacterPositionFromScroll = useCallback((): number => {
|
||||||
if (!contentRef.current || !story) return 0;
|
if (!contentRef.current || !story) return 0;
|
||||||
|
|
||||||
const content = contentRef.current;
|
const content = contentRef.current;
|
||||||
const scrolled = window.scrollY;
|
const scrolled = window.scrollY;
|
||||||
const contentTop = content.offsetTop;
|
const contentTop = content.offsetTop;
|
||||||
const contentHeight = content.scrollHeight;
|
const contentHeight = content.scrollHeight;
|
||||||
const windowHeight = window.innerHeight;
|
const windowHeight = window.innerHeight;
|
||||||
|
|
||||||
// Calculate how far through the content we are (0-1)
|
// Calculate how far through the content we are (0-1)
|
||||||
const scrollRatio = Math.min(1, Math.max(0,
|
const scrollRatio = Math.min(1, Math.max(0,
|
||||||
(scrolled - contentTop + windowHeight * 0.3) / contentHeight
|
(scrolled - contentTop + windowHeight * 0.3) / contentHeight
|
||||||
));
|
));
|
||||||
|
|
||||||
// Convert to character position in the plain text content
|
// Convert to character position in the HTML content (ALWAYS use contentHtml for consistency)
|
||||||
const textLength = story.contentPlain?.length || story.contentHtml?.length || 0;
|
const textLength = story.contentHtml?.length || 0;
|
||||||
return Math.floor(scrollRatio * textLength);
|
return Math.floor(scrollRatio * textLength);
|
||||||
}, [story]);
|
}, [story]);
|
||||||
|
|
||||||
@@ -116,7 +116,8 @@ export default function StoryReadingPage() {
|
|||||||
const calculateReadingPercentage = useCallback((currentPosition: number): number => {
|
const calculateReadingPercentage = useCallback((currentPosition: number): number => {
|
||||||
if (!story) return 0;
|
if (!story) return 0;
|
||||||
|
|
||||||
const totalLength = story.contentPlain?.length || story.contentHtml?.length || 0;
|
// ALWAYS use contentHtml for consistency with position calculation
|
||||||
|
const totalLength = story.contentHtml?.length || 0;
|
||||||
if (totalLength === 0) return 0;
|
if (totalLength === 0) return 0;
|
||||||
|
|
||||||
return Math.round((currentPosition / totalLength) * 100);
|
return Math.round((currentPosition / totalLength) * 100);
|
||||||
@@ -126,7 +127,8 @@ export default function StoryReadingPage() {
|
|||||||
const scrollToCharacterPosition = useCallback((position: number) => {
|
const scrollToCharacterPosition = useCallback((position: number) => {
|
||||||
if (!contentRef.current || !story || hasScrolledToPosition) return;
|
if (!contentRef.current || !story || hasScrolledToPosition) return;
|
||||||
|
|
||||||
const textLength = story.contentPlain?.length || story.contentHtml?.length || 0;
|
// ALWAYS use contentHtml for consistency with position calculation
|
||||||
|
const textLength = story.contentHtml?.length || 0;
|
||||||
if (textLength === 0 || position === 0) return;
|
if (textLength === 0 || position === 0) return;
|
||||||
|
|
||||||
const ratio = position / textLength;
|
const ratio = position / textLength;
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
<!-- Reading Status Fields -->
|
<!-- Reading Status Fields -->
|
||||||
<field name="isRead" type="boolean" indexed="true" stored="true"/>
|
<field name="isRead" type="boolean" indexed="true" stored="true"/>
|
||||||
<field name="readingPosition" type="pint" indexed="true" stored="true"/>
|
<field name="readingPosition" type="pint" indexed="true" stored="true"/>
|
||||||
|
<field name="readingProgressPercentage" type="pint" indexed="true" stored="true"/>
|
||||||
<field name="lastReadAt" type="pdate" indexed="true" stored="true"/>
|
<field name="lastReadAt" type="pdate" indexed="true" stored="true"/>
|
||||||
<field name="lastRead" type="pdate" indexed="true" stored="true"/>
|
<field name="lastRead" type="pdate" indexed="true" stored="true"/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user