mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
chore(annotations): add KOReader highlight drawer
Keep long dashboard highlights readable while preserving quick access to book details.
This commit is contained in:
@@ -231,8 +231,8 @@ function CatalogBoard:boardItemAction(item, d)
|
||||
return { kind = "books", params = { sort = "title" }, title = _("All books") }
|
||||
elseif t == "also" or t == "goalpace" then
|
||||
return { kind = "books", params = { sort = "recently_read", readStatus = "reading" }, title = _("In progress") }
|
||||
elseif t == "highlight" and d.highlight and d.highlight.bookId then
|
||||
return { kind = "detail", book_id = d.highlight.bookId }
|
||||
elseif t == "highlight" and d.highlight then
|
||||
return { kind = "highlight", highlight = d.highlight }
|
||||
elseif t == "shelf" then
|
||||
local source = item.props and item.props.source
|
||||
if source == "want-to-read" and not (item.props and item.props.params) then return nil end
|
||||
@@ -800,6 +800,8 @@ function CatalogBoard:boardRun(action, tile)
|
||||
self:onMenuSelect({ kind = "dashboard-book", book_id = action.book.id, book = action.book, resume = action.resume, list = action.list })
|
||||
elseif kind == "detail" then
|
||||
self:loadBookDetail(action.book_id, { list = action.list })
|
||||
elseif kind == "highlight" then
|
||||
require("bookorbit_highlight_sheet").open(self, action.highlight)
|
||||
elseif kind == "books" then
|
||||
self:loadBooks(action.params or {}, action.title)
|
||||
elseif kind == "shelf" then
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
--[[--
|
||||
The Highlight of the day drawer. The dashboard tile is deliberately concise;
|
||||
this sheet keeps the full passage readable and leaves book details one tap away.
|
||||
Long passages turn page by page inside the drawer.
|
||||
]]
|
||||
|
||||
local Device = require("device")
|
||||
local TextBoxWidget = require("ui/widget/textboxwidget")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local _ = require("gettext")
|
||||
|
||||
local BoardMetrics = require("bookorbit_board_metrics")
|
||||
local BoardUI = require("bookorbit_board_ui")
|
||||
local DialogUI = require("bookorbit_dialog_ui")
|
||||
local TextPages = require("bookorbit_text_pages")
|
||||
|
||||
local INK = BoardMetrics.INK
|
||||
local px, lh = BoardMetrics.px, BoardMetrics.lh
|
||||
local text, vstack, hstack, space = BoardUI.text, BoardUI.vstack, BoardUI.hstack, BoardUI.space
|
||||
|
||||
local HighlightSheet = {}
|
||||
|
||||
local BODY_SIZE = 16
|
||||
|
||||
local function hOf(widget) return widget:getSize().h end
|
||||
|
||||
function HighlightSheet.pageWindow(total_lines, available_height, line_height, index)
|
||||
total_lines = math.max(1, tonumber(total_lines) or 1)
|
||||
line_height = math.max(1, tonumber(line_height) or 1)
|
||||
local per_page = math.max(1, math.floor(math.max(1, available_height) / line_height))
|
||||
local count = math.max(1, math.ceil(total_lines / per_page))
|
||||
index = math.max(1, math.min(count, tonumber(index) or 1))
|
||||
return {
|
||||
count = count,
|
||||
index = index,
|
||||
per_page = per_page,
|
||||
first_line = (index - 1) * per_page + 1,
|
||||
height = math.min(total_lines, per_page) * line_height,
|
||||
}
|
||||
end
|
||||
|
||||
local function chapterLine(highlight, w)
|
||||
local chapter = type(highlight.chapterTitle) == "string" and highlight.chapterTitle:match("^%s*(.-)%s*$") or nil
|
||||
if not chapter or chapter == "" then return nil end
|
||||
return text(chapter, { s = 13, b = true, col = INK.g3, w = w, data = true })
|
||||
end
|
||||
|
||||
local function buildBody(page, w, max_body_h)
|
||||
local highlight = page.highlight
|
||||
local head = DialogUI.titleRow(w, _("Highlight of the day"), highlight.bookTitle)
|
||||
local chapter = chapterLine(highlight, w)
|
||||
local details = highlight.bookId and DialogUI.pillRow(w, {
|
||||
{ label = _("Book details"), icon = "book-open-text", kind = "outline", action = { kind = "details" } },
|
||||
}) or nil
|
||||
local gap = px(14)
|
||||
local line_height = lh(BODY_SIZE)
|
||||
local bar = math.max(2, px(3))
|
||||
local quote_gap = px(14)
|
||||
local quote_w = math.max(1, w - bar - quote_gap)
|
||||
local total_lines = TextPages.linesNeeded(highlight.text or "", { s = BODY_SIZE, serif = true, i = true }, quote_w)
|
||||
|
||||
local function fixedHeight(with_pager)
|
||||
local fixed_items = { head }
|
||||
if chapter then table.insert(fixed_items, chapter) end
|
||||
if with_pager then table.insert(fixed_items, true) end
|
||||
if details then table.insert(fixed_items, details) end
|
||||
local height = 0
|
||||
for _i, item in ipairs(fixed_items) do
|
||||
height = height + (item == true and px(40) or hOf(item))
|
||||
end
|
||||
return height + #fixed_items * gap
|
||||
end
|
||||
|
||||
local window = HighlightSheet.pageWindow(total_lines, max_body_h - fixedHeight(false), line_height, page.index)
|
||||
if window.count > 1 then
|
||||
window = HighlightSheet.pageWindow(total_lines, max_body_h - fixedHeight(true), line_height, page.index)
|
||||
end
|
||||
page.index = window.index
|
||||
page.count = window.count
|
||||
|
||||
local quote = TextBoxWidget:new{
|
||||
text = highlight.text or "",
|
||||
face = BoardMetrics.face(BODY_SIZE, "serif_italic"),
|
||||
width = quote_w,
|
||||
height = window.height,
|
||||
virtual_line_num = window.first_line,
|
||||
fgcolor = INK.black,
|
||||
bgcolor = INK.white,
|
||||
}
|
||||
local quote_block = hstack({ w = w, valign = "top" }, {
|
||||
BoardUI.rect(bar, window.height, INK.black, math.floor(bar / 2)),
|
||||
space(quote_gap, 1),
|
||||
quote,
|
||||
})
|
||||
local items = { head, quote_block }
|
||||
if chapter then table.insert(items, chapter) end
|
||||
if window.count > 1 then table.insert(items, TextPages.pager(w, window.index, window.count)) end
|
||||
if details then table.insert(items, details) end
|
||||
return items
|
||||
end
|
||||
|
||||
local function turn(page, delta)
|
||||
local next_index = math.max(1, math.min(page.count or 1, page.index + delta))
|
||||
if next_index == page.index then return end
|
||||
page.index = next_index
|
||||
page:refresh(Device:hasEinkScreen() and "partial" or "ui")
|
||||
end
|
||||
|
||||
function HighlightSheet.open(catalog, highlight)
|
||||
if type(highlight) ~= "table" then return nil end
|
||||
return DialogUI.open{
|
||||
name = "bookorbit_highlight_of_the_day",
|
||||
build = function(page, sw, sh)
|
||||
return DialogUI.sheet(page, sw, sh, function(w, max_body_h)
|
||||
return buildBody(page, w, max_body_h)
|
||||
end, { pop_w = 520 })
|
||||
end,
|
||||
on_action = function(page, action)
|
||||
if action.kind == "page" then
|
||||
turn(page, action.delta)
|
||||
elseif action.kind == "details" and page.highlight.bookId then
|
||||
local book_id = page.highlight.bookId
|
||||
page:close()
|
||||
UIManager:tickAfterNext(function() catalog:loadBookDetail(book_id) end)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
on_swipe = function(page, ges)
|
||||
if ges.direction == "west" then turn(page, 1) return true end
|
||||
if ges.direction == "east" then turn(page, -1) return true end
|
||||
return false
|
||||
end,
|
||||
on_page_key = function(page, delta) turn(page, delta) end,
|
||||
highlight = highlight,
|
||||
index = 1,
|
||||
}
|
||||
end
|
||||
|
||||
return HighlightSheet
|
||||
@@ -0,0 +1,34 @@
|
||||
-- The Highlight of the day drawer keeps every line reachable in bounded pages.
|
||||
|
||||
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
|
||||
|
||||
local function identity(value) return value end
|
||||
for _, name in ipairs({ "device", "ui/widget/textboxwidget", "ui/uimanager", "bookorbit_board_ui",
|
||||
"bookorbit_dialog_ui", "bookorbit_text_pages" }) do
|
||||
package.loaded[name] = {}
|
||||
end
|
||||
package.loaded["gettext"] = identity
|
||||
package.loaded["bookorbit_board_metrics"] = { INK = {}, px = identity, lh = identity }
|
||||
|
||||
local HighlightSheet = require("bookorbit_highlight_sheet")
|
||||
|
||||
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 window = HighlightSheet.pageWindow(23, 100, 10, 1)
|
||||
assertEqual(window.per_page, 10, "the drawer fits ten lines")
|
||||
assertEqual(window.count, 3, "all lines are split across three pages")
|
||||
assertEqual(window.first_line, 1, "the first page starts at the first line")
|
||||
assertEqual(window.height, 100, "multipage drawers keep a stable text height")
|
||||
|
||||
window = HighlightSheet.pageWindow(23, 100, 10, 3)
|
||||
assertEqual(window.first_line, 21, "the last page starts after the first twenty lines")
|
||||
assertEqual(window.height, 100, "the last page keeps the drawer from jumping")
|
||||
|
||||
window = HighlightSheet.pageWindow(3, 100, 10, 8)
|
||||
assertEqual(window.count, 1, "a short highlight needs one page")
|
||||
assertEqual(window.index, 1, "the requested page is clamped")
|
||||
assertEqual(window.height, 30, "a short highlight keeps the drawer compact")
|
||||
|
||||
print("bookorbit_highlight_sheet_test.lua: ok")
|
||||
Reference in New Issue
Block a user