mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
* [VirtualKeyboard] Add support for keynaviguation Also rename the variable "layout" to "keyboard_layout" because conflict with the layout from the focusmanager * Make the goto dialog compatible with key naviguation My solution is to change the order of the widget. The last one will the virtualkeybard so it catch all the keybinding, and below it, make the dialog "is_always_active = true" so it can receive touch event. * Correctly show the virtual keyboard on dpad devices * change the order to call the virtualKeyboard so it end up on top * Handle the multi input dialog * Support reopening the virtualKeyboard by the Press key * add check focusmanager * Fix https://github.com/koreader/koreader/issues/3797 * MultiInputDialog : Now work on non touch-device * Set the virtualkeyboard to be a modal widget * Fix the layout in multiinputwidget * Fix for the various combination of hasKeys,hasDpad,isTouchDevice * [Focusmanager] Better handling of malformed layout
587 lines
17 KiB
Lua
587 lines
17 KiB
Lua
local Blitbuffer = require("ffi/blitbuffer")
|
|
local Button = require("ui/widget/button")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local CloseButton = require("ui/widget/closebutton")
|
|
local Device = require("device")
|
|
local Font = require("ui/font")
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
local Geom = require("ui/geometry")
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local InputText = require("ui/widget/inputtext")
|
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
|
local LineWidget = require("ui/widget/linewidget")
|
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
|
local ProgressWidget = require("ui/widget/progresswidget")
|
|
local Size = require("ui/size")
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
local TimeVal = require("ui/timeval")
|
|
local ToggleSwitch = require("ui/widget/toggleswitch")
|
|
local UIManager = require("ui/uimanager")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local Screen = Device.screen
|
|
local template = require("ffi/util").template
|
|
|
|
local stats_book = {}
|
|
|
|
--[[
|
|
--Save into sdr folder addtional section
|
|
["summary"] = {
|
|
["rating"] = 5,
|
|
["note"] = "Some text",
|
|
["status"] = "Reading"
|
|
["modified"] = "24.01.2016"
|
|
},]]
|
|
local BookStatusWidget = InputContainer:new{
|
|
padding = Size.padding.fullscreen,
|
|
settings = nil,
|
|
thumbnail = nil,
|
|
props = nil,
|
|
star = {},
|
|
summary = {
|
|
rating = nil,
|
|
note = nil,
|
|
status = "",
|
|
modified = "",
|
|
},
|
|
}
|
|
|
|
function BookStatusWidget:init()
|
|
if self.settings then
|
|
self.summary = self.settings:readSetting("summary") or {
|
|
rating = nil,
|
|
note = nil,
|
|
status = "",
|
|
modified = "",
|
|
}
|
|
end
|
|
self.total_pages = self.view.document:getPageCount()
|
|
stats_book = self:getStats()
|
|
|
|
self.small_font_face = Font:getFace("smallffont")
|
|
self.medium_font_face = Font:getFace("ffont")
|
|
self.large_font_face = Font:getFace("largeffont")
|
|
|
|
local button_enabled = true
|
|
if self.readonly then
|
|
button_enabled = false
|
|
end
|
|
|
|
self.star = Button:new{
|
|
icon = "resources/icons/stats.star.empty.png",
|
|
bordersize = 0,
|
|
radius = 0,
|
|
margin = 0,
|
|
enabled = button_enabled,
|
|
show_parent = self,
|
|
readonly = self.readonly,
|
|
}
|
|
|
|
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
|
|
|
|
local screen_size = Screen:getSize()
|
|
self.covers_fullscreen = true -- hint for UIManager:_repaint()
|
|
self[1] = FrameContainer:new{
|
|
width = screen_size.w,
|
|
height = screen_size.h,
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
bordersize = 0,
|
|
padding = 0,
|
|
self:getStatusContent(screen_size.w),
|
|
}
|
|
end
|
|
|
|
function BookStatusWidget:getStats()
|
|
return {}
|
|
end
|
|
|
|
function BookStatusWidget:getStatDays()
|
|
if stats_book.days then
|
|
return stats_book.days
|
|
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
|
|
|
|
function BookStatusWidget:getStatusContent(width)
|
|
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{
|
|
align = "left",
|
|
OverlapGroup:new{
|
|
dimen = Geom:new{ w = width, h = Size.item.height_default },
|
|
close_button,
|
|
},
|
|
self:genBookInfoGroup(),
|
|
self:genHeader(_("Statistics")),
|
|
self:genStatisticsGroup(width),
|
|
self:genHeader(_("Review")),
|
|
self:genSummaryGroup(width),
|
|
status_header,
|
|
self:generateSwitchGroup(width),
|
|
}
|
|
return content
|
|
end
|
|
|
|
function BookStatusWidget:genHeader(title)
|
|
local width, height = Screen:getWidth(), Size.item.height_default
|
|
|
|
local header_title = TextWidget:new{
|
|
text = title,
|
|
face = self.medium_font_face,
|
|
fgcolor = Blitbuffer.gray(0.4),
|
|
}
|
|
|
|
local padding_span = HorizontalSpan:new{ width = self.padding }
|
|
local line_width = (width - header_title:getSize().w) / 2 - self.padding * 2
|
|
local line_container = LeftContainer:new{
|
|
dimen = Geom:new{ w = line_width, h = height },
|
|
LineWidget:new{
|
|
background = Blitbuffer.gray(0.2),
|
|
dimen = Geom:new{
|
|
w = line_width,
|
|
h = Size.line.thick,
|
|
}
|
|
}
|
|
}
|
|
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
|
|
|
|
return VerticalGroup:new{
|
|
span_top,
|
|
HorizontalGroup:new{
|
|
align = "center",
|
|
padding_span,
|
|
line_container,
|
|
padding_span,
|
|
header_title,
|
|
padding_span,
|
|
line_container,
|
|
padding_span,
|
|
},
|
|
span_bottom,
|
|
}
|
|
end
|
|
|
|
function BookStatusWidget: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 BookStatusWidget: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 BookStatusWidget: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 BookStatusWidget:genBookInfoGroup()
|
|
local screen_width = Screen:getWidth()
|
|
local split_span_width = screen_width * 0.05
|
|
|
|
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
|
|
-- title
|
|
local book_meta_info_group = VerticalGroup:new{
|
|
align = "center",
|
|
VerticalSpan:new{ width = height * 0.2 },
|
|
TextBoxWidget:new{
|
|
text = self.props.title,
|
|
width = width,
|
|
face = self.medium_font_face,
|
|
alignment = "center",
|
|
},
|
|
|
|
}
|
|
-- author
|
|
local text_author = TextBoxWidget:new{
|
|
text = self.props.authors,
|
|
face = self.small_font_face,
|
|
width = width,
|
|
alignment = "center",
|
|
}
|
|
table.insert(book_meta_info_group,
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = width, h = text_author:getSize().h },
|
|
text_author
|
|
}
|
|
)
|
|
-- progress bar
|
|
local read_percentage = self.view.state.page / self.total_pages
|
|
local progress_bar = ProgressWidget:new{
|
|
width = width * 0.7,
|
|
height = Screen:scaleBySize(10),
|
|
percentage = read_percentage,
|
|
ticks = nil,
|
|
last = nil,
|
|
}
|
|
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{
|
|
text = template(_("%1% Completed"),
|
|
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
|
|
table.insert(book_info_group, ImageWidget:new{
|
|
image = self.thumbnail,
|
|
width = img_width,
|
|
height = img_height,
|
|
})
|
|
-- dereference thumbnail since we let imagewidget manages its lifecycle
|
|
self.thumbnail = nil
|
|
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)
|
|
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 = self:getStatDays(),
|
|
face = self.medium_font_face,
|
|
},
|
|
},
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
TextWidget:new{
|
|
text = self:getStatHours(),
|
|
face = self.medium_font_face,
|
|
},
|
|
},
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = tile_width, h = tile_height },
|
|
TextWidget:new{
|
|
text = self:getStatReadPages(),
|
|
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 BookStatusWidget:genSummaryGroup(width)
|
|
local height
|
|
if Screen:getScreenMode() == "landscape" then
|
|
height = Screen:scaleBySize(80)
|
|
else
|
|
height = Screen:scaleBySize(160)
|
|
end
|
|
|
|
local text_padding = Size.padding.default
|
|
self.input_note = InputText:new{
|
|
text = self.summary.note,
|
|
face = self.medium_font_face,
|
|
width = width - self.padding * 3,
|
|
height = height * 0.75,
|
|
scroll = true,
|
|
bordersize = Size.border.default,
|
|
focused = false,
|
|
padding = text_padding,
|
|
parent = self,
|
|
readonly = self.readonly,
|
|
hint = _("A few words about the book"),
|
|
}
|
|
|
|
return VerticalGroup:new{
|
|
VerticalSpan:new{ width = Size.span.vertical_large },
|
|
CenterContainer:new{
|
|
dimen = Geom:new{ w = width, h = height },
|
|
self.input_note
|
|
}
|
|
}
|
|
end
|
|
|
|
function BookStatusWidget:onUpdateNote()
|
|
self.summary.note = self.input_note:getText()
|
|
self:saveSummary()
|
|
return true
|
|
end
|
|
|
|
function BookStatusWidget:saveSummary()
|
|
if self.summary then
|
|
self.settings:saveSetting("summary", self.summary)
|
|
self.settings:flush()
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
local args = { "complete", "reading", "abandoned" }
|
|
|
|
local current_status = self.summary.status
|
|
local position = 2
|
|
for k, v in pairs(args) do
|
|
if v == current_status then
|
|
position = k
|
|
end
|
|
end
|
|
|
|
local config = {
|
|
event = "ChangeBookStatus",
|
|
default_value = 2,
|
|
args = args,
|
|
default_arg = "reading",
|
|
toggle = { _("Finished"), _("Reading"), _("On hold") },
|
|
values = { 1, 2, 3 },
|
|
name = "book_status",
|
|
alternate = false,
|
|
enabled = true,
|
|
}
|
|
|
|
if self.readonly then
|
|
config.enable = false
|
|
end
|
|
|
|
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,
|
|
readonly = self.readonly,
|
|
}
|
|
switch:setPosition(position)
|
|
|
|
return VerticalGroup:new{
|
|
VerticalSpan:new{ width = Screen:scaleBySize(10) },
|
|
CenterContainer:new{
|
|
ignore = "height",
|
|
dimen = Geom:new{ w = width, h = height },
|
|
switch,
|
|
}
|
|
}
|
|
end
|
|
|
|
function BookStatusWidget: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 BookStatusWidget:onAnyKeyPressed()
|
|
return self:onClose()
|
|
end
|
|
|
|
function BookStatusWidget:onClose()
|
|
self:saveSummary()
|
|
UIManager:setDirty("all")
|
|
UIManager:close(self)
|
|
return true
|
|
end
|
|
|
|
function BookStatusWidget:onSwitchFocus(inputbox)
|
|
self.note_dialog = InputDialog:new{
|
|
title = _("Review"),
|
|
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 = _("Save review"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
self.input_note:setText(self.note_dialog:getInputText())
|
|
self:closeInputDialog()
|
|
self:onUpdateNote()
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
UIManager:show(self.note_dialog)
|
|
self.note_dialog:onShowKeyboard()
|
|
end
|
|
|
|
function BookStatusWidget:closeInputDialog()
|
|
UIManager:close(self.note_dialog)
|
|
end
|
|
|
|
return BookStatusWidget
|