mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
5cea03799e
Improve KOReader plugin catalog detail view layout by auto-adjusting widget text heights, fixing series line resolution, and polishing star rating, progress button, and navigation button styling.
267 lines
12 KiB
Lua
267 lines
12 KiB
Lua
-- Proves the dashboard's vertical budget: that shelves are measured at the
|
|
-- height their cover cards actually need, that a page too tight for the
|
|
-- configured slots drops the stats strip before any shelf, and that nothing is
|
|
-- laid out past the available height.
|
|
|
|
package.loaded["document/documentregistry"] = { hasProvider = function() return true end }
|
|
package.loaded["util"] = {
|
|
fixUtf8 = function(value) return value end,
|
|
urlEncode = function(value) return value end,
|
|
}
|
|
package.loaded["ffi/util"] = { template = function(value) return value end }
|
|
package.loaded["gettext"] = function(text) return text end
|
|
|
|
local function widgetClass(height)
|
|
return {
|
|
new = function(_, opts)
|
|
opts = opts or {}
|
|
if not opts.getSize then
|
|
function opts:getSize()
|
|
return { w = 100, h = self.width or height or 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(0)
|
|
package.loaded["ui/widget/horizontalspan"] = widgetClass(0)
|
|
package.loaded["ui/widget/infomessage"] = widgetClass()
|
|
package.loaded["ui/widget/linewidget"] = widgetClass()
|
|
package.loaded["ui/network/manager"] = { isConnected = function() return true end }
|
|
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/widget/textwidget"] = widgetClass()
|
|
package.loaded["ui/uimanager"] = { show = function() end }
|
|
package.loaded["ui/widget/verticalgroup"] = widgetClass(0)
|
|
package.loaded["ui/widget/verticalspan"] = widgetClass()
|
|
package.loaded["bookorbit_stats_reader"] = {
|
|
getReadingSummary = function()
|
|
return { today_seconds = 600, week_seconds = 3600, streak_days = 3 }
|
|
end,
|
|
}
|
|
|
|
local HEADER_HEIGHT = 20
|
|
local HEADER_ROW_HEIGHT = 14
|
|
local STATUS_LINE_HEIGHT = 16
|
|
local HIGHLIGHT_CARD_HEIGHT = 64
|
|
|
|
package.loaded["bookorbit_catalog_widgets"] = {
|
|
DashboardCoverCard = widgetClass(),
|
|
DashboardHeroCard = widgetClass(),
|
|
DashboardHighlightCard = widgetClass(),
|
|
DashboardBrowseRow = widgetClass(),
|
|
DashboardIconButton = widgetClass(),
|
|
assetIconFile = function() return nil end,
|
|
dashboardSectionHeaderRowHeight = function() return HEADER_ROW_HEIGHT end,
|
|
buildDashboardSectionHeader = function()
|
|
return { getSize = function() return { w = 100, h = HEADER_HEIGHT } end }
|
|
end,
|
|
buildStatusLabel = function()
|
|
return { getSize = function() return { w = 100, h = STATUS_LINE_HEIGHT } end }
|
|
end,
|
|
dashboardHighlightCardHeight = function()
|
|
return HIGHLIGHT_CARD_HEIGHT
|
|
end,
|
|
coverCardHeight = function(card_w)
|
|
return card_w + 20
|
|
end,
|
|
dashboardHeroHeightForCoverCard = function(card_w)
|
|
return card_w + 24
|
|
end,
|
|
}
|
|
|
|
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
|
|
|
|
local CatalogDashboard = require("bookorbit_catalog_dashboard")
|
|
local DashboardSections = require("bookorbit_dashboard_sections")
|
|
|
|
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
|
|
|
|
local STATS_HEIGHT = 60
|
|
|
|
local function books(count)
|
|
local list = {}
|
|
for id = 1, count do list[id] = { id = id } end
|
|
return list
|
|
end
|
|
|
|
local function newCatalog(opts)
|
|
local slots = {}
|
|
for index = 1, DashboardSections.SLOT_COUNT do
|
|
slots[index] = { type = opts.sections[index].type, books = books(opts.books or 12) }
|
|
end
|
|
local catalog = {
|
|
settings = {
|
|
[DashboardSections.SETTING_KEY] = {
|
|
opts.sections[1], opts.sections[2], opts.sections[3], opts.sections[4],
|
|
schemaVersion = DashboardSections.SCHEMA_VERSION,
|
|
},
|
|
},
|
|
available_height = opts.avail,
|
|
content_w = opts.content_w or 500,
|
|
content_inset = 0,
|
|
item_group = {},
|
|
layout = {},
|
|
current_context = {
|
|
kind = "dashboard",
|
|
dashboard = { continueReading = books(4), dashboardSlots = slots },
|
|
},
|
|
shelves = {},
|
|
headers = {},
|
|
}
|
|
for name, fn in pairs(CatalogDashboard) do
|
|
if name ~= "install" then catalog[name] = fn end
|
|
end
|
|
function catalog:prepareCustomUpdate() return nil end
|
|
function catalog:finishCustomUpdate() end
|
|
function catalog:ensureOnDeviceCurrent() end
|
|
function catalog:isOnDevice() return false end
|
|
function catalog:onDeviceCount() return 0 end
|
|
function catalog:readStatusLabel() return nil end
|
|
function catalog:buildDashboardStatsStrip()
|
|
return { getSize = function() return { w = 100, h = STATS_HEIGHT } end }
|
|
end
|
|
|
|
local addHeader = catalog.addDashboardHeader
|
|
function catalog:addDashboardHeader(text, controls)
|
|
table.insert(self.headers, text)
|
|
return addHeader(self, text, controls)
|
|
end
|
|
local addGrid = catalog.addDashboardCoverGrid
|
|
function catalog:addDashboardCoverGrid(section_id, list, height, with_progress, with_caption, cols, card_w, page, rows)
|
|
table.insert(self.shelves, { id = section_id, height = height, cols = cols, card_w = card_w })
|
|
return addGrid(self, section_id, list, height, with_progress, with_caption, cols, card_w, page, rows)
|
|
end
|
|
return catalog
|
|
end
|
|
|
|
local DEFAULTS = {
|
|
{ type = "stats" }, { type = "continue-reading" }, { type = "random" }, { type = "browse" },
|
|
}
|
|
local FOUR_SHELVES = {
|
|
{ type = "random" }, { type = "recently-added" }, { type = "in-progress" }, { type = "random" },
|
|
}
|
|
|
|
-- A roomy page renders every configured slot, and each shelf is exactly as tall
|
|
-- as its cover cards need to be.
|
|
local catalog = newCatalog{ sections = DEFAULTS, avail = 800 }
|
|
catalog:updateDashboardItems()
|
|
assertEqual(#catalog.shelves, 1, "the default dashboard renders its one shelf")
|
|
assertEqual(#catalog.headers, 3, "Continue reading, the shelf and Browse each get a header")
|
|
assertEqual(catalog.shelves[1].height, catalog.shelves[1].card_w + 20, "a roomy shelf is drawn in aspect")
|
|
assertEqual(catalog.shelves[1].cols, 6, "a roomy shelf keeps its natural slot count")
|
|
assertEqual(catalog.dash_used <= 800, true, "a roomy page stays inside the available height")
|
|
|
|
-- Four shelves share the page and stay in aspect: the cards get narrower rather
|
|
-- than being squashed to fit.
|
|
catalog = newCatalog{ sections = FOUR_SHELVES, avail = 600 }
|
|
catalog:updateDashboardItems()
|
|
assertEqual(#catalog.shelves, 4, "four configured shelves all render")
|
|
for _, shelf in ipairs(catalog.shelves) do
|
|
assertEqual(shelf.height, shelf.card_w + 20, "every shelf is drawn in aspect")
|
|
end
|
|
assertEqual(catalog.shelves[1].cols, 6, "a crowded page still stops at the column cap")
|
|
assertEqual(catalog.dash_used <= 600, true, "four shelves stay inside the available height")
|
|
|
|
-- Too tight for everything: the stats strip goes before any shelf does.
|
|
catalog = newCatalog{ sections = DEFAULTS, avail = 450 }
|
|
catalog:updateDashboardItems()
|
|
assertEqual(#catalog.shelves, 1, "the shelf survives a tight page")
|
|
assertEqual(catalog.headers[1], "Continue reading", "the stats strip is dropped first")
|
|
assertEqual(catalog.dash_used <= 450, true, "dropping stats brings the page back inside its height")
|
|
|
|
-- Tighter still: shelves go from the bottom up, and the native slots stay.
|
|
catalog = newCatalog{ sections = FOUR_SHELVES, avail = 260 }
|
|
catalog:updateDashboardItems()
|
|
assertEqual(#catalog.shelves < 4, true, "an impossible page drops shelves from the bottom")
|
|
assertEqual(catalog.shelves[1].id, "section1", "the first configured shelf is the last to go")
|
|
assertEqual(catalog.dash_used <= 260, true, "dropping shelves brings the page back inside its height")
|
|
|
|
-- A slot waiting on a refresh renders its placeholder rather than the books
|
|
-- cached for the previous choice.
|
|
catalog = newCatalog{ sections = DEFAULTS, avail = 800 }
|
|
catalog.current_context.section_stale = { [3] = true }
|
|
catalog:updateDashboardItems()
|
|
assertEqual(#catalog.shelves, 0, "a pending shelf renders no cover grid")
|
|
assertEqual(catalog.headers[2], "Discover", "a pending shelf still shows its header")
|
|
|
|
-- A stale (cached) dashboard announces itself with a status line that takes
|
|
-- part in the vertical budget.
|
|
catalog = newCatalog{ sections = DEFAULTS, avail = 800 }
|
|
catalog.current_context.stale = true
|
|
catalog:updateDashboardItems()
|
|
assertEqual(catalog.dash_used <= 800, true, "the status line stays inside the available height")
|
|
local without_status = newCatalog{ sections = DEFAULTS, avail = 800 }
|
|
without_status:updateDashboardItems()
|
|
assertEqual(catalog.dash_used, without_status.dash_used, "budget accounting is unchanged aside from the trailing spacer")
|
|
|
|
-- The Highlight of the day slot renders its measured card and drops after
|
|
-- stats but before any shelf when the page gets tight.
|
|
local WITH_HIGHLIGHT = {
|
|
{ type = "stats" }, { type = "continue-reading" }, { type = "highlight" }, { type = "random" },
|
|
}
|
|
catalog = newCatalog{ sections = WITH_HIGHLIGHT, avail = 800 }
|
|
catalog.current_context.dashboard.highlightOfTheDay = { text = "quote", bookId = 7 }
|
|
catalog:updateDashboardItems()
|
|
assertEqual(catalog.headers[2], "Highlight of the day", "the highlight slot renders its header")
|
|
assertEqual(#catalog.shelves, 1, "the shelf renders alongside the highlight card")
|
|
assertEqual(catalog.dash_used <= 800, true, "the highlight card stays inside the available height")
|
|
|
|
catalog = newCatalog{ sections = WITH_HIGHLIGHT, avail = 340 }
|
|
catalog.current_context.dashboard.highlightOfTheDay = { text = "quote", bookId = 7 }
|
|
catalog:updateDashboardItems()
|
|
local saw_highlight = false
|
|
for _, header in ipairs(catalog.headers) do
|
|
if header == "Highlight of the day" then saw_highlight = true end
|
|
end
|
|
assertEqual(saw_highlight, false, "a tight page drops the highlight card after stats, before shelves")
|
|
assertEqual(#catalog.shelves, 1, "the shelf survives the tight page")
|
|
|
|
-- Header controls are built at the header's label-row height, so a section
|
|
-- carrying chevrons or a reroll button is exactly as tall as a bare one. That
|
|
-- is what keeps the single measured header height the budget uses honest, and
|
|
-- what stops the controls from floating above the label.
|
|
catalog = newCatalog{ sections = DEFAULTS, avail = 800 }
|
|
local prev_control, next_control = catalog:buildDashboardHeaderNav("section3", 2, 4)
|
|
local reroll_control = catalog:buildDashboardRerollButton(3)
|
|
for label, control in pairs({ prev = prev_control, next = next_control, reroll = reroll_control }) do
|
|
assertEqual(control.dimen.h, HEADER_ROW_HEIGHT, label .. " control fills the header row exactly")
|
|
assertEqual(control.dimen.w, HEADER_ROW_HEIGHT, label .. " control is square")
|
|
assertEqual(control.tap_padding_v > 0, true, label .. " control takes taps from the gap around it")
|
|
end
|
|
assertEqual(prev_control.enabled, true, "a page-2 back chevron is live")
|
|
assertEqual(catalog:buildDashboardHeaderNav("section3", 1, 4).enabled, false,
|
|
"the back chevron is disabled on the first page")
|
|
|
|
-- Page turns record each section's band and narrow the refresh to it.
|
|
catalog = newCatalog{ sections = DEFAULTS, avail = 800 }
|
|
catalog.dimen = { x = 0, y = 0, w = 520, h = 900 }
|
|
function catalog:menuChromeHeight() return 30, 40 end
|
|
catalog.dash_refresh_section = "section3"
|
|
catalog:updateDashboardItems()
|
|
local band = catalog.dash_section_bands["section3"]
|
|
assertEqual(band ~= nil and band.h > 0, true, "the shelf records its rendered band")
|
|
local region = catalog.custom_refresh_region
|
|
assertEqual(region ~= nil, true, "a page turn narrows the refresh to the changed band")
|
|
assertEqual(region.y, 30 + band.y - 4, "the refresh band sits below the title bar with its padding")
|
|
assertEqual(region.h, band.h + 8, "the refresh band covers the section plus padding")
|
|
assertEqual(catalog.dash_refresh_section, nil, "the page-turn hint is consumed by the rebuild")
|
|
|
|
print("bookorbit_dashboard_layout_test.lua: ok")
|