diff --git a/frontend/apps/reader/modules/readerstatus.lua b/frontend/apps/reader/modules/readerstatus.lua
new file mode 100644
index 000000000..580203bba
--- /dev/null
+++ b/frontend/apps/reader/modules/readerstatus.lua
@@ -0,0 +1,66 @@
+local InputContainer = require("ui/widget/container/inputcontainer")
+local StatusWidget = require("ui/widget/statuswidget")
+
+local UIManager = require("ui/uimanager")
+local _ = require("gettext")
+
+local ReaderStatus = InputContainer:new {
+ document = nil,
+ summary = {
+ rating = 0,
+ note = nil,
+ status = "",
+ modified = "",
+ },
+ enabled = true,
+ total_pages = 0
+}
+
+function ReaderStatus:init()
+ if self.ui.document.is_djvu or self.ui.document.is_pdf or self.ui.document.is_pic then
+ self.enabled = false
+ return
+ end
+ self.total_pages = self.document:getPageCount()
+ UIManager:scheduleIn(0.1, function() self.ui.menu:registerToMainMenu(self) end)
+end
+
+function ReaderStatus:addToMainMenu(tab_item_table)
+ table.insert(tab_item_table.typeset, {
+ text = _("Status"),
+ callback = function()
+ self:showStatus()
+ UIManager:setDirty("all")
+ end,
+ })
+end
+
+function ReaderStatus:showStatus()
+ local statusWidget = StatusWidget:new {
+ thumbnail = self.document:getCoverPageImage(),
+ props = self.document:getProps(),
+ document = self.document,
+ settings = self.settings,
+ }
+ UIManager:show(statusWidget)
+end
+
+function ReaderStatus:onPageUpdate(pageno)
+ if self.enabled then
+ --in case when pageUpdate event generated before _document:render()
+ if pageno > self.total_pages or self.total_pages == 1 then
+ self.total_pages = self.document:getPageCount()
+ end
+
+ if pageno == self.total_pages and self.total_pages ~= 1 then
+ self:showStatus()
+ end
+ end
+end
+
+function ReaderStatus:onReadSettings(config)
+ self.settings = config
+end
+
+return ReaderStatus
+
diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua
index 646fd8ef8..a6859f61e 100644
--- a/frontend/apps/reader/readerui.lua
+++ b/frontend/apps/reader/readerui.lua
@@ -42,6 +42,7 @@ local ReaderActivityIndicator = require("apps/reader/modules/readeractivityindic
local FileManagerHistory = require("apps/filemanager/filemanagerhistory")
local ReaderSearch = require("apps/reader/modules/readersearch")
local ReaderLink = require("apps/reader/modules/readerlink")
+local ReaderStatus = require("apps/reader/modules/readerstatus")
local PluginLoader = require("apps/reader/pluginloader")
--[[
@@ -300,6 +301,11 @@ function ReaderUI:init()
ui = self
})
+ self:registerModule("status", ReaderStatus:new{
+ ui = self,
+ document = self.document,
+ })
+
-- koreader plugins
for _,plugin_module in ipairs(PluginLoader:loadPlugins()) do
DEBUG("Loaded plugin", plugin_module.name, "at", plugin_module.path)
diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua
index f8a325d2c..8e8b6a395 100644
--- a/frontend/ui/widget/inputdialog.lua
+++ b/frontend/ui/widget/inputdialog.lua
@@ -24,6 +24,9 @@ local InputDialog = InputContainer:new{
width = nil,
height = nil,
+ text_width = nil,
+ text_height = nil,
+
title_face = Font:getFace("tfont", 22),
input_face = Font:getFace("cfont", 20),
@@ -58,7 +61,8 @@ function InputDialog:init()
text = self.input,
hint = self.input_hint,
face = self.input_face,
- width = self.width * 0.9,
+ width = self.text_width or self.width * 0.9,
+ height = self.text_height or nil,
input_type = self.input_type,
text_type = self.text_type,
enter_callback = self.enter_callback,
diff --git a/frontend/ui/widget/statuswidget.lua b/frontend/ui/widget/statuswidget.lua
new file mode 100644
index 000000000..39ae094a2
--- /dev/null
+++ b/frontend/ui/widget/statuswidget.lua
@@ -0,0 +1,573 @@
+local InputContainer = require("ui/widget/container/inputcontainer")
+local FrameContainer = require("ui/widget/container/framecontainer")
+local CenterContainer = require("ui/widget/container/centercontainer")
+local RightContainer = require("ui/widget/container/rightcontainer")
+local LeftContainer = require("ui/widget/container/leftcontainer")
+local HorizontalGroup = require("ui/widget/horizontalgroup")
+local VerticalGroup = require("ui/widget/verticalgroup")
+local HorizontalSpan = require("ui/widget/horizontalspan")
+local VerticalSpan = require("ui/widget/verticalspan")
+local InputText = require("ui/widget/inputtext")
+local ToggleSwitch = require("ui/widget/toggleswitch")
+local Button = require("ui/widget/button")
+local ProgressWidget = require("ui/widget/progresswidget")
+local LineWidget = require("ui/widget/linewidget")
+local TextWidget = require("ui/widget/textwidget")
+local ImageWidget = require("ui/widget/imagewidget")
+local TextBoxWidget = require("ui/widget/textboxwidget")
+
+local CloseButton = require("ui/widget/closebutton")
+local InputDialog = require("ui/widget/inputdialog")
+
+local UIManager = require("ui/uimanager")
+local Geom = require("ui/geometry")
+local Blitbuffer = require("ffi/blitbuffer")
+local Screen = require("device").screen
+local Font = require("ui/font")
+local TimeVal = require("ui/timeval")
+local RenderText = require("ui/rendertext")
+
+local DocSettings = require("docsettings")
+local DEBUG = require("dbg")
+local util = require("util")
+local _ = require("gettext")
+
+--[[
+--Save into sdr folder addtional section
+["summary"] = {
+ ["rating"] = 5,
+ ["note"] = "Some text",
+ ["status"] = "Reading"
+ ["modified"] = "24.01.2016"
+},]]
+local StatusWidget = InputContainer:new {
+ settings = nil,
+ thumbnail = nil,
+ props = nil,
+ star = {},
+ summary = {
+ rating = nil,
+ note = nil,
+ status = "",
+ modified = "",
+ },
+ stats = {
+ total_time_in_sec = 0,
+ performance_in_pages = {},
+ pages = 0,
+ }
+}
+
+function StatusWidget:init()
+ self.stats.pages = self.document:getPageCount()
+ self:getStatisticsSettings()
+ if self.settings then
+ self.summary = self.settings:readSetting("summary")
+ end
+
+ self.small_font_face = Font:getFace("ffont", 15)
+ self.medium_font_face = Font:getFace("ffont", 20)
+ self.large_font_face = Font:getFace("ffont", 25)
+
+ self.star = Button:new {
+ icon = "resources/icons/stats.star.empty.png",
+ bordersize = 0,
+ radius = 0,
+ margin = 0,
+ enabled = true,
+ show_parent = self,
+ }
+
+ local statusContainer = FrameContainer:new {
+ dimen = Screen:getSize(),
+ background = Blitbuffer.COLOR_WHITE,
+ bordersize = 0,
+ padding = 0,
+ self:showStatus(),
+ }
+ self[1] = statusContainer
+end
+
+function StatusWidget:showStatus()
+ local main_group = VerticalGroup:new { align = "left" }
+
+ local img_width = Screen:scaleBySize(132 * 1.5)
+ local img_height = Screen:scaleBySize(184 * 1.5)
+
+ if Screen:getScreenMode() == "landscape" then
+ img_width = Screen:scaleBySize(132)
+ img_height = Screen:scaleBySize(184)
+ end
+
+ local thumb = nil
+ if self.thumbnail then
+ thumb = ImageWidget:new {
+ image = self.thumbnail,
+ width = img_width,
+ height = img_height,
+ autoscale = false,
+ }
+ end
+
+ local screen_width = Screen:getWidth()
+
+ local cover_with_title_and_author_container = CenterContainer:new {
+ dimen = Geom:new { w = screen_width, h = img_height },
+ }
+
+ local cover_with_title_and_author_group = HorizontalGroup:new { align = "top" }
+
+ local span = HorizontalSpan:new { width = screen_width * 0.05 }
+
+ table.insert(cover_with_title_and_author_group, span)
+
+ if self.thumbnail then
+ table.insert(cover_with_title_and_author_group, thumb)
+ end
+ table.insert(cover_with_title_and_author_group,
+ self:generateTitleAuthorProgressGroup(screen_width - span.width - img_width,
+ img_height,
+ self.props.title,
+ self.props.authors,
+ self.document:getCurrentPage(),
+ self.document:getPageCount()))
+ table.insert(cover_with_title_and_author_container, cover_with_title_and_author_group)
+
+ --portrait mode
+ local rateHeight = Screen:scaleBySize(60)
+ local statisticsHeight = Screen:scaleBySize(60)
+ local summaryHeight = Screen:scaleBySize(140)
+ local statusHeight = Screen:scaleBySize(105)
+
+ --landscape mode
+ if Screen:getScreenMode() == "landscape" then
+ summaryHeight = Screen:scaleBySize(70)
+ statusHeight = Screen:scaleBySize(60)
+ end
+
+ local header_group = HorizontalGroup:new {
+ align = "center",
+ self:addHeader(screen_width * 0.95, Screen:scaleBySize(15), _("Progress")),
+ CloseButton:new { window = self }
+ }
+
+ table.insert(main_group, header_group)
+ table.insert(main_group, cover_with_title_and_author_container)
+ table.insert(main_group, self:addHeader(screen_width, Screen:scaleBySize(25), _("Rate")))
+ table.insert(main_group, self:generateRateGroup(screen_width, rateHeight, self.summary.rating))
+ table.insert(main_group, self:addHeader(screen_width, Screen:scaleBySize(35), _("Statistics")))
+ table.insert(main_group, self:generateStatisticsGroup(screen_width, statisticsHeight,
+ self:getStatDays(self.stats), self:getStatHours(self.stats), self:getReadPages(self.stats)))
+ table.insert(main_group, self:addHeader(screen_width, Screen:scaleBySize(35), _("Review")))
+ table.insert(main_group, self:generateSummaryGroup(screen_width, summaryHeight, self.summary.note))
+ table.insert(main_group, self:addHeader(screen_width, Screen:scaleBySize(25), _("Update Status")))
+ table.insert(main_group, self:generateSwitchGroup(screen_width, statusHeight, self.summary.status))
+ return main_group
+end
+
+function StatusWidget:getStatDays(stats)
+ if stats and stats.performance_in_pages then
+ local dates = {}
+ for k, v in pairs(stats.performance_in_pages) do
+ dates[os.date("%Y-%m-%d", k)] = ""
+ end
+ return util.tableSize(dates)
+ end
+ return "none"
+end
+
+
+function StatusWidget:getStatHours(stats)
+ if stats and stats.total_time_in_sec then
+ return util.secondsToClock(stats.total_time_in_sec, false)
+ end
+ return "none"
+end
+
+
+function StatusWidget:getReadPages(stats)
+ if stats and stats.performance_in_pages and stats.pages then
+ return util.tableSize(stats.performance_in_pages) .. "/" .. stats.pages
+ end
+ return "none"
+end
+
+function StatusWidget:addHeader(width, height, title)
+ local group = HorizontalGroup:new {
+ align = "center",
+ bordersize = 0
+ }
+
+ local bold = false
+
+ local titleWidget = TextWidget:new {
+ text = title,
+ face = self.large_font_face,
+ bold = bold,
+ }
+ local titleSize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.large_font_face, title, true, bold)
+ local lineWidth = ((width - titleSize.x) * 0.5)
+
+ local line_container = LeftContainer:new {
+ dimen = Geom:new { w = lineWidth, h = height },
+ LineWidget:new {
+ background = Blitbuffer.gray(0.2),
+ dimen = Geom:new {
+ w = lineWidth,
+ h = 2,
+ }
+ }
+ }
+
+ local text_container = CenterContainer:new {
+ dimen = Geom:new { w = titleSize.x, h = height },
+ titleWidget,
+ }
+
+ table.insert(group, line_container)
+ table.insert(group, text_container)
+ table.insert(group, line_container)
+ return group
+end
+
+function StatusWidget:generateSwitchGroup(width, height, book_status)
+ local switch_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = height },
+ }
+
+ local args = {
+ [1] = "complete",
+ [2] = "reading",
+ [3] = "abandoned",
+ }
+
+ local position = 2
+ for k, v in pairs(args) do
+ if v == book_status then
+ position = k
+ end
+ end
+
+ local config = {
+ event = "ChangeBookStatus",
+ default_value = 2,
+ toggle = {
+ [1] = _("Complete"),
+ [2] = _("Reading"),
+ [3] = _("Abandoned"),
+ },
+ args = args,
+ default_arg = "reading",
+ values = {
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ },
+ name = "book_status",
+ alternate = false,
+ enabled = true,
+ }
+
+ local switch = ToggleSwitch:new {
+ width = width * 0.6,
+ default_value = config.default_value,
+ name = config.name,
+ name_text = config.name_text,
+ event = config.event,
+ toggle = config.toggle,
+ args = config.args,
+ alternate = config.alternate,
+ default_arg = config.default_arg,
+ values = config.values,
+ enabled = config.enable,
+ config = self,
+ }
+
+ switch:setPosition(position)
+
+ table.insert(switch_container, switch)
+ return switch_container
+end
+
+function StatusWidget:onConfigChoose(values, name, event, args, events, position)
+ UIManager:scheduleIn(0.05, function()
+ if values then
+ self:onChangeBookStatus(args, position)
+ end
+ UIManager:setDirty("all")
+ end)
+end
+
+function StatusWidget:onChangeBookStatus(option_name, option_value)
+ local curr_time = TimeVal:now()
+ self.summary.status = option_name[option_value]
+ self.summary.modified = os.date("%Y-%m-%d", curr_time.sec)
+ self:saveSummary()
+ return true
+end
+
+function StatusWidget:onUpdateNote()
+ self.summary.note = self.input_note:getText()
+ self:saveSummary()
+ return true
+end
+
+
+function StatusWidget:saveSummary()
+ self.settings:saveSetting("summary", self.summary)
+ self.settings:flush()
+end
+
+
+function StatusWidget:generateSummaryGroup(width, height, text)
+
+ self.input_note = InputText:new {
+ text = text,
+ face = self.medium_font_face,
+ width = width * 0.95,
+ height = height * 0.55,
+ scroll = true,
+ focused = false,
+ margin = 5,
+ padding = 0,
+ parent = self,
+ hint = _("A few words about the book"),
+ }
+
+ local note_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = height },
+ self.input_note
+ }
+ return note_container
+end
+
+function StatusWidget:generateRateGroup(width, height, rating)
+ self.stars_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = height },
+ }
+
+ self:setStar(rating)
+ return self.stars_container
+end
+
+function StatusWidget:setStar(num)
+ --clear previous data
+ self.stars_container:clear()
+
+ local stars_group = HorizontalGroup:new { align = "center" }
+ if num then
+ self.summary.rating = num
+ self:saveSummary()
+
+ for i = 1, num do
+ table.insert(stars_group, self.star:new { icon = "resources/icons/stats.star.full.png", callback = function() self:setStar(i) end })
+ end
+ else
+ num = 0
+ end
+
+ for i = num + 1, 5 do
+ table.insert(stars_group, self.star:new { callback = function() self:setStar(i) end })
+ end
+
+ table.insert(self.stars_container, stars_group)
+
+ UIManager:setDirty(nil, "partial")
+ return true
+end
+
+function StatusWidget:generateStatisticsGroup(width, height, days, average, pages)
+ local statistics_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = height },
+ }
+
+ local statistics_group = VerticalGroup:new { align = "left" }
+
+ local tile_width = width / 3
+ local tile_height = height / 2
+
+ local titles_group = HorizontalGroup:new {
+ align = "center",
+ CenterContainer:new {
+ dimen = Geom:new { w = tile_width, h = tile_height },
+ TextWidget:new {
+ text = _("Days"),
+ face = self.small_font_face,
+ },
+ },
+ CenterContainer:new {
+ dimen = Geom:new { w = tile_width, h = tile_height },
+ TextWidget:new {
+ text = _("Time"),
+ face = self.small_font_face,
+ },
+ },
+ CenterContainer:new {
+ dimen = Geom:new { w = tile_width, h = tile_height },
+ TextWidget:new {
+ text = _("Read pages"),
+ face = self.small_font_face,
+ }
+ }
+ }
+
+
+ local data_group = HorizontalGroup:new {
+ align = "center",
+ CenterContainer:new {
+ dimen = Geom:new { w = tile_width, h = tile_height },
+ TextWidget:new {
+ text = days,
+ face = self.medium_font_face,
+ },
+ },
+ CenterContainer:new {
+ dimen = Geom:new { w = tile_width, h = tile_height },
+ TextWidget:new {
+ text = average,
+ face = self.medium_font_face,
+ },
+ },
+ CenterContainer:new {
+ dimen = Geom:new { w = tile_width, h = tile_height },
+ TextWidget:new {
+ text = pages,
+ face = self.medium_font_face,
+ }
+ }
+ }
+
+ table.insert(statistics_group, titles_group)
+ table.insert(statistics_group, data_group)
+
+ table.insert(statistics_container, statistics_group)
+ return statistics_container
+end
+
+function StatusWidget:generateTitleAuthorProgressGroup(width, height, title, authors, current_page, total_pages)
+
+ local title_author_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = height },
+ }
+
+ local title_author_progressbar_group = VerticalGroup:new {
+ align = "center",
+ VerticalSpan:new { width = height * 0.2 },
+ TextBoxWidget:new {
+ text = title,
+ width = width,
+ face = self.medium_font_face,
+ alignment = "center",
+ }
+ }
+ local text_author = TextWidget:new {
+ text = authors,
+ face = self.small_font_face,
+ padding = 2,
+ }
+
+ local author_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = text_author:getSize().h },
+ text_author
+ }
+
+ table.insert(title_author_progressbar_group, author_container)
+
+ local progressWidget = ProgressWidget:new {
+ width = width * 0.7,
+ height = Screen:scaleBySize(10),
+ percentage = current_page / total_pages,
+ ticks = {},
+ tick_width = 0,
+ last = total_pages,
+ }
+
+ local progress_bar_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = progressWidget:getSize().h },
+ progressWidget
+ }
+
+ table.insert(title_author_progressbar_group, progress_bar_container)
+ local text_complete = TextWidget:new {
+ text = string.format("%1.f", progressWidget.percentage * 100) .. "% " .. _("Completed"),
+ face = self.small_font_face,
+ }
+
+ local progress_bar_text_container = CenterContainer:new {
+ dimen = Geom:new { w = width, h = text_complete:getSize().h },
+ text_complete
+ }
+
+ table.insert(title_author_progressbar_group, progress_bar_text_container)
+ table.insert(title_author_container, title_author_progressbar_group)
+ return title_author_container
+end
+
+
+function StatusWidget:onAnyKeyPressed()
+ return self:onClose()
+end
+
+function StatusWidget:onClose()
+ self:saveSummary()
+ UIManager:setDirty("all")
+ UIManager:close(self)
+ return true
+end
+
+function StatusWidget:getStatisticsSettings()
+ if self.settings then
+ local stats = self.settings:readSetting("stats")
+ if stats then
+ self.stats.total_time_in_sec = self.stats.total_time_in_sec + stats.total_time_in_sec
+ for k, v in pairs(stats.performance_in_pages) do
+ self.stats.performance_in_pages[k] = v
+ end
+ end
+ end
+end
+
+
+function StatusWidget:onSwitchFocus(inputbox)
+ self.note_dialog = InputDialog:new {
+ title = "Note",
+ input = self.input_note:getText(),
+ input_hint = "",
+ input_type = "text",
+ scroll = true,
+ text_height = Screen:scaleBySize(150),
+ buttons = {
+ {
+ {
+ text = _("Cancel"),
+ callback = function()
+ self:closeInputDialog()
+ end,
+ },
+ {
+ text = _("OK"),
+ callback = function()
+ self.input_note:setText(self.note_dialog:getInputText())
+ self:closeInputDialog()
+ self:onUpdateNote()
+ end,
+ },
+ },
+ },
+ enter_callback = function()
+ self:closeInputDialog()
+ end,
+ width = Screen:getWidth() * 0.8,
+ height = Screen:getHeight() * 0.2,
+ }
+ self.note_dialog:onShowKeyboard()
+ UIManager:show(self.note_dialog)
+end
+
+function StatusWidget:closeInputDialog()
+ self.note_dialog:onClose()
+ UIManager:close(self.note_dialog)
+end
+
+return StatusWidget
+
diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua
index 853118d65..ad3a172d0 100644
--- a/frontend/ui/widget/textboxwidget.lua
+++ b/frontend/ui/widget/textboxwidget.lua
@@ -180,7 +180,16 @@ function TextBoxWidget:_render(v_list)
local y = font_height
local pen_x
for _,l in ipairs(v_list) do
- pen_x = 0
+ if self.alignment == "center" then
+ local line_len = 0
+ for _,w in ipairs(l) do
+ line_len = line_len + w.width
+ end
+ pen_x = (self.width - line_len)/2
+ else
+ pen_x = 0
+ end
+
for _,w in ipairs(l) do
w.box.y = y - line_height_px - font_height
--@TODO Don't use kerning for monospaced fonts. (houqp)
diff --git a/resources/icons/src/status.star.empty.svg b/resources/icons/src/status.star.empty.svg
new file mode 100644
index 000000000..5b43fd4bd
--- /dev/null
+++ b/resources/icons/src/status.star.empty.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/resources/icons/src/status.star.svg b/resources/icons/src/status.star.svg
new file mode 100644
index 000000000..c4bdaef67
--- /dev/null
+++ b/resources/icons/src/status.star.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/resources/icons/stats.star.empty.png b/resources/icons/stats.star.empty.png
new file mode 100644
index 000000000..75ba56ed5
Binary files /dev/null and b/resources/icons/stats.star.empty.png differ
diff --git a/resources/icons/stats.star.full.png b/resources/icons/stats.star.full.png
new file mode 100644
index 000000000..131baddfd
Binary files /dev/null and b/resources/icons/stats.star.full.png differ