2017-04-29 08:38:09 +00:00
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
local Button = require("ui/widget/button")
|
2016-02-02 17:38:14 +00:00
|
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
2017-04-29 08:38:09 +00:00
|
|
|
local CloseButton = require("ui/widget/closebutton")
|
2018-03-14 21:16:38 +00:00
|
|
|
local Device = require("device")
|
2017-04-29 08:38:09 +00:00
|
|
|
local Font = require("ui/font")
|
|
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
|
|
local Geom = require("ui/geometry")
|
2018-09-21 13:48:40 +00:00
|
|
|
local GestureRange = require("ui/gesturerange")
|
2016-02-02 17:38:14 +00:00
|
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
|
|
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
2017-04-29 08:38:09 +00:00
|
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
2016-02-02 17:38:14 +00:00
|
|
|
local InputText = require("ui/widget/inputtext")
|
2017-04-29 08:38:09 +00:00
|
|
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
2016-02-02 17:38:14 +00:00
|
|
|
local LineWidget = require("ui/widget/linewidget")
|
2017-04-29 08:38:09 +00:00
|
|
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
|
|
|
local ProgressWidget = require("ui/widget/progresswidget")
|
2019-06-28 02:46:16 +00:00
|
|
|
local RenderImage = require("ui/renderimage")
|
2017-09-13 14:56:20 +00:00
|
|
|
local Size = require("ui/size")
|
2016-02-09 12:51:55 +00:00
|
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
2017-04-29 08:38:09 +00:00
|
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
|
|
local TimeVal = require("ui/timeval")
|
|
|
|
local ToggleSwitch = require("ui/widget/toggleswitch")
|
2016-02-02 17:38:14 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2017-04-29 08:38:09 +00:00
|
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
2016-02-02 17:38:14 +00:00
|
|
|
local util = require("util")
|
|
|
|
local _ = require("gettext")
|
2018-03-14 21:16:38 +00:00
|
|
|
local Screen = Device.screen
|
2018-05-20 10:16:14 +00:00
|
|
|
local T = require("ffi/util").template
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2017-09-14 20:29:09 +00:00
|
|
|
local stats_book = {}
|
|
|
|
|
2016-02-02 17:38:14 +00:00
|
|
|
--[[
|
|
|
|
--Save into sdr folder addtional section
|
|
|
|
["summary"] = {
|
|
|
|
["rating"] = 5,
|
|
|
|
["note"] = "Some text",
|
|
|
|
["status"] = "Reading"
|
|
|
|
["modified"] = "24.01.2016"
|
|
|
|
},]]
|
2016-03-07 05:38:20 +00:00
|
|
|
local BookStatusWidget = InputContainer:new{
|
2017-09-13 14:56:20 +00:00
|
|
|
padding = Size.padding.fullscreen,
|
2016-02-02 17:38:14 +00:00
|
|
|
settings = nil,
|
|
|
|
thumbnail = nil,
|
|
|
|
props = nil,
|
|
|
|
star = {},
|
|
|
|
summary = {
|
|
|
|
rating = nil,
|
|
|
|
note = nil,
|
|
|
|
status = "",
|
|
|
|
modified = "",
|
|
|
|
},
|
2016-04-06 04:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function BookStatusWidget:init()
|
2016-02-02 17:38:14 +00:00
|
|
|
if self.settings then
|
2019-06-28 02:46:16 +00:00
|
|
|
-- What a blank, full summary table should look like
|
|
|
|
local new_summary = {
|
2017-02-26 00:48:34 +00:00
|
|
|
rating = nil,
|
|
|
|
note = nil,
|
|
|
|
status = "",
|
|
|
|
modified = "",
|
|
|
|
}
|
2019-06-28 02:46:16 +00:00
|
|
|
local summary = self.settings:readSetting("summary")
|
|
|
|
-- Check if the summary table we get is a full one, or a minimal one from CoverMenu...
|
|
|
|
if summary then
|
|
|
|
if summary.modified then
|
|
|
|
-- Complete, use it as-is
|
|
|
|
self.summary = summary
|
|
|
|
else
|
|
|
|
-- Incomplete, fill it up
|
|
|
|
self.summary = new_summary
|
|
|
|
util.tableMerge(self.summary, summary)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self.summary = new_summary
|
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
2017-09-14 20:29:09 +00:00
|
|
|
self.total_pages = self.view.document:getPageCount()
|
|
|
|
stats_book = self:getStats()
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2017-04-29 08:38:09 +00:00
|
|
|
self.small_font_face = Font:getFace("smallffont")
|
|
|
|
self.medium_font_face = Font:getFace("ffont")
|
|
|
|
self.large_font_face = Font:getFace("largeffont")
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2017-12-17 17:27:24 +00:00
|
|
|
local button_enabled = true
|
|
|
|
if self.readonly then
|
|
|
|
button_enabled = false
|
|
|
|
end
|
|
|
|
|
2016-02-10 07:01:19 +00:00
|
|
|
self.star = Button:new{
|
2016-02-02 17:38:14 +00:00
|
|
|
icon = "resources/icons/stats.star.empty.png",
|
|
|
|
bordersize = 0,
|
|
|
|
radius = 0,
|
|
|
|
margin = 0,
|
2017-12-17 17:27:24 +00:00
|
|
|
enabled = button_enabled,
|
2016-02-02 17:38:14 +00:00
|
|
|
show_parent = self,
|
2017-12-17 17:27:24 +00:00
|
|
|
readonly = self.readonly,
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
2018-03-14 21:16:38 +00:00
|
|
|
|
|
|
|
if Device:hasKeys() then
|
|
|
|
self.key_events = {
|
|
|
|
--don't get locked in on non touch devices
|
|
|
|
AnyKeyPressed = { { Device.input.group.Any },
|
|
|
|
seqtext = "any key", doc = "close dialog" }
|
|
|
|
}
|
|
|
|
end
|
2018-09-21 13:48:40 +00:00
|
|
|
if Device:isTouchDevice() then
|
|
|
|
self.ges_events.Swipe = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "swipe",
|
|
|
|
range = function() return self.dimen end,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
2018-03-14 21:16:38 +00:00
|
|
|
|
2016-03-14 02:48:28 +00:00
|
|
|
local screen_size = Screen:getSize()
|
2018-03-17 22:02:32 +00:00
|
|
|
self.covers_fullscreen = true -- hint for UIManager:_repaint()
|
2016-03-14 02:48:28 +00:00
|
|
|
self[1] = FrameContainer:new{
|
|
|
|
width = screen_size.w,
|
|
|
|
height = screen_size.h,
|
2016-02-02 17:38:14 +00:00
|
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
|
|
bordersize = 0,
|
|
|
|
padding = 0,
|
2016-03-14 02:48:28 +00:00
|
|
|
self:getStatusContent(screen_size.w),
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
|
|
|
|
self.dithered = true
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
|
|
|
|
2017-09-14 20:29:09 +00:00
|
|
|
function BookStatusWidget:getStats()
|
|
|
|
return {}
|
|
|
|
end
|
|
|
|
|
|
|
|
function BookStatusWidget:getStatDays()
|
|
|
|
if stats_book.days then
|
2019-11-19 20:06:03 +00:00
|
|
|
return tostring(stats_book.days)
|
2017-09-14 20:29:09 +00:00
|
|
|
else
|
|
|
|
return _("N/A")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function BookStatusWidget:getStatHours()
|
|
|
|
if stats_book.time then
|
|
|
|
return util.secondsToClock(stats_book.time, false)
|
|
|
|
else
|
|
|
|
return _("N/A")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function BookStatusWidget:getStatReadPages()
|
|
|
|
if stats_book.pages then
|
|
|
|
return string.format("%s/%s",stats_book.pages, self.total_pages)
|
|
|
|
else
|
|
|
|
return _("N/A")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-14 02:48:28 +00:00
|
|
|
function BookStatusWidget:getStatusContent(width)
|
2017-12-17 17:27:24 +00:00
|
|
|
local close_button = nil
|
|
|
|
local status_header = self:genHeader(_("Book Status"))
|
|
|
|
|
|
|
|
if self.readonly ~= true then
|
|
|
|
close_button = CloseButton:new{ window = self }
|
|
|
|
status_header = self:genHeader(_("Update Status"))
|
|
|
|
end
|
|
|
|
local content = VerticalGroup:new{
|
2016-03-07 05:38:20 +00:00
|
|
|
align = "left",
|
|
|
|
OverlapGroup:new{
|
2017-09-13 14:56:20 +00:00
|
|
|
dimen = Geom:new{ w = width, h = Size.item.height_default },
|
2017-12-17 17:27:24 +00:00
|
|
|
close_button,
|
2016-03-07 05:38:20 +00:00
|
|
|
},
|
|
|
|
self:genBookInfoGroup(),
|
|
|
|
self:genHeader(_("Statistics")),
|
2016-03-14 02:48:28 +00:00
|
|
|
self:genStatisticsGroup(width),
|
2016-03-07 05:38:20 +00:00
|
|
|
self:genHeader(_("Review")),
|
2016-03-14 02:48:28 +00:00
|
|
|
self:genSummaryGroup(width),
|
2017-12-17 17:27:24 +00:00
|
|
|
status_header,
|
2016-03-14 02:48:28 +00:00
|
|
|
self:generateSwitchGroup(width),
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
2017-12-17 17:27:24 +00:00
|
|
|
return content
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:genHeader(title)
|
2017-09-13 14:56:20 +00:00
|
|
|
local width, height = Screen:getWidth(), Size.item.height_default
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
local header_title = TextWidget:new{
|
2016-02-02 17:38:14 +00:00
|
|
|
text = title,
|
2016-03-07 05:38:20 +00:00
|
|
|
face = self.medium_font_face,
|
2019-03-14 19:58:45 +00:00
|
|
|
fgcolor = Blitbuffer.COLOR_WEB_GRAY,
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
local padding_span = HorizontalSpan:new{ width = self.padding }
|
|
|
|
local line_width = (width - header_title:getSize().w) / 2 - self.padding * 2
|
2016-02-10 07:01:19 +00:00
|
|
|
local line_container = LeftContainer:new{
|
2016-03-07 05:38:20 +00:00
|
|
|
dimen = Geom:new{ w = line_width, h = height },
|
2016-02-10 07:01:19 +00:00
|
|
|
LineWidget:new{
|
2019-03-14 19:58:45 +00:00
|
|
|
background = Blitbuffer.COLOR_LIGHT_GRAY,
|
2016-02-10 07:01:19 +00:00
|
|
|
dimen = Geom:new{
|
2016-03-07 05:38:20 +00:00
|
|
|
w = line_width,
|
2017-09-13 14:56:20 +00:00
|
|
|
h = Size.line.thick,
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-19 08:15:54 +00:00
|
|
|
local span_top, span_bottom
|
|
|
|
if Screen:getScreenMode() == "landscape" then
|
|
|
|
span_top = VerticalSpan:new{ width = Size.span.horizontal_default }
|
|
|
|
span_bottom = VerticalSpan:new{ width = Size.span.horizontal_default }
|
|
|
|
else
|
|
|
|
span_top = VerticalSpan:new{ width = Size.item.height_default }
|
|
|
|
span_bottom = VerticalSpan:new{ width = Size.span.vertical_large }
|
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
return VerticalGroup:new{
|
2017-11-19 08:15:54 +00:00
|
|
|
span_top,
|
2016-03-07 05:38:20 +00:00
|
|
|
HorizontalGroup:new{
|
|
|
|
align = "center",
|
|
|
|
padding_span,
|
|
|
|
line_container,
|
|
|
|
padding_span,
|
|
|
|
header_title,
|
|
|
|
padding_span,
|
|
|
|
line_container,
|
|
|
|
padding_span,
|
2016-02-02 17:38:14 +00:00
|
|
|
},
|
2017-11-19 08:15:54 +00:00
|
|
|
span_bottom,
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:onChangeBookStatus(option_name, option_value)
|
2016-02-02 17:38:14 +00:00
|
|
|
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
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:generateRateGroup(width, height, rating)
|
2016-02-10 07:01:19 +00:00
|
|
|
self.stars_container = CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = height },
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self:setStar(rating)
|
|
|
|
return self.stars_container
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:setStar(num)
|
2016-02-02 17:38:14 +00:00
|
|
|
--clear previous data
|
|
|
|
self.stars_container:clear()
|
|
|
|
|
2016-02-10 07:01:19 +00:00
|
|
|
local stars_group = HorizontalGroup:new{ align = "center" }
|
2016-02-02 17:38:14 +00:00
|
|
|
if num then
|
|
|
|
self.summary.rating = num
|
|
|
|
self:saveSummary()
|
|
|
|
|
|
|
|
for i = 1, num do
|
2016-02-10 07:01:19 +00:00
|
|
|
table.insert(stars_group, self.star:new{
|
|
|
|
icon = "resources/icons/stats.star.full.png",
|
|
|
|
callback = function() self:setStar(i) end
|
|
|
|
})
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
num = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
for i = num + 1, 5 do
|
2016-02-10 07:01:19 +00:00
|
|
|
table.insert(stars_group, self.star:new{ callback = function() self:setStar(i) end })
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
table.insert(self.stars_container, stars_group)
|
|
|
|
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
-- Individual stars are Button, w/ flash_ui, they'll have their own flash.
|
|
|
|
-- And we need to redraw the full widget, because we don't know the coordinates of stars_container :/.
|
|
|
|
UIManager:setDirty(self, "ui", nil, true)
|
2016-02-02 17:38:14 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:genBookInfoGroup()
|
|
|
|
local screen_width = Screen:getWidth()
|
2020-06-12 23:56:36 +00:00
|
|
|
local split_span_width = math.floor(screen_width * 0.05)
|
2016-03-07 05:38:20 +00:00
|
|
|
|
|
|
|
local img_width, img_height
|
|
|
|
if Screen:getScreenMode() == "landscape" then
|
|
|
|
img_width = Screen:scaleBySize(132)
|
|
|
|
img_height = Screen:scaleBySize(184)
|
|
|
|
else
|
|
|
|
img_width = Screen:scaleBySize(132 * 1.5)
|
|
|
|
img_height = Screen:scaleBySize(184 * 1.5)
|
|
|
|
end
|
|
|
|
|
|
|
|
local height = img_height
|
|
|
|
local width = screen_width - split_span_width - img_width
|
2020-01-23 16:35:57 +00:00
|
|
|
|
|
|
|
-- Get a chance to have title and authors rendered with alternate
|
|
|
|
-- glyphs for the book language
|
|
|
|
local lang = nil
|
|
|
|
if self.props.language and self.props.language ~= "" then
|
|
|
|
lang = self.props.language
|
|
|
|
end
|
2016-03-07 05:38:20 +00:00
|
|
|
-- title
|
|
|
|
local book_meta_info_group = VerticalGroup:new{
|
|
|
|
align = "center",
|
|
|
|
VerticalSpan:new{ width = height * 0.2 },
|
|
|
|
TextBoxWidget:new{
|
|
|
|
text = self.props.title,
|
2020-01-23 16:35:57 +00:00
|
|
|
lang = lang,
|
2016-03-07 05:38:20 +00:00
|
|
|
width = width,
|
|
|
|
face = self.medium_font_face,
|
|
|
|
alignment = "center",
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
-- author
|
2018-01-21 21:33:40 +00:00
|
|
|
local text_author = TextBoxWidget:new{
|
2016-03-07 05:38:20 +00:00
|
|
|
text = self.props.authors,
|
2020-01-23 16:35:57 +00:00
|
|
|
lang = lang,
|
2016-03-07 05:38:20 +00:00
|
|
|
face = self.small_font_face,
|
2018-01-21 21:33:40 +00:00
|
|
|
width = width,
|
|
|
|
alignment = "center",
|
2016-03-07 05:38:20 +00:00
|
|
|
}
|
|
|
|
table.insert(book_meta_info_group,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = text_author:getSize().h },
|
|
|
|
text_author
|
|
|
|
}
|
|
|
|
)
|
|
|
|
-- progress bar
|
2017-09-14 20:29:09 +00:00
|
|
|
local read_percentage = self.view.state.page / self.total_pages
|
2016-03-07 05:38:20 +00:00
|
|
|
local progress_bar = ProgressWidget:new{
|
2020-06-12 23:56:36 +00:00
|
|
|
width = math.floor(width * 0.7),
|
2016-03-07 05:38:20 +00:00
|
|
|
height = Screen:scaleBySize(10),
|
|
|
|
percentage = read_percentage,
|
2016-04-21 03:48:50 +00:00
|
|
|
ticks = nil,
|
|
|
|
last = nil,
|
2016-03-07 05:38:20 +00:00
|
|
|
}
|
|
|
|
table.insert(book_meta_info_group,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = progress_bar:getSize().h },
|
|
|
|
progress_bar
|
|
|
|
}
|
|
|
|
)
|
|
|
|
-- complete text
|
|
|
|
local text_complete = TextWidget:new{
|
2018-05-20 10:16:14 +00:00
|
|
|
text = T(_("%1% Completed"),
|
2016-03-07 05:38:20 +00:00
|
|
|
string.format("%1.f", read_percentage * 100)),
|
|
|
|
face = self.small_font_face,
|
|
|
|
}
|
|
|
|
table.insert(book_meta_info_group,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = text_complete:getSize().h },
|
|
|
|
text_complete
|
|
|
|
}
|
|
|
|
)
|
|
|
|
-- rating
|
|
|
|
table.insert(book_meta_info_group,
|
|
|
|
VerticalSpan:new{ width = Screen:scaleBySize(30) })
|
|
|
|
local rateHeight = Screen:scaleBySize(60)
|
|
|
|
table.insert(book_meta_info_group,
|
|
|
|
self:generateRateGroup(screen_width, rateHeight, self.summary.rating))
|
|
|
|
|
|
|
|
-- build the final group
|
|
|
|
local book_info_group = HorizontalGroup:new{
|
|
|
|
align = "top",
|
|
|
|
HorizontalSpan:new{ width = split_span_width }
|
|
|
|
}
|
|
|
|
-- thumbnail
|
|
|
|
if self.thumbnail then
|
2019-06-28 02:46:16 +00:00
|
|
|
-- Much like BookInfoManager, honor AR here
|
|
|
|
local cbb_w, cbb_h = self.thumbnail:getWidth(), self.thumbnail:getHeight()
|
|
|
|
if cbb_w > img_width or cbb_h > img_height then
|
|
|
|
local scale_factor = math.min(img_width / cbb_w, img_height / cbb_h)
|
|
|
|
cbb_w = math.min(math.floor(cbb_w * scale_factor)+1, img_width)
|
|
|
|
cbb_h = math.min(math.floor(cbb_h * scale_factor)+1, img_height)
|
|
|
|
self.thumbnail = RenderImage:scaleBlitBuffer(self.thumbnail, cbb_w, cbb_h, true)
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
table.insert(book_info_group, ImageWidget:new{
|
|
|
|
image = self.thumbnail,
|
2019-06-28 02:46:16 +00:00
|
|
|
width = cbb_w,
|
|
|
|
height = cbb_h,
|
2016-03-07 05:38:20 +00:00
|
|
|
})
|
2017-05-08 07:43:34 +00:00
|
|
|
-- dereference thumbnail since we let imagewidget manages its lifecycle
|
|
|
|
self.thumbnail = nil
|
2016-03-07 05:38:20 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
table.insert(book_info_group, CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = height },
|
|
|
|
book_meta_info_group,
|
|
|
|
})
|
|
|
|
|
|
|
|
return CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = screen_width, h = img_height },
|
|
|
|
book_info_group,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function BookStatusWidget:genStatisticsGroup(width)
|
|
|
|
local height = Screen:scaleBySize(60)
|
2016-02-10 07:01:19 +00:00
|
|
|
local statistics_container = CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = height },
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
|
2016-02-10 07:01:19 +00:00
|
|
|
local statistics_group = VerticalGroup:new{ align = "left" }
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-02-09 12:51:55 +00:00
|
|
|
local tile_width = width / 3
|
|
|
|
local tile_height = height / 2
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-02-10 07:01:19 +00:00
|
|
|
local titles_group = HorizontalGroup:new{
|
2016-02-09 12:51:55 +00:00
|
|
|
align = "center",
|
2016-02-10 07:01:19 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
|
|
TextWidget:new{
|
2016-02-09 12:51:55 +00:00
|
|
|
text = _("Days"),
|
|
|
|
face = self.small_font_face,
|
|
|
|
},
|
2016-02-02 17:38:14 +00:00
|
|
|
},
|
2016-02-10 07:01:19 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
|
|
TextWidget:new{
|
2016-02-09 12:51:55 +00:00
|
|
|
text = _("Time"),
|
|
|
|
face = self.small_font_face,
|
|
|
|
},
|
2016-02-02 17:38:14 +00:00
|
|
|
},
|
2016-02-10 07:01:19 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
|
|
TextWidget:new{
|
2016-02-09 12:51:55 +00:00
|
|
|
text = _("Read pages"),
|
|
|
|
face = self.small_font_face,
|
|
|
|
}
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-10 07:01:19 +00:00
|
|
|
local data_group = HorizontalGroup:new{
|
2016-02-09 12:51:55 +00:00
|
|
|
align = "center",
|
2016-02-10 07:01:19 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
|
|
TextWidget:new{
|
2017-09-14 20:29:09 +00:00
|
|
|
text = self:getStatDays(),
|
2016-02-09 12:51:55 +00:00
|
|
|
face = self.medium_font_face,
|
|
|
|
},
|
2016-02-02 17:38:14 +00:00
|
|
|
},
|
2016-02-10 07:01:19 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
|
|
TextWidget:new{
|
2017-09-14 20:29:09 +00:00
|
|
|
text = self:getStatHours(),
|
2016-02-09 12:51:55 +00:00
|
|
|
face = self.medium_font_face,
|
|
|
|
},
|
2016-02-02 17:38:14 +00:00
|
|
|
},
|
2016-02-10 07:01:19 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
|
|
TextWidget:new{
|
2017-09-14 20:29:09 +00:00
|
|
|
text = self:getStatReadPages(),
|
2016-02-09 12:51:55 +00:00
|
|
|
face = self.medium_font_face,
|
|
|
|
}
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
table.insert(statistics_group, titles_group)
|
|
|
|
table.insert(statistics_group, data_group)
|
|
|
|
|
|
|
|
table.insert(statistics_container, statistics_group)
|
|
|
|
return statistics_container
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:genSummaryGroup(width)
|
|
|
|
local height
|
|
|
|
if Screen:getScreenMode() == "landscape" then
|
2017-09-13 14:56:20 +00:00
|
|
|
height = Screen:scaleBySize(80)
|
2016-03-07 05:38:20 +00:00
|
|
|
else
|
2017-09-13 14:56:20 +00:00
|
|
|
height = Screen:scaleBySize(160)
|
2016-03-07 05:38:20 +00:00
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2017-09-13 14:56:20 +00:00
|
|
|
local text_padding = Size.padding.default
|
2016-03-07 05:38:20 +00:00
|
|
|
self.input_note = InputText:new{
|
|
|
|
text = self.summary.note,
|
|
|
|
face = self.medium_font_face,
|
|
|
|
width = width - self.padding * 3,
|
2020-06-12 23:56:36 +00:00
|
|
|
height = math.floor(height * 0.75),
|
2016-03-07 05:38:20 +00:00
|
|
|
scroll = true,
|
2017-09-13 14:56:20 +00:00
|
|
|
bordersize = Size.border.default,
|
2016-03-07 05:38:20 +00:00
|
|
|
focused = false,
|
|
|
|
padding = text_padding,
|
|
|
|
parent = self,
|
2017-12-17 17:27:24 +00:00
|
|
|
readonly = self.readonly,
|
2016-03-07 05:38:20 +00:00
|
|
|
hint = _("A few words about the book"),
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
return VerticalGroup:new{
|
2017-09-13 14:56:20 +00:00
|
|
|
VerticalSpan:new{ width = Size.span.vertical_large },
|
2016-03-07 05:38:20 +00:00
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = width, h = height },
|
|
|
|
self.input_note
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
2016-02-09 12:51:55 +00:00
|
|
|
}
|
2016-03-07 05:38:20 +00:00
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:onUpdateNote()
|
|
|
|
self.summary.note = self.input_note:getText()
|
|
|
|
self:saveSummary()
|
|
|
|
return true
|
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:saveSummary()
|
|
|
|
if self.summary then
|
|
|
|
self.settings:saveSetting("summary", self.summary)
|
|
|
|
self.settings:flush()
|
|
|
|
end
|
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:generateSwitchGroup(width)
|
|
|
|
local height
|
|
|
|
if Screen:getScreenMode() == "landscape" then
|
|
|
|
-- landscape mode
|
|
|
|
height = Screen:scaleBySize(60)
|
|
|
|
else
|
|
|
|
-- portrait mode
|
|
|
|
height = Screen:scaleBySize(105)
|
|
|
|
end
|
2016-02-10 07:10:37 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
local args = { "complete", "reading", "abandoned" }
|
|
|
|
|
|
|
|
local current_status = self.summary.status
|
|
|
|
local position = 2
|
|
|
|
for k, v in pairs(args) do
|
2016-03-07 06:47:24 +00:00
|
|
|
if v == current_status then
|
2016-03-07 05:38:20 +00:00
|
|
|
position = k
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local config = {
|
|
|
|
event = "ChangeBookStatus",
|
|
|
|
default_value = 2,
|
|
|
|
args = args,
|
|
|
|
default_arg = "reading",
|
2018-03-02 16:22:41 +00:00
|
|
|
toggle = { _("Finished"), _("Reading"), _("On hold") },
|
2016-03-07 05:38:20 +00:00
|
|
|
values = { 1, 2, 3 },
|
|
|
|
name = "book_status",
|
|
|
|
alternate = false,
|
|
|
|
enabled = true,
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
|
|
|
|
2017-12-17 17:27:24 +00:00
|
|
|
if self.readonly then
|
|
|
|
config.enable = false
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
local switch = ToggleSwitch:new{
|
2020-06-12 23:56:36 +00:00
|
|
|
width = math.floor(width * 0.6),
|
2016-03-07 05:38:20 +00:00
|
|
|
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,
|
2017-12-17 17:27:24 +00:00
|
|
|
readonly = self.readonly,
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
2016-03-07 05:38:20 +00:00
|
|
|
switch:setPosition(position)
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
return VerticalGroup:new{
|
|
|
|
VerticalSpan:new{ width = Screen:scaleBySize(10) },
|
|
|
|
CenterContainer:new{
|
|
|
|
ignore = "height",
|
|
|
|
dimen = Geom:new{ w = width, h = height },
|
|
|
|
switch,
|
|
|
|
}
|
2016-02-02 17:38:14 +00:00
|
|
|
}
|
2016-03-07 05:38:20 +00:00
|
|
|
end
|
2016-02-02 17:38:14 +00:00
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:onConfigChoose(values, name, event, args, events, position)
|
2018-06-02 16:10:55 +00:00
|
|
|
UIManager:tickAfterNext(function()
|
2016-03-07 05:38:20 +00:00
|
|
|
if values then
|
|
|
|
self:onChangeBookStatus(args, position)
|
|
|
|
end
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
UIManager:setDirty(nil, "ui", nil, true)
|
2016-03-07 05:38:20 +00:00
|
|
|
end)
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:onAnyKeyPressed()
|
2016-02-02 17:38:14 +00:00
|
|
|
return self:onClose()
|
|
|
|
end
|
|
|
|
|
2018-09-21 13:48:40 +00:00
|
|
|
function BookStatusWidget:onSwipe(arg, ges_ev)
|
|
|
|
if ges_ev.direction == "south" then
|
|
|
|
-- Allow easier closing with swipe down
|
|
|
|
self:onClose()
|
|
|
|
elseif ges_ev.direction == "east" or ges_ev.direction == "west" or ges_ev.direction == "north" then
|
|
|
|
-- no use for now
|
|
|
|
do end -- luacheck: ignore 541
|
|
|
|
else -- diagonal swipe
|
|
|
|
-- trigger full refresh
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
UIManager:setDirty(nil, "full", nil, true)
|
2018-09-21 13:48:40 +00:00
|
|
|
-- a long diagonal swipe may also be used for taking a screenshot,
|
|
|
|
-- so let it propagate
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:onClose()
|
2016-02-02 17:38:14 +00:00
|
|
|
self:saveSummary()
|
2018-06-02 16:10:55 +00:00
|
|
|
-- NOTE: Flash on close to avoid ghosting, since we show an image.
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
UIManager:close(self, "flashpartial")
|
2016-02-02 17:38:14 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:onSwitchFocus(inputbox)
|
2016-02-10 07:01:19 +00:00
|
|
|
self.note_dialog = InputDialog:new{
|
2017-09-13 14:56:20 +00:00
|
|
|
title = _("Review"),
|
2016-02-02 17:38:14 +00:00
|
|
|
input = self.input_note:getText(),
|
|
|
|
input_hint = "",
|
|
|
|
input_type = "text",
|
|
|
|
scroll = true,
|
Text input fixes and enhancements (#4084)
InputText, ScrollTextWidget, TextBoxWidget:
- proper line scrolling when moving cursor or inserting/deleting text
to behave like most text editors do
- fix cursor navigation, optimize refreshes when moving only the cursor,
don't recreate the textwidget when moving cursor up/down
- optimize refresh areas, stick to "ui" to avoid a "partial" black
flash every 6 appended or deleted chars
InputText:
- fix issue when toggling Show password multiple times
- new option: InputText.cursor_at_end (default: true)
- if no InputText.height provided, measure the text widget height
that we would start with, and use a ScrollTextWidget with that
fixed height, so widget does not overflow container if we extend
the text and increase the number of lines
- as we are using "ui" refreshes while text editing, allows refreshing
the InputText with a diagonal swipe on it (actually, refresh the
whole screen, which allows refreshing the keyboard too if needed)
ScrollTextWidget:
- properly align scrollbar with its TextBoxWidget
TextBoxWidget:
- some cleanup (added new properties to avoid many method calls), added
proxy methods for upper widgets to get them
- reordered/renamed/refactored the *CharPos* methods for easier reading
(sorry for the diff that won't help reviewing, but that was needed)
InputDialog:
- new options:
allow_newline = false, -- allow entering new lines
cursor_at_end = true, -- starts with cursor at end of text, ready to append
fullscreen = false, -- adjust to full screen minus keyboard
condensed = false, -- true will prevent adding air and balance between elements
add_scroll_buttons = false, -- add scroll Up/Down buttons to first row of buttons
add_nav_bar = false, -- append a row of page navigation buttons
- find the most adequate text height, when none provided or fullscreen, to
not overflow screen (and not be stuck with Cancel/Save buttons hidden)
- had to disable the use of a MovableContainer (many issues like becoming
transparent when a PathChooser comes in front, Hold to paste from
clipboard, moving the InputDialog under the keyboard and getting stuck...)
GestureRange: fix possible crash (when event processed after widget
destruction ?)
LoginDialog: fix some ui stack increase and possible crash when switching
focus many times.
2018-07-19 06:30:40 +00:00
|
|
|
allow_newline = true,
|
2016-02-02 17:38:14 +00:00
|
|
|
text_height = Screen:scaleBySize(150),
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
self:closeInputDialog()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
2017-09-13 14:56:20 +00:00
|
|
|
text = _("Save review"),
|
2016-05-26 06:09:49 +00:00
|
|
|
is_enter_default = true,
|
2016-02-02 17:38:14 +00:00
|
|
|
callback = function()
|
|
|
|
self.input_note:setText(self.note_dialog:getInputText())
|
|
|
|
self:closeInputDialog()
|
|
|
|
self:onUpdateNote()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
UIManager:show(self.note_dialog)
|
2018-03-30 10:46:36 +00:00
|
|
|
self.note_dialog:onShowKeyboard()
|
2016-02-02 17:38:14 +00:00
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
function BookStatusWidget:closeInputDialog()
|
2016-02-02 17:38:14 +00:00
|
|
|
UIManager:close(self.note_dialog)
|
|
|
|
end
|
|
|
|
|
2016-03-07 05:38:20 +00:00
|
|
|
return BookStatusWidget
|