mirror of https://github.com/koreader/koreader
parent
eb8c0525de
commit
2599c39d42
@ -0,0 +1,352 @@
|
|||||||
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||||
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
||||||
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
||||||
|
local VerticalSpan = require("ui/widget/verticalspan")
|
||||||
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
||||||
|
local LeftContainer = require("ui/widget/container/topcontainer")
|
||||||
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
||||||
|
local LineWidget = require("ui/widget/linewidget")
|
||||||
|
local Blitbuffer = require("ffi/blitbuffer")
|
||||||
|
local CloseButton = require("ui/widget/closebutton")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local TextWidget = require("ui/widget/textwidget")
|
||||||
|
local GestureRange = require("ui/gesturerange")
|
||||||
|
local RenderText = require("ui/rendertext")
|
||||||
|
local Geom = require("ui/geometry")
|
||||||
|
local Font = require("ui/font")
|
||||||
|
local Device = require("device")
|
||||||
|
local Screen = Device.screen
|
||||||
|
local GoodReadersApi = require("goodreadsapi")
|
||||||
|
local LuaSettings = require("luasettings")
|
||||||
|
local DataStorage = require("datastorage")
|
||||||
|
local _ = require("gettext")
|
||||||
|
local InfoMessage = require("ui/widget/infomessage")
|
||||||
|
|
||||||
|
local DoubleKeyValueTitle = VerticalGroup:new{
|
||||||
|
kv_page = nil,
|
||||||
|
title = "",
|
||||||
|
tface = Font:getFace("tfont"),
|
||||||
|
align = "left",
|
||||||
|
}
|
||||||
|
|
||||||
|
function DoubleKeyValueTitle:init()
|
||||||
|
self.close_button = CloseButton:new{ window = self }
|
||||||
|
local btn_width = self.close_button:getSize().w
|
||||||
|
local title_txt_width = RenderText:sizeUtf8Text(
|
||||||
|
0, self.width, self.tface, self.title).x
|
||||||
|
local show_title_txt
|
||||||
|
if self.width < (title_txt_width + btn_width) then
|
||||||
|
show_title_txt = RenderText:truncateTextByWidth(
|
||||||
|
self.title, self.tface, self.width - btn_width)
|
||||||
|
else
|
||||||
|
show_title_txt = self.title
|
||||||
|
end
|
||||||
|
-- title and close button
|
||||||
|
table.insert(self, OverlapGroup:new{
|
||||||
|
dimen = { w = self.width },
|
||||||
|
TextWidget:new{
|
||||||
|
text = show_title_txt,
|
||||||
|
face = self.tface,
|
||||||
|
},
|
||||||
|
self.close_button,
|
||||||
|
})
|
||||||
|
-- page count and separation line
|
||||||
|
self.page_cnt = FrameContainer:new{
|
||||||
|
padding = 4,
|
||||||
|
margin = 0,
|
||||||
|
bordersize = 0,
|
||||||
|
background = Blitbuffer.COLOR_WHITE,
|
||||||
|
-- overlap offset x will be updated in setPageCount method
|
||||||
|
overlap_offset = {0, -15},
|
||||||
|
TextWidget:new{
|
||||||
|
text = "", -- page count
|
||||||
|
fgcolor = Blitbuffer.COLOR_GREY,
|
||||||
|
face = Font:getFace("ffont", 20),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.title_bottom = OverlapGroup:new{
|
||||||
|
dimen = { w = self.width, h = Screen:scaleBySize(2) },
|
||||||
|
LineWidget:new{
|
||||||
|
dimen = Geom:new{ w = self.width, h = Screen:scaleBySize(2) },
|
||||||
|
background = Blitbuffer.COLOR_GREY,
|
||||||
|
style = "solid",
|
||||||
|
},
|
||||||
|
self.page_cnt,
|
||||||
|
}
|
||||||
|
table.insert(self, self.title_bottom)
|
||||||
|
table.insert(self, VerticalSpan:new{ width = Screen:scaleBySize(5) })
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValueTitle:setPageCount(curr, total)
|
||||||
|
if total == 1 then
|
||||||
|
-- remove page count if there is only one page
|
||||||
|
table.remove(self.title_bottom, 2)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.page_cnt[1]:setText(curr .. "/" .. total)
|
||||||
|
self.page_cnt.overlap_offset[1] = (self.width - self.page_cnt:getSize().w - 10)
|
||||||
|
self.title_bottom[2] = self.page_cnt
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValueTitle:onClose()
|
||||||
|
self.kv_page:onClose()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local DoubleKeyValueItem = InputContainer:new{
|
||||||
|
key = nil,
|
||||||
|
value = nil,
|
||||||
|
cface_up = Font:getFace("cfont", 22),
|
||||||
|
cface_down = Font:getFace("cfont", 18),
|
||||||
|
width = nil,
|
||||||
|
height = nil,
|
||||||
|
align = "left",
|
||||||
|
}
|
||||||
|
|
||||||
|
function DoubleKeyValueItem:init()
|
||||||
|
self.dimen = Geom:new{align = "left", w = self.width, h = self.height}
|
||||||
|
if self.callback and Device:isTouchDevice() then
|
||||||
|
self.ges_events.Tap = {
|
||||||
|
GestureRange:new{
|
||||||
|
ges = "tap",
|
||||||
|
range = self.dimen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local key_w = RenderText:sizeUtf8Text(0, self.width, self.cface_down, self.key).x
|
||||||
|
local value_w = RenderText:sizeUtf8Text(0, self.width, self.cface_up, self.value).x
|
||||||
|
if key_w > self.width then
|
||||||
|
self.show_key = RenderText:truncateTextByWidth(self.key, self.cface_down, self.width)
|
||||||
|
else
|
||||||
|
self.show_key = self.key
|
||||||
|
end
|
||||||
|
if value_w > self.width then
|
||||||
|
self.show_value = RenderText:truncateTextByWidth(self.value, self.cface_up, self.width)
|
||||||
|
else
|
||||||
|
self.show_value = self.value
|
||||||
|
end
|
||||||
|
local h = self.dimen.h / 2
|
||||||
|
local w = self.dimen.w
|
||||||
|
self[1] = FrameContainer:new{
|
||||||
|
padding = 10,
|
||||||
|
bordersize = 0,
|
||||||
|
VerticalGroup:new{
|
||||||
|
dimen = Geom:new{ h = h, w = w },
|
||||||
|
padding = 10,
|
||||||
|
LeftContainer:new{
|
||||||
|
padding = 10,
|
||||||
|
dimen = Geom:new{ h = h, w = w },
|
||||||
|
TextWidget:new{
|
||||||
|
text = self.show_value,
|
||||||
|
padding = 10,
|
||||||
|
face = self.cface_up,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LeftContainer:new{
|
||||||
|
padding = 10,
|
||||||
|
dimen = Geom:new{ h = h / 5 , w = w },
|
||||||
|
HorizontalSpan:new{ width = Screen:scaleBySize(15), height = 3 }
|
||||||
|
},
|
||||||
|
LeftContainer:new{
|
||||||
|
padding = 10,
|
||||||
|
dimen = Geom:new{ h = h, w = w },
|
||||||
|
TextWidget:new{
|
||||||
|
text = self.show_key,
|
||||||
|
padding = 10,
|
||||||
|
face = self.cface_down,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValueItem:onTap()
|
||||||
|
local info = InfoMessage:new{text = _("Please wait...")}
|
||||||
|
UIManager:show(info)
|
||||||
|
UIManager:forceRePaint()
|
||||||
|
self.callback()
|
||||||
|
UIManager:close(info)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local DoubleKeyValuePage = InputContainer:new{
|
||||||
|
title = "",
|
||||||
|
width = nil,
|
||||||
|
height = nil,
|
||||||
|
show_page = 1,
|
||||||
|
text_input = "",
|
||||||
|
pages = 1,
|
||||||
|
goodreadersKey = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:readGRSettings()
|
||||||
|
self.gr_settings = LuaSettings:open(DataStorage:getSettingsDir().."/goodreadssettings.lua")
|
||||||
|
return self.gr_settings
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:saveGRSettings(setting)
|
||||||
|
if not self.gr_settings then self:readGRSettings() end
|
||||||
|
self.gr_settings:saveSetting("goodreads", setting)
|
||||||
|
self.gr_settings:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:init()
|
||||||
|
self.screen_width = Screen:getSize().w
|
||||||
|
self.screen_height = Screen:getSize().h
|
||||||
|
local gr_sett = self:readGRSettings().data
|
||||||
|
if gr_sett.goodreads then
|
||||||
|
self.goodreadersKey = gr_sett.goodreads.key
|
||||||
|
self.goodreadersSecret = gr_sett.goodreads.secret
|
||||||
|
end
|
||||||
|
self.kv_pairs = GoodReadersApi:showData(self.text_input, self.search_type, 1, self.goodreadersKey)
|
||||||
|
self.total_res = GoodReadersApi:getTotalResults()
|
||||||
|
if self.total_res == nil then
|
||||||
|
self.total_res = 0
|
||||||
|
end
|
||||||
|
self.total_res = tonumber(self.total_res)
|
||||||
|
if self.kv_pairs == nil then
|
||||||
|
self.kv_pairs = {}
|
||||||
|
end
|
||||||
|
self.dimen = Geom:new{
|
||||||
|
w = self.width or self.screen_width,
|
||||||
|
h = self.height or self.screen_height,
|
||||||
|
}
|
||||||
|
if Device:isTouchDevice() then
|
||||||
|
self.ges_events.Swipe = {
|
||||||
|
GestureRange:new{
|
||||||
|
ges = "swipe",
|
||||||
|
range = self.dimen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local padding = Screen:scaleBySize(10)
|
||||||
|
self.item_width = self.dimen.w - 2 * padding
|
||||||
|
self.item_height = Screen:scaleBySize(45)
|
||||||
|
-- setup title bar
|
||||||
|
self.title_bar = DoubleKeyValueTitle:new{
|
||||||
|
title = self.title,
|
||||||
|
width = self.item_width,
|
||||||
|
height = self.item_height,
|
||||||
|
kv_page = self,
|
||||||
|
}
|
||||||
|
-- setup main content
|
||||||
|
self.item_margin = self.item_height / 4
|
||||||
|
local line_height = self.item_height + 2 * self.item_margin
|
||||||
|
local content_height = self.dimen.h - self.title_bar:getSize().h
|
||||||
|
self.max_loaded_pages = 1
|
||||||
|
self.items_per_page = math.floor(content_height / line_height)
|
||||||
|
self.pages = math.ceil(self.total_res / self.items_per_page)
|
||||||
|
self.main_content = VerticalGroup:new{}
|
||||||
|
self:_populateItems()
|
||||||
|
-- assemble page
|
||||||
|
self[1] = FrameContainer:new{
|
||||||
|
height = self.dimen.h,
|
||||||
|
padding = padding,
|
||||||
|
bordersize = 0,
|
||||||
|
background = Blitbuffer.COLOR_WHITE,
|
||||||
|
VerticalGroup:new{
|
||||||
|
self.title_bar,
|
||||||
|
self.main_content,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:nextPage()
|
||||||
|
local new_page = math.min(self.show_page + 1, self.pages)
|
||||||
|
if (new_page * self.items_per_page > #self.kv_pairs) and (self.max_loaded_pages < new_page)
|
||||||
|
and #self.kv_pairs < self.total_res then
|
||||||
|
local api_page = math.floor(new_page * self.items_per_page / 20 ) + 1
|
||||||
|
-- load new portion of data
|
||||||
|
local new_pair = GoodReadersApi:showData(self.text_input, self.search_type, api_page, self.goodreadersKey )
|
||||||
|
if new_pair == nil then return end
|
||||||
|
for _, v in pairs(new_pair) do
|
||||||
|
table.insert(self.kv_pairs, v)
|
||||||
|
end
|
||||||
|
self.refresh = true
|
||||||
|
end
|
||||||
|
if new_page > self.show_page then
|
||||||
|
if self.max_loaded_pages == self.show_page then
|
||||||
|
self.max_loaded_pages = self.max_loaded_pages + 1
|
||||||
|
end
|
||||||
|
self.show_page = new_page
|
||||||
|
self:_populateItems()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:prevPage()
|
||||||
|
local new_page = math.max(self.show_page - 1, 1)
|
||||||
|
if new_page < self.show_page then
|
||||||
|
self.show_page = new_page
|
||||||
|
self:_populateItems()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make sure self.item_margin and self.item_height are set before calling this
|
||||||
|
function DoubleKeyValuePage:_populateItems()
|
||||||
|
self.main_content:clear()
|
||||||
|
local idx_offset = (self.show_page - 1) * self.items_per_page
|
||||||
|
for idx = 1, self.items_per_page do
|
||||||
|
local entry = self.kv_pairs[idx_offset + idx]
|
||||||
|
if entry == nil then break end
|
||||||
|
table.insert(self.main_content,
|
||||||
|
VerticalSpan:new{ align = "left", width = self.item_margin })
|
||||||
|
if type(entry) == "table" then
|
||||||
|
table.insert(
|
||||||
|
self.main_content,
|
||||||
|
DoubleKeyValueItem:new{
|
||||||
|
height = self.item_height,
|
||||||
|
width = self.item_width,
|
||||||
|
key = entry[1],
|
||||||
|
value = entry[2],
|
||||||
|
align = "left",
|
||||||
|
callback = entry.callback,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elseif type(entry) == "string" then
|
||||||
|
local c = string.sub(entry, 1, 1)
|
||||||
|
if c == "-" then
|
||||||
|
table.insert(self.main_content, LineWidget:new{
|
||||||
|
background = Blitbuffer.COLOR_LIGHT_GREY,
|
||||||
|
dimen = Geom:new{
|
||||||
|
w = self.item_width,
|
||||||
|
h = Screen:scaleBySize(2)
|
||||||
|
},
|
||||||
|
style = "solid",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(self.main_content,
|
||||||
|
VerticalSpan:new{ width = self.item_margin })
|
||||||
|
end
|
||||||
|
self.title_bar:setPageCount(self.show_page, self.pages)
|
||||||
|
UIManager:setDirty(self, function()
|
||||||
|
return "ui", self.dimen
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:onSwipe(arg, ges_ev)
|
||||||
|
if ges_ev.direction == "west" then
|
||||||
|
local new_page = math.min(self.show_page + 1, self.pages)
|
||||||
|
if (new_page * self.items_per_page > #self.kv_pairs) and (self.max_loaded_pages < new_page)
|
||||||
|
and #self.kv_pairs < self.total_res then
|
||||||
|
local info = InfoMessage:new{text = _("Please wait...")}
|
||||||
|
UIManager:show(info)
|
||||||
|
UIManager:forceRePaint()
|
||||||
|
self:nextPage()
|
||||||
|
UIManager:close(info)
|
||||||
|
else
|
||||||
|
self:nextPage()
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
elseif ges_ev.direction == "east" then
|
||||||
|
self:prevPage()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DoubleKeyValuePage:onClose()
|
||||||
|
UIManager:close(self)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return DoubleKeyValuePage
|
@ -0,0 +1,259 @@
|
|||||||
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||||
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
||||||
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
||||||
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
||||||
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
||||||
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
||||||
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
||||||
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
||||||
|
local VerticalSpan = require("ui/widget/verticalspan")
|
||||||
|
local LineWidget = require("ui/widget/linewidget")
|
||||||
|
local TextWidget = require("ui/widget/textwidget")
|
||||||
|
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
||||||
|
local ImageWidget = require("ui/widget/imagewidget")
|
||||||
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
||||||
|
local CloseButton = require("ui/widget/closebutton")
|
||||||
|
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 _ = require("gettext")
|
||||||
|
local T = require("ffi/util").template
|
||||||
|
local Pic = require("ffi/pic")
|
||||||
|
|
||||||
|
local GoodReaderBook = InputContainer:new{
|
||||||
|
padding = Screen:scaleBySize(15),
|
||||||
|
}
|
||||||
|
|
||||||
|
function GoodReaderBook:init()
|
||||||
|
self.small_font_face = Font:getFace("ffont", 16)
|
||||||
|
self.medium_font_face = Font:getFace("ffont", 18)
|
||||||
|
self.large_font_face = Font:getFace("ffont", 22)
|
||||||
|
self.screen_width = Screen:getSize().w
|
||||||
|
self.screen_height = Screen:getSize().h
|
||||||
|
UIManager:setDirty(self, function()
|
||||||
|
return "ui", self.dimen
|
||||||
|
end)
|
||||||
|
self[1] = FrameContainer:new{
|
||||||
|
width = self.screen_width,
|
||||||
|
height = self.screen_height,
|
||||||
|
background = Blitbuffer.COLOR_WHITE,
|
||||||
|
bordersize = 0,
|
||||||
|
padding = 0,
|
||||||
|
self:getStatusContent(self.screen_width),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReaderBook:getStatusContent(width)
|
||||||
|
return VerticalGroup:new{
|
||||||
|
align = "left",
|
||||||
|
OverlapGroup:new{
|
||||||
|
dimen = Geom:new{ w = width, h = Screen:scaleBySize(30) },
|
||||||
|
CloseButton:new{ window = self },
|
||||||
|
},
|
||||||
|
self:genHeader(_("Book info")),
|
||||||
|
self:genBookInfoGroup(),
|
||||||
|
self:genHeader(_("Review")),
|
||||||
|
self:bookReview(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReaderBook:genHeader(title)
|
||||||
|
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 = (self.screen_width - header_title:getSize().w) / 2 - self.padding * 2
|
||||||
|
local line_container = LeftContainer:new{
|
||||||
|
dimen = Geom:new{ w = line_width, h = self.screen_height / 25 },
|
||||||
|
LineWidget:new{
|
||||||
|
background = Blitbuffer.gray(0.2),
|
||||||
|
dimen = Geom:new{
|
||||||
|
w = line_width,
|
||||||
|
h = 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VerticalGroup:new{
|
||||||
|
VerticalSpan:new{ width = Screen:scaleBySize(5) },
|
||||||
|
HorizontalGroup:new{
|
||||||
|
align = "center",
|
||||||
|
padding_span,
|
||||||
|
line_container,
|
||||||
|
padding_span,
|
||||||
|
header_title,
|
||||||
|
padding_span,
|
||||||
|
line_container,
|
||||||
|
padding_span,
|
||||||
|
},
|
||||||
|
VerticalSpan:new{ width = Screen:scaleBySize(5) },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReaderBook:genBookInfoGroup()
|
||||||
|
local split_span_width = self.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 = self.screen_width - 1.5 * split_span_width - img_width
|
||||||
|
-- title
|
||||||
|
local book_meta_info_group = VerticalGroup:new{
|
||||||
|
align = "center",
|
||||||
|
TextBoxWidget:new{
|
||||||
|
text = self.dates.title,
|
||||||
|
face = self.medium_font_face,
|
||||||
|
padding = 2,
|
||||||
|
alignment = "center",
|
||||||
|
width = width,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
-- author
|
||||||
|
local text_author = TextBoxWidget:new{
|
||||||
|
text = self.dates.author,
|
||||||
|
width = width,
|
||||||
|
face = self.large_font_face,
|
||||||
|
alignment = "center",
|
||||||
|
}
|
||||||
|
table.insert(book_meta_info_group,
|
||||||
|
CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = width, h = text_author:getSize().h },
|
||||||
|
text_author
|
||||||
|
}
|
||||||
|
)
|
||||||
|
--span
|
||||||
|
local span_author = VerticalSpan:new{ width = height * 0.1 }
|
||||||
|
table.insert(book_meta_info_group,
|
||||||
|
CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = width, h = Screen:scaleBySize(10) },
|
||||||
|
span_author
|
||||||
|
}
|
||||||
|
)
|
||||||
|
-- series
|
||||||
|
local text_series = TextWidget:new{
|
||||||
|
text = T(_("Series: %1"), self.dates.series),
|
||||||
|
face = self.small_font_face,
|
||||||
|
padding = 2,
|
||||||
|
}
|
||||||
|
table.insert(book_meta_info_group,
|
||||||
|
CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = width, h = text_series:getSize().h },
|
||||||
|
text_series
|
||||||
|
}
|
||||||
|
)
|
||||||
|
-- rating
|
||||||
|
local text_rating = TextWidget:new{
|
||||||
|
text = T(_("Rating: %1"), self.dates.rating),
|
||||||
|
face = self.small_font_face,
|
||||||
|
padding = 2,
|
||||||
|
}
|
||||||
|
table.insert(book_meta_info_group,
|
||||||
|
CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = width, h = text_rating:getSize().h },
|
||||||
|
text_rating
|
||||||
|
}
|
||||||
|
)
|
||||||
|
-- pages
|
||||||
|
local text_pages = TextWidget:new{
|
||||||
|
text = T(_("Pages: %1"), self.dates.pages),
|
||||||
|
face = self.small_font_face,
|
||||||
|
padding = 2,
|
||||||
|
}
|
||||||
|
table.insert(book_meta_info_group,
|
||||||
|
CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = width, h = text_pages:getSize().h },
|
||||||
|
text_pages
|
||||||
|
}
|
||||||
|
)
|
||||||
|
-- relesse date
|
||||||
|
local text_release = TextWidget:new{
|
||||||
|
text = T(_("Release date: %1"), self.dates.release),
|
||||||
|
face = self.small_font_face,
|
||||||
|
padding = 2,
|
||||||
|
}
|
||||||
|
table.insert(book_meta_info_group,
|
||||||
|
CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = width, h = text_release:getSize().h },
|
||||||
|
text_release
|
||||||
|
}
|
||||||
|
)
|
||||||
|
local book_info_group = HorizontalGroup:new{
|
||||||
|
align = "top",
|
||||||
|
HorizontalSpan:new{ width = split_span_width }
|
||||||
|
}
|
||||||
|
--thumbnail
|
||||||
|
local http = require("socket.http")
|
||||||
|
local body = http.request(self.dates.image)
|
||||||
|
local image = false
|
||||||
|
if body then image = Pic.openJPGDocumentFromMem(body) end
|
||||||
|
if image then
|
||||||
|
table.insert(book_info_group, ImageWidget:new{
|
||||||
|
image = image.image_bb,
|
||||||
|
width = img_width,
|
||||||
|
height = img_height,
|
||||||
|
autoscale = false,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
table.insert(book_info_group, ImageWidget:new{
|
||||||
|
file = "resources/goodreadsnophoto.png",
|
||||||
|
width = img_width,
|
||||||
|
height = img_height,
|
||||||
|
autoscale = false,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local book_info_group_span = HorizontalGroup:new{
|
||||||
|
align = "top",
|
||||||
|
HorizontalSpan:new{ width = split_span_width / 2 }
|
||||||
|
}
|
||||||
|
table.insert(book_info_group, book_info_group_span)
|
||||||
|
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 = self.screen_width, h = self.screen_height * 0.35 },
|
||||||
|
book_info_group,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReaderBook:bookReview()
|
||||||
|
local book_meta_info_group = VerticalGroup:new{
|
||||||
|
align = "center",
|
||||||
|
padding = 0,
|
||||||
|
bordersize = 0,
|
||||||
|
ScrollTextWidget:new{
|
||||||
|
text = self.dates.description,
|
||||||
|
face = self.medium_font_face,
|
||||||
|
padding = 0,
|
||||||
|
width = self.screen_width * 0.9,
|
||||||
|
height = self.screen_height * 0.48,
|
||||||
|
dialog = self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CenterContainer:new{
|
||||||
|
dimen = Geom:new{ w = self.screen_width, h = self.screen_height * 0.50 },
|
||||||
|
book_meta_info_group,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReaderBook:onAnyKeyPressed()
|
||||||
|
return self:onClose()
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReaderBook:onClose()
|
||||||
|
UIManager:setDirty("all")
|
||||||
|
UIManager:close(self)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return GoodReaderBook
|
@ -0,0 +1,212 @@
|
|||||||
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||||
|
local GoodReaderBook = require("goodreaderbook")
|
||||||
|
local InfoMessage = require("ui/widget/infomessage")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local url = require('socket.url')
|
||||||
|
local socket = require('socket')
|
||||||
|
local http = require('socket.http')
|
||||||
|
local https = require('ssl.https')
|
||||||
|
local ltn12 = require('ltn12')
|
||||||
|
local _ = require("gettext")
|
||||||
|
|
||||||
|
local GoodReadsApi = InputContainer:new {
|
||||||
|
goodreaders_key = "",
|
||||||
|
goodreaders_secret = "",
|
||||||
|
total_result = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
function GoodReadsApi:init()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function genSearchURL(text_search, userApi, search_type, npage)
|
||||||
|
if (text_search) then
|
||||||
|
text_search = string.gsub (text_search, "\n", "\r\n")
|
||||||
|
text_search = string.gsub (text_search, "([^%w %-%_%.%~])",
|
||||||
|
function (c) return string.format ("%%%02X", string.byte(c)) end)
|
||||||
|
text_search = string.gsub (text_search, " ", "+")
|
||||||
|
end
|
||||||
|
return (string.format(
|
||||||
|
"http://www.goodreads.com/search?q=%s&search[field]=%s&format=xml&key=%s&page=%s",
|
||||||
|
text_search,
|
||||||
|
search_type,
|
||||||
|
userApi,
|
||||||
|
npage
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function genIdUrl(id, userApi)
|
||||||
|
return (string.format(
|
||||||
|
"https://www.goodreads.com/book/show/%s?format=xml&key=%s",
|
||||||
|
id,
|
||||||
|
userApi
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReadsApi:fetchXml(s_url)
|
||||||
|
local request, sink = {}, {}
|
||||||
|
local parsed = url.parse(s_url)
|
||||||
|
request['url'] = s_url
|
||||||
|
request['method'] = 'GET'
|
||||||
|
request['sink'] = ltn12.sink.table(sink)
|
||||||
|
http.TIMEOUT = 5
|
||||||
|
https.TIMEOUT = 5
|
||||||
|
local httpRequest = parsed.scheme == 'http' and http.request or https.request
|
||||||
|
local headers = socket.skip(1, httpRequest(request))
|
||||||
|
if headers == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local xml = table.concat(sink)
|
||||||
|
if xml ~= "" then
|
||||||
|
return xml
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReadsApi:showSearchTable(data)
|
||||||
|
local books = {}
|
||||||
|
if data == nil then
|
||||||
|
UIManager:show(InfoMessage:new{text =_("Network problem.\nCheck connection.")})
|
||||||
|
return books
|
||||||
|
end
|
||||||
|
self.total_result = data:match("<total[-]results>(.*)</total[-]results>")
|
||||||
|
|
||||||
|
for work in data:gmatch("<work>(.-)</work>") do
|
||||||
|
local book = work:match("<best_book[^>]+>(.*)</best_book>")
|
||||||
|
local id = book:match("<id[^>]+>([^<]+)</id>")
|
||||||
|
local title = book:match("<title>([^<]+)</title>"):gsub(" %(.*#%d+%)$", "")
|
||||||
|
local author = book:match("<name>([^<]+)</name>")
|
||||||
|
table.insert(books, {
|
||||||
|
author = author,
|
||||||
|
title = title,
|
||||||
|
id = id,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if #books == 0 then
|
||||||
|
UIManager:show(InfoMessage:new{text =_("Search not found!")})
|
||||||
|
end
|
||||||
|
return books
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReadsApi:getTotalResults()
|
||||||
|
return self.total_result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanHTMLTags(str_html)
|
||||||
|
local cleaner = {
|
||||||
|
{ "&", "&" },
|
||||||
|
{ "—", "-" },
|
||||||
|
{ "’", "'" },
|
||||||
|
{ " ", " " },
|
||||||
|
{ "<!%[CDATA%[(.*)%]%]>", "%1" },
|
||||||
|
{ "<br%s/>", "\n" },
|
||||||
|
{ "%-%-", "%-" },
|
||||||
|
{ "</p>", "\n" },
|
||||||
|
{ "(%b<>)", "" },
|
||||||
|
{ "\n\n*", "\n" },
|
||||||
|
{ "\n*$", "" },
|
||||||
|
{ "^\n*", "" },
|
||||||
|
}
|
||||||
|
for i=1, #cleaner do
|
||||||
|
local cleans = cleaner[i]
|
||||||
|
str_html = string.gsub(str_html, cleans[1], cleans[2])
|
||||||
|
end
|
||||||
|
return str_html
|
||||||
|
end
|
||||||
|
|
||||||
|
local function showIdTable(data)
|
||||||
|
if data == nil then
|
||||||
|
UIManager:show(InfoMessage:new{text =_("Network problem.\nCheck connection.")})
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
local data1 = data:match("<book>(.*)</reviews_widget>")
|
||||||
|
local title_all = data1:match("<title>(.*)</title>"):gsub("<![[]CDATA[[]", ""):gsub("]]>$", "")
|
||||||
|
local title = title_all:gsub(" %(.*#%d+%)$", "")
|
||||||
|
local average_rating = data1:match("<average_rating>([^<]+)</average_rating>")
|
||||||
|
local series = title_all:match("%(.*#%d+%)$")
|
||||||
|
if series ~= nil then
|
||||||
|
series = series:match("[(](.*)[)]")
|
||||||
|
else
|
||||||
|
series = _("N/A")
|
||||||
|
end
|
||||||
|
local num_pages = data1:match("<num_pages>(.*)</num_pages>"):gsub("<![[]CDATA[[]", ""):gsub("]]>$", "")
|
||||||
|
if num_pages == nil or num_pages =="" then
|
||||||
|
num_pages = _("N/A")
|
||||||
|
end
|
||||||
|
local id = data1:match("<id>([^<]+)</id>"):gsub("<![[]CDATA[[]", ""):gsub("]]>$", "")
|
||||||
|
local author = data1:match("<name>([^<]+)</name>")
|
||||||
|
local description = data1:match("<description>(.*)</description>")
|
||||||
|
description = cleanHTMLTags(description)
|
||||||
|
--change format from medium to large
|
||||||
|
local image = data1:match("<image_url>([^<]+)</image_url>"):gsub("([0-9]+)m/", "%1l/")
|
||||||
|
local day = data1:match("<original_publication_day[^>]+>([^<]+)</original_publication_day>")
|
||||||
|
local month = data1:match("<original_publication_month[^>]+>([^<]+)</original_publication_month>")
|
||||||
|
local year = data1:match("<original_publication_year[^>]+>([^<]+)</original_publication_year>")
|
||||||
|
|
||||||
|
local release = {}
|
||||||
|
if (year) then
|
||||||
|
table.insert(release, year)
|
||||||
|
end
|
||||||
|
if (month) then
|
||||||
|
table.insert(release, string.format("%02d", month))
|
||||||
|
end
|
||||||
|
if (day) then
|
||||||
|
table.insert(release, string.format("%02d", day))
|
||||||
|
end
|
||||||
|
release = table.concat(release, "-")
|
||||||
|
if release == "" then
|
||||||
|
release = _("N/A")
|
||||||
|
end
|
||||||
|
local book_info = {
|
||||||
|
title = title,
|
||||||
|
author = author,
|
||||||
|
series = series,
|
||||||
|
rating = average_rating,
|
||||||
|
pages = num_pages,
|
||||||
|
release = release,
|
||||||
|
description = description,
|
||||||
|
image = image,
|
||||||
|
id = id,
|
||||||
|
}
|
||||||
|
if id == nil then
|
||||||
|
UIManager:show(InfoMessage:new{text = _("Search not found!")})
|
||||||
|
end
|
||||||
|
return book_info
|
||||||
|
end
|
||||||
|
|
||||||
|
-- search_type = all - search all
|
||||||
|
-- search_type = author - serch book by author
|
||||||
|
-- search_type = title - search book by title
|
||||||
|
function GoodReadsApi:showData(search_text, search_type, page, goodreaders_key)
|
||||||
|
local stats = {}
|
||||||
|
local gen_url = genSearchURL(search_text, goodreaders_key, search_type, page)
|
||||||
|
local gen_xml = self:fetchXml(gen_url)
|
||||||
|
local tbl = self:showSearchTable(gen_xml)
|
||||||
|
if #tbl == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
for _, v in pairs(tbl) do
|
||||||
|
local author = v.author
|
||||||
|
local title = v.title
|
||||||
|
local id = v.id
|
||||||
|
table.insert(stats, { author,
|
||||||
|
title,
|
||||||
|
callback = function()
|
||||||
|
local dates = self:showIdData(id, goodreaders_key)
|
||||||
|
if dates.id ~= nil then
|
||||||
|
UIManager:show(GoodReaderBook:new{
|
||||||
|
dates = dates,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
return stats
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReadsApi:showIdData(id, goodreaders_key)
|
||||||
|
local gen_url = genIdUrl(id, goodreaders_key)
|
||||||
|
local gen_xml = self:fetchXml(gen_url)
|
||||||
|
local tbl = showIdTable(gen_xml)
|
||||||
|
return tbl
|
||||||
|
end
|
||||||
|
|
||||||
|
return GoodReadsApi
|
@ -0,0 +1,224 @@
|
|||||||
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||||
|
local InputDialog = require("ui/widget/inputdialog")
|
||||||
|
local DoubleKeyValuePage = require("doublekeyvaluepage")
|
||||||
|
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
||||||
|
local InfoMessage = require("ui/widget/infomessage")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local Screen = require("device").screen
|
||||||
|
local _ = require("gettext")
|
||||||
|
|
||||||
|
local GoodReads = InputContainer:new {
|
||||||
|
goodreaders_key = "",
|
||||||
|
goodreaders_secret = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
function GoodReads:init()
|
||||||
|
local gr_sett = DoubleKeyValuePage:readGRSettings().data
|
||||||
|
if gr_sett.goodreads then
|
||||||
|
self.goodreaders_key = gr_sett.goodreads.key
|
||||||
|
self.goodreaders_secret = gr_sett.goodreads.secret
|
||||||
|
end
|
||||||
|
self.ui.menu:registerToMainMenu(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReads:addToMainMenu(tab_item_table)
|
||||||
|
table.insert(tab_item_table.plugins, {
|
||||||
|
text = _("GoodReads"),
|
||||||
|
sub_item_table = {
|
||||||
|
{
|
||||||
|
text = _("Settings"),
|
||||||
|
callback = function() self:updateSettings() end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Search book all"),
|
||||||
|
callback = function()
|
||||||
|
if self.goodreaders_key ~= "" then
|
||||||
|
self:search("all")
|
||||||
|
else
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Please set up GoodReads key in settings"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Search book by title"),
|
||||||
|
callback = function()
|
||||||
|
if self.goodreaders_key ~= "" then
|
||||||
|
self:search("title")
|
||||||
|
else
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Please set up GoodReads key in settings"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Search book by author"),
|
||||||
|
callback = function()
|
||||||
|
if self.goodreaders_key ~= "" then
|
||||||
|
self:search("author")
|
||||||
|
|
||||||
|
else
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Please set up GoodReads key in settings"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReads:updateSettings()
|
||||||
|
local hint_top
|
||||||
|
local text_top
|
||||||
|
local hint_bottom
|
||||||
|
local text_bottom
|
||||||
|
local text_info = "How to generate key and secret key:\n"..
|
||||||
|
"1. Go to https://www.goodreads.com/user/sign_up and create account\n" ..
|
||||||
|
"2. Register development key on page: https://www.goodreads.com/user/sign_in?rd=true\n" ..
|
||||||
|
"3. Your key and secret key are on https://www.goodreads.com/api/keys\n" ..
|
||||||
|
"4. Enter your generated key and secret key in settings (Login to GoodReads window)"
|
||||||
|
if self.goodreaders_key == "" then
|
||||||
|
hint_top = _("GoodReaders Key Not Set")
|
||||||
|
text_top = ""
|
||||||
|
else
|
||||||
|
hint_top = ""
|
||||||
|
text_top = self.goodreaders_key
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.goodreaders_secret == "" then
|
||||||
|
hint_bottom = _("GoodReaders Secret Key Not Set (optional)")
|
||||||
|
text_bottom = ""
|
||||||
|
else
|
||||||
|
hint_bottom = ""
|
||||||
|
text_bottom = self.goodreaders_key
|
||||||
|
end
|
||||||
|
self.settings_dialog = MultiInputDialog:new {
|
||||||
|
title = _("Login to GoodReads"),
|
||||||
|
fields = {
|
||||||
|
{
|
||||||
|
text = text_top,
|
||||||
|
input_type = "string",
|
||||||
|
hint = hint_top ,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = text_bottom,
|
||||||
|
input_type = "string",
|
||||||
|
hint = hint_bottom,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttons = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
text = _("Cancel"),
|
||||||
|
callback = function()
|
||||||
|
self.settings_dialog:onClose()
|
||||||
|
UIManager:close(self.settings_dialog)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Info"),
|
||||||
|
callback = function()
|
||||||
|
UIManager:show(InfoMessage:new{text = text_info })
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Apply"),
|
||||||
|
callback = function()
|
||||||
|
self:saveSettings(MultiInputDialog:getFields())
|
||||||
|
self.settings_dialog:onClose()
|
||||||
|
UIManager:close(self.settings_dialog)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
width = Screen:getWidth() * 0.95,
|
||||||
|
height = Screen:getHeight() * 0.2,
|
||||||
|
input_type = "text",
|
||||||
|
}
|
||||||
|
self.settings_dialog:onShowKeyboard()
|
||||||
|
UIManager:show(self.settings_dialog)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GoodReads:saveSettings(fields)
|
||||||
|
if fields then
|
||||||
|
self.goodreaders_key = fields[1]
|
||||||
|
self.goodreaders_secret = fields[2]
|
||||||
|
end
|
||||||
|
local settings = {
|
||||||
|
key = self.goodreaders_key,
|
||||||
|
secret = self.goodreaders_secret,
|
||||||
|
}
|
||||||
|
DoubleKeyValuePage:saveGRSettings(settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- search_type = all - search all
|
||||||
|
-- search_type = author - serch book by author
|
||||||
|
-- search_type = title - search book by title
|
||||||
|
function GoodReads:search(search_type)
|
||||||
|
local title_header
|
||||||
|
local hint
|
||||||
|
local search_input
|
||||||
|
local text_input
|
||||||
|
local info
|
||||||
|
local result
|
||||||
|
if search_type == "all" then
|
||||||
|
title_header = _("Search book all in GoodReads")
|
||||||
|
hint = _("Title, author or ISBN")
|
||||||
|
elseif search_type == "author" then
|
||||||
|
title_header = _("Search book by author in GoodReads")
|
||||||
|
hint = _("Author")
|
||||||
|
elseif search_type == "title" then
|
||||||
|
title_header = _("Search book by title in GoodReads")
|
||||||
|
hint = _("Title")
|
||||||
|
end
|
||||||
|
|
||||||
|
search_input = InputDialog:new{
|
||||||
|
title = title_header,
|
||||||
|
input = "",
|
||||||
|
input_hint = hint,
|
||||||
|
input_type = "string",
|
||||||
|
buttons = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
text = _("Cancel"),
|
||||||
|
callback = function()
|
||||||
|
UIManager:close(search_input)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Find"),
|
||||||
|
is_enter_default = true,
|
||||||
|
callback = function()
|
||||||
|
text_input = search_input:getInputText()
|
||||||
|
if text_input ~= nil and text_input ~= "" then
|
||||||
|
info = InfoMessage:new{text = _("Please wait..."), timeout = 0.0}
|
||||||
|
UIManager:show(info)
|
||||||
|
UIManager:nextTick(function()
|
||||||
|
result = DoubleKeyValuePage:new{
|
||||||
|
title = _("Select book"),
|
||||||
|
text_input = text_input,
|
||||||
|
search_type = search_type,
|
||||||
|
}
|
||||||
|
if #result.kv_pairs > 0 then
|
||||||
|
UIManager:show(result)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
UIManager:close(search_input)
|
||||||
|
else
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text =_("Please input text"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
search_input:onShowKeyboard()
|
||||||
|
UIManager:show(search_input)
|
||||||
|
end
|
||||||
|
|
||||||
|
return GoodReads
|
Binary file not shown.
After Width: | Height: | Size: 599 B |
Loading…
Reference in New Issue