Merge pull request #1785 from apletnev/#1730

#1730 Create complete book feature
pull/1808/head
Huang Xin 8 years ago
commit e4a9b60277

@ -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

@ -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)

@ -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,

@ -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

@ -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)

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path stroke="black" fill="none" stroke-width="4" stroke-opacity="null" d="m9.5,31.80331l21.772198,0l6.727806,-20.053268l6.72773,20.053268l21.772266,0l-17.614128,12.393391l6.728096,20.053261l-17.613964,-12.393784l-17.614088,12.393784l6.728119,-20.053261l-17.614035,-12.393391l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path stroke="black" fill="#000" stroke-width="4" stroke-opacity="null" d="m9.5,31.80331l21.772198,0l6.727806,-20.053268l6.72773,20.053268l21.772266,0l-17.614128,12.393391l6.728096,20.053261l-17.613964,-12.393784l-17.614088,12.393784l6.728119,-20.053261l-17.614035,-12.393391l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Loading…
Cancel
Save