Skip to content
Merged
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
8 changes: 4 additions & 4 deletions KMReader.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@
"CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "KMReader/KMReader-macOS.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 502;
DEVELOPMENT_TEAM = M777UHWZA4;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down Expand Up @@ -500,7 +500,7 @@
"CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "KMReader/KMReader-macOS.entitlements";
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 502;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = M777UHWZA4;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = M777UHWZA4;
Expand Down Expand Up @@ -560,7 +560,7 @@
CODE_SIGN_ENTITLEMENTS = KMReaderWidgets/KMReaderWidgets.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 502;
DEVELOPMENT_TEAM = M777UHWZA4;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = KMReaderWidgets/Info.plist;
Expand Down Expand Up @@ -594,7 +594,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 502;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = M777UHWZA4;
GENERATE_INFOPLIST_FILE = YES;
Expand Down
4 changes: 2 additions & 2 deletions KMReader/Core/Storage/AppConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1130,15 +1130,15 @@ enum AppConfig {

static nonisolated var readingStatsCache: ReadingStatsCache {
get {
if let stored = UserDefaults.standard.string(forKey: "readingStatsCache"),
if let stored = UserDefaults.standard.string(forKey: "readingStatsCacheV2"),
let cache = ReadingStatsCache(rawValue: stored)
{
return cache
}
return ReadingStatsCache()
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: "readingStatsCache")
UserDefaults.standard.set(newValue.rawValue, forKey: "readingStatsCacheV2")
}
}

Expand Down
11 changes: 11 additions & 0 deletions KMReader/Features/History/Models/ReadingPagesHeatmapWeek.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// ReadingPagesHeatmapWeek.swift
//
//

import Foundation

nonisolated struct ReadingPagesHeatmapWeek: Equatable, Sendable, Identifiable {
let id: String
let days: [ReadingStatsTimePoint?]
}
31 changes: 20 additions & 11 deletions KMReader/Features/History/Services/ReadingStatsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,10 @@ nonisolated enum ReadingStatsService {
let completedBooks = filteredBooks.filter(\.isCompleted)

let totalPagesRead = completedBooks.reduce(0.0) { partial, book in
partial + Double(book.readProgress?.page ?? 0)
partial + Double(readPageCount(for: book))
}

let averagePagesPerBook = completedBooks.isEmpty ? 0 : (totalPagesRead / Double(completedBooks.count)).rounded()
let estimatedReadingHours = (totalPagesRead / 2 / 60).rounded()

let readDates = completedBooks.compactMap { $0.readProgress?.readDate }
let lastReadDate = readDates.max()
let uniqueReadingDays = Set(readDates.map(Self.dayKey))
Expand All @@ -73,7 +71,7 @@ nonisolated enum ReadingStatsService {
totalPagesRead: totalPagesRead,
averagePagesPerBook: averagePagesPerBook,
readingDays: Double(uniqueReadingDays.count),
estimatedReadingHours: estimatedReadingHours,
estimatedReadingHours: 0,
lastReadAt: lastReadDate.map(Self.isoDateTime)
)

Expand All @@ -85,7 +83,7 @@ nonisolated enum ReadingStatsService {

let dailyDistribution = buildDailyDistribution(completedBooks: completedBooks)
let hourlyDistribution = buildHourlyDistribution(completedBooks: completedBooks)
let readingTimeSeries = buildReadingTimeSeries(completedBooks: completedBooks)
let dailyPagesSeries = buildDailyPagesSeries(completedBooks: completedBooks)

let dimensions = buildDimensions(completedBooks: completedBooks, readSeries: readSeries)

Expand All @@ -94,7 +92,7 @@ nonisolated enum ReadingStatsService {
statusDistribution: statusDistribution,
dailyDistribution: dailyDistribution,
hourlyDistribution: hourlyDistribution,
readingTimeSeries: readingTimeSeries,
readingTimeSeries: dailyPagesSeries,
topAuthors: dimensions.topAuthors,
topGenres: dimensions.topGenres,
topTags: dimensions.topTags,
Expand Down Expand Up @@ -153,7 +151,7 @@ nonisolated enum ReadingStatsService {
}
}

private static func buildReadingTimeSeries(completedBooks: [Book]) -> [ReadingStatsTimePoint] {
private static func buildDailyPagesSeries(completedBooks: [Book]) -> [ReadingStatsTimePoint] {
guard !completedBooks.isEmpty else { return [] }

let today = Date()
Expand All @@ -164,12 +162,12 @@ nonisolated enum ReadingStatsService {
.map { Calendar.current.startOfDay(for: $0) }
?? Calendar.current.startOfDay(for: today)

var hoursByDay: [String: Double] = [:]
var pagesByDay: [String: Double] = [:]
for book in completedBooks {
guard let progress = book.readProgress else { continue }
let dayKey = Self.dayKey(progress.readDate)
let hours = Double(progress.page) / 2 / 60
hoursByDay[dayKey, default: 0] += hours
let pages = Double(readPageCount(for: book))
pagesByDay[dayKey, default: 0] += pages
}

var points: [ReadingStatsTimePoint] = []
Expand All @@ -181,7 +179,7 @@ nonisolated enum ReadingStatsService {
points.append(
ReadingStatsTimePoint(
name: dayKey,
value: hoursByDay[dayKey, default: 0],
value: pagesByDay[dayKey, default: 0],
dateString: dayKey
)
)
Expand All @@ -192,6 +190,17 @@ nonisolated enum ReadingStatsService {
return points
}

private static func readPageCount(for book: Book) -> Int {
guard let progress = book.readProgress else { return 0 }

let mediaPagesCount = max(book.media.pagesCount, 0)
if progress.completed, mediaPagesCount > 0 {
return mediaPagesCount
}

return max(progress.page, 0)
}

private static func buildDimensions(completedBooks: [Book], readSeries: [Series]) -> ReadingStatsDimensions {
let seriesById = Dictionary(uniqueKeysWithValues: readSeries.map { ($0.id, $0) })
let booksBySeries = Dictionary(grouping: completedBooks, by: \.seriesId)
Expand Down
Loading