@ -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 = _ ( " S tatistics" ) ,
title = _ ( " Current s tatistics" ) ,
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 = _ ( " S tatistics" ) ,
title = _ ( " Current s tatistics" ) ,
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, " dail y" ) ,
kv_pairs = self : getDatesFromAll ( 7, " daily_weekda y" ) ,
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 ( 3 65, " weekl y" ) ,
kv_pairs = self : getDatesFromAll ( 3 0, " daily_weekda y" ) ,
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, " month ly" ) ,
kv_pairs = self : getDatesFromAll ( 365, " dai ly" ) ,
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 ,
} ,
{ _ ( " Book s by month" ) , " " ,
{ _ ( " All stat s by month" ) , " " ,
callback = function ( )
local kv = self.kv
UIManager : close ( self.kv )
self.kv = KeyValuePage : new {
title = _ ( " Book s by month" ) ,
title = _ ( " All stat s 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, note s = 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 ( )