vocabbuilder.koplugin: always show more button, add book filtering (#9393)

Always show more button instead of delete regardless of review count per #9383.
Adds book filtering per #9256 (from menu or by swiping up).
reviewable/pr9394/r1
weijiuqiao 2 years ago committed by GitHub
parent 70642ba040
commit 30915546f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -93,10 +93,12 @@ function SortItemWidget:init()
end
function SortItemWidget:onTap(_, ges)
if self.item.checked_func and ges.pos:intersectWith(self.checkmark_widget.dimen) then
if self.item.checked_func and ( self.show_parent.sort_disabled or ges.pos:intersectWith(self.checkmark_widget.dimen) ) then
if self.item.callback then
self.item:callback()
end
elseif self.show_parent.sort_disabled then
return true
elseif self.show_parent.marked == self.index then
self.show_parent.marked = 0
else
@ -123,6 +125,7 @@ local SortWidget = FocusManager:new{
-- table of items to sort
item_table = nil, -- mandatory (array)
callback = nil,
sort_disabled = false
}
function SortWidget:init()

@ -5,7 +5,7 @@ local LuaData = require("luadata")
local db_location = DataStorage:getSettingsDir() .. "/vocabulary_builder.sqlite3"
local DB_SCHEMA_VERSION = 20220608
local DB_SCHEMA_VERSION = 20220730
local VOCABULARY_DB_SCHEMA = [[
-- To store looked up words
CREATE TABLE IF NOT EXISTS "vocabulary" (
@ -22,34 +22,25 @@ local VOCABULARY_DB_SCHEMA = [[
CREATE TABLE IF NOT EXISTS "title" (
"id" INTEGER NOT NULL UNIQUE,
"name" TEXT UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
"filter" INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY("id")
);
CREATE INDEX IF NOT EXISTS due_time_index ON vocabulary(due_time);
CREATE INDEX IF NOT EXISTS title_name_index ON title(name);
]]
local VocabularyBuilder = {
count = 0,
}
local VocabularyBuilder = {}
function VocabularyBuilder:init()
VocabularyBuilder:createDB()
end
function VocabularyBuilder:hasItems()
if self.count > 0 then
return true
end
self.count = self:selectCount()
return self.count > 0
end
function VocabularyBuilder:selectCount(conn)
if conn then
return tonumber(conn:rowexec("SELECT count(0) FROM vocabulary;"))
return tonumber(conn:rowexec("SELECT count(0) FROM vocabulary INNER JOIN title ON filter=true AND title_id=id;"))
else
local db_conn = SQ3.open(db_location)
local count = tonumber(db_conn:rowexec("SELECT count(0) FROM vocabulary;"))
local count = tonumber(db_conn:rowexec("SELECT count(0) FROM vocabulary INNER JOIN title ON filter=true AND title_id=id;"))
db_conn:close()
return count
end
@ -67,10 +58,12 @@ function VocabularyBuilder:createDB()
db_conn:exec(VOCABULARY_DB_SCHEMA)
-- Check version
local db_version = tonumber(db_conn:rowexec("PRAGMA user_version;"))
if db_version < DB_SCHEMA_VERSION then
if db_version == 0 then
self:insertLookupData(db_conn)
elseif db_version < 20220608 then
if db_version == 0 then
self:insertLookupData(db_conn)
-- Update version
db_conn:exec(string.format("PRAGMA user_version=%d;", DB_SCHEMA_VERSION))
elseif db_version < DB_SCHEMA_VERSION then
if db_version < 20220608 then
db_conn:exec([[ ALTER TABLE vocabulary ADD prev_context TEXT;
ALTER TABLE vocabulary ADD next_context TEXT;
ALTER TABLE vocabulary ADD title_id INTEGER;
@ -84,6 +77,9 @@ function VocabularyBuilder:createDB()
ALTER TABLE vocabulary DROP book_title;]])
end
if db_version < 20220730 then
db_conn:exec("ALTER TABLE title ADD filter INTEGER NOT NULL DEFAULT 1;")
end
db_conn:exec("CREATE INDEX IF NOT EXISTS title_id_index ON vocabulary(title_id);")
-- Update version
@ -113,13 +109,13 @@ function VocabularyBuilder:insertLookupData(db_conn)
local words = {}
local insert_sql = [[INSERT OR REPLACE INTO vocabulary
(word, title_id, create_time, due_time) values
(?, (SELECT id FROM title WHERE name = ?), ?, ?);]]
(word, title_id, create_time, due_time, review_time) values
(?, (SELECT id FROM title WHERE name = ?), ?, ?, ?);]]
stmt = db_conn:prepare(insert_sql)
for i = #lookup_history_table, 1, -1 do
local value = lookup_history_table[i]
if not words[value.word] then
stmt:bind(value.word, value.book_title or "", value.time, value.time + 5*60)
stmt:bind(value.word, value.book_title or "", value.time, value.time + 5*60, value.time)
stmt:step()
stmt:clearbind():reset()
words[value.word] = true
@ -131,7 +127,7 @@ end
function VocabularyBuilder:_select_items(items, start_idx)
local conn = SQ3.open(db_location)
local sql = string.format("SELECT * FROM vocabulary LEFT JOIN title ON title_id = title.id ORDER BY due_time limit %d OFFSET %d;", 32, start_idx-1)
local 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)
local results = conn:exec(sql)
conn:close()
@ -143,7 +139,7 @@ function VocabularyBuilder:_select_items(items, start_idx)
local item = items[start_idx+i-1]
if item and not item.word then
item.word = results.word[i]
item.review_count = math.max(0, math.min(8, tonumber(results.review_count[i])))
item.review_count = math.max(0, tonumber(results.review_count[i]))
item.book_title = results.name[i] or ""
item.create_time = tonumber( results.create_time[i])
item.review_time = nil --use this field to flag change
@ -188,7 +184,7 @@ function VocabularyBuilder:gotOrForgot(item, isGot)
local current_time = os.time()
local due_time
local target_count = math.min(math.max(item.review_count + (isGot and 1 or -1), 0), 8)
local target_count = math.max(item.review_count + (isGot and 1 or -1), 0)
if not isGot or target_count == 0 then
due_time = current_time + 5 * 60
elseif target_count == 1 then
@ -243,22 +239,58 @@ function VocabularyBuilder:insertOrUpdate(entry)
stmt:step()
stmt:clearbind():reset()
stmt = conn:prepare([[INSERT INTO vocabulary (word, title_id, create_time, due_time, prev_context, next_context)
VALUES (?, (SELECT id FROM title WHERE name = ?), ?, ?, ?, ?)
stmt = conn:prepare([[INSERT INTO vocabulary (word, title_id, create_time, due_time, review_time, prev_context, next_context)
VALUES (?, (SELECT id FROM title WHERE name = ?), ?, ?, ?, ?, ?)
ON CONFLICT(word) DO UPDATE SET title_id = excluded.title_id,
create_time = excluded.create_time,
review_count = MAX(review_count-1, 0),
due_time = ?,
prev_context = ifnull(excluded.prev_context, prev_context),
next_context = ifnull(excluded.next_context, next_context);]]);
stmt:bind(entry.word, entry.book_title, entry.time, entry.time+300,
stmt:bind(entry.word, entry.book_title, entry.time, entry.time+300, entry.time,
entry.prev_context, entry.next_context, entry.time+300)
stmt:step()
stmt:clearbind():reset()
self.count = tonumber(conn:rowexec("SELECT count(0) from vocabulary;"))
conn:close()
end
function VocabularyBuilder:toggleBookFilter(ids)
local id_string = ""
for key, _ in pairs(ids) do
id_string = id_string .. (id_string == "" and "" or ",") .. key
end
local conn = SQ3.open(db_location)
conn:exec("UPDATE title SET filter = (filter | 1) - (filter & 1) WHERE id in ("..id_string..");")
conn:close()
end
function VocabularyBuilder:selectBooks()
local conn = SQ3.open(db_location)
local sql = string.format("SELECT * FROM title")
local results = conn:exec(sql)
conn:close()
local items = {}
if not results then return items end
for i = 1, #results.id do
table.insert(items, {
id = tonumber(results.id[i]),
name = results.name[i],
filter = tonumber(results.filter[i]) ~= 0
})
end
return items
end
function VocabularyBuilder:hasFilteredBook()
local conn = SQ3.open(db_location)
local has_filter = tonumber(conn:rowexec("SELECT count(0) FROM title WHERE filter = false limit 1;"))
conn:close()
return has_filter ~= 0
end
function VocabularyBuilder:remove(item)
local conn = SQ3.open(db_location)
local stmt = conn:prepare("DELETE FROM vocabulary WHERE word = ? ;")
@ -266,7 +298,6 @@ function VocabularyBuilder:remove(item)
stmt:step()
stmt:clearbind():reset()
self.count = self.count - 1
conn:close()
end
@ -280,7 +311,6 @@ end
function VocabularyBuilder:purge()
local conn = SQ3.open(db_location)
conn:exec("DELETE FROM vocabulary; DELETE FROM title;")
self.count = 0
conn:close()
end

@ -32,6 +32,7 @@ local RightContainer = require("ui/widget/container/rightcontainer")
local OverlapGroup = require("ui/widget/overlapgroup")
local Screen = Device.screen
local Size = require("ui/size")
local SortWidget = require("ui/widget/sortwidget")
local TextWidget = require("ui/widget/textwidget")
local TextBoxWidget = require("ui/widget/textboxwidget")
local TitleBar = require("ui/widget/titlebar")
@ -52,6 +53,45 @@ local subtitle_color = Blitbuffer.COLOR_DARK_GRAY
local dim_color = Blitbuffer.Color8(0x22)
local settings = G_reader_settings:readSetting("vocabulary_builder", {enabled = true})
local function onShowFilter(widget)
local sort_items = {}
local book_data = DB:selectBooks()
local toggled = {}
for _, ifo in pairs(book_data) do
table.insert(sort_items, {
text = ifo.name or "",
callback = function()
ifo.filter = not ifo.filter
if toggled[ifo.id] then
toggled[ifo.id] = nil
else
toggled[ifo.id] = true
end
end,
checked_func = function()
return ifo.filter
end,
ifo = ifo,
})
end
local sort_widget = SortWidget:new{
title = _("Filter words from books"),
item_table = sort_items,
sort_disabled = true,
callback = function()
if #toggled then
DB:toggleBookFilter(toggled)
widget:reloadItems()
end
UIManager:setDirty(nil, "ui")
end
}
UIManager:show(sort_widget)
end
--[[--
Menu dialogue widget
--]]--
@ -134,6 +174,14 @@ function MenuDialog:init()
self.context_switch:setPosition(settings.with_context and 2 or 1)
self:mergeLayoutInVertical(self.context_switch)
local filter_button = {
text = _("Filter books"),
callback = function()
self:onClose()
onShowFilter(self.show_parent)
end
}
local edit_button = {
text = self.is_edit_mode and _("Resume") or _("Quick deletion"),
callback = function()
@ -175,6 +223,7 @@ function MenuDialog:init()
local buttons = ButtonTable:new{
width = width,
buttons = {
{filter_button},
{edit_button},
{reset_button},
{clean_button}
@ -287,7 +336,6 @@ function MenuDialog:onConfigChoose(values, name, event, args, position)
end)
end
--[[--
Individual word info dialogue widget
--]]--
@ -549,7 +597,7 @@ end
function VocabItemWidget:initItemWidget()
for i = 1, #self.layout do self.layout[i] = nil end
if not self.show_parent.is_edit_mode and self.item.review_count < 6 then
if not self.show_parent.is_edit_mode then
self.more_button = Button:new{
text = (self.item.prev_context or self.item.next_context) and "" or "",
padding = Size.padding.button,
@ -616,9 +664,32 @@ function VocabItemWidget:initItemWidget()
height = star_width,
dim = true
}
right_side_width = Size.padding.large * 4 + self.item.review_count * (star:getSize().w)
if self.item.review_count > 0 then
if self.item.review_count > 6 then
right_side_width = Size.padding.large * 4 + 9 * (star:getSize().w)
right_widget = HorizontalGroup:new {
dimen = Geom:new{w=0, h = self.height}
}
for i=1, 6, 1 do
table.insert(right_widget, star)
end
table.insert(right_widget,
TextWidget:new {
text = " + ",
face = word_face,
fgcolor = Blitbuffer.COLOR_DARK_GRAY
}
)
table.insert(right_widget, star)
table.insert(right_widget,
TextWidget:new {
text = "× " .. self.item.review_count-6,
face = word_face,
fgcolor = Blitbuffer.COLOR_DARK_GRAY
}
)
elseif self.item.review_count > 0 then
right_side_width = Size.padding.large * 4 + self.item.review_count * (star:getSize().w)
right_widget = HorizontalGroup:new {
dimen = Geom:new{w=0, h = self.height}
}
@ -627,6 +698,7 @@ function VocabItemWidget:initItemWidget()
end
else
star:free()
right_side_width = Size.padding.large * 4
right_widget = HorizontalGroup:new{
dimen = Geom:new{w=0, h = self.height},
HorizontalSpan:new {width = Size.padding.default }
@ -711,7 +783,6 @@ function VocabItemWidget:initItemWidget()
end
function VocabItemWidget:getTimeSinceDue()
if self.item.review_count >= 8 then return "" end
local elapsed = os.time() - self.item.due_time
local abs = math.abs(elapsed)
@ -781,18 +852,14 @@ function VocabItemWidget:onTap(_, ges)
elseif ges.pos.x > self.got_it_button.dimen.x and ges.pos.x < self.got_it_button.dimen.x + self.got_it_button.dimen.w then
self:onGotIt()
elseif ges.pos.x > self.more_button.dimen.x and ges.pos.x < self.more_button.dimen.x + self.more_button.dimen.w then
if self.item.review_count < 6 then
self:showMore()
else
self:remover()
end
self:showMore()
elseif self.item.callback then
self.item.callback(self.item)
end
else
if BD.mirroredUILayout() then
if ges.pos.x > self.more_button.dimen.x and ges.pos.x < self.more_button.dimen.x + self.more_button.dimen.w * 2 then
if self.show_parent.is_edit_mode or self.item.review_count >= 6 then
if self.show_parent.is_edit_mode then
self:remover()
else
self:showMore()
@ -802,7 +869,7 @@ function VocabItemWidget:onTap(_, ges)
end
else
if ges.pos.x > self.more_button.dimen.x - self.more_button.dimen.w and ges.pos.x < self.more_button.dimen.x + self.more_button.dimen.w then
if self.show_parent.is_edit_mode or self.item.review_count >= 6 then
if self.show_parent.is_edit_mode then
self:remover()
else
self:showMore()
@ -1045,8 +1112,8 @@ function VocabularyBuilderWidget:setupItemHeight()
local content_height = self.dimen.h - self.title_bar:getHeight() - self.footer_height - Size.padding.large
self.items_per_page = math.floor(content_height / line_height)
self.item_margin = self.item_margin + math.floor((content_height - self.items_per_page * line_height ) / self.items_per_page )
self.pages = math.ceil(DB:selectCount() / self.items_per_page)
self.show_page = math.min(self.pages, self.show_page)
self.pages = math.ceil(#self.item_table / self.items_per_page)
self.show_page = math.max(1, math.min(self.pages, self.show_page))
end
function VocabularyBuilderWidget:nextPage()
@ -1115,6 +1182,9 @@ function VocabularyBuilderWidget:_populateItems()
item
)
end
if #self.main_content == 0 then
table.insert(self.main_content, HorizontalSpan:new{width = self.item_width})
end
self.footer_page:setText(T(_("Page %1 of %2"), self.show_page, self.pages), self.footer_center_width)
if self.pages > 1 then
self.footer_page:enable()
@ -1122,11 +1192,17 @@ function VocabularyBuilderWidget:_populateItems()
self.footer_page:disableWithoutDimming()
end
if self.pages == 0 then
self.footer_page:setText(_("No items"), self.footer_center_width)
local has_filtered_book = DB:hasFilteredBook()
self.footer_page:setText(has_filtered_book and _("Filter in effect") or _("No items"), self.footer_center_width)
self.footer_first_up:hide()
self.footer_last_down:hide()
self.footer_left:hide()
self.footer_right:hide()
elseif self.footer_left.hidden then
self.footer_first_up:show()
self.footer_last_down:show()
self.footer_left:show()
self.footer_right:show()
end
local chevron_first = "chevron.first"
local chevron_last = "chevron.last"
@ -1193,9 +1269,17 @@ function VocabularyBuilderWidget:showMenu()
reset_callback = function()
self:resetItems()
end,
show_parent = self
})
end
function VocabularyBuilderWidget:reloadItems()
DB:batchUpdateItems(self.item_table)
self.item_table = self.reload_items_callback()
self.pages = math.ceil(#self.item_table / self.items_per_page)
self:goToPage(1)
end
function VocabularyBuilderWidget:onShow()
UIManager:setDirty(self, "flashui")
end
@ -1220,8 +1304,8 @@ function VocabularyBuilderWidget:onSwipe(arg, ges_ev)
-- Allow easier closing with swipe down
self:onClose()
elseif direction == "north" then
-- no use for now
do end -- luacheck: ignore 541
-- open filter
onShowFilter(self)
else -- diagonal swipe
-- trigger full refresh
UIManager:setDirty(nil, "full")
@ -1272,65 +1356,69 @@ function VocabBuilder:addToMainMenu(menu_items)
menu_items.vocabbuilder = {
text = _("Vocabulary builder"),
callback = function()
local vocab_items = {}
for i = 1, DB:selectCount() do
table.insert(vocab_items, {
callback = function(item)
-- custom button table
local tweak_buttons_func
if item.due_time <= os.time() then
tweak_buttons_func = function(buttons)
local tweaked_button_count = 0
local early_break
for j = 1, #buttons do
for k = 1, #buttons[j] do
if buttons[j][k].id == "highlight" and not buttons[j][k].enabled then
buttons[j][k] = {
id = "got_it",
text = _("Got it"),
callback = function()
self.builder_widget:gotItFromDict(item.word)
UIManager:sendEvent(Event:new("Close"))
local reload_items = function()
local vocab_items = {}
for i = 1, DB:selectCount() do
table.insert(vocab_items, {
callback = function(item)
-- custom button table
local tweak_buttons_func
if item.due_time <= os.time() then
tweak_buttons_func = function(buttons)
local tweaked_button_count = 0
local early_break
for j = 1, #buttons do
for k = 1, #buttons[j] do
if buttons[j][k].id == "highlight" and not buttons[j][k].enabled then
buttons[j][k] = {
id = "got_it",
text = _("Got it"),
callback = function()
self.builder_widget:gotItFromDict(item.word)
UIManager:sendEvent(Event:new("Close"))
end
}
if tweaked_button_count == 1 then
early_break = true
break
end
}
if tweaked_button_count == 1 then
early_break = true
break
end
tweaked_button_count = tweaked_button_count + 1
elseif buttons[j][k].id == "search" and not buttons[j][k].enabled then
buttons[j][k] = {
id = "forgot",
text = _("Forgot"),
callback = function()
self.builder_widget:forgotFromDict(item.word)
UIManager:sendEvent(Event:new("Close"))
tweaked_button_count = tweaked_button_count + 1
elseif buttons[j][k].id == "search" and not buttons[j][k].enabled then
buttons[j][k] = {
id = "forgot",
text = _("Forgot"),
callback = function()
self.builder_widget:forgotFromDict(item.word)
UIManager:sendEvent(Event:new("Close"))
end
}
if tweaked_button_count == 1 then
early_break = true
break
end
}
if tweaked_button_count == 1 then
early_break = true
break
tweaked_button_count = tweaked_button_count + 1
end
tweaked_button_count = tweaked_button_count + 1
end
if early_break then break end
end
if early_break then break end
end
end
end
self.builder_widget.current_lookup_word = item.word
self.ui:handleEvent(Event:new("LookupWord", item.word, true, nil, nil, nil, tweak_buttons_func))
end
})
self.builder_widget.current_lookup_word = item.word
self.ui:handleEvent(Event:new("LookupWord", item.word, true, nil, nil, nil, tweak_buttons_func))
end
})
end
return vocab_items
end
self.builder_widget = VocabularyBuilderWidget:new{
title = _("Vocabulary builder"),
item_table = vocab_items,
item_table = reload_items(),
select_items_callback = function(items, start_idx, end_idx)
DB:select_items(items, start_idx, end_idx)
end
end,
reload_items_callback = reload_items
}
UIManager:show(self.builder_widget)
@ -1344,7 +1432,7 @@ function VocabBuilder:onWordLookedUp(word, title)
if self.builder_widget and self.builder_widget.current_lookup_word == word then return true end
local prev_context
local next_context
if settings.with_context then
if settings.with_context and self.ui.highlight then
prev_context, next_context = self.ui.highlight:getSelectedWordContext(15)
end
DB:insertOrUpdate({

Loading…
Cancel
Save