diff --git a/frontend/ui/widget/keyvaluepage.lua b/frontend/ui/widget/keyvaluepage.lua index 44ab17880..362fc5c79 100644 --- a/frontend/ui/widget/keyvaluepage.lua +++ b/frontend/ui/widget/keyvaluepage.lua @@ -124,20 +124,14 @@ local KeyValueItem = InputContainer:new{ textviewer_width = nil, textviewer_height = nil, value_overflow_align = "left", + -- "right": only align right if value overflow 1/2 width + -- "right_always": align value right even when small and + -- only key overflows 1/2 width } function KeyValueItem:init() self.dimen = Geom:new{w = self.width, h = self.height} - if self.callback and Device:isTouchDevice() then - self.ges_events.Tap = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - } - } - end - -- self.value may contain some control characters (\n \t...) that would -- be rendered as a square. Replace them with a shorter and nicer '|'. -- (Let self.value untouched, as with Hold, the original value can be @@ -190,12 +184,13 @@ function KeyValueItem:init() key_w = key_w_rendered end value_align_right = true -- so the ellipsis touches the screen right border - if self.value_overflow_align ~= "right" and self.value_align ~= "right" then + if self.value_align ~= "right" and self.value_overflow_align ~= "right" + and self.value_overflow_align ~= "right_always" then -- Don't adjust the ellipsis to the screen right border, -- so the left of text is aligned with other truncated texts fit_right_align = false end - -- Allow for displaying the non-truncated texts with Hold + -- Allow for displaying the non-truncated text with Hold if Device:isTouchDevice() then self.ges_events.Hold = { GestureRange:new{ @@ -203,10 +198,18 @@ function KeyValueItem:init() range = self.dimen, } } + -- If no tap callback, allow for displaying the non-truncated + -- text with Tap too + if not self.callback then + self.callback = function() + self:onHold() + end + end end else -- Both can fit: break the 1/2 widths - if self.value_overflow_align == "right" or self.value_align == "right" then + if self.value_align == "right" or self.value_overflow_align == "right_always" + or (self.value_overflow_align == "right" and value_w_rendered > value_w) then key_w = available_width - value_w_rendered value_align_right = true else @@ -237,6 +240,15 @@ function KeyValueItem:init() -- For debugging positioning: -- value_widget = FrameContainer:new{ padding=0, margin=0, bordersize=1, value_widget } + if self.callback and Device:isTouchDevice() then + self.ges_events.Tap = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + } + } + end + self[1] = FrameContainer:new{ padding = frame_padding, bordersize = 0, diff --git a/plugins/statistics.koplugin/main.lua b/plugins/statistics.koplugin/main.lua index ede4a081d..8403df6f5 100644 --- a/plugins/statistics.koplugin/main.lua +++ b/plugins/statistics.koplugin/main.lua @@ -6,6 +6,7 @@ local Device = require("device") local DocSettings = require("docsettings") local InfoMessage = require("ui/widget/infomessage") local KeyValuePage = require("ui/widget/keyvaluepage") +local Math = require("optmath") local ReaderFooter = require("apps/reader/modules/readerfooter") local ReaderProgress = require("readerprogress") local ReadHistory = require("readhistory") @@ -171,7 +172,7 @@ function ReaderStatistics:init() ReaderGesture.getBookStats = function() if self:isDocless() or not self.is_enabled then return end local stats = KeyValuePage:new{ - title = _("Statistics"), + title = _("Current statistics"), kv_pairs = self:getCurrentStat(self.id_curr_book), } return stats @@ -834,7 +835,7 @@ The max value ensures a page you stay on for a long time (because you fell aslee keep_menu_open = true, callback = function() UIManager:show(KeyValuePage:new{ - title = _("Statistics"), + title = _("Current statistics"), kv_pairs = self:getCurrentStat(self.id_curr_book), }) end, @@ -902,15 +903,14 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - "----", - { _("Last week"),"", + { _("Books by week"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Last week"), + title = _("Books by week"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(7, "daily_weekday"), + kv_pairs = self:getDatesFromAll(0, "weekly", true), callback_return = function() UIManager:show(kv) self.kv = kv @@ -919,14 +919,14 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - { _("Last month by day"),"", + { _("Books by month"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Last month by day"), + title = _("Books by month"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(30, "daily_weekday"), + kv_pairs = self:getDatesFromAll(0, "monthly", true), callback_return = function() UIManager:show(kv) self.kv = kv @@ -935,14 +935,15 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - { _("Last year by day"),"", + "----", + { _("Last week"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Last year by day"), + title = _("Last week"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(365, "daily"), + kv_pairs = self:getDatesFromAll(7, "daily_weekday"), callback_return = function() UIManager:show(kv) self.kv = kv @@ -951,14 +952,14 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - { _("Last year by week"),"", + { _("Last month by day"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Last year by week"), + title = _("Last month by day"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(365, "weekly"), + kv_pairs = self:getDatesFromAll(30, "daily_weekday"), callback_return = function() UIManager:show(kv) self.kv = kv @@ -967,14 +968,14 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - { _("All stats by month"),"", + { _("Last year by day"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("All stats by month"), + title = _("Last year by day"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(0, "monthly"), + kv_pairs = self:getDatesFromAll(365, "daily"), callback_return = function() UIManager:show(kv) self.kv = kv @@ -983,15 +984,14 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - "----", - { _("Books by week"),"", + { _("Last year by week"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Books by week"), + title = _("Last year by week"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(0, "weekly", true), + kv_pairs = self:getDatesFromAll(365, "weekly"), callback_return = function() UIManager:show(kv) self.kv = kv @@ -1000,14 +1000,14 @@ function ReaderStatistics:statMenu() UIManager:show(self.kv) end, }, - { _("Books by month"),"", + { _("All stats by month"),"", callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Books by month"), + title = _("All stats by month"), value_overflow_align = "right", - kv_pairs = self:getDatesFromAll(0, "monthly", true), + kv_pairs = self:getDatesFromAll(0, "monthly"), callback_return = function() UIManager:show(kv) self.kv = kv @@ -1015,7 +1015,7 @@ function ReaderStatistics:statMenu() } UIManager:show(self.kv) end, - } + }, } } UIManager:show(self.kv) @@ -1083,7 +1083,7 @@ function ReaderStatistics:getCurrentStat(id_book) local current_period, current_pages = self:getCurrentBookStats() local conn = SQ3.open(db_location) - local notes, highlights = conn:rowexec(string.format("SELECT notes, highlights FROM book WHERE id = '%s';)", id_book)) + local highlights, notes = conn:rowexec(string.format("SELECT highlights, notes FROM book WHERE id = '%s';)", id_book)) -- luacheck: no unused local sql_stmt = [[ SELECT count(*) FROM ( @@ -1094,13 +1094,15 @@ function ReaderStatistics:getCurrentStat(id_book) ) ]] local total_days = conn:rowexec(string.format(sql_stmt, id_book)) + sql_stmt = [[ SELECT sum(period), - count(DISTINCT page) + count(DISTINCT page), + min(start_time) FROM page_stat WHERE id_book = '%s' ]] - local total_time_book, total_read_pages = conn:rowexec(string.format(sql_stmt, id_book)) + local total_time_book, total_read_pages, first_open = conn:rowexec(string.format(sql_stmt, id_book)) conn:close() if total_time_book == nil then @@ -1109,6 +1111,9 @@ function ReaderStatistics:getCurrentStat(id_book) if total_read_pages == nil then total_read_pages = 0 end + if first_open == nil then + first_open = TimeVal:now().sec + end self.data.pages = self.view.document:getPageCount() total_time_book = tonumber(total_time_book) total_read_pages = tonumber(total_read_pages) @@ -1117,22 +1122,32 @@ function ReaderStatistics:getCurrentStat(id_book) local estimate_end_of_read_date = os.date("%Y-%m-%d", tonumber(os.time() + estimate_days_to_read * 86400)) local formatstr = "%.0f%%" return { + -- Global statistics (may consider other books than current book) + -- since last resume { _("Time spent reading this session"), util.secondsToClock(current_period, false) }, { _("Pages read this session"), tonumber(current_pages) }, + -- today { _("Time spent reading today"), util.secondsToClock(today_period, false) }, { _("Pages read today"), tonumber(today_pages) }, - { _("Time to read"), util.secondsToClock(time_to_read), false}, - { _("Total time"), util.secondsToClock(total_time_book, false) }, - { _("Total highlights"), tonumber(highlights) }, - { _("Total notes"), tonumber(notes) }, - { _("Total pages read"), tonumber(total_read_pages) }, - { _("Total days"), tonumber(total_days) }, + "----", + -- Current book statistics + { _("Time spent reading this book"), util.secondsToClock(total_time_book, false) }, + -- per days + { _("Reading started"), os.date("%Y-%m-%d (%H:%M)", tonumber(first_open))}, + { _("Days reading this book"), tonumber(total_days) }, + { _("Average time per day"), util.secondsToClock(total_time_book/tonumber(total_days), false) }, + -- per page + { _("Pages read"), tonumber(total_read_pages) }, { _("Average time per page"), util.secondsToClock(self.avg_time, false) }, - { _("Current pages read/Total pages"), self.curr_page .. "/" .. self.data.pages }, + -- estimation, from current page to end of book + { _("Current page/Total pages"), self.curr_page .. "/" .. self.data.pages }, { _("Percentage completed"), formatstr:format(self.curr_page/self.data.pages * 100) }, - { _("Average time per day"), util.secondsToClock(total_time_book/tonumber(total_days)), false }, + { _("Estimated time to read"), util.secondsToClock(time_to_read, false) }, { _("Estimated reading finished"), T(N_("%1 (1 day)", "%1 (%2 days)", estimate_days_to_read), estimate_end_of_read_date, estimate_days_to_read) }, + + { _("Highlights"), tonumber(highlights) }, + -- { _("Total notes"), tonumber(notes) }, -- not accurate, don't show it } end @@ -1142,11 +1157,21 @@ function ReaderStatistics:getBookStat(id_book) end local conn = SQ3.open(db_location) local sql_stmt = [[ - SELECT title, authors, notes, highlights, pages, last_open + SELECT title, authors, pages, last_open, highlights, notes FROM book WHERE id = '%s' ]] - local title, authors, notes, highlights, pages, last_open = conn:rowexec(string.format(sql_stmt, id_book)) + local title, authors, pages, last_open, highlights, notes = conn:rowexec(string.format(sql_stmt, id_book)) + + -- Due to some bug, some books opened around April 2020 might + -- have notes and highlight NULL in the DB. + -- See: https://github.com/koreader/koreader/issues/6190#issuecomment-633693940 + -- (We made these last in the SQL so NULL/nil doesn't prevent + -- fetching the other fields.) + -- Show "?" when these values are not known (they will be + -- fixed next time this book is opened). + highlights = highlights and tonumber(highlights) or "?" + notes = notes and tonumber(notes) or "?" -- luacheck: no unused sql_stmt = [[ SELECT count(*) @@ -1161,18 +1186,13 @@ function ReaderStatistics:getBookStat(id_book) sql_stmt = [[ SELECT sum(period), - count(DISTINCT page) + count(DISTINCT page), + min(start_time) FROM page_stat WHERE id_book = '%s' ]] - local total_time_book, total_read_pages = conn:rowexec(string.format(sql_stmt, id_book)) + local total_time_book, total_read_pages, first_open = conn:rowexec(string.format(sql_stmt, id_book)) - sql_stmt = [[ - SELECT min(start_time) - FROM page_stat - WHERE id_book = '%s' - ]] - local first_open = conn:rowexec(string.format(sql_stmt, id_book)) conn:close() if total_time_book == nil then @@ -1181,6 +1201,9 @@ function ReaderStatistics:getBookStat(id_book) if total_read_pages == nil then total_read_pages = 0 end + if first_open == nil then + first_open = TimeVal:now().sec + end total_time_book = tonumber(total_time_book) total_read_pages = tonumber(total_read_pages) pages = tonumber(pages) @@ -1191,23 +1214,24 @@ function ReaderStatistics:getBookStat(id_book) return { { _("Title"), title}, { _("Authors"), authors}, - { _("First opened"), os.date("%Y-%m-%d (%H:%M)", tonumber(first_open))}, - { _("Last opened"), os.date("%Y-%m-%d (%H:%M)", tonumber(last_open))}, - { _("Total time"), util.secondsToClock(total_time_book, false) }, - { _("Total highlights"), tonumber(highlights) }, - { _("Total notes"), tonumber(notes) }, - { _("Total days"), tonumber(total_days) }, + { _("Reading started"), os.date("%Y-%m-%d (%H:%M)", tonumber(first_open))}, + { _("Last read"), os.date("%Y-%m-%d (%H:%M)", tonumber(last_open))}, + { _("Days reading this book"), tonumber(total_days) }, + { _("Time spent reading this book"), util.secondsToClock(total_time_book, false) }, + { _("Average time per day"), util.secondsToClock(total_time_book/tonumber(total_days), false) }, { _("Average time per page"), util.secondsToClock(avg_time_per_page, false) }, + -- These 2 ones are about page actually read (not the current page and % into book) { _("Read pages/Total pages"), total_read_pages .. "/" .. pages }, - -- adding 0.5 rounds to nearest integer with math.floor - { _("Percentage completed"), math.floor(total_read_pages / pages * 100 + 0.5) .. "%" }, + { _("Percentage read"), Math.round(total_read_pages / pages * 100) .. "%" }, + { _("Highlights"), highlights }, + -- { _("Total notes"), notes }, -- not accurate, don't show it "----", { _("Show days"), _("Tap to display"), callback = function() local kv = self.kv UIManager:close(self.kv) self.kv = KeyValuePage:new{ - title = _("Read in days"), + title = T(_("Days reading %1"), title), value_overflow_align = "right", kv_pairs = self:getDatesForBook(id_book), callback_return = function() @@ -1286,7 +1310,7 @@ function ReaderStatistics:callbackMonthly(begin, finish, date_text, book_mode) UIManager:close(kv) if book_mode then self.kv = KeyValuePage:new{ - title = T(_("Books from: %1"), date_text), + title = T(_("Books read in %1"), date_text), value_align = "right", kv_pairs = self:getBooksFromPeriod(begin, finish), callback_return = function() @@ -1313,7 +1337,7 @@ function ReaderStatistics:callbackWeekly(begin, finish, date_text, book_mode) UIManager:close(kv) if book_mode then self.kv = KeyValuePage:new{ - title = T(_("Books from: %1"), date_text), + title = T(_("Books read in %1"), date_text), value_align = "right", kv_pairs = self:getBooksFromPeriod(begin, finish), callback_return = function() @@ -1482,7 +1506,7 @@ function ReaderStatistics:getDaysFromPeriod(period_begin, period_end) local kv = self.kv UIManager:close(kv) self.kv = KeyValuePage:new{ - title = T(_("Books in %1"), result_book[1][i]), + title = T(_("Books read %1"), result_book[1][i]), value_overflow_align = "right", kv_pairs = self:getBooksFromPeriod(time_begin, time_begin + 86400), callback_return = function() @@ -1497,7 +1521,7 @@ function ReaderStatistics:getDaysFromPeriod(period_begin, period_end) return results end -function ReaderStatistics:getBooksFromPeriod(period_begin, period_end) +function ReaderStatistics:getBooksFromPeriod(period_begin, period_end, callback_shows_days) local results = {} local sql_stmt_res_book = [[ SELECT book_tbl.title AS title, @@ -1522,15 +1546,27 @@ function ReaderStatistics:getBooksFromPeriod(period_begin, period_end) callback = function() local kv = self.kv UIManager:close(self.kv) - self.kv = KeyValuePage:new{ - title = _("Read in days"), - value_overflow_align = "right", - kv_pairs = self:getDatesForBook(tonumber(result_book[4][i])), - callback_return = function() - UIManager:show(kv) - self.kv = kv - end - } + if callback_shows_days then -- not used currently by any code + self.kv = KeyValuePage:new{ + title = T(_("Days reading %1"), result_book[1][i]), + kv_pairs = self:getDatesForBook(tonumber(result_book[4][i])), + value_overflow_align = "right", + callback_return = function() + UIManager:show(kv) + self.kv = kv + end + } + else + self.kv = KeyValuePage:new{ + title = result_book[1][i], + kv_pairs = self:getBookStat(tonumber(result_book[4][i])), + value_overflow_align = "right", + callback_return = function() + UIManager:show(kv) + self.kv = kv + end + } + end UIManager:show(self.kv) end, }) @@ -1659,8 +1695,8 @@ function ReaderStatistics:getTotalStats() self.kv = KeyValuePage:new{ title = book_title, - value_overflow_align = "right", kv_pairs = self:getBookStat(id_book), + value_overflow_align = "right", callback_return = function() UIManager:show(kv) self.kv = kv @@ -1671,7 +1707,7 @@ function ReaderStatistics:getTotalStats() }) end conn:close() - return T(_("Total hours read %1"), util.secondsToClock(total_books_time, false)), total_stats + return T(_("Total time spent reading: %1"), util.secondsToClock(total_books_time, false)), total_stats end function ReaderStatistics:genResetBookSubItemTable()