diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index 3cf47dfdd..d9b332553 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -206,11 +206,12 @@ function Device:retrieveNetworkInfo() end -- Return an integer value to indicate the brightness of the environment. The value should be in --- range [0, 3]. +-- range [0, 4]. -- 0: dark. -- 1: dim, frontlight is needed. --- 2: bright, frontlight is not needed. --- 3: dazzling. +-- 2: neutral, turning frontlight on or off does not impact the reading experience. +-- 3: bright, frontlight is not needed. +-- 4: dazzling. function Device:ambientBrightnessLevel() return 0 end diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 3d34fa2e1..bf429e9f4 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -131,9 +131,10 @@ function Kindle:ambientBrightnessLevel() lipc_handle:close() if type(value) ~= "number" then return 0 end if value < 10 then return 0 end - if value < 256 then return 1 end - if value < 32768 then return 2 end - return 3 + if value < 96 then return 1 end + if value < 192 then return 2 end + if value < 32768 then return 3 end + return 4 end diff --git a/frontend/ui/elements/common_info_menu_table.lua b/frontend/ui/elements/common_info_menu_table.lua index 70a5b2491..d2043ab25 100644 --- a/frontend/ui/elements/common_info_menu_table.lua +++ b/frontend/ui/elements/common_info_menu_table.lua @@ -23,6 +23,9 @@ common_info.version = { common_info.help = { text = _("Help"), } +common_info.more_plugins = { + text = _("More plugins"), +} common_info.quickstart_guide = { text = _("Quickstart guide"), callback = function() diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index ddf812de8..fee325f66 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -29,20 +29,26 @@ local order = { tools = { "calibre_wireless_connection", "evernote", - "keep_alive", - "frontlight_gesture_controller", "statistics", - "battery_statistics", "storage_stat", "cloud_storage", "read_timer", "news_downloader", - "synchronize_time", - "terminal", + "----------------------------", + "more_plugins", "----------------------------", "advanced_settings", "developer_options", }, + more_plugins = { + "auto_frontlight", + "frontlight_gesture_controller", + "battery_statistics", + "synchronize_time", + "keep_alive", + "terminal", + "storage_stat", + }, search = { "dictionary_lookup", "wikipedia_lookup", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 1f7a15888..c63fd9cf0 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -52,16 +52,21 @@ local order = { "read_timer", "calibre_wireless_connection", "evernote", - "keep_alive", - "frontlight_gesture_controller", "statistics", - "battery_statistics", - "storage_stat", - "synchronize_time", "progress_sync", "zsync", "news_downloader", + "----------------------------", + "more_plugins", + }, + more_plugins = { + "auto_frontlight", + "frontlight_gesture_controller", + "battery_statistics", + "synchronize_time", + "keep_alive", "terminal", + "storage_stat", }, search = { "dictionary_lookup", diff --git a/frontend/ui/menusorter.lua b/frontend/ui/menusorter.lua index 72a63f51f..563f65d93 100644 --- a/frontend/ui/menusorter.lua +++ b/frontend/ui/menusorter.lua @@ -103,17 +103,28 @@ function MenuSorter:sort(item_table, order) end end - -- now do the submenus - for i,sub_menu in ipairs(sub_menus) do - local sub_menu_position = self:findById(menu_table["KOMenu:menu_buttons"], sub_menu) - if sub_menu_position then - local sub_menu_content = menu_table[sub_menu] - sub_menu_position.text = sub_menu_content.text - sub_menu_position.sub_item_table = sub_menu_content - -- remove reference from top level output - menu_table[sub_menu] = nil - -- remove reference from input so it won't show up as orphaned - item_table[sub_menu] = nil + -- We should not rely on Lua to magically order the items as we expected: + -- Some menu items cannot be referred until its parent menu item is inserted into + -- menu_table["KOMenu:menu_buttons"]. + -- So we loop until nothing changed anymore. + local changed = true + while changed do + changed = false + -- now do the submenus + for i,sub_menu in ipairs(sub_menus) do + if menu_table[sub_menu] ~= nil then + local sub_menu_position = self:findById(menu_table["KOMenu:menu_buttons"], sub_menu) + if sub_menu_position then + changed = true + local sub_menu_content = menu_table[sub_menu] + sub_menu_position.text = sub_menu_content.text + sub_menu_position.sub_item_table = sub_menu_content + -- remove reference from top level output + menu_table[sub_menu] = nil + -- remove reference from input so it won't show up as orphaned + item_table[sub_menu] = nil + end + end end end -- @TODO avoid this extra mini-loop diff --git a/plugins/autofrontlight.koplugin/main.lua b/plugins/autofrontlight.koplugin/main.lua index 73fa31f94..302845f7c 100644 --- a/plugins/autofrontlight.koplugin/main.lua +++ b/plugins/autofrontlight.koplugin/main.lua @@ -5,54 +5,78 @@ if not Device:isKindle() or return { disabled = true, } end +local ConfirmBox = require("ui/widget/confirmbox") local DataStorage = require("datastorage") local LuaSettings = require("luasettings") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") +local _ = require("gettext") +local T = require("ffi/util").template local AutoFrontlight = { settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/autofrontlight.lua"), settings_id = 0, enabled = false, + last_brightness = -1, } -function AutoFrontlight:_schedule() +function AutoFrontlight:_schedule(settings_id) if not self.enabled then logger.dbg("AutoFrontlight:_schedule() is disabled") return end - - local settings_id = self.settings_id - logger.dbg("AutoFrontlight:_schedule() @ ", os.time(), ", it should be executed at ", os.time() + 1) - UIManager:scheduleIn(1, function() - self:_action(settings_id) - self:_schedule(self.settings_id) - end) -end - -function AutoFrontlight:_action(settings_id) if settings_id ~= self.settings_id then - logger.dbg("AutoFrontlight:_action(): registered settings_id ", + logger.dbg("AutoFrontlight:_schedule(): registered settings_id ", settings_id, " does not equal to current one ", self.settings_id) return end + + logger.dbg("AutoFrontlight:_schedule() starts with settings_id ", settings_id) + + self:_action() + + logger.dbg("AutoFrontlight:_schedule() @ ", os.time(), ", it should be executed at ", os.time() + 2) + UIManager:scheduleIn(2, function() + self:_schedule(settings_id) + end) +end + +function AutoFrontlight:_action() logger.dbg("AutoFrontlight:_action() @ ", os.time()) - if Device:ambientBrightnessLevel() <= 1 then + local current_level = Device:ambientBrightnessLevel() + logger.dbg("AutoFrontlight:_action(): Retrieved ambient brightness level: ", current_level) + if self.last_brightness == current_level then + logger.dbg("AutoFrontlight:_action(): recorded brightness is same as current level ", + self.last_brightness) + return + end + if current_level <= 1 then logger.dbg("AutoFrontlight: going to turn on frontlight") Device:getPowerDevice():turnOnFrontlight() - else + elseif current_level >= 3 then logger.dbg("AutoFrontlight: going to turn off frontlight") Device:getPowerDevice():turnOffFrontlight() end + self.last_brightness = current_level end function AutoFrontlight:init() - self.enabled = not self.settings:nilOrFalse("enable") - logger.dbg("AutoFrontlight:init() self.enabled: ", self.enabled) - self:_schedule() + self.enabled = self.settings:nilOrTrue("enable") + self.settings_id = self.settings_id + 1 + logger.dbg("AutoFrontlight:init() self.enabled: ", self.enabled, " with id ", self.settings_id) + self:_schedule(self.settings_id) +end + +function AutoFrontlight:flipSetting() + self.settings:flipNilOrTrue("enable") + self:init() +end + +function AutoFrontlight:onFlushSettings() + self.settings:flush() end AutoFrontlight:init() @@ -61,4 +85,42 @@ local AutoFrontlightWidget = WidgetContainer:new{ name = "AutoFrontlight", } +function AutoFrontlightWidget:init() + -- self.ui and self.ui.menu are nil in unittests. + if self.ui ~= nil and self.ui.menu ~= nil then + self.ui.menu:registerToMainMenu(self) + end +end + +function AutoFrontlightWidget:flipSetting() + AutoFrontlight:flipSetting() +end + +-- For test only. +function AutoFrontlightWidget:deprecateLastTask() + logger.dbg("AutoFrontlightWidget:deprecateLastTask() @ ", AutoFrontlight.settings_id) + AutoFrontlight.settings_id = AutoFrontlight.settings_id + 1 +end + +function AutoFrontlightWidget:addToMainMenu(menu_items) + menu_items.auto_frontlight = { + text = _("Auto frontlight"), + callback = function() + UIManager:show(ConfirmBox:new{ + text = T(_("Auto frontlight detects the brightness of the environment and automatically turn on and off the frontlight.\nFrontlight will be turned off to save battery in bright environment, and turned on in dark environment.\nDo you want to %1 it?"), + AutoFrontlight.enabled and _("disable") or _("enable")), + ok_text = AutoFrontlight.enabled and _("Disable") or _("Enable"), + ok_callback = function() + self:flipSetting() + end + }) + end, + checked_func = function() return AutoFrontlight.enabled end, + } +end + +function AutoFrontlightWidget:onFlushSettings() + AutoFrontlight:onFlushSettings() +end + return AutoFrontlightWidget diff --git a/spec/unit/autofrontlight_spec.lua b/spec/unit/autofrontlight_spec.lua index bbbe5a0ee..91992e78c 100644 --- a/spec/unit/autofrontlight_spec.lua +++ b/spec/unit/autofrontlight_spec.lua @@ -1,5 +1,5 @@ describe("AutoFrontlight widget tests", function() - local Device, PowerD, MockTime + local Device, PowerD, MockTime, AutoFrontlight setup(function() require("commonrequire") @@ -37,37 +37,99 @@ describe("AutoFrontlight widget tests", function() return self.brightness end Device.input.waitEvent = function() end - end) - - it("should automatically turn on or off frontlight", function() - local UIManager = require("ui/uimanager") - UIManager._run_forever = true require("luasettings"): open(require("datastorage"):getSettingsDir() .. "/autofrontlight.lua"): saveSetting("enable", "true"): close() + require("ui/uimanager")._run_forever = true + end) + + after_each(function() + AutoFrontlight:deprecateLastTask() + -- Ensure the scheduled task from this test case won't impact others. + MockTime:increase(2) + require("ui/uimanager"):handleInput() + AutoFrontlight = nil + end) + + it("should automatically turn on or off frontlight", function() + local UIManager = require("ui/uimanager") local class = dofile("plugins/autofrontlight.koplugin/main.lua") - local AutoFrontlight = class:new() - AutoFrontlight:init() + AutoFrontlight = class:new() Device.brightness = 3 - MockTime:increase(1) + MockTime:increase(2) UIManager:handleInput() assert.are.equal(0, Device:getPowerDevice().frontlight) Device.brightness = 0 - MockTime:increase(1) + MockTime:increase(2) UIManager:handleInput() assert.are.equal(2, Device:getPowerDevice().frontlight) Device.brightness = 1 - MockTime:increase(1) + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + Device.brightness = 2 + MockTime:increase(2) UIManager:handleInput() assert.are.equal(2, Device:getPowerDevice().frontlight) + Device.brightness = 3 + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(0, Device:getPowerDevice().frontlight) + Device.brightness = 4 + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(0, Device:getPowerDevice().frontlight) + Device.brightness = 3 + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(0, Device:getPowerDevice().frontlight) Device.brightness = 2 - MockTime:increase(1) + MockTime:increase(2) UIManager:handleInput() assert.are.equal(0, Device:getPowerDevice().frontlight) + Device.brightness = 1 + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + Device.brightness = 0 + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + end) + + it("should turn on frontlight at the begining", function() + local UIManager = require("ui/uimanager") + local class = dofile("plugins/autofrontlight.koplugin/main.lua") + Device.brightness = 0 + AutoFrontlight = class:new() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + end) + + it("should turn off frontlight at the begining", function() + local UIManager = require("ui/uimanager") + local class = dofile("plugins/autofrontlight.koplugin/main.lua") Device.brightness = 3 - MockTime:increase(1) + AutoFrontlight = class:new() + MockTime:increase(2) UIManager:handleInput() assert.are.equal(0, Device:getPowerDevice().frontlight) end) + + it("should handle configuration update", function() + local UIManager = require("ui/uimanager") + local class = dofile("plugins/autofrontlight.koplugin/main.lua") + Device.brightness = 0 + AutoFrontlight = class:new() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + AutoFrontlight:flipSetting() + Device.brightness = 3 + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + end) end)