mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Statistics: revamp settings, add calendar settings (#5867)
Also adds options to remove statistics for books with low reading duration (to clean up stats for books just quickly browsed and not yet read). Also adds TextWidget:getFontSizeToFitHeight() even if we ended up not using it.
This commit is contained in:
@@ -83,6 +83,8 @@ function DoubleSpinWidget:update()
|
||||
value = self.left_value,
|
||||
value_min = self.left_min,
|
||||
value_max = self.left_max,
|
||||
value_step = self.left_step,
|
||||
value_hold_step = self.left_hold_step,
|
||||
wrap = false,
|
||||
update_callback = function() picker_update_callback() end,
|
||||
}
|
||||
@@ -92,6 +94,8 @@ function DoubleSpinWidget:update()
|
||||
value = self.right_value,
|
||||
value_min = self.right_min,
|
||||
value_max = self.right_max,
|
||||
value_step = self.right_step,
|
||||
value_hold_step = self.right_hold_step,
|
||||
wrap = false,
|
||||
update_callback = function() picker_update_callback() end,
|
||||
}
|
||||
|
||||
@@ -56,6 +56,29 @@ local TextWidget = Widget:new{
|
||||
_xshaping = nil,
|
||||
}
|
||||
|
||||
-- Helper function to be used before instantiating a TextWidget instance
|
||||
-- (This is more precise than the one with the same name in TextBoxWidget,
|
||||
-- as we use the real font metrics.)
|
||||
function TextWidget:getFontSizeToFitHeight(font_name, height_px, padding)
|
||||
-- Get a font size that would fit the text in height_px.
|
||||
if not padding then
|
||||
padding = self.padding -- (TextWidget default above: Size.padding.small)
|
||||
end
|
||||
-- We need to iterate (skip 1 early as font_size is always smaller
|
||||
-- than font height)
|
||||
local font_size = height_px
|
||||
repeat
|
||||
font_size = font_size - 1
|
||||
if font_size <= 1 then
|
||||
break
|
||||
end
|
||||
local face = Font:getFace(font_name, font_size)
|
||||
local face_height = face.ftface:getHeightAndAscender()
|
||||
face_height = math.ceil(face_height) + 2*padding
|
||||
until face_height <= height_px
|
||||
return font_size
|
||||
end
|
||||
|
||||
function TextWidget:updateSize()
|
||||
if self._updated then
|
||||
return
|
||||
|
||||
@@ -115,8 +115,8 @@ local CalendarDay = InputContainer:new{
|
||||
is_future = false,
|
||||
font_face = "xx_smallinfofont",
|
||||
font_size = nil,
|
||||
show_histo = true,
|
||||
histo_height = nil,
|
||||
nb_not_shown = nil,
|
||||
}
|
||||
|
||||
function CalendarDay:init()
|
||||
@@ -154,15 +154,20 @@ function CalendarDay:init()
|
||||
}
|
||||
local inner_w = self.width - 2*self.border
|
||||
local inner_h = self.height - 2*self.border
|
||||
if not self.histo_height then
|
||||
self.histo_height = inner_h / 3
|
||||
if self.show_histo then
|
||||
if not self.histo_height then
|
||||
self.histo_height = inner_h / 3
|
||||
end
|
||||
self.histo_w = BottomContainer:new{
|
||||
dimen = Geom:new{w = inner_w, h = inner_h},
|
||||
HistogramWidget:new{
|
||||
width = inner_w,
|
||||
height = self.histo_height,
|
||||
nb_items = 24,
|
||||
ratios = self.ratio_per_hour,
|
||||
}
|
||||
}
|
||||
end
|
||||
self.histo_w = HistogramWidget:new{
|
||||
width = inner_w,
|
||||
height = self.histo_height,
|
||||
nb_items = 24,
|
||||
ratios = self.ratio_per_hour,
|
||||
}
|
||||
self[1] = FrameContainer:new{
|
||||
padding = 0,
|
||||
color = self.is_future and Blitbuffer.COLOR_GRAY or Blitbuffer.COLOR_BLACK,
|
||||
@@ -170,12 +175,10 @@ function CalendarDay:init()
|
||||
width = self.width,
|
||||
height = self.height,
|
||||
OverlapGroup:new{
|
||||
dimen = { w = inner_w },
|
||||
self.daynum_w,
|
||||
self.nb_not_shown_w,
|
||||
BottomContainer:new{
|
||||
dimen = Geom:new{w = inner_w, h = inner_h},
|
||||
self.histo_w,
|
||||
},
|
||||
self.histo_w, -- nil if not show_histo
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -203,6 +206,7 @@ local CalendarWeek = InputContainer:new{
|
||||
day_padding = 0,
|
||||
day_border = 0,
|
||||
nb_book_spans = 0,
|
||||
histo_shown = nil,
|
||||
span_height = nil,
|
||||
font_size = 0,
|
||||
font_face = "xx_smallinfofont",
|
||||
@@ -299,17 +303,50 @@ function CalendarWeek:update()
|
||||
-- Create and add BookSpans
|
||||
local bspan_margin_h = Size.margin.tiny + self.day_border
|
||||
local bspan_margin_v = Size.margin.tiny
|
||||
local bspan_padding = Size.padding.tiny -- only used to compute the adequate font size
|
||||
local bspan_padding_h = Size.padding.tiny
|
||||
local bspan_border = Size.border.thin
|
||||
local text_height = self.span_height - 2 * (bspan_margin_v + bspan_border + bspan_padding)
|
||||
local inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0)
|
||||
|
||||
-- We need a smaller font size than the one provided
|
||||
local text_height = self.span_height - 2 * (bspan_margin_v + bspan_border)
|
||||
-- We don't use any bspan_padding_v, we let that be handled by CenterContainer
|
||||
-- and choosing an appropriate font size.
|
||||
-- We use TextBoxWidget:getFontSizeToFitHeight() to get a fitting
|
||||
-- font size. It's less precise than the TextWidget equivalent,
|
||||
-- but it handles padding as 'em'.
|
||||
-- Use a 1.3em line height
|
||||
local inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.3)
|
||||
-- If font size gets really small, get a larger one by using a smaller
|
||||
-- line height: tall glyphs may bleed on the border, but we won't notice
|
||||
-- at such small size, and we'll appreciate the readability.
|
||||
-- (threshold values decided from visual testing)
|
||||
if inner_font_size <= 12 then
|
||||
inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.1)
|
||||
elseif inner_font_size <= 15 then
|
||||
inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.2)
|
||||
end
|
||||
-- But cap it to the day num font size
|
||||
inner_font_size = math.min(inner_font_size, self.font_size)
|
||||
|
||||
local offset_y_fixup
|
||||
if self.histo_shown then
|
||||
-- No real y positionning needed, but push it a bit down
|
||||
-- over histogram, as histograms rarely reach 100%, and
|
||||
-- will be drawn last, so possibly over last book span if
|
||||
-- really near 100%
|
||||
offset_y_fixup = Size.margin.small
|
||||
else
|
||||
-- No histogram: ensure last book span bottom margin
|
||||
-- is equal to bspan_margin_v for a nice fit
|
||||
offset_y_fixup = self.height - self.span_height * (self.nb_book_spans + 1) - bspan_margin_v
|
||||
end
|
||||
|
||||
for col, day_books in ipairs(self.days_books) do
|
||||
for row, book in ipairs(day_books) do
|
||||
if book and book.start_day == col then
|
||||
local fgcolor, bgcolor = unpack(SPAN_COLORS[(book.id % #SPAN_COLORS)+1])
|
||||
local offset_x = (col-1) * (self.day_width + self.day_padding)
|
||||
local offset_y = row * self.span_height -- 1st real row used by day num
|
||||
offset_y = offset_y + Size.margin.small -- push it a bit down, over histogram
|
||||
offset_y = offset_y + offset_y_fixup
|
||||
local width = book.span_days * self.day_width + self.day_padding * (book.span_days-1)
|
||||
-- We use two FrameContainers, as (unlike HTML) a FrameContainer
|
||||
-- draws the background color outside its borders, in the margins
|
||||
@@ -337,7 +374,7 @@ function CalendarWeek:update()
|
||||
},
|
||||
TextWidget:new{
|
||||
text = BD.auto(book.title),
|
||||
max_width = width - 2 * (bspan_margin_h + bspan_border + bspan_padding),
|
||||
max_width = width - 2 * (bspan_margin_h + bspan_border + bspan_padding_h),
|
||||
face = Font:getFace(self.font_face, inner_font_size),
|
||||
padding = 0,
|
||||
fgcolor = fgcolor,
|
||||
@@ -363,16 +400,19 @@ local CalendarView = InputContainer:new{
|
||||
reader_statistics = nil,
|
||||
monthTranslation = nil,
|
||||
shortDayOfWeekTranslation = nil,
|
||||
startDayOfWeek = 2, -- 2 = Monday, 1-7 = Sunday-Saturday
|
||||
nbFutureMonths = 0, -- nb of future months to allow browsing
|
||||
longDayOfWeekTranslation = nil,
|
||||
start_day_of_week = 2, -- 2 = Monday, 1-7 = Sunday-Saturday
|
||||
show_hourly_histogram = true,
|
||||
browse_future_months = false,
|
||||
nb_book_spans = 3,
|
||||
font_face = "xx_smallinfofont",
|
||||
title = "",
|
||||
width = nil,
|
||||
height = nil,
|
||||
cur_month = nil,
|
||||
weekdays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } -- in Lua wday order
|
||||
-- (These do not need translations: they are the key into
|
||||
-- the provided self.shortDayOfWeekTranslation)
|
||||
-- (These do not need translations: they are the key into the provided
|
||||
-- self.shortDayOfWeekTranslation and self.longDayOfWeekTranslation)
|
||||
}
|
||||
|
||||
function CalendarView:init()
|
||||
@@ -419,15 +459,6 @@ function CalendarView:init()
|
||||
if not self.cur_month then
|
||||
self.cur_month = self.max_month
|
||||
end
|
||||
if self.nbFutureMonths > 0 then
|
||||
local t = os.time({
|
||||
year = self.max_month:sub(1,4),
|
||||
month = self.max_month:sub(6),
|
||||
day = 15,
|
||||
})
|
||||
t = t + 86400 * 30 * self.nbFutureMonths
|
||||
self.max_month = os.date("%Y-%m", t)
|
||||
end
|
||||
|
||||
-- group for page info
|
||||
local chevron_left = "resources/icons/appbar.chevron.left.png"
|
||||
@@ -522,7 +553,7 @@ function CalendarView:init()
|
||||
self.day_names = HorizontalGroup:new{}
|
||||
for i = 0, 6 do
|
||||
local dayname = TextWidget:new{
|
||||
text = self.shortDayOfWeekTranslation[self.weekdays[(self.startDayOfWeek-1+i)%7 + 1]],
|
||||
text = self.shortDayOfWeekTranslation[self.weekdays[(self.start_day_of_week-1+i)%7 + 1]],
|
||||
face = Font:getFace("xx_smallinfofont"),
|
||||
bold = true,
|
||||
}
|
||||
@@ -544,9 +575,32 @@ function CalendarView:init()
|
||||
- self.page_info:getSize().h - self.day_names:getSize().h
|
||||
self.week_height = math.floor((available_height - 5*self.inner_padding) / 6)
|
||||
self.day_border = Size.border.default
|
||||
-- day num + nb_book_spans + histogram
|
||||
self.span_height = math.ceil((self.week_height - 2*self.day_border) / (self.nb_book_spans + 2))
|
||||
self.span_font_size = TextBoxWidget:getFontSizeToFitHeight(self.span_height, 1, 0.3)
|
||||
if self.show_hourly_histogram then
|
||||
-- day num + nb_book_spans + histogram: ceil() as histogram rarely
|
||||
-- reaches 100% and is stuck to bottom
|
||||
self.span_height = math.ceil((self.week_height - 2*self.day_border) / (self.nb_book_spans+2))
|
||||
else
|
||||
-- day num + nb_book_span: floor() to get some room for bottom padding
|
||||
self.span_height = math.floor((self.week_height - 2*self.day_border) / (self.nb_book_spans+1))
|
||||
end
|
||||
-- Limit font size to 1/3 of available height, and so that
|
||||
-- the day number and the +nb-not-shown do not overlap
|
||||
local text_height = math.min(self.span_height, self.week_height/3)
|
||||
self.span_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.3)
|
||||
local day_inner_width = self.day_width - 2*self.day_border -2*self.inner_padding
|
||||
while true do
|
||||
local test_w = TextWidget:new{
|
||||
text = " 30 + 99 ", -- we want this to be displayed in the available width
|
||||
face = Font:getFace(self.font_face, self.span_font_size),
|
||||
bold = true,
|
||||
}
|
||||
if test_w:getWidth() <= day_inner_width then
|
||||
test_w:free()
|
||||
break
|
||||
end
|
||||
self.span_font_size = self.span_font_size - 1
|
||||
test_w:free()
|
||||
end
|
||||
|
||||
self.main_content = VerticalGroup:new{}
|
||||
self:_populateItems()
|
||||
@@ -593,9 +647,9 @@ function CalendarView:_populateItems()
|
||||
-- Update footer
|
||||
self.page_info_text:setText(self.cur_month)
|
||||
self.page_info_left_chev:enableDisable(self.cur_month > self.min_month)
|
||||
self.page_info_right_chev:enableDisable(self.cur_month < self.max_month)
|
||||
self.page_info_right_chev:enableDisable(self.cur_month < self.max_month or self.browse_future_months)
|
||||
self.page_info_first_chev:enableDisable(self.cur_month > self.min_month)
|
||||
self.page_info_last_chev:enableDisable(self.cur_month < self.max_month)
|
||||
self.page_info_last_chev:enableDisable(self.cur_month < self.max_month or self.browse_future_months)
|
||||
|
||||
local ratio_per_hour_by_day = self.reader_statistics:getReadingRatioPerHourByDay(self.cur_month)
|
||||
local books_by_day = self.reader_statistics:getReadBookByDay(self.cur_month)
|
||||
@@ -612,7 +666,7 @@ function CalendarView:_populateItems()
|
||||
if cur_date.month ~= this_month then
|
||||
break
|
||||
end
|
||||
if not cur_week or cur_date.wday == self.startDayOfWeek then
|
||||
if not cur_week or cur_date.wday == self.start_day_of_week then
|
||||
if cur_week then
|
||||
table.insert(self.main_content, VerticalSpan:new{ width = self.inner_padding })
|
||||
end
|
||||
@@ -623,15 +677,17 @@ function CalendarView:_populateItems()
|
||||
day_padding = self.inner_padding,
|
||||
day_border = self.day_border,
|
||||
nb_book_spans = self.nb_book_spans,
|
||||
histo_shown = self.show_hourly_histogram,
|
||||
span_height = self.span_height,
|
||||
font_face = self.font_face,
|
||||
font_size = self.span_font_size,
|
||||
show_parent = self,
|
||||
}
|
||||
table.insert(self.weeks, cur_week)
|
||||
table.insert(self.main_content, cur_week)
|
||||
if cur_date.wday ~= self.startDayOfWeek then
|
||||
if cur_date.wday ~= self.start_day_of_week then
|
||||
-- Add fake days to fill week
|
||||
local day = self.startDayOfWeek
|
||||
local day = self.start_day_of_week
|
||||
while day ~= cur_date.wday do
|
||||
cur_week:addDay(CalendarDay:new{
|
||||
filler = true,
|
||||
@@ -649,25 +705,28 @@ function CalendarView:_populateItems()
|
||||
end
|
||||
local day_s = os.date("%Y-%m-%d", cur_ts)
|
||||
local day_text = string.format("%s (%s)", day_s,
|
||||
self.shortDayOfWeekTranslation[self.weekdays[cur_date.wday]])
|
||||
self.longDayOfWeekTranslation[self.weekdays[cur_date.wday]])
|
||||
local day_ts = os.time({
|
||||
year = cur_date.year,
|
||||
month = cur_date.month,
|
||||
day = cur_date.day,
|
||||
hour = 0,
|
||||
})
|
||||
local is_future = day_s > today_s
|
||||
cur_week:addDay(CalendarDay:new{
|
||||
show_histo = self.show_hourly_histogram,
|
||||
histo_height = self.span_height,
|
||||
font_face = self.font_face,
|
||||
font_size = self.span_font_size,
|
||||
border = self.day_border,
|
||||
is_future = day_s > today_s,
|
||||
is_future = is_future,
|
||||
daynum = cur_date.day,
|
||||
height = self.week_height,
|
||||
width = self.day_width,
|
||||
ratio_per_hour = ratio_per_hour_by_day[day_s],
|
||||
read_books = books_by_day[day_s],
|
||||
show_parent = self,
|
||||
callback = function()
|
||||
callback = not is_future and function()
|
||||
-- Just as ReaderStatistics:callbackDaily(), but without any window stacking
|
||||
UIManager:show(KeyValuePage:new{
|
||||
title = day_text,
|
||||
@@ -696,7 +755,7 @@ function CalendarView:nextMonth()
|
||||
})
|
||||
t = t + 86400 * 30 -- 30 days later
|
||||
local next_month = os.date("%Y-%m", t)
|
||||
if next_month <= self.max_month then
|
||||
if self.browse_future_months or next_month <= self.max_month then
|
||||
self.cur_month = next_month
|
||||
self:_populateItems()
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@ local Device = require("device")
|
||||
local DocSettings = require("docsettings")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local KeyValuePage = require("ui/widget/keyvaluepage")
|
||||
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
||||
local ReaderFooter = require("apps/reader/modules/readerfooter")
|
||||
local ReaderProgress = require("readerprogress")
|
||||
local ReadHistory = require("readhistory")
|
||||
@@ -29,18 +28,24 @@ local db_location = DataStorage:getSettingsDir() .. "/statistics.sqlite3"
|
||||
local PAGE_INSERT = 50
|
||||
local DEFAULT_MIN_READ_SEC = 5
|
||||
local DEFAULT_MAX_READ_SEC = 120
|
||||
local DEFAULT_CALENDAR_START_DAY_OF_WEEK = 2 -- Monday
|
||||
local DEFAULT_CALENDAR_NB_BOOK_SPANS = 3
|
||||
|
||||
local ReaderStatistics = Widget:extend{
|
||||
name = "statistics",
|
||||
page_min_read_sec = DEFAULT_MIN_READ_SEC,
|
||||
page_max_read_sec = DEFAULT_MAX_READ_SEC,
|
||||
calendar_start_day_of_week = DEFAULT_CALENDAR_START_DAY_OF_WEEK,
|
||||
calendar_nb_book_spans = DEFAULT_CALENDAR_NB_BOOK_SPANS,
|
||||
calendar_show_histogram = true,
|
||||
calendar_browse_future_months = false,
|
||||
start_current_period = 0,
|
||||
curr_page = 0,
|
||||
id_curr_book = nil,
|
||||
curr_total_time = 0,
|
||||
curr_total_pages = 0,
|
||||
is_enabled = nil,
|
||||
convert_to_db = nil,
|
||||
convert_to_db = nil, -- true when migration to DB has been done
|
||||
total_read_pages = 0,
|
||||
total_read_time = 0,
|
||||
avg_time = nil,
|
||||
@@ -59,6 +64,8 @@ local ReaderStatistics = Widget:extend{
|
||||
},
|
||||
}
|
||||
|
||||
local weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } -- in Lua wday order
|
||||
|
||||
local shortDayOfWeekTranslation = {
|
||||
["Mon"] = _("Mon"),
|
||||
["Tue"] = _("Tue"),
|
||||
@@ -69,6 +76,16 @@ local shortDayOfWeekTranslation = {
|
||||
["Sun"] = _("Sun"),
|
||||
}
|
||||
|
||||
local longDayOfWeekTranslation = {
|
||||
["Mon"] = _("Monday"),
|
||||
["Tue"] = _("Tuesday"),
|
||||
["Wed"] = _("Wednesday"),
|
||||
["Thu"] = _("Thursday"),
|
||||
["Fri"] = _("Friday"),
|
||||
["Sat"] = _("Saturday"),
|
||||
["Sun"] = _("Sunday"),
|
||||
}
|
||||
|
||||
local monthTranslation = {
|
||||
["January"] = _("January"),
|
||||
["February"] = _("February"),
|
||||
@@ -97,6 +114,10 @@ function ReaderStatistics:init()
|
||||
local settings = G_reader_settings:readSetting("statistics") or {}
|
||||
self.page_min_read_sec = tonumber(settings.min_sec)
|
||||
self.page_max_read_sec = tonumber(settings.max_sec)
|
||||
self.calendar_start_day_of_week = settings.calendar_start_day_of_week
|
||||
self.calendar_nb_book_spans = settings.calendar_nb_book_spans
|
||||
self.calendar_show_histogram = settings.calendar_show_histogram
|
||||
self.calendar_browse_future_months = settings.calendar_browse_future_months
|
||||
self.is_enabled = not (settings.is_enabled == false)
|
||||
self.convert_to_db = settings.convert_to_db
|
||||
self.ui.menu:registerToMainMenu(self)
|
||||
@@ -228,7 +249,7 @@ end
|
||||
|
||||
function ReaderStatistics:checkInitDatabase()
|
||||
local conn = SQ3.open(db_location)
|
||||
if self.convert_to_db then -- if conversion to sqlite was doing earlier
|
||||
if self.convert_to_db then -- if conversion to sqlite DB has already been done
|
||||
if not conn:exec("pragma table_info('book');") then
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = T(_([[
|
||||
@@ -251,7 +272,7 @@ Do you want to create an empty database?
|
||||
end,
|
||||
})
|
||||
end
|
||||
else -- first time convertion to sqlite database
|
||||
else -- Migrate stats for books in history from metadata.lua to sqlite database
|
||||
self.convert_to_db = true
|
||||
if not conn:exec("pragma table_info('book');") then
|
||||
local filename_first_history, quickstart_filename, __
|
||||
@@ -671,48 +692,6 @@ function ReaderStatistics:getStatisticEnabledMenuItem()
|
||||
}
|
||||
end
|
||||
|
||||
function ReaderStatistics:updateSettings()
|
||||
self.settings_dialog = MultiInputDialog:new {
|
||||
title = _("Statistics settings"),
|
||||
fields = {
|
||||
{
|
||||
text = self.page_min_read_sec,
|
||||
description = T(_("Min seconds, default is %1"), DEFAULT_MIN_READ_SEC),
|
||||
input_type = "number",
|
||||
},
|
||||
{
|
||||
text = self.page_max_read_sec,
|
||||
description = T(_("Max seconds, default is %1"), DEFAULT_MAX_READ_SEC),
|
||||
input_type = "number",
|
||||
},
|
||||
},
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
callback = function()
|
||||
self.settings_dialog:onClose()
|
||||
UIManager:close(self.settings_dialog)
|
||||
end
|
||||
},
|
||||
{
|
||||
text = _("Apply"),
|
||||
callback = function()
|
||||
self:saveSettings(MultiInputDialog:getFields())
|
||||
self.settings_dialog:onClose()
|
||||
UIManager:close(self.settings_dialog)
|
||||
end
|
||||
},
|
||||
},
|
||||
},
|
||||
width = Screen:getWidth() * 0.95,
|
||||
height = Screen:getHeight() * 0.2,
|
||||
input_type = "number",
|
||||
}
|
||||
UIManager:show(self.settings_dialog)
|
||||
self.settings_dialog:onShowKeyboard()
|
||||
end
|
||||
|
||||
function ReaderStatistics:addToMainMenu(menu_items)
|
||||
menu_items.statistics = {
|
||||
text = _("Reading statistics"),
|
||||
@@ -720,15 +699,130 @@ function ReaderStatistics:addToMainMenu(menu_items)
|
||||
self:getStatisticEnabledMenuItem(),
|
||||
{
|
||||
text = _("Settings"),
|
||||
keep_menu_open = true,
|
||||
callback = function() self:updateSettings() end,
|
||||
sub_item_table = {
|
||||
{
|
||||
text_func = function()
|
||||
return T(_("Read page duration limits: %1 s / %2 s"),
|
||||
self.page_min_read_sec, self.page_max_read_sec)
|
||||
end,
|
||||
callback = function(touchmenu_instance)
|
||||
local DoubleSpinWidget = require("/ui/widget/doublespinwidget")
|
||||
local durations_widget
|
||||
durations_widget = DoubleSpinWidget:new{
|
||||
left_text = _("Min"),
|
||||
left_value = self.page_min_read_sec,
|
||||
left_default = DEFAULT_MIN_READ_SEC,
|
||||
left_min = 3,
|
||||
left_max = 120,
|
||||
left_step = 1,
|
||||
left_hold_step = 10,
|
||||
right_text = _("Max"),
|
||||
right_value = self.page_max_read_sec,
|
||||
right_default = DEFAULT_MAX_READ_SEC,
|
||||
right_min = 10,
|
||||
right_max = 7200,
|
||||
right_step = 10,
|
||||
right_hold_step = 60,
|
||||
default_values = true,
|
||||
default_text = _("Use defaults"),
|
||||
title_text = _("Read page duration limits"),
|
||||
info_text = _([[
|
||||
Set min and max time spent (in seconds) on a page for it to be counted as read in statistics.
|
||||
The min value ensures pages you quickly browse and skip are not included.
|
||||
The max value ensures a page you stay on for a long time (because you fell asleep or went away) will be included, but with a duration capped to this specified max value.]]),
|
||||
callback = function(min, max)
|
||||
if not min then min = DEFAULT_MIN_READ_SEC end
|
||||
if not max then max = DEFAULT_MAX_READ_SEC end
|
||||
if min > max then
|
||||
min, max = max, min
|
||||
end
|
||||
self.page_min_read_sec = min
|
||||
self.page_max_read_sec = max
|
||||
self:saveSettings()
|
||||
UIManager:close(durations_widget)
|
||||
touchmenu_instance:updateItems()
|
||||
end,
|
||||
}
|
||||
UIManager:show(durations_widget)
|
||||
end,
|
||||
keep_menu_open = true,
|
||||
separator = true,
|
||||
},
|
||||
{
|
||||
text_func = function()
|
||||
return T(_("Calendar weeks start on %1"),
|
||||
longDayOfWeekTranslation[weekDays[self.calendar_start_day_of_week]])
|
||||
end,
|
||||
sub_item_table = {
|
||||
{ -- Friday (Bangladesh and Maldives)
|
||||
text = longDayOfWeekTranslation[weekDays[6]],
|
||||
checked_func = function() return self.calendar_start_day_of_week == 6 end,
|
||||
callback = function() self.calendar_start_day_of_week = 6 end
|
||||
},
|
||||
{ -- Saturday (some Middle East countries)
|
||||
text = longDayOfWeekTranslation[weekDays[7]],
|
||||
checked_func = function() return self.calendar_start_day_of_week == 7 end,
|
||||
callback = function() self.calendar_start_day_of_week = 7 end
|
||||
},
|
||||
{ -- Sunday
|
||||
text = longDayOfWeekTranslation[weekDays[1]],
|
||||
checked_func = function() return self.calendar_start_day_of_week == 1 end,
|
||||
callback = function() self.calendar_start_day_of_week = 1 end
|
||||
},
|
||||
{ -- Monday
|
||||
text = longDayOfWeekTranslation[weekDays[2]],
|
||||
checked_func = function() return self.calendar_start_day_of_week == 2 end,
|
||||
callback = function() self.calendar_start_day_of_week = 2 end
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
text_func = function()
|
||||
return T(_("Books per calendar day: %1"), self.calendar_nb_book_spans)
|
||||
end,
|
||||
callback = function(touchmenu_instance)
|
||||
local SpinWidget = require("ui/widget/spinwidget")
|
||||
UIManager:show(SpinWidget:new{
|
||||
width = Screen:getWidth() * 0.6,
|
||||
value = self.calendar_nb_book_spans,
|
||||
value_min = 1,
|
||||
value_max = 5,
|
||||
ok_text = _("Set"),
|
||||
title_text = _("Books per calendar day"),
|
||||
text = _("Set the max number of book spans to show for a day"),
|
||||
callback = function(spin)
|
||||
self.calendar_nb_book_spans = spin.value
|
||||
touchmenu_instance:updateItems()
|
||||
end,
|
||||
extra_text = _("Use default"),
|
||||
extra_callback = function()
|
||||
self.calendar_nb_book_spans = DEFAULT_CALENDAR_NB_BOOK_SPANS
|
||||
touchmenu_instance:updateItems()
|
||||
end
|
||||
})
|
||||
end,
|
||||
keep_menu_open = true,
|
||||
},
|
||||
{
|
||||
text = _("Show hourly histogram in calendar days"),
|
||||
checked_func = function() return self.calendar_show_histogram end,
|
||||
callback = function()
|
||||
self.calendar_show_histogram = not self.calendar_show_histogram
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Allow browsing coming months"),
|
||||
checked_func = function() return self.calendar_browse_future_months end,
|
||||
callback = function()
|
||||
self.calendar_browse_future_months = not self.calendar_browse_future_months
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
text = _("Reset book statistics"),
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
self:resetBook()
|
||||
end
|
||||
text = _("Reset statistics"),
|
||||
sub_item_table = self:genResetBookSubItemTable(),
|
||||
separator = true,
|
||||
},
|
||||
{
|
||||
text = _("Current book"),
|
||||
@@ -779,6 +873,11 @@ function ReaderStatistics:addToMainMenu(menu_items)
|
||||
reader_statistics = self,
|
||||
monthTranslation = monthTranslation,
|
||||
shortDayOfWeekTranslation = shortDayOfWeekTranslation,
|
||||
longDayOfWeekTranslation = longDayOfWeekTranslation,
|
||||
start_day_of_week = self.calendar_start_day_of_week,
|
||||
nb_book_spans = self.calendar_nb_book_spans,
|
||||
show_hourly_histogram = self.calendar_show_histogram,
|
||||
browse_future_months = self.calendar_browse_future_months,
|
||||
})
|
||||
end,
|
||||
},
|
||||
@@ -1580,6 +1679,32 @@ function ReaderStatistics:getTotalStats()
|
||||
return T(_("Total hours read %1"), util.secondsToClock(total_books_time, false)), total_stats
|
||||
end
|
||||
|
||||
function ReaderStatistics:genResetBookSubItemTable()
|
||||
local sub_item_table = {}
|
||||
table.insert(sub_item_table, {
|
||||
text = _("Reset statistics per book"),
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
self:resetBook()
|
||||
end,
|
||||
separator = true,
|
||||
})
|
||||
local reset_minutes = { 1, 5, 15, 30, 60 }
|
||||
for _, minutes in ipairs(reset_minutes) do
|
||||
local text = T(N_("Reset stats for books read for < 1 m",
|
||||
"Reset stats for books read for < %1 m",
|
||||
minutes), minutes)
|
||||
table.insert(sub_item_table, {
|
||||
text = text,
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
self:deleteBooksByTotalDuration(minutes)
|
||||
end,
|
||||
})
|
||||
end
|
||||
return sub_item_table
|
||||
end
|
||||
|
||||
function ReaderStatistics:resetBook()
|
||||
local total_stats = {}
|
||||
local kv_reset_book
|
||||
@@ -1675,6 +1800,56 @@ function ReaderStatistics:deleteBook(id_book)
|
||||
conn:close()
|
||||
end
|
||||
|
||||
function ReaderStatistics:deleteBooksByTotalDuration(max_total_duration_mn)
|
||||
local max_total_duration_sec = max_total_duration_mn * 60
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = T(N_("Permanently remove statistics for books read for less than 1 minute?",
|
||||
"Permanently remove statistics for books read for less than %1 minutes?",
|
||||
max_total_duration_mn), max_total_duration_mn),
|
||||
ok_text = _("Remove"),
|
||||
ok_callback = function()
|
||||
local conn = SQ3.open(db_location)
|
||||
local sql_stmt = [[
|
||||
DELETE from page_stat
|
||||
WHERE id_book in (
|
||||
select id from book where id != ? and (total_read_time is NULL or total_read_time < ?)
|
||||
)
|
||||
]]
|
||||
local stmt = conn:prepare(sql_stmt)
|
||||
stmt:reset():bind(self.id_curr_book, max_total_duration_sec):step()
|
||||
sql_stmt = [[
|
||||
DELETE from book
|
||||
WHERE id != ? and (total_read_time is NULL or total_read_time < ?)
|
||||
]]
|
||||
stmt = conn:prepare(sql_stmt)
|
||||
stmt:reset():bind(self.id_curr_book, max_total_duration_sec):step()
|
||||
stmt:close()
|
||||
-- Get nb of deleted books
|
||||
sql_stmt = [[
|
||||
SELECT changes()
|
||||
]]
|
||||
local nb_deleted = conn:rowexec(sql_stmt)
|
||||
nb_deleted = nb_deleted and tonumber(nb_deleted) or 0
|
||||
if max_total_duration_mn >= 30 and nb_deleted >= 10 then
|
||||
-- Do a VACUUM to reduce db size (but not worth doing if not much was removed)
|
||||
conn:exec("PRAGMA temp_store = 2") -- use memory for temp files
|
||||
local ok, errmsg = pcall(conn.exec, conn, "VACUUM") -- this may take some time
|
||||
if not ok then
|
||||
logger.warn("Failed compacting statistics database:", errmsg)
|
||||
end
|
||||
end
|
||||
conn:close()
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = nb_deleted > 0 and T(N_("Statistics for 1 book removed.",
|
||||
"Statistics for %1 books removed.",
|
||||
nb_deleted), nb_deleted)
|
||||
or T(_("No statistics removed."))
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
function ReaderStatistics:onPosUpdate(pos, pageno)
|
||||
if self.curr_page ~= pageno then
|
||||
self:onPageUpdate(pageno)
|
||||
@@ -1777,17 +1952,16 @@ function ReaderStatistics:onResume()
|
||||
self.pages_stats[self.start_current_period] = self.curr_page
|
||||
end
|
||||
|
||||
function ReaderStatistics:saveSettings(fields)
|
||||
if fields then
|
||||
self.page_min_read_sec = tonumber(fields[1])
|
||||
self.page_max_read_sec = tonumber(fields[2])
|
||||
end
|
||||
|
||||
function ReaderStatistics:saveSettings()
|
||||
local settings = {
|
||||
min_sec = self.page_min_read_sec,
|
||||
max_sec = self.page_max_read_sec,
|
||||
is_enabled = self.is_enabled,
|
||||
convert_to_db = self.convert_to_db
|
||||
convert_to_db = self.convert_to_db,
|
||||
calendar_start_day_of_week = self.calendar_start_day_of_week,
|
||||
calendar_nb_book_spans = self.calendar_nb_book_spans,
|
||||
calendar_show_histogram = self.calendar_show_histogram,
|
||||
calendar_browse_future_months = self.calendar_browse_future_months,
|
||||
}
|
||||
G_reader_settings:saveSetting("statistics", settings)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user