Vocabulary builder: add search ability (#9881)

And allow circle multiswipe to reload words.
reviewable/pr9905/r1
weijiuqiao 2 years ago committed by GitHub
parent e1fe897c9b
commit a76f3f5bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -41,8 +41,13 @@ end
function VocabularyBuilder:selectCount(vocab_widget)
local db_conn = SQ3.open(db_location)
local where_clause = vocab_widget:check_reverse() and " WHERE due_time <= " .. vocab_widget.reload_time or ""
local sql = "SELECT count(0) FROM vocabulary INNER JOIN title ON filter=true AND title_id=id" .. where_clause .. ";"
local sql
if vocab_widget.search_text_sql then
sql = "SELECT count(0) FROM vocabulary WHERE word LIKE '" .. vocab_widget.search_text_sql .. "'"
else
local where_clause = vocab_widget:check_reverse() and " WHERE due_time <= " .. vocab_widget.reload_time or ""
sql = "SELECT count(0) FROM vocabulary INNER JOIN title ON filter=true AND title_id=id" .. where_clause .. ";"
end
local count = tonumber(db_conn:rowexec(sql))
db_conn:close()
return count
@ -138,10 +143,12 @@ function VocabularyBuilder:insertLookupData(db_conn)
end
end
function VocabularyBuilder:_select_items(items, start_idx, reload_time)
function VocabularyBuilder:_select_items(items, start_idx, reload_time, search_text)
local conn = SQ3.open(db_location)
local sql
if not reload_time then
if search_text then
sql = string.format("SELECT * FROM vocabulary INNER JOIN title ON title_id = title.id WHERE word LIKE '%s' LIMIT 32 OFFSET %d", search_text, start_idx-1)
elseif not reload_time then
sql = string.format("SELECT * FROM vocabulary INNER JOIN title ON title_id = title.id AND filter = true ORDER BY due_time limit %d OFFSET %d;", 32, start_idx-1)
else
sql = string.format([[SELECT * FROM vocabulary INNER JOIN title
@ -199,7 +206,7 @@ function VocabularyBuilder:select_items(vocab_widget, start_idx, end_idx)
end
if not start_cursor then return end
self:_select_items(items, start_cursor, vocab_widget:check_reverse() and vocab_widget.reload_time)
self:_select_items(items, start_cursor, vocab_widget:check_reverse() and vocab_widget.reload_time, vocab_widget.search_text_sql)
end

@ -26,7 +26,9 @@ local HorizontalGroup = require("ui/widget/horizontalgroup")
local HorizontalSpan = require("ui/widget/horizontalspan")
local IconButton = require("ui/widget/iconbutton")
local IconWidget = require("ui/widget/iconwidget")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
local InputDialog = require("ui/widget/inputdialog")
local LeftContainer = require("ui/widget/container/leftcontainer")
local LineWidget = require("ui/widget/linewidget")
local MovableContainer = require("ui/widget/container/movablecontainer")
@ -54,8 +56,8 @@ local word_face = Font:getFace("x_smallinfofont")
local subtitle_face = Font:getFace("cfont", 12)
local subtitle_italic_face = Font:getFace("NotoSans-Italic.ttf", 12)
local subtitle_color = Blitbuffer.COLOR_DARK_GRAY
local dim_color = Blitbuffer.Color8(0x22)
local settings = G_reader_settings:readSetting("vocabulary_builder", {enabled = true})
local dim_color = Blitbuffer.COLOR_GRAY_3
local settings = G_reader_settings:readSetting("vocabulary_builder", {enabled = false, with_context = true})
local function resetButtonOnLookupWindow()
if not settings.enabled then -- auto add words
@ -312,12 +314,20 @@ function MenuDialog:init()
show_sync_settings()
end
}
local search_button = {
text = _("Search"),
callback = function()
UIManager:close(self)
self.show_parent:showSearchDialog()
end
}
local buttons = ButtonTable:new{
width = width,
buttons = {
{reverse_button},
{sync_button},
{search_button},
{filter_button, edit_button},
{reset_button, clean_button},
},
@ -938,6 +948,7 @@ function VocabItemWidget:resetProgress()
self.item.due_time = os.time()
self.item.review_time = self.item.due_time
self.item.last_due_time = nil
self.item.is_dim = false
self:initItemWidget()
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen end)
@ -952,6 +963,7 @@ function VocabItemWidget:undo()
self.item.last_review_count = nil
self.item.last_review_time = nil
self.item.last_due_time = nil
self.item.is_dim = false
self:initItemWidget()
UIManager:setDirty(self.show_parent, function()
return "ui", self[1].dimen end)
@ -1086,7 +1098,7 @@ function VocabItemWidget:onShowBookAssignment(title_changed_cb)
face = Font:getFace("smallinfofontbold"),
callback = function()
local dialog
dialog = require("ui/widget/inputdialog"):new{
dialog = InputDialog:new{
title = _("Enter book title:"),
input = "",
input_type = "text",
@ -1202,13 +1214,104 @@ function VocabularyBuilderWidget:init()
}
}
end
self.page_info = HorizontalGroup:new{}
self:refreshFooter()
local bottom_line = LineWidget:new{
dimen = Geom:new{ w = self.item_width, h = Size.line.thick },
background = Blitbuffer.COLOR_LIGHT_GRAY,
}
local vertical_footer = VerticalGroup:new{
bottom_line,
self.page_info,
}
self.footer_height = vertical_footer:getSize().h
local footer = BottomContainer:new{
dimen = self.dimen:copy(),
vertical_footer,
}
-- setup title bar
self.title_bar = TitleBar:new{
width = self.dimen.w,
align = "center",
title_face = Font:getFace("smallinfofontbold"),
bottom_line_color = Blitbuffer.COLOR_LIGHT_GRAY,
with_bottom_line = true,
bottom_line_h_padding = Size.padding.large,
left_icon = "appbar.menu",
left_icon_tap_callback = function() self:showMenu() end,
title = self.title,
close_callback = function() self:onClose() end,
show_parent = self,
}
self:setupItemHeight()
self.main_content = VerticalGroup:new{}
-- calculate item's review button width once
local temp_button = Button:new{
text = _("Got it"),
padding_h = Size.padding.large
}
self.review_button_width = temp_button:getSize().w
temp_button:setText(_("Forgot"))
self.review_button_width = math.min(math.max(self.review_button_width, temp_button:getSize().w), Screen:getWidth()/4)
temp_button:free()
self:_populateItems()
local frame_content = FrameContainer:new{
height = self.dimen.h,
padding = 0,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
VerticalGroup:new{
self.title_bar,
self.main_content,
},
}
local content = OverlapGroup:new{
dimen = self.dimen:copy(),
frame_content,
footer,
}
-- assemble page
self[1] = FrameContainer:new{
height = self.dimen.h,
padding = 0,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
content
}
end
function VocabularyBuilderWidget:refreshFooter()
local has_sync = settings.server ~= nil
local has_search = self.search_text_sql
if self.footer_left ~= nil then -- check whether refresh needed
local should_refresh = has_sync and self.page_info[1] ~= self.footer_sync
or not has_sync and self.page_info[1] == self.footer_sync
if not should_refresh then
should_refresh = has_search and self.page_info[#self.page_info] ~= self.footer_search
or not has_search and self.page_info[#self.page_info] == self.footer_search
end
if not should_refresh then return end
end
self.page_info:clear()
local padding = Size.padding.large
self.width_widget = self.dimen.w - 2 * padding
self.item_width = self.dimen.w - 2 * padding
self.footer_center_width = math.floor(self.width_widget * (32/100))
self.footer_button_width = math.floor(self.width_widget * (12/100))
self.footer_left_corner_width = math.floor(self.width_widget * (8/100))
self.footer_right_corner_width = math.floor(self.width_widget * (12/100))
local left_ratio = 10
local right_ratio = 10
if has_sync and not has_search then
left_ratio = 9
right_ratio = 11
end
self.footer_left_corner_width = math.floor(self.width_widget * left_ratio/100)
self.footer_right_corner_width = math.floor(self.width_widget * right_ratio/100)
-- group for footer
local chevron_left = "chevron.left"
local chevron_right = "chevron.right"
@ -1254,14 +1357,18 @@ function VocabularyBuilderWidget:init()
radius = 0,
show_parent = self,
}
local footer_height = self.footer_last_down:getSize().h
local sync_size = TextWidget:getFontSizeToFitHeight("cfont", footer_height, Size.padding.buttontable*2)
self.footer_sync = Button:new{
text = "",
width = self.footer_left_corner_width,
text_font_size = 18,
width = self.footer_left_corner_width - Size.padding.large * 2,
text_font_size = sync_size,
text_font_bold = false,
bordersize = 0,
radius = 0,
padding = Size.padding.large,
padding_h = Size.padding.large,
padding_v = Size.padding.button,
margin = 0,
show_parent = self,
callback = function()
if not settings.server then
@ -1289,6 +1396,18 @@ function VocabularyBuilderWidget:init()
}
self.footer_sync.label_widget.fgcolor = Blitbuffer.COLOR_GRAY_3
self.footer_search = Button:new{
icon = "appbar.search",
width = self.footer_right_corner_width,
icon_width = math.floor(footer_height - Size.padding.large),
icon_height = math.floor(footer_height - Size.padding.large),
callback = function()
self:showSearchDialog()
end,
bordersize = 0,
radius = 0,
show_parent = self,
}
self.footer_page = Button:new{
text = "",
hold_input = {
@ -1314,82 +1433,61 @@ function VocabularyBuilderWidget:init()
width = self.footer_center_width,
show_parent = self,
}
self.page_info = HorizontalGroup:new{
self.footer_sync,
self.footer_first_up,
self.footer_left,
self.footer_page,
self.footer_right,
self.footer_last_down,
HorizontalSpan:new{ width = self.footer_right_corner_width }
}
local bottom_line = LineWidget:new{
dimen = Geom:new{ w = self.item_width, h = Size.line.thick },
background = Blitbuffer.COLOR_LIGHT_GRAY,
}
local vertical_footer = VerticalGroup:new{
bottom_line,
self.page_info,
}
self.footer_height = vertical_footer:getSize().h
local footer = BottomContainer:new{
dimen = self.dimen:copy(),
vertical_footer,
}
-- setup title bar
self.title_bar = TitleBar:new{
width = self.dimen.w,
align = "center",
title_face = Font:getFace("smallinfofontbold"),
bottom_line_color = Blitbuffer.COLOR_LIGHT_GRAY,
with_bottom_line = true,
bottom_line_h_padding = padding,
left_icon = "appbar.menu",
left_icon_tap_callback = function() self:showMenu() end,
title = self.title,
close_callback = function() self:onClose() end,
show_parent = self,
}
self:setupItemHeight()
self.main_content = VerticalGroup:new{}
-- calculate item's review button width once
local temp_button = Button:new{
text = _("Got it"),
padding_h = Size.padding.large
}
self.review_button_width = temp_button:getSize().w
temp_button:setText(_("Forgot"))
self.review_button_width = math.min(math.max(self.review_button_width, temp_button:getSize().w), Screen:getWidth()/4)
temp_button:free()
table.insert(self.page_info, has_sync and self.footer_sync or HorizontalSpan:new{width=self.footer_left_corner_width})
table.insert(self.page_info, self.footer_first_up)
table.insert(self.page_info, self.footer_left)
table.insert(self.page_info, self.footer_page)
table.insert(self.page_info, self.footer_right)
table.insert(self.page_info, self.footer_last_down)
table.insert(self.page_info, has_search and self.footer_search or HorizontalSpan:new{ width = self.footer_right_corner_width })
end
self:_populateItems()
function VocabularyBuilderWidget:showSearchDialog()
local dialog
dialog = InputDialog:new{
title = _("Search words"),
input = self.search_text or "",
input_hint = _("Search empty content to exit"),
input_type = "text",
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(dialog)
end,
},
{
text = _("Info"),
callback = function()
local text_info = _([[You can use two wildcards when searching: the percent sign (%) and the underscore (_).
% represents any zero or more number of characters and _ represents any single character.
local frame_content = FrameContainer:new{
height = self.dimen.h,
padding = 0,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
VerticalGroup:new{
self.title_bar,
self.main_content,
If no wildcard is used, the searched text will be enclosed with two %'s by default.]])
UIManager:show(InfoMessage:new{ text = text_info })
end,
},
{
text = _("Search"),
is_enter_default = true,
callback = function()
self.search_text = dialog:getInputText()
if self.search_text == "" then
self.search_text_sql = nil
elseif self.search_text:find("%", 1, true) or self.search_text:find("_") then
self.search_text_sql = self.search_text:gsub("'", "''")
else
self.search_text_sql = "%" .. self.search_text:gsub("'", "''") .. "%"
end
UIManager:close(dialog)
self:reloadItems()
end,
},
}
},
}
local content = OverlapGroup:new{
dimen = self.dimen:copy(),
frame_content,
footer,
}
-- assemble page
self[1] = FrameContainer:new{
height = self.dimen.h,
padding = 0,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
content
}
UIManager:show(dialog)
dialog:onShowKeyboard()
end
function VocabularyBuilderWidget:setupItemHeight()
@ -1470,7 +1568,10 @@ function VocabularyBuilderWidget:_populateItems()
item
)
end
table.insert(self.layout, #self.layout, {self.footer_sync})
self:refreshFooter()
if settings.server then
table.insert(self.layout, #self.layout, {self.footer_sync})
end
if #self.main_content == 0 then
table.insert(self.main_content, HorizontalSpan:new{width = self.item_width})
end
@ -1481,9 +1582,14 @@ function VocabularyBuilderWidget:_populateItems()
self.footer_page:disableWithoutDimming()
end
if self.pages == 0 then
local has_filtered_book = DB:hasFilteredBook()
local text = has_filtered_book and _("Filter in effect") or
self:check_reverse() and _("No reviewable items") or _("No items")
local text
if self.search_text_sql then
text = _("Search in effect")
else
local has_filtered_book = DB:hasFilteredBook()
text = has_filtered_book and _("Filter in effect") or
self:check_reverse() and _("No reviewable items") or _("No items")
end
self.footer_page:setText(text, self.footer_center_width)
self.footer_first_up:hide()
self.footer_last_down:hide()
@ -1612,7 +1718,7 @@ end
function VocabularyBuilderWidget:showChangeBookTitleDialog(sort_item, onSuccess)
local dialog
dialog = require("ui/widget/inputdialog"):new {
dialog = InputDialog:new {
title = _("Change book title to:"),
input = sort_item.text,
input_type = "text",
@ -1700,10 +1806,24 @@ function VocabularyBuilderWidget:onSwipe(arg, ges_ev)
end
function VocabularyBuilderWidget:onMultiSwipe(arg, ges_ev)
-- For consistency with other fullscreen widgets where swipe south can't be
-- used to close and where we then allow any multiswipe to close, allow any
-- multiswipe to close this widget too.
self:onClose()
-- if user is drawing a circle or half circle (full circle not always easy), reload
local space_count = 0
for space in ges_ev.multiswipe_directions:gmatch(" ") do
space_count = space_count + 1
if space_count == 2 then break end
end
if space_count == 2 and (
string.find("east south west north east south west", ges_ev.multiswipe_directions)
or string.find("east north west south east north west", ges_ev.multiswipe_directions)
) then
self:reloadItems()
UIManager:show(Notification:new{ text = _("Words reloaded") })
else
-- For consistency with other fullscreen widgets where swipe south can't be
-- used to close and where we then allow any multiswipe to close, allow any
-- multiswipe to close this widget too.
self:onClose()
end
return true
end

Loading…
Cancel
Save