fix(kobo): use server reading streak on plugin dashboard

Prefer the server account reading streak when provided by the dashboard endpoint, falling back to local KOReader statistics database data when offline or absent. This aligns the KOReader plugin day streak display with the web app.

Closes #711
This commit is contained in:
neon
2026-07-26 22:27:26 +00:00
parent 8db3656633
commit 3fabadcf0d
4 changed files with 148 additions and 3 deletions
@@ -36,6 +36,7 @@ local BookOrbitStatsReader = require("bookorbit_stats_reader")
local formatDuration = CatalogUtil.formatDuration
local formatProgress = CatalogUtil.formatProgress
local readingStreakDays = CatalogUtil.readingStreakDays
local BookOrbitDashboardCoverCard = CatalogWidgets.DashboardCoverCard
local BookOrbitDashboardHeroCard = CatalogWidgets.DashboardHeroCard
@@ -455,8 +456,8 @@ function CatalogDashboard:addDashboardEmptyState(text, button_text, callback)
end
-- Local reading activity from statistics.sqlite3, cached briefly so row page
-- turns do not reopen the database. Returns nil when there is nothing to
-- show (no stats database, or no recorded reading at all).
-- turns do not reopen the database. The local streak is retained as a fallback
-- for cached responses from servers that do not provide the account streak.
function CatalogDashboard:dashboardStatsSummary()
local cache = self._dash_stats_cache
if not cache or os.time() - cache.at >= STATS_CACHE_TTL then
@@ -477,10 +478,11 @@ end
function CatalogDashboard:buildDashboardStatsStrip(summary, dashboard, height)
local sep_w = Size.line.thin
local today_seconds = summary.today_seconds or 0
local streak_days = readingStreakDays(dashboard, summary.streak_days)
local blocks = {
{ value = today_seconds > 0 and formatDuration(today_seconds) or _("Not yet"), label = _("Today") },
{ value = formatDuration(summary.week_seconds or 0), label = _("Past 7 days") },
{ value = tostring(summary.streak_days or 0), label = _("Day streak") },
{ value = tostring(streak_days), label = _("Day streak") },
}
local total = dashboard and tonumber(dashboard.totalBooks or dashboard.bookCount)
if total then
@@ -136,6 +136,13 @@ function CatalogUtil.formatProgress(value)
return tostring(math.floor(value + 0.5)) .. "%"
end
function CatalogUtil.readingStreakDays(dashboard, fallback)
local reading_streak = dashboard and dashboard.readingStreak
local current_streak = reading_streak and tonumber(reading_streak.currentStreak)
if current_streak then return current_streak end
return tonumber(fallback) or 0
end
function CatalogUtil.formatRating(value)
if not value then return nil end
if value == math.floor(value) then
@@ -0,0 +1,124 @@
package.loaded["document/documentregistry"] = {
hasProvider = function()
return true
end,
}
package.loaded["util"] = {
fixUtf8 = function(value)
return value
end,
}
package.loaded["ffi/util"] = {
template = function(value)
return value
end,
}
package.loaded["gettext"] = function(text)
return text
end
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
local CatalogUtil = require("bookorbit_catalog_util")
local function assertEqual(actual, expected, label)
if actual ~= expected then
error(string.format("%s: expected %s, got %s", label, tostring(expected), tostring(actual)))
end
end
assertEqual(
CatalogUtil.readingStreakDays({ readingStreak = { currentStreak = 20 } }, 14),
20,
"account streak overrides local streak"
)
assertEqual(
CatalogUtil.readingStreakDays({ readingStreak = { currentStreak = 0 } }, 14),
0,
"zero account streak overrides local streak"
)
assertEqual(CatalogUtil.readingStreakDays({}, 14), 14, "missing account streak falls back to local streak")
assertEqual(CatalogUtil.readingStreakDays(nil, nil), 0, "missing streak data falls back to zero")
local function widgetClass()
return {
new = function(_, opts)
opts = opts or {}
if not opts.getSize then
function opts:getSize()
return { w = 100, h = 20 }
end
end
return opts
end,
}
end
package.loaded["ffi/blitbuffer"] = {
COLOR_DARK_GRAY = 1,
COLOR_LIGHT_GRAY = 2,
}
package.loaded["ui/widget/button"] = widgetClass()
package.loaded["ui/widget/container/centercontainer"] = widgetClass()
package.loaded["ui/font"] = {
getFace = function()
return {}
end,
}
package.loaded["ui/geometry"] = widgetClass()
package.loaded["ui/widget/horizontalgroup"] = widgetClass()
package.loaded["ui/widget/horizontalspan"] = widgetClass()
package.loaded["ui/widget/infomessage"] = widgetClass()
package.loaded["ui/widget/linewidget"] = widgetClass()
package.loaded["ui/network/manager"] = {}
package.loaded["device"] = {
screen = {
scaleBySize = function(_, value)
return value
end,
},
}
package.loaded["ui/size"] = {
line = { thin = 1 },
padding = { large = 8 },
span = {
horizontal_default = 4,
vertical_default = 4,
},
}
package.loaded["ui/widget/textboxwidget"] = widgetClass()
package.loaded["ui/uimanager"] = {}
package.loaded["ui/widget/verticalgroup"] = widgetClass()
package.loaded["ui/widget/verticalspan"] = widgetClass()
package.loaded["bookorbit_stats_reader"] = {}
local rendered_stats = {}
package.loaded["bookorbit_catalog_widgets"] = {
buildDashboardStat = function(value, label, width)
table.insert(rendered_stats, { value = value, label = label, width = width })
return widgetClass():new()
end,
}
local CatalogDashboard = require("bookorbit_catalog_dashboard")
CatalogDashboard.buildDashboardStatsStrip({
content_w = 500,
onDeviceCount = function()
return 2
end,
}, {
today_seconds = 600,
week_seconds = 3600,
streak_days = 14,
}, {
totalBooks = 99,
readingStreak = { currentStreak = 20 },
})
assertEqual(rendered_stats[3].label, "Day streak", "dashboard renders a day streak stat")
assertEqual(rendered_stats[3].value, "20", "dashboard renders the account streak instead of the local streak")
print("bookorbit_catalog_streak_test.lua: ok")