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