diff --git a/plugins/vocabbuilder.koplugin/db.lua b/plugins/vocabbuilder.koplugin/db.lua index 807a4be0d..0a7160996 100644 --- a/plugins/vocabbuilder.koplugin/db.lua +++ b/plugins/vocabbuilder.koplugin/db.lua @@ -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 diff --git a/plugins/vocabbuilder.koplugin/main.lua b/plugins/vocabbuilder.koplugin/main.lua index 26c0fc29a..96381a26e 100644 --- a/plugins/vocabbuilder.koplugin/main.lua +++ b/plugins/vocabbuilder.koplugin/main.lua @@ -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