|
|
|
--[[--
|
|
|
|
Plugin for setting screen warmth based on the sun position and/or a time schedule
|
|
|
|
|
|
|
|
@module koplugin.autowarmth
|
|
|
|
--]]--
|
|
|
|
|
|
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
|
|
local Device = require("device")
|
|
|
|
local DateTimeWidget = require("ui/widget/datetimewidget")
|
|
|
|
local DoubleSpinWidget = require("/ui/widget/doublespinwidget")
|
|
|
|
local DeviceListener = require("device/devicelistener")
|
|
|
|
local Dispatcher = require("dispatcher")
|
|
|
|
local Event = require("ui/event")
|
|
|
|
local FFIUtil = require("ffi/util")
|
|
|
|
local Font = require("ui/font")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
|
|
local Notification = require("ui/widget/notification")
|
|
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
|
|
local SunTime = require("suntime")
|
|
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
|
|
local logger = require("logger")
|
|
|
|
local _ = require("gettext")
|
|
|
|
local C_ = _.pgettext
|
|
|
|
local Powerd = Device.powerd
|
|
|
|
local T = FFIUtil.template
|
|
|
|
local Screen = require("device").screen
|
|
|
|
local datetime = require("datetime")
|
|
|
|
|
|
|
|
local activate_sun = 1
|
|
|
|
local activate_schedule = 2
|
|
|
|
local activate_closer_noon = 3
|
|
|
|
local activate_closer_midnight = 4
|
|
|
|
|
|
|
|
local midnight_index = 11
|
|
|
|
|
|
|
|
local device_max_warmth = Device:hasNaturalLight() and Powerd.fl_warmth_max or 100
|
|
|
|
local device_warmth_fit_scale = device_max_warmth * (1/100)
|
|
|
|
|
|
|
|
local function frac(x)
|
|
|
|
return x - math.floor(x)
|
|
|
|
end
|
|
|
|
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2 years ago
|
|
|
local AutoWarmth = WidgetContainer:extend{
|
|
|
|
name = "autowarmth",
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2 years ago
|
|
|
sched_times_s = nil, -- array
|
|
|
|
sched_warmths = nil, -- array
|
|
|
|
|
|
|
|
-- Static member that shall survive reloading of the plugin but not a restart
|
|
|
|
fl_turned_off = nil -- true/false if someone (AutoWarmth, gesture ...) has toggled the frontlight
|
|
|
|
}
|
|
|
|
|
|
|
|
function AutoWarmth:init()
|
|
|
|
self:onDispatcherRegisterActions()
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
|
|
|
|
|
|
self.easy_mode = G_reader_settings:nilOrTrue("autowarmth_easy_mode")
|
|
|
|
self.activate = G_reader_settings:readSetting("autowarmth_activate") or 0
|
|
|
|
self.location = G_reader_settings:readSetting("autowarmth_location") or "Geysir"
|
|
|
|
self.latitude = G_reader_settings:readSetting("autowarmth_latitude") or 64.31 --great Geysir in Iceland
|
|
|
|
self.longitude = G_reader_settings:readSetting("autowarmth_longitude") or -20.30
|
|
|
|
self.altitude = G_reader_settings:readSetting("autowarmth_altitude") or 200
|
|
|
|
self.timezone = G_reader_settings:readSetting("autowarmth_timezone") or 0
|
|
|
|
self.scheduler_times = G_reader_settings:readSetting("autowarmth_scheduler_times")
|
|
|
|
or {0.0, 5.5, 6.0, 6.5, 7.0, 13.0, 21.5, 22.0, 22.5, 23.0, 24.0}
|
|
|
|
self.warmth = G_reader_settings:readSetting("autowarmth_warmth")
|
|
|
|
or { 90, 90, 80, 60, 20, 20, 20, 60, 80, 90, 90}
|
|
|
|
|
|
|
|
self.fl_off_during_day = G_reader_settings:readSetting("autowarmth_fl_off_during_day")
|
|
|
|
self.fl_off_during_day_offset_s = G_reader_settings:readSetting("autowarmth_fl_off_during_day_offset_s", 0)
|
|
|
|
if self.easy_mode then
|
|
|
|
self.fl_off_during_day_offset_s = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
self.control_warmth = G_reader_settings:nilOrTrue("autowarmth_control_warmth")
|
|
|
|
self.control_nightmode = G_reader_settings:nilOrTrue("autowarmth_control_nightmode")
|
|
|
|
if not self.control_warmth and not self.control_nightmode then
|
|
|
|
logger.dbg("AutoWarmth: autowarmth_control_warmth and autowarmth_control_nightmode are both false, set them to true")
|
|
|
|
self.control_warmth = true
|
|
|
|
self.control_nightmode = true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Fix entries not in ascending order (only happens by manual editing of settings.reader.lua)
|
|
|
|
for i = 1, #self.scheduler_times - 1 do
|
|
|
|
if self.scheduler_times[i] > self.scheduler_times[i + 1] then
|
|
|
|
self.scheduler_times[i + 1] = self.scheduler_times[i]
|
|
|
|
logger.warn("AutoWarmth: scheduling times fixed.")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- schedule recalculation shortly afer midnight
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:onDispatcherRegisterActions()
|
|
|
|
Dispatcher:registerAction("show_ephemeris",
|
|
|
|
{category="none", event="ShowEphemeris", title=_("Show ephemeris"), general=true})
|
|
|
|
Dispatcher:registerAction("auto_warmth_off",
|
|
|
|
{category="none", event="AutoWarmthOff", title=_("Auto warmth off"), screen=true})
|
|
|
|
Dispatcher:registerAction("auto_warmth_cycle_trough",
|
|
|
|
{category="none", event="AutoWarmthMode", title=_("Auto warmth cycle through modes"), screen=true})
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:onShowEphemeris()
|
|
|
|
self:showTimesInfo(_("Information about the sun in"), true, activate_sun, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:onAutoWarmthOff()
|
|
|
|
self.activate = 0
|
|
|
|
G_reader_settings:saveSetting("autowarmth_activate", self.activate)
|
|
|
|
Notification:notify(_("Auto warmth turned off"))
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:onAutoWarmthMode()
|
|
|
|
if self.activate > 0 then
|
|
|
|
self.activate = self.activate - 1
|
|
|
|
else
|
|
|
|
self.activate = activate_closer_midnight
|
|
|
|
end
|
|
|
|
local notify_text
|
|
|
|
if self.activate == 0 then
|
|
|
|
notify_text = _("Auto warmth turned off")
|
|
|
|
elseif self.activate == activate_sun then
|
|
|
|
notify_text = _("Auto warmth use sun position")
|
|
|
|
elseif self.activate == activate_schedule then
|
|
|
|
notify_text = _("Auto warmth use schedule")
|
|
|
|
elseif self.activate == activate_closer_midnight then
|
|
|
|
notify_text = _("Auto warmth use whatever is closer to midnight")
|
|
|
|
elseif self.activate == activate_closer_noon then
|
|
|
|
notify_text = _("Auto warmth use whatever is closer to noon")
|
|
|
|
end
|
|
|
|
G_reader_settings:saveSetting("autowarmth_activate", self.activate)
|
|
|
|
Notification:notify(notify_text)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:_onResume()
|
|
|
|
logger.dbg("AutoWarmth: onResume")
|
|
|
|
|
|
|
|
local resume_date = os.date("*t")
|
|
|
|
|
|
|
|
-- check if resume and suspend are done on the same day
|
|
|
|
if resume_date.day == SunTime.date.day and resume_date.month == SunTime.date.month
|
|
|
|
and resume_date.year == SunTime.date.year then
|
|
|
|
|
|
|
|
local now_s = SunTime:getTimeInSec(resume_date)
|
|
|
|
self.sched_warmth_index = self.sched_warmth_index - 1 -- scheduleNextWarmth will check this
|
|
|
|
self:scheduleNextWarmthChange(true)
|
|
|
|
self:scheduleToggleFrontlight(now_s) -- reset user toggles at sun set or sun rise
|
|
|
|
self:toggleFrontlight(now_s)
|
|
|
|
-- Reschedule 1sec after midnight
|
|
|
|
UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self)
|
|
|
|
else
|
|
|
|
self:scheduleMidnightUpdate(true) -- resume is on the other day, do all calcs again
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:_onSuspend()
|
|
|
|
logger.dbg("AutoWarmth: onSuspend")
|
|
|
|
UIManager:unschedule(self.scheduleMidnightUpdate)
|
|
|
|
UIManager:unschedule(self.setWarmth)
|
|
|
|
UIManager:unschedule(self.setFrontlight)
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:_onToggleNightMode()
|
|
|
|
logger.dbg("AutoWarmth: onToggleNightMode")
|
|
|
|
if not self.hide_nightmode_warning then
|
|
|
|
local RadioButtonWidget = require("ui/widget/radiobuttonwidget")
|
|
|
|
local radio_buttons = {
|
|
|
|
{{
|
|
|
|
text = _("Show this warning again"),
|
|
|
|
provider = function() end
|
|
|
|
}},
|
|
|
|
{{
|
|
|
|
text = _("Hide the warning until the next book is opened"),
|
|
|
|
provider = function()
|
|
|
|
self.hide_nightmode_warning = true
|
|
|
|
end,
|
|
|
|
}},
|
|
|
|
{{
|
|
|
|
text = _("Disable AutoWarmth's nightmode control"),
|
|
|
|
provider = function()
|
|
|
|
self.control_nightmode = false
|
|
|
|
G_reader_settings:makeFalse("autowarmth_control_nightmode")
|
|
|
|
end,
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
UIManager:show(RadioButtonWidget:new{
|
|
|
|
title_text = _("Night mode changed"),
|
|
|
|
info_text = _("The AutoWarmth plugin might change it again."),
|
|
|
|
width_factor = 0.9,
|
|
|
|
radio_buttons = radio_buttons,
|
|
|
|
callback = function(radio)
|
|
|
|
radio.provider()
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:_onToggleFrontlight()
|
|
|
|
logger.dbg("AutoWarmth: onToggleFrontlight")
|
|
|
|
local now_s = SunTime:getTimeInSec()
|
|
|
|
AutoWarmth.fl_turned_off = now_s >= self.current_times_h[5]*3600 + self.fl_off_during_day_offset_s and
|
|
|
|
now_s < self.current_times_h[7]*3600 - self.fl_off_during_day_offset_s
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:setEventHandlers()
|
|
|
|
self.onResume = self._onResume
|
|
|
|
self.onSuspend = self._onSuspend
|
|
|
|
if self.control_nightmode then
|
|
|
|
self.onToggleNightMode = self._onToggleNightMode
|
|
|
|
self.onSetNightMode = self._onToggleNightMode
|
|
|
|
end
|
|
|
|
if self.fl_off_during_day then
|
|
|
|
self.onToggleFrontlight = self._onToggleFrontlight
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:clearEventHandlers()
|
|
|
|
self.onResume = nil
|
|
|
|
self.onSuspend = nil
|
|
|
|
self.onToggleNightMode = nil
|
|
|
|
self.onSetNightMode = nil
|
|
|
|
self.onToggleFrontlight = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
-- from_resume ... true if called from onResume
|
|
|
|
function AutoWarmth:scheduleMidnightUpdate(from_resume)
|
|
|
|
logger.dbg("AutoWarmth: scheduleMidnightUpdate")
|
|
|
|
-- first unschedule all old functions
|
|
|
|
UIManager:unschedule(self.scheduleMidnightUpdate)
|
|
|
|
UIManager:unschedule(self.setWarmth)
|
|
|
|
UIManager:unschedule(self.setFrontlight)
|
|
|
|
|
|
|
|
-- Calculate current timezone of device, which might change due to daylight saving.
|
|
|
|
local timezone = SunTime:getTimezoneOffset()
|
|
|
|
if timezone ~= self.timezone then
|
|
|
|
G_reader_settings:saveSetting("autowarmth_timezone", self.timezone)
|
|
|
|
self.timezone = timezone
|
|
|
|
end
|
|
|
|
|
|
|
|
SunTime:setPosition(self.location, self.latitude, self.longitude, self.timezone, self.altitude, true)
|
|
|
|
SunTime:setAdvanced()
|
|
|
|
SunTime:setDate() -- today
|
|
|
|
SunTime:calculateTimes() -- calculates times in hours
|
|
|
|
|
|
|
|
self.sched_times_s = {}
|
|
|
|
self.sched_warmths = {}
|
|
|
|
|
|
|
|
local function prepareSchedule(times_h, index1, index2)
|
|
|
|
local time1_h = times_h[index1]
|
|
|
|
if not time1_h then return end
|
|
|
|
|
|
|
|
local time1_s = SunTime:getTimeInSec(time1_h)
|
|
|
|
self.sched_times_s[#self.sched_times_s + 1] = time1_s
|
|
|
|
self.sched_warmths[#self.sched_warmths + 1] = self.warmth[index1]
|
|
|
|
|
|
|
|
local time2_h = times_h[index2]
|
|
|
|
if not time2_h then return end -- to near to the pole
|
|
|
|
local warmth_diff = math.min(self.warmth[index2], 100) - math.min(self.warmth[index1], 100)
|
|
|
|
local time_diff_s = SunTime:getTimeInSec(time2_h) - time1_s
|
|
|
|
if warmth_diff ~= 0 and time_diff_s > 0 then
|
|
|
|
local delta_t = time_diff_s / math.abs(warmth_diff) -- cannot be inf, no problem
|
|
|
|
local delta_w = warmth_diff > 0 and 1 or -1
|
|
|
|
for i = 1, math.abs(warmth_diff) - 1 do
|
|
|
|
local next_warmth = math.min(self.warmth[index1], 100) + delta_w * i
|
|
|
|
-- only apply warmth for steps the hardware has (e.g. Tolino has 0-10 hw steps
|
|
|
|
-- which map to warmth 0, 10, 20, 30 ... 100)
|
|
|
|
if frac(next_warmth * device_warmth_fit_scale) == 0 then
|
|
|
|
table.insert(self.sched_times_s, time1_s + delta_t * i)
|
|
|
|
table.insert(self.sched_warmths, next_warmth)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.activate == activate_sun then
|
|
|
|
self.current_times_h = {unpack(SunTime.times, 1, midnight_index)}
|
|
|
|
elseif self.activate == activate_schedule then
|
|
|
|
self.current_times_h = {unpack(self.scheduler_times, 1, midnight_index)}
|
|
|
|
else
|
|
|
|
self.current_times_h = {unpack(SunTime.times, 1, midnight_index)}
|
|
|
|
if self.activate == activate_closer_noon then
|
|
|
|
for i = 1, midnight_index do
|
|
|
|
if not self.current_times_h[i] then
|
|
|
|
self.current_times_h[i] = self.scheduler_times[i]
|
|
|
|
elseif self.scheduler_times[i] and
|
|
|
|
math.abs(self.current_times_h[i]%24 - 12) > math.abs(self.scheduler_times[i]%24 - 12) then
|
|
|
|
self.current_times_h[i] = self.scheduler_times[i]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else -- activate_closer_midnight
|
|
|
|
for i = 1, midnight_index do
|
|
|
|
if not self.current_times_h[i] then
|
|
|
|
self.current_times_h[i] = self.scheduler_times[i]
|
|
|
|
elseif self.scheduler_times[i] and
|
|
|
|
math.abs(self.current_times_h[i]%24 - 12) < math.abs(self.scheduler_times[i]%24 - 12) then
|
|
|
|
self.current_times_h[i] = self.scheduler_times[i]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.easy_mode then
|
|
|
|
self.current_times_h[1] = nil -- Solar midnight prev. day
|
|
|
|
self.current_times_h[2] = nil -- Astronomical dawn
|
|
|
|
self.current_times_h[3] = nil -- Nautical dawn
|
|
|
|
self.current_times_h[6] = nil -- Solar noon
|
|
|
|
self.current_times_h[9] = nil -- Nautical dust
|
|
|
|
self.current_times_h[10] = nil -- Astronomical dust
|
|
|
|
self.current_times_h[11] = nil -- Solar midnight
|
|
|
|
end
|
|
|
|
|
|
|
|
-- here are dragons
|
|
|
|
local prev_index = 1
|
|
|
|
-- find first valid entry (~= nil)
|
|
|
|
while not self.current_times_h[prev_index] and prev_index <= midnight_index do
|
|
|
|
prev_index = prev_index + 1
|
|
|
|
end
|
|
|
|
local next_index
|
|
|
|
while prev_index <= midnight_index do
|
|
|
|
next_index = prev_index + 1
|
|
|
|
-- find next valid entry (~= nil)
|
|
|
|
while not self.current_times_h[next_index] and next_index <= midnight_index do
|
|
|
|
next_index = next_index + 1
|
|
|
|
end
|
|
|
|
-- now we have two valid indices: prev_index and next_index
|
|
|
|
prepareSchedule(self.current_times_h, prev_index, next_index)
|
|
|
|
-- move on prev_index to the next valid entry
|
|
|
|
prev_index = next_index
|
|
|
|
end
|
|
|
|
|
|
|
|
local now_s = SunTime:getTimeInSec()
|
|
|
|
|
|
|
|
-- Reschedule 1sec after midnight
|
|
|
|
UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self)
|
|
|
|
|
|
|
|
-- set event handlers
|
|
|
|
if self.activate ~= 0 then
|
|
|
|
self:setEventHandlers()
|
|
|
|
-- Schedule the first warmth change
|
|
|
|
self.sched_warmth_index = 1
|
|
|
|
self:scheduleNextWarmthChange(from_resume)
|
|
|
|
self:scheduleToggleFrontlight(now_s) -- reset user toggles at sun set or sun rise
|
|
|
|
self:toggleFrontlight(now_s)
|
|
|
|
else
|
|
|
|
self:clearEventHandlers()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:scheduleToggleFrontlight(now_s)
|
|
|
|
logger.dbg("AutoWarmth: scheduleToggleFrontlight")
|
|
|
|
|
|
|
|
UIManager:unschedule(self.setFrontlight)
|
|
|
|
|
|
|
|
if not self.fl_off_during_day then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
-- Reset user fl toggles at sunset or sunrise with offset, as `scheduleNextWarmthChange` gets called only
|
|
|
|
-- on scheduled warmth changes.
|
|
|
|
local sunset_in_s = self.current_times_h[7] * 3600 - self.fl_off_during_day_offset_s - now_s
|
|
|
|
if sunset_in_s >= 0 then
|
|
|
|
UIManager:scheduleIn(sunset_in_s, self.setFrontlight, self, true)
|
|
|
|
local sunrise_in_s = self.current_times_h[5] * 3600 + self.fl_off_during_day_offset_s - now_s
|
|
|
|
if sunrise_in_s >= 0 then
|
|
|
|
UIManager:scheduleIn(sunrise_in_s, self.setFrontlight, self, false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- turns frontlight on or off and notice AutoDim on turning it off
|
|
|
|
-- enable ... true to enable frontlight, false/nil to disable it
|
|
|
|
function AutoWarmth:setFrontlight(enable)
|
|
|
|
logger.dbg("AutoWarmth: setFrontlight", enable)
|
|
|
|
AutoWarmth.fl_turned_off = not enable
|
|
|
|
if enable then
|
|
|
|
Powerd:turnOnFrontlight()
|
|
|
|
else
|
|
|
|
Powerd:turnOffFrontlight()
|
|
|
|
UIManager:broadcastEvent(Event:new("FrontlightTurnedOff")) -- used e.g. in AutoDim
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- toggles Frontlight on or off, only depending on the time
|
|
|
|
function AutoWarmth:forceToggleFrontlight()
|
|
|
|
if not self.fl_off_during_day then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local now_s = SunTime:getTimeInSec()
|
|
|
|
local is_fl_on = now_s < self.current_times_h[5] * 3600 + self.fl_off_during_day_offset_s
|
|
|
|
or now_s > self.current_times_h[7] * 3600 - self.fl_off_during_day_offset_s
|
|
|
|
|
|
|
|
self:setFrontlight(is_fl_on)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- toggles Frontlight on or off, depending on `now_s`
|
|
|
|
-- decide with the help of `Autowarmth.fl_turned_off` if the fl should be on or off
|
|
|
|
function AutoWarmth:toggleFrontlight(now_s)
|
|
|
|
if self.fl_off_during_day then
|
|
|
|
if now_s >= self.current_times_h[5]*3600 + self.fl_off_during_day_offset_s
|
|
|
|
and now_s < self.current_times_h[7]*3600 - self.fl_off_during_day_offset_s then
|
|
|
|
-- during daytime turn on frontlight off once, user can override this selection by a gesture
|
|
|
|
if AutoWarmth.fl_turned_off ~= true then -- can be false or nil
|
|
|
|
if Powerd:isFrontlightOn() then
|
|
|
|
self:setFrontlight(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
AutoWarmth.fl_turned_off = true -- in case fl_turned_off was nil
|
|
|
|
else
|
|
|
|
-- outside of selected daytime, turn on frontlight once, user can override this selection
|
|
|
|
if AutoWarmth.fl_turned_off ~= false then -- can be true or nil
|
|
|
|
if Powerd:isFrontlightOff() then
|
|
|
|
self:setFrontlight(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
AutoWarmth.fl_turned_off = false -- in case fl_turned_off was nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- schedules the next warmth change
|
|
|
|
-- search_pos ... start searching from that index
|
|
|
|
-- from_resume ... true if first call after resume
|
|
|
|
function AutoWarmth:scheduleNextWarmthChange(from_resume)
|
|
|
|
logger.dbg("AutoWarmth: scheduleNextWarmthChange")
|
|
|
|
UIManager:unschedule(self.scheduleNextWarmthChange)
|
|
|
|
|
|
|
|
if self.activate == 0 or #self.sched_warmths == 0 then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.sched_warmth_index < 1 then
|
|
|
|
self.sched_warmth_index = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
-- `warmth_now` is the value which should be applied now.
|
|
|
|
-- `warmth_in_1p5_s` is valid `1.5` seconds after now for resume on some devices (KA1)
|
|
|
|
-- Most of the times this will be the same as `warmth_now`.
|
|
|
|
-- We need both, as we could have a very rapid change in warmth (depending on user settings)
|
|
|
|
-- or by chance a change in warmth very shortly after (a few ms) resume time.
|
|
|
|
-- Use the last (== solar midnight) warmth value, so that we have a valid value when resuming
|
|
|
|
-- before 24:00:00 but after true midnight. OK, this value is actually not quite the right one,
|
|
|
|
-- as it is calculated for the current day (and not the previous one), but this is for a corner case
|
|
|
|
-- and the error is small.
|
|
|
|
local warmth_now = self.sched_warmths[self.sched_warmth_index]
|
|
|
|
local warmth_in_1p5_s = warmth_now
|
|
|
|
local now_s = SunTime:getTimeInSec()
|
|
|
|
while self.sched_warmth_index <= #self.sched_warmths do
|
|
|
|
if self.sched_times_s[self.sched_warmth_index] > now_s then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
-- update warmth_now
|
|
|
|
warmth_now = self.sched_warmths[self.sched_warmth_index]
|
|
|
|
local j = self.sched_warmth_index
|
|
|
|
if from_resume then
|
|
|
|
-- update warmth_in_1p5_s
|
|
|
|
while j <= #self.sched_warmths and self.sched_times_s[j] <= now_s + 1.5 do
|
|
|
|
-- Most times only one iteration through this loop
|
|
|
|
warmth_in_1p5_s = self.sched_warmths[j]
|
|
|
|
j = j + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- It might be possible that self.sched_warmth_index gets > #self.sched_warmths
|
|
|
|
self.sched_warmth_index = self.sched_warmth_index + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
-- update current warmth immediately
|
|
|
|
self:setWarmth(warmth_now, from_resume) -- force warmth, when from_resume
|
|
|
|
|
|
|
|
if self.sched_warmth_index <= #self.sched_warmths then -- and only then, schedule next warmth change
|
|
|
|
local next_sched_time_s = self.sched_times_s[self.sched_warmth_index] - now_s
|
|
|
|
UIManager:scheduleIn(next_sched_time_s, self.scheduleNextWarmthChange, self, false) -- no force warmth
|
|
|
|
end
|
|
|
|
|
|
|
|
if from_resume then
|
|
|
|
-- On some strange devices like KA1 setWarmth doesn't work right after a resume so
|
|
|
|
-- schedule setting of another valid warmth (=`warmth_in_1p5_s`) again (one time) in 1.5s.
|
|
|
|
-- On sane devices this schedule does no harm.
|
|
|
|
-- see https://github.com/koreader/koreader/issues/8363
|
|
|
|
UIManager:scheduleIn(1.5, self.setWarmth, self, warmth_in_1p5_s, true) -- force warmth one time
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Set warmth and schedule the next warmth change
|
|
|
|
function AutoWarmth:setWarmth(val, force_warmth)
|
|
|
|
if val then
|
|
|
|
if self.control_nightmode then
|
|
|
|
DeviceListener:onSetNightMode(val > 100)
|
|
|
|
end
|
|
|
|
|
|
|
|
if self.control_warmth and Device:hasNaturalLight() then
|
|
|
|
val = math.min(val, 100) -- "mask" night mode
|
|
|
|
Powerd:setWarmth(val, force_warmth)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:hoursToClock(hours)
|
|
|
|
if hours then
|
|
|
|
hours = hours % 24 * 3600 + 0.01 -- round up, due to reduced precision in settings.reader.lua
|
|
|
|
end
|
|
|
|
return datetime.secondsToClock(hours, self.easy_mode)
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:addToMainMenu(menu_items)
|
|
|
|
menu_items.autowarmth = {
|
|
|
|
text = Device:hasNaturalLight() and _("Auto warmth and night mode") or _("Auto night mode"),
|
|
|
|
checked_func = function() return self.activate ~= 0 end,
|
|
|
|
sub_item_table = self:getSubMenuItems(),
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local function tidy_menu(menu, request)
|
|
|
|
for i = #menu, 1, -1 do
|
|
|
|
if menu[i].mode ~=nil then
|
|
|
|
if menu[i].mode ~= request then
|
|
|
|
table.remove(menu, i)
|
|
|
|
else
|
|
|
|
menu[i].mode = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return menu
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:updateItems(touchmenu_instance)
|
|
|
|
touchmenu_instance:updateItems()
|
|
|
|
UIManager:broadcastEvent(Event:new("UpdateFooter", self.view and self.view.footer_visible or false))
|
|
|
|
end
|
|
|
|
|
|
|
|
local about_text = _([[Set the frontlight warmth (if available) and night mode based on a time schedule or the sun's position.
|
|
|
|
|
|
|
|
There are three types of twilight:
|
|
|
|
|
|
|
|
• Civil: You can read a newspaper
|
|
|
|
• Nautical: You can see the first stars
|
|
|
|
• Astronomical: It is really dark
|
|
|
|
|
|
|
|
Custom warmth values can be set for every kind of twilight and sunrise, noon, sunset and midnight.
|
|
|
|
The screen warmth is continuously adjusted to the current time.
|
|
|
|
|
|
|
|
To use the sun's position, a geographical location must be entered. The calculations are very precise, with a deviation less than minute and a half.]])
|
|
|
|
function AutoWarmth:getSubMenuItems()
|
|
|
|
return {
|
|
|
|
{
|
|
|
|
text = Device:hasNaturalLight() and _("About auto warmth and night mode") or _("About auto night mode"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = about_text,
|
|
|
|
width = math.floor(Screen:getWidth() * 0.9),
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
separator = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Activate"),
|
|
|
|
checked_func = function()
|
|
|
|
return self.activate ~= 0
|
|
|
|
end,
|
|
|
|
sub_item_table = self:getActivateMenu(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Expert mode"),
|
|
|
|
checked_func = function()
|
|
|
|
return not self.easy_mode
|
|
|
|
end,
|
|
|
|
help_text = _("In the expert mode, different types of twilight can be used in addition to civil twilight."),
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
self.easy_mode = not self.easy_mode
|
|
|
|
G_reader_settings:saveSetting("autowarmth_easy_mode", self.easy_mode)
|
|
|
|
if self.easy_mode then
|
|
|
|
self.fl_off_during_day_offset_s = 0 -- don't store that value
|
|
|
|
else
|
|
|
|
self.fl_off_during_day_offset_s =
|
|
|
|
G_reader_settings:readSetting("autowarmth_fl_off_during_day_offset_s", 0)
|
|
|
|
end
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then
|
|
|
|
touchmenu_instance.item_table = self:getSubMenuItems()
|
|
|
|
self:updateItems(touchmenu_instance)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Location settings"),
|
|
|
|
sub_item_table = self:getLocationMenu(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Fixed schedule settings"),
|
|
|
|
enabled_func = function()
|
|
|
|
return self.activate ~= activate_sun and self.activate ~= 0
|
|
|
|
end,
|
|
|
|
sub_item_table = self:getScheduleMenu(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
enabled_func = function()
|
|
|
|
return self.activate ~= 0
|
|
|
|
end,
|
|
|
|
text = Device:hasNaturalLight() and _("Warmth and night mode settings") or _("Night mode settings"),
|
|
|
|
sub_item_table = self:getWarmthMenu(),
|
|
|
|
},
|
|
|
|
self:getFlOffDuringDayMenu(),
|
|
|
|
self:getTimesMenu(_("Currently active parameters")),
|
|
|
|
self:getTimesMenu(_("Sun position information for"), true, activate_sun),
|
|
|
|
self:getTimesMenu(_("Fixed schedule information"), false, activate_schedule),
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:getFlOffDuringDayMenu()
|
|
|
|
return {
|
|
|
|
checked_func = function()
|
|
|
|
return self.fl_off_during_day
|
|
|
|
end,
|
|
|
|
text_func = function()
|
|
|
|
if self.fl_off_during_day and self.fl_off_during_day_offset_s ~= 0 and not self.easy_mode then
|
|
|
|
return T(_("Frontlight off during day: %1 min offset"), math.floor(self.fl_off_during_day_offset_s/60))
|
|
|
|
else
|
|
|
|
return _("Frontlight off during day")
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
if self.easy_mode then
|
|
|
|
self.fl_off_during_day = not self.fl_off_during_day
|
|
|
|
G_reader_settings:saveSetting("autowarmth_fl_off_during_day", self.fl_off_during_day)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
self:forceToggleFrontlight()
|
|
|
|
else
|
|
|
|
-- hard limit offset to 15 min after sunset and 30 mins before sunset; sunrise vice versa
|
|
|
|
UIManager:show(SpinWidget:new{
|
|
|
|
title_text = _("Offset for frontlight off"),
|
|
|
|
info_text = _[[At this time the frontlight will be turned
|
|
|
|
• off after sunrise and
|
|
|
|
• on before sunset.]],
|
|
|
|
ok_always_enabled = true,
|
|
|
|
-- read the saved setting, as this get's not overwritten by toggling easy_mode
|
|
|
|
value = G_reader_settings:readSetting("autowarmth_fl_off_during_day_offset_s", 0) * (1/60),
|
|
|
|
value_min = -15,
|
|
|
|
value_max = 30,
|
|
|
|
wrap = false,
|
|
|
|
value_step = 5,
|
|
|
|
value_hold_step = 10,
|
|
|
|
unit = "min",
|
|
|
|
ok_text = _("Set"),
|
|
|
|
callback = function(spin)
|
|
|
|
self.fl_off_during_day_offset_s = spin.value * 60
|
|
|
|
G_reader_settings:saveSetting("autowarmth_fl_off_during_day_offset_s",
|
|
|
|
self.fl_off_during_day_offset_s)
|
|
|
|
self.fl_off_during_day = true
|
|
|
|
G_reader_settings:saveSetting("autowarmth_fl_off_during_day", true)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
self:forceToggleFrontlight()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
extra_text = _("Disable"),
|
|
|
|
extra_callback = self.control_nightmode and function()
|
|
|
|
self.fl_off_during_day = nil
|
|
|
|
G_reader_settings:saveSetting("autowarmth_fl_off_during_day", nil)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
self:forceToggleFrontlight()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
if touchmenu_instance then
|
|
|
|
self:updateItems(touchmenu_instance)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
hold_callback = function()
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _([[This feature turns your front light on at sunset and off at sunrise according to the “Current Active Parameters” in this plugin.
|
|
|
|
|
|
|
|
You can override this change by manually turning the front light on/off. At the next sunrise/sunset, AutoWarmth will toggle again if needed.
|
|
|
|
|
|
|
|
For cloudy autumn days, the switch-on/off time can be shifted by an offset.]]),
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
separator = true,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:getActivateMenu()
|
|
|
|
local function getActivateMenuEntry(text, help_text, activator)
|
|
|
|
return {
|
|
|
|
text = text,
|
|
|
|
help_text = help_text,
|
|
|
|
checked_func = function() return self.activate == activator end,
|
|
|
|
callback = function()
|
|
|
|
self.activate = self.activate ~= activator and activator or 0
|
|
|
|
G_reader_settings:saveSetting("autowarmth_activate", self.activate)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
UIManager:broadcastEvent(Event:new("UpdateFooter", self.view and self.view.footer_visible or false))
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
return {
|
|
|
|
getActivateMenuEntry(_("According to the sun's position"),
|
|
|
|
_("Only use the times calculated from the position of the sun."),
|
|
|
|
activate_sun),
|
|
|
|
getActivateMenuEntry(_("According to the fixed schedule"),
|
|
|
|
_("Only use the times from the fixed schedule."),
|
|
|
|
activate_schedule),
|
|
|
|
getActivateMenuEntry(_("Whatever is closer to noon"),
|
|
|
|
_("Use the times from the sun position or schedule that are closer to noon."),
|
|
|
|
activate_closer_noon),
|
|
|
|
getActivateMenuEntry(_("Whatever is closer to midnight"),
|
|
|
|
_("Use the times from the sun position or schedule that are closer to midnight."),
|
|
|
|
activate_closer_midnight),
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:getLocationMenu()
|
|
|
|
return {{
|
|
|
|
text_func = function()
|
|
|
|
if self.location ~= "" then
|
|
|
|
return T(_("Location: %1"), self.location)
|
|
|
|
else
|
|
|
|
return _("Location")
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
local location_name_dialog
|
|
|
|
location_name_dialog = InputDialog:new{
|
|
|
|
title = _("Location name"),
|
|
|
|
input = self.location,
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
id = "close",
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(location_name_dialog)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("OK"),
|
|
|
|
callback = function()
|
|
|
|
self.location = location_name_dialog:getInputText()
|
|
|
|
G_reader_settings:saveSetting("autowarmth_location",
|
|
|
|
self.location)
|
|
|
|
UIManager:close(location_name_dialog)
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
UIManager:show(location_name_dialog)
|
|
|
|
location_name_dialog:onShowKeyboard()
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text_func = function()
|
|
|
|
return T(_("Coordinates: (%1°, %2°)"), self.latitude, self.longitude)
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
local location_widget = DoubleSpinWidget:new{
|
|
|
|
title_text = _("Set location"),
|
|
|
|
info_text = _("Enter decimal degrees, northern hemisphere and eastern length are '+'."),
|
|
|
|
left_text = _("Latitude"),
|
|
|
|
left_value = self.latitude,
|
|
|
|
left_min = -90,
|
|
|
|
left_max = 90,
|
|
|
|
left_step = 0.1,
|
|
|
|
left_hold_step = 5,
|
|
|
|
left_precision = "%0.2f",
|
|
|
|
right_text = _("Longitude"),
|
|
|
|
right_value = self.longitude,
|
|
|
|
right_min = -180,
|
|
|
|
right_max = 180,
|
|
|
|
right_step = 0.1,
|
|
|
|
right_hold_step = 5,
|
|
|
|
right_precision = "%0.2f",
|
|
|
|
unit = "°",
|
|
|
|
callback = function(lat, long)
|
|
|
|
self.latitude = lat
|
|
|
|
self.longitude = long
|
|
|
|
self.timezone = SunTime:getTimezoneOffset() -- use timezone of device
|
|
|
|
G_reader_settings:saveSetting("autowarmth_latitude", self.latitude)
|
|
|
|
G_reader_settings:saveSetting("autowarmth_longitude", self.longitude)
|
|
|
|
G_reader_settings:saveSetting("autowarmth_timezone", self.timezone)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
UIManager:show(location_widget)
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text_func = function()
|
|
|
|
return T(_("Altitude: %1 m"), self.altitude)
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
UIManager:show(SpinWidget:new{
|
|
|
|
title_text = _("Altitude"),
|
|
|
|
info_text = _("Enter the altitude in meters above sea level."),
|
|
|
|
value = self.altitude,
|
|
|
|
value_min = -100,
|
|
|
|
value_max = 15000, -- intercontinental flight
|
|
|
|
wrap = false,
|
|
|
|
value_step = 10,
|
|
|
|
value_hold_step = 100,
|
|
|
|
unit = C_("Length", "m"),
|
|
|
|
ok_text = _("Set"),
|
|
|
|
callback = function(spin)
|
|
|
|
self.altitude = spin.value
|
|
|
|
G_reader_settings:saveSetting("autowarmth_altitude", self.altitude)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
extra_text = _("Default"),
|
|
|
|
extra_callback = function()
|
|
|
|
self.altitude = 200
|
|
|
|
G_reader_settings:saveSetting("autowarmth_altitude", self.altitude)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
}}
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:getScheduleMenu()
|
|
|
|
local function store_times(touchmenu_instance, new_time, num)
|
|
|
|
self.scheduler_times[num] = new_time
|
|
|
|
G_reader_settings:saveSetting("autowarmth_scheduler_times", self.scheduler_times)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end
|
|
|
|
-- mode == nil ... show always
|
|
|
|
-- == true ... easy mode
|
|
|
|
-- == false ... expert mode
|
|
|
|
local function getScheduleMenuEntry(text, num, mode)
|
|
|
|
return {
|
|
|
|
mode = mode,
|
|
|
|
text_func = function()
|
|
|
|
return T(_("%1: %2"), text,
|
|
|
|
self:hoursToClock(self.scheduler_times[num]))
|
|
|
|
end,
|
|
|
|
checked_func = function()
|
|
|
|
return self.scheduler_times[num] ~= nil
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
local hh = 12
|
|
|
|
local mm = 0
|
|
|
|
if self.scheduler_times[num] then
|
|
|
|
hh = math.floor(self.scheduler_times[num])
|
|
|
|
mm = math.floor(frac(self.scheduler_times[num]) * 60 + 0.5)
|
|
|
|
end
|
|
|
|
UIManager:show(DateTimeWidget:new{
|
|
|
|
title_text = _("Set time"),
|
|
|
|
info_text = _("Enter time in hours and minutes."),
|
|
|
|
hour = hh,
|
|
|
|
hour_min = -1,
|
|
|
|
hour_max = 24,
|
|
|
|
min = mm,
|
|
|
|
ok_text = _("Set time"),
|
|
|
|
callback = function(time)
|
|
|
|
local new_time = time.hour + time.min * (1/60)
|
|
|
|
local function get_valid_time(n, dir)
|
|
|
|
for i = n+dir, dir > 0 and midnight_index or 1, dir do
|
|
|
|
if self.scheduler_times[i] then
|
|
|
|
return self.scheduler_times[i]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return dir > 0 and 0 or 26
|
|
|
|
end
|
|
|
|
if num > 1 and new_time < get_valid_time(num, -1) then
|
|
|
|
UIManager:show(ConfirmBox:new{
|
|
|
|
text = _("This time is before the previous time.\nAdjust the previous time?"),
|
|
|
|
ok_callback = function()
|
|
|
|
for i = num - 1, 1, -1 do
|
|
|
|
if self.scheduler_times[i] then
|
|
|
|
if new_time < self.scheduler_times[i] then
|
|
|
|
self.scheduler_times[i] = new_time
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
store_times(touchmenu_instance, new_time, num)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
elseif num < midnight_index and new_time > get_valid_time(num, 1) then
|
|
|
|
UIManager:show(ConfirmBox:new{
|
|
|
|
text = _("This time is after the subsequent time.\nAdjust the subsequent time?"),
|
|
|
|
ok_callback = function()
|
|
|
|
for i = num + 1, midnight_index do
|
|
|
|
if self.scheduler_times[i] then
|
|
|
|
if new_time > self.scheduler_times[i] then
|
|
|
|
self.scheduler_times[i] = new_time
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
store_times(touchmenu_instance, new_time, num)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
else
|
|
|
|
store_times(touchmenu_instance, new_time, num)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
extra_text = _("Invalidate"),
|
|
|
|
extra_callback = function()
|
|
|
|
store_times(touchmenu_instance, nil, num)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local retval = {
|
|
|
|
getScheduleMenuEntry(_("Solar midnight (previous day)"), 1, false),
|
|
|
|
getScheduleMenuEntry(_("Astronomical dawn"), 2, false),
|
|
|
|
getScheduleMenuEntry(_("Nautical dawn"), 3, false),
|
|
|
|
getScheduleMenuEntry(_("Civil dawn"), 4),
|
|
|
|
getScheduleMenuEntry(_("Sunrise"), 5),
|
|
|
|
getScheduleMenuEntry(_("Solar noon"), 6, false),
|
|
|
|
getScheduleMenuEntry(_("Sunset"), 7),
|
|
|
|
getScheduleMenuEntry(_("Civil dusk"), 8),
|
|
|
|
getScheduleMenuEntry(_("Nautical dusk"), 9, false),
|
|
|
|
getScheduleMenuEntry(_("Astronomical dusk"), 10, false),
|
|
|
|
getScheduleMenuEntry(_("Solar midnight"), 11, false),
|
|
|
|
}
|
|
|
|
|
|
|
|
return tidy_menu(retval, self.easy_mode)
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:getWarmthMenu()
|
|
|
|
-- mode == nil ... show always
|
|
|
|
-- == true ... easy mode
|
|
|
|
-- == false ... expert mode
|
|
|
|
local function getWarmthMenuEntry(text, num, mode)
|
|
|
|
return {
|
|
|
|
mode = mode,
|
|
|
|
text_func = function()
|
|
|
|
if Device:hasNaturalLight() and self.control_warmth then
|
|
|
|
if self.warmth[num] <= 100 then
|
|
|
|
return T(_("%1: %2 %"), text, self.warmth[num])
|
|
|
|
else
|
|
|
|
return T(_("%1: 100 % %2"), text, self.control_nightmode and "+ ☾" or "")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if self.warmth[num] <= 100 then
|
|
|
|
return T(_("%1: ☼"), text)
|
|
|
|
else
|
|
|
|
return T(_("%1: ☾"), text)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
if Device:hasNaturalLight() and self.control_warmth then
|
|
|
|
UIManager:show(SpinWidget:new{
|
|
|
|
title_text = text,
|
|
|
|
info_text = _("Enter percentage of warmth."),
|
|
|
|
value = self.warmth[num],
|
|
|
|
value_min = 0,
|
|
|
|
value_max = 100,
|
|
|
|
wrap = false,
|
|
|
|
value_step = math.floor(100 / device_max_warmth),
|
|
|
|
value_hold_step = 10,
|
|
|
|
unit = "%",
|
|
|
|
ok_text = _("Set"),
|
|
|
|
callback = function(spin)
|
|
|
|
self.warmth[num] = spin.value
|
|
|
|
self.warmth[#self.warmth - num + 1] = spin.value
|
|
|
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
extra_text = self.control_nightmode and _("Use night mode"),
|
|
|
|
extra_callback = self.control_nightmode and function()
|
|
|
|
self.warmth[num] = 110
|
|
|
|
self.warmth[#self.warmth - num + 1] = 110
|
|
|
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
else
|
|
|
|
UIManager:show(ConfirmBox:new{
|
|
|
|
text = _("Night mode"),
|
|
|
|
ok_text = _("Set"),
|
|
|
|
ok_callback = function()
|
|
|
|
self.warmth[num] = 110
|
|
|
|
self.warmth[#self.warmth - num + 1] = 110
|
|
|
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
cancel_text = _("Unset"),
|
|
|
|
cancel_callback = function()
|
|
|
|
self.warmth[num] = 0
|
|
|
|
self.warmth[#self.warmth - num + 1] = 0
|
|
|
|
G_reader_settings:saveSetting("autowarmth_warmth", self.warmth)
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
other_buttons = {{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
}
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local retval = {
|
|
|
|
{
|
|
|
|
text_func = function()
|
|
|
|
if Device:hasNaturalLight() then
|
|
|
|
return T(_("Control: %1%2%3"), self.control_warmth and _("warmth") or "",
|
|
|
|
self.control_warmth and self.control_nightmode and T(" %1 ", _("and")) or "",
|
|
|
|
self.control_nightmode and _("night mode") or "")
|
|
|
|
else
|
|
|
|
return _("Control: night mode")
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
enabled_func = function()
|
|
|
|
return Device:hasNaturalLight()
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
if self.control_warmth and self.control_nightmode then
|
|
|
|
self.control_nightmode = false
|
|
|
|
G_reader_settings:makeFalse("autowarmth_control_nightmode")
|
|
|
|
elseif self.control_warmth and not self.control_nightmode then
|
|
|
|
self.control_warmth = false
|
|
|
|
self.control_nightmode = true
|
|
|
|
G_reader_settings:makeFalse("autowarmth_control_warmth")
|
|
|
|
G_reader_settings:makeTrue("autowarmth_control_nightmode")
|
|
|
|
else
|
|
|
|
self.control_warmth = true
|
|
|
|
self.control_nightmode = true
|
|
|
|
G_reader_settings:makeTrue("autowarmth_control_warmth")
|
|
|
|
G_reader_settings:makeTrue("autowarmth_control_nightmode")
|
|
|
|
end
|
|
|
|
self:scheduleMidnightUpdate()
|
|
|
|
if touchmenu_instance then self:updateItems(touchmenu_instance) end
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
separator = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = Device:hasNaturalLight() and _("Set warmth and night mode for:") or _("Set night mode for:"),
|
|
|
|
enabled = false,
|
|
|
|
},
|
|
|
|
getWarmthMenuEntry(_("Solar noon"), 6, false),
|
|
|
|
getWarmthMenuEntry(_("Sunset and sunrise"), 5),
|
|
|
|
getWarmthMenuEntry(_("Darkest time of civil twilight"), 4),
|
|
|
|
getWarmthMenuEntry(_("Darkest time of nautical twilight"), 3, false),
|
|
|
|
getWarmthMenuEntry(_("Darkest time of astronomical twilight"), 2, false),
|
|
|
|
getWarmthMenuEntry(_("Solar midnight"), 1, false),
|
|
|
|
}
|
|
|
|
|
|
|
|
return tidy_menu(retval, self.easy_mode)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- title
|
|
|
|
-- location: add a location string
|
|
|
|
-- activator: nil .. current_times_h,
|
|
|
|
-- activate_sun .. sun times
|
|
|
|
-- activate_schedule .. scheduler times
|
|
|
|
-- request_easy: true if easy_mode should be used
|
|
|
|
function AutoWarmth:showTimesInfo(title, location, activator, request_easy)
|
|
|
|
local times
|
|
|
|
if not activator then
|
|
|
|
times = self.current_times_h
|
|
|
|
elseif activator == activate_sun then
|
|
|
|
times = SunTime.times
|
|
|
|
elseif activator == activate_schedule then
|
|
|
|
times = self.scheduler_times
|
|
|
|
end
|
|
|
|
|
|
|
|
-- text to show
|
|
|
|
-- t .. times
|
|
|
|
-- num .. index in times
|
|
|
|
local function info_line(indent, text, time, num, face, easy)
|
|
|
|
-- get width of space
|
|
|
|
local unit = " "
|
|
|
|
local tmp = TextWidget:new{
|
|
|
|
text = unit,
|
|
|
|
face = face,
|
|
|
|
}
|
|
|
|
local space_w = tmp:getSize().w
|
|
|
|
|
|
|
|
-- get width of text
|
|
|
|
unit = text
|
|
|
|
tmp = TextWidget:new{
|
|
|
|
text = unit,
|
|
|
|
face = face,
|
|
|
|
}
|
|
|
|
local text_w = tmp:getSize().w
|
|
|
|
tmp:free()
|
|
|
|
|
|
|
|
-- width of text in spaces
|
|
|
|
local str_len = math.floor(text_w / space_w + 0.5)
|
|
|
|
|
|
|
|
local tab_width = 18 - indent
|
|
|
|
local retval = string.rep(" ", indent) .. text .. string.rep(" ", tab_width - str_len)
|
|
|
|
.. self:hoursToClock(time)
|
|
|
|
if easy then
|
|
|
|
if time and num and self.current_times_h[num] and self.current_times_h[num] ~= time then
|
|
|
|
return text .. "\n"
|
|
|
|
else
|
|
|
|
return ""
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not time then -- entry deactivated
|
|
|
|
return retval .. "\n"
|
|
|
|
elseif Device:hasNaturalLight() and self.control_warmth then
|
|
|
|
if self.current_times_h[num] == time then
|
|
|
|
if self.warmth[num] <= 100 then
|
|
|
|
return retval .. " (💡" .. self.warmth[num] .. "%)\n"
|
|
|
|
else
|
|
|
|
return retval .. " (💡100%" .. (self.control_nightmode and " + ☾" or "") .. ")\n"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
return retval .. "\n"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if self.current_times_h[num] == time then
|
|
|
|
if self.warmth[num] <= 100 then
|
|
|
|
return retval .. " (☼)\n"
|
|
|
|
else
|
|
|
|
return retval .. " (☾)\n"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
return retval .. "\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local location_string = ""
|
|
|
|
if location then
|
|
|
|
location_string = " " .. self:getLocationString()
|
|
|
|
end
|
|
|
|
|
|
|
|
local function add_line(indent, text, easy)
|
|
|
|
return easy and "" or (string.rep(" ", indent) .. text .. "\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
local face = Font:getFace("scfont")
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
face = face,
|
|
|
|
width = math.floor(Screen:getWidth() * (self.easy_mode and 0.75 or 0.90)),
|
|
|
|
text = title .. location_string .. ":\n\n" ..
|
|
|
|
info_line(0, _("Solar midnight:"), times[1], 1, face, request_easy) ..
|
|
|
|
add_line(2, _("Dawn"), request_easy) ..
|
|
|
|
info_line(4, _("Astronomic:"), times[2], 2, face, request_easy) ..
|
|
|
|
info_line(4, _("Nautical:"), times[3], 3, face, request_easy)..
|
|
|
|
info_line(request_easy and 0 or 4,
|
|
|
|
request_easy and _("Twilight:") or _("Civil:"), times[4], 4, face) ..
|
|
|
|
add_line(2, _("Dawn"), request_easy) ..
|
|
|
|
info_line(0, _("Sunrise:"), times[5], 5, face) ..
|
|
|
|
"\n" ..
|
|
|
|
info_line(0, _("Solar noon:"), times[6], 6, face, request_easy) ..
|
|
|
|
add_line(0, "", request_easy) ..
|
|
|
|
info_line(0, _("Sunset:"), times[7], 7, face) ..
|
|
|
|
add_line(2, _("Dusk"), request_easy) ..
|
|
|
|
info_line(request_easy and 0 or 4,
|
|
|
|
request_easy and _("Twilight:") or _("Civil:"), times[8], 8, face) ..
|
|
|
|
info_line(4, _("Nautical:"), times[9], 9, face, request_easy) ..
|
|
|
|
info_line(4, _("Astronomic:"), times[10], 10, face, request_easy) ..
|
|
|
|
add_line(2, _("Dusk"), request_easy) ..
|
|
|
|
info_line(0, _("Solar midnight:"), times[midnight_index], midnight_index, face, request_easy) ..
|
|
|
|
-- add fl toggle
|
|
|
|
add_line(0, "", not self.fl_off_during_day) ..
|
|
|
|
add_line(0, _("Toggle frontlight off between"), not self.fl_off_during_day) ..
|
|
|
|
add_line(4, T(_("%1 and %2"),
|
|
|
|
self:hoursToClock(times[5] + self.fl_off_during_day_offset_s * (1/3600)),
|
|
|
|
self:hoursToClock(times[7] - self.fl_off_during_day_offset_s * (1/3600))),
|
|
|
|
not self.fl_off_during_day),
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
-- title
|
|
|
|
-- location: add a location string
|
|
|
|
-- activator: nil .. current_times_h,
|
|
|
|
-- activate_sun .. sun times
|
|
|
|
-- activate_schedule .. scheduler times
|
|
|
|
function AutoWarmth:getTimesMenu(title, location, activator)
|
|
|
|
return {
|
|
|
|
enabled_func = function()
|
|
|
|
-- always show sun position times so you can see ephemeris
|
|
|
|
return self.activate ~= 0 and (self.activate ~= activate_sun or activator == nil)
|
|
|
|
or activator == activate_sun
|
|
|
|
end,
|
|
|
|
text_func = function()
|
|
|
|
if location then
|
|
|
|
return title .. " " .. self:getLocationString()
|
|
|
|
end
|
|
|
|
return title
|
|
|
|
end,
|
|
|
|
callback = function()
|
|
|
|
self:showTimesInfo(title, location, activator, self.easy_mode)
|
|
|
|
end,
|
|
|
|
keep_menu_open = true,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoWarmth:getLocationString()
|
|
|
|
if self.location ~= "" then
|
|
|
|
return self.location
|
|
|
|
else
|
|
|
|
return string.format("(%.2f°,%.2f°)", self.latitude, self.longitude)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return AutoWarmth
|