mod: add clock settings tab, timezone support, and clock size option
Fix clock persistence bug caused by stale legacy read in settings deserialization. Add clock size setting (Small/Medium/Large) and timezone selection with North American presets plus custom UTC offset. Move all clock-related settings into a dedicated Clock tab, rename "Home Screen Clock" to "Clock", and move minute-change detection to main loop so the header clock updates on every screen. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -119,6 +119,7 @@ enum class StrId : uint16_t {
|
||||
STR_CAT_READER,
|
||||
STR_CAT_CONTROLS,
|
||||
STR_CAT_SYSTEM,
|
||||
STR_CAT_CLOCK,
|
||||
STR_SLEEP_SCREEN,
|
||||
STR_SLEEP_COVER_MODE,
|
||||
STR_STATUS_BAR,
|
||||
@@ -371,10 +372,24 @@ enum class StrId : uint16_t {
|
||||
STR_PREFERRED_PORTRAIT,
|
||||
STR_PREFERRED_LANDSCAPE,
|
||||
STR_CHOOSE_SOMETHING,
|
||||
STR_HOME_SCREEN_CLOCK,
|
||||
STR_CLOCK,
|
||||
STR_CLOCK_AMPM,
|
||||
STR_CLOCK_24H,
|
||||
STR_SET_TIME,
|
||||
STR_CLOCK_SIZE,
|
||||
STR_CLOCK_SIZE_SMALL,
|
||||
STR_CLOCK_SIZE_MEDIUM,
|
||||
STR_CLOCK_SIZE_LARGE,
|
||||
STR_TIMEZONE,
|
||||
STR_TZ_UTC,
|
||||
STR_TZ_EASTERN,
|
||||
STR_TZ_CENTRAL,
|
||||
STR_TZ_MOUNTAIN,
|
||||
STR_TZ_PACIFIC,
|
||||
STR_TZ_ALASKA,
|
||||
STR_TZ_HAWAII,
|
||||
STR_TZ_CUSTOM,
|
||||
STR_SET_UTC_OFFSET,
|
||||
// Sentinel - must be last
|
||||
_COUNT
|
||||
};
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Displej"
|
||||
STR_CAT_READER: "Čtečka"
|
||||
STR_CAT_CONTROLS: "Ovládací prvky"
|
||||
STR_CAT_SYSTEM: "Systém"
|
||||
STR_CAT_CLOCK: "Hodiny"
|
||||
STR_SLEEP_SCREEN: "Obrazovka spánku"
|
||||
STR_SLEEP_COVER_MODE: "Obrazovka spánku Režim krytu"
|
||||
STR_STATUS_BAR: "Stavový řádek"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Styl knihy"
|
||||
STR_EMBEDDED_STYLE: "Vložený styl"
|
||||
STR_OPDS_SERVER_URL: "URL serveru OPDS"
|
||||
STR_CHOOSE_SOMETHING: "Vyberte si něco ke čtení"
|
||||
STR_HOME_SCREEN_CLOCK: "Hodiny na domovské obrazovce"
|
||||
STR_CLOCK: "Hodiny"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 hodin"
|
||||
STR_SET_TIME: "Nastavit čas"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Display"
|
||||
STR_CAT_READER: "Reader"
|
||||
STR_CAT_CONTROLS: "Controls"
|
||||
STR_CAT_SYSTEM: "System"
|
||||
STR_CAT_CLOCK: "Clock"
|
||||
STR_SLEEP_SCREEN: "Sleep Screen"
|
||||
STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode"
|
||||
STR_STATUS_BAR: "Status Bar"
|
||||
@@ -337,7 +338,21 @@ STR_OVERRIDE_LETTERBOX_FILL: "Override Letterbox Fill"
|
||||
STR_PREFERRED_PORTRAIT: "Preferred Portrait"
|
||||
STR_PREFERRED_LANDSCAPE: "Preferred Landscape"
|
||||
STR_CHOOSE_SOMETHING: "Choose something to read"
|
||||
STR_HOME_SCREEN_CLOCK: "Home Screen Clock"
|
||||
STR_CLOCK: "Clock"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 Hour"
|
||||
STR_SET_TIME: "Set Time"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Affichage"
|
||||
STR_CAT_READER: "Lecteur"
|
||||
STR_CAT_CONTROLS: "Commandes"
|
||||
STR_CAT_SYSTEM: "Système"
|
||||
STR_CAT_CLOCK: "Horloge"
|
||||
STR_SLEEP_SCREEN: "Écran de veille"
|
||||
STR_SLEEP_COVER_MODE: "Mode d’image de l’écran de veille"
|
||||
STR_STATUS_BAR: "Barre d’état"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Style du livre"
|
||||
STR_EMBEDDED_STYLE: "Style intégré"
|
||||
STR_OPDS_SERVER_URL: "URL du serveur OPDS"
|
||||
STR_CHOOSE_SOMETHING: "Choisissez quelque chose à lire"
|
||||
STR_HOME_SCREEN_CLOCK: "Horloge écran d'accueil"
|
||||
STR_CLOCK: "Horloge"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 heures"
|
||||
STR_SET_TIME: "Régler l'heure"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Anzeige"
|
||||
STR_CAT_READER: "Lesen"
|
||||
STR_CAT_CONTROLS: "Bedienung"
|
||||
STR_CAT_SYSTEM: "System"
|
||||
STR_CAT_CLOCK: "Uhr"
|
||||
STR_SLEEP_SCREEN: "Standby-Bild"
|
||||
STR_SLEEP_COVER_MODE: "Standby-Bildmodus"
|
||||
STR_STATUS_BAR: "Statusleiste"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Buch-Stil"
|
||||
STR_EMBEDDED_STYLE: "Eingebetteter Stil"
|
||||
STR_OPDS_SERVER_URL: "OPDS-Server-URL"
|
||||
STR_CHOOSE_SOMETHING: "Wähle etwas zum Lesen"
|
||||
STR_HOME_SCREEN_CLOCK: "Startbildschirm-Uhr"
|
||||
STR_CLOCK: "Uhr"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 Stunden"
|
||||
STR_SET_TIME: "Uhrzeit einstellen"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Tela"
|
||||
STR_CAT_READER: "Leitor"
|
||||
STR_CAT_CONTROLS: "Controles"
|
||||
STR_CAT_SYSTEM: "Sistema"
|
||||
STR_CAT_CLOCK: "Relógio"
|
||||
STR_SLEEP_SCREEN: "Tela de repouso"
|
||||
STR_SLEEP_COVER_MODE: "Modo capa tela repouso"
|
||||
STR_STATUS_BAR: "Barra de status"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Estilo do livro"
|
||||
STR_EMBEDDED_STYLE: "Estilo embutido"
|
||||
STR_OPDS_SERVER_URL: "URL do servidor OPDS"
|
||||
STR_CHOOSE_SOMETHING: "Escolha algo para ler"
|
||||
STR_HOME_SCREEN_CLOCK: "Relógio da tela inicial"
|
||||
STR_CLOCK: "Relógio"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 horas"
|
||||
STR_SET_TIME: "Definir hora"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Экран"
|
||||
STR_CAT_READER: "Чтение"
|
||||
STR_CAT_CONTROLS: "Управление"
|
||||
STR_CAT_SYSTEM: "Система"
|
||||
STR_CAT_CLOCK: "Часы"
|
||||
STR_SLEEP_SCREEN: "Экран сна"
|
||||
STR_SLEEP_COVER_MODE: "Режим обложки сна"
|
||||
STR_STATUS_BAR: "Строка состояния"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Стиль книги"
|
||||
STR_EMBEDDED_STYLE: "Встроенный стиль"
|
||||
STR_OPDS_SERVER_URL: "URL OPDS сервера"
|
||||
STR_CHOOSE_SOMETHING: "Выберите что-нибудь для чтения"
|
||||
STR_HOME_SCREEN_CLOCK: "Часы на главном экране"
|
||||
STR_CLOCK: "Часы"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 часа"
|
||||
STR_SET_TIME: "Установить время"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Pantalla"
|
||||
STR_CAT_READER: "Lector"
|
||||
STR_CAT_CONTROLS: "Control"
|
||||
STR_CAT_SYSTEM: "Sistema"
|
||||
STR_CAT_CLOCK: "Reloj"
|
||||
STR_SLEEP_SCREEN: "Salva Pantallas"
|
||||
STR_SLEEP_COVER_MODE: "Modo de salva pantallas"
|
||||
STR_STATUS_BAR: "Barra de estado"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Estilo del libro"
|
||||
STR_EMBEDDED_STYLE: "Estilo integrado"
|
||||
STR_OPDS_SERVER_URL: "URL del servidor OPDS"
|
||||
STR_CHOOSE_SOMETHING: "Elige algo para leer"
|
||||
STR_HOME_SCREEN_CLOCK: "Reloj de pantalla de inicio"
|
||||
STR_CLOCK: "Reloj"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 horas"
|
||||
STR_SET_TIME: "Establecer hora"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -85,6 +85,7 @@ STR_CAT_DISPLAY: "Skärm"
|
||||
STR_CAT_READER: "Läsare"
|
||||
STR_CAT_CONTROLS: "Kontroller"
|
||||
STR_CAT_SYSTEM: "System"
|
||||
STR_CAT_CLOCK: "Klocka"
|
||||
STR_SLEEP_SCREEN: "Viloskärm"
|
||||
STR_SLEEP_COVER_MODE: "Viloskärmens omslagsläge"
|
||||
STR_STATUS_BAR: "Statusrad"
|
||||
@@ -316,7 +317,21 @@ STR_BOOK_S_STYLE: "Bokstil"
|
||||
STR_EMBEDDED_STYLE: "Inbäddad stil"
|
||||
STR_OPDS_SERVER_URL: "OPDS-serveradress"
|
||||
STR_CHOOSE_SOMETHING: "Välj något att läsa"
|
||||
STR_HOME_SCREEN_CLOCK: "Klocka på hemskärmen"
|
||||
STR_CLOCK: "Klocka"
|
||||
STR_CLOCK_AMPM: "AM/PM"
|
||||
STR_CLOCK_24H: "24 timmar"
|
||||
STR_SET_TIME: "Ställ in tid"
|
||||
STR_CLOCK_SIZE: "Clock Size"
|
||||
STR_CLOCK_SIZE_SMALL: "Small"
|
||||
STR_CLOCK_SIZE_MEDIUM: "Medium"
|
||||
STR_CLOCK_SIZE_LARGE: "Large"
|
||||
STR_TIMEZONE: "Timezone"
|
||||
STR_TZ_UTC: "UTC"
|
||||
STR_TZ_EASTERN: "Eastern"
|
||||
STR_TZ_CENTRAL: "Central"
|
||||
STR_TZ_MOUNTAIN: "Mountain"
|
||||
STR_TZ_PACIFIC: "Pacific"
|
||||
STR_TZ_ALASKA: "Alaska"
|
||||
STR_TZ_HAWAII: "Hawaii"
|
||||
STR_TZ_CUSTOM: "Custom"
|
||||
STR_SET_UTC_OFFSET: "Set UTC Offset"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <Logging.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
@@ -138,7 +139,10 @@ uint8_t CrossPointSettings::writeSettings(FsFile& file, bool count_only) const {
|
||||
// New fields added at end for backward compatibility
|
||||
writer.writeItem(file, preferredPortrait);
|
||||
writer.writeItem(file, preferredLandscape);
|
||||
writer.writeItem(file, homeScreenClock);
|
||||
writer.writeItem(file, clockFormat);
|
||||
writer.writeItem(file, clockSize);
|
||||
writer.writeItem(file, timezone);
|
||||
writer.writeItem(file, timezoneOffsetHours);
|
||||
|
||||
return writer.item_count;
|
||||
}
|
||||
@@ -267,14 +271,19 @@ bool CrossPointSettings::loadFromFile() {
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
readAndValidate(inputFile, sleepScreenLetterboxFill, SLEEP_SCREEN_LETTERBOX_FILL_COUNT);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
{ uint8_t _ignore; serialization::readPod(inputFile, _ignore); } // legacy: sleepScreenGradientDir
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
// New fields added at end for backward compatibility
|
||||
readAndValidate(inputFile, preferredPortrait, ORIENTATION_COUNT);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
readAndValidate(inputFile, preferredLandscape, ORIENTATION_COUNT);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
readAndValidate(inputFile, homeScreenClock, CLOCK_FORMAT_COUNT);
|
||||
readAndValidate(inputFile, clockFormat, CLOCK_FORMAT_COUNT);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
readAndValidate(inputFile, clockSize, CLOCK_SIZE_COUNT);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
readAndValidate(inputFile, timezone, TZ_COUNT);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, timezoneOffsetHours);
|
||||
if (timezoneOffsetHours < -12 || timezoneOffsetHours > 14) timezoneOffsetHours = 0;
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
} while (false);
|
||||
|
||||
@@ -442,3 +451,31 @@ int CrossPointSettings::getReaderFontId() const {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
const char* CrossPointSettings::getTimezonePosixStr() const {
|
||||
switch (timezone) {
|
||||
case TZ_EASTERN:
|
||||
return "EST5EDT,M3.2.0,M11.1.0";
|
||||
case TZ_CENTRAL:
|
||||
return "CST6CDT,M3.2.0,M11.1.0";
|
||||
case TZ_MOUNTAIN:
|
||||
return "MST7MDT,M3.2.0,M11.1.0";
|
||||
case TZ_PACIFIC:
|
||||
return "PST8PDT,M3.2.0,M11.1.0";
|
||||
case TZ_ALASKA:
|
||||
return "AKST9AKDT,M3.2.0,M11.1.0";
|
||||
case TZ_HAWAII:
|
||||
return "HST10";
|
||||
case TZ_CUSTOM: {
|
||||
// Build "UTC<offset>" string where offset sign is inverted per POSIX convention
|
||||
// POSIX TZ: positive = west of UTC, so we negate the user-facing offset
|
||||
static char buf[16];
|
||||
int posixOffset = -timezoneOffsetHours;
|
||||
snprintf(buf, sizeof(buf), "UTC%d", posixOffset);
|
||||
return buf;
|
||||
}
|
||||
case TZ_UTC:
|
||||
default:
|
||||
return "UTC0";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,22 @@ class CrossPointSettings {
|
||||
// Home screen clock format
|
||||
enum CLOCK_FORMAT { CLOCK_OFF = 0, CLOCK_AMPM = 1, CLOCK_24H = 2, CLOCK_FORMAT_COUNT };
|
||||
|
||||
// Clock size
|
||||
enum CLOCK_SIZE { CLOCK_SIZE_SMALL = 0, CLOCK_SIZE_MEDIUM = 1, CLOCK_SIZE_LARGE = 2, CLOCK_SIZE_COUNT };
|
||||
|
||||
// Timezone presets
|
||||
enum TIMEZONE {
|
||||
TZ_UTC = 0,
|
||||
TZ_EASTERN = 1,
|
||||
TZ_CENTRAL = 2,
|
||||
TZ_MOUNTAIN = 3,
|
||||
TZ_PACIFIC = 4,
|
||||
TZ_ALASKA = 5,
|
||||
TZ_HAWAII = 6,
|
||||
TZ_CUSTOM = 7,
|
||||
TZ_COUNT
|
||||
};
|
||||
|
||||
// Sleep screen settings
|
||||
uint8_t sleepScreen = DARK;
|
||||
// Sleep screen cover mode settings
|
||||
@@ -192,8 +208,15 @@ class CrossPointSettings {
|
||||
uint8_t preferredPortrait = PORTRAIT;
|
||||
uint8_t preferredLandscape = LANDSCAPE_CW;
|
||||
|
||||
// Home screen clock display format (OFF by default)
|
||||
uint8_t homeScreenClock = CLOCK_OFF;
|
||||
// Clock display format (OFF by default)
|
||||
uint8_t clockFormat = CLOCK_OFF;
|
||||
// Clock display size
|
||||
uint8_t clockSize = CLOCK_SIZE_SMALL;
|
||||
|
||||
// Timezone setting
|
||||
uint8_t timezone = TZ_UTC;
|
||||
// Custom timezone offset in hours from UTC (-12 to +14)
|
||||
int8_t timezoneOffsetHours = 0;
|
||||
|
||||
~CrossPointSettings() = default;
|
||||
|
||||
@@ -214,6 +237,7 @@ class CrossPointSettings {
|
||||
float getReaderLineCompression() const;
|
||||
unsigned long getSleepTimeoutMs() const;
|
||||
int getRefreshFrequency() const;
|
||||
const char* getTimezonePosixStr() const;
|
||||
};
|
||||
|
||||
// Helper macro to access settings
|
||||
|
||||
@@ -76,9 +76,17 @@ inline std::vector<SettingInfo> getSettingsList() {
|
||||
{StrId::STR_THEME_CLASSIC, StrId::STR_THEME_LYRA}, "uiTheme", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Toggle(StrId::STR_SUNLIGHT_FADING_FIX, &CrossPointSettings::fadingFix, "fadingFix",
|
||||
StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_HOME_SCREEN_CLOCK, &CrossPointSettings::homeScreenClock,
|
||||
{StrId::STR_STATE_OFF, StrId::STR_CLOCK_AMPM, StrId::STR_CLOCK_24H}, "homeScreenClock",
|
||||
StrId::STR_CAT_DISPLAY),
|
||||
// --- Clock ---
|
||||
SettingInfo::Enum(StrId::STR_CLOCK, &CrossPointSettings::clockFormat,
|
||||
{StrId::STR_STATE_OFF, StrId::STR_CLOCK_AMPM, StrId::STR_CLOCK_24H}, "clockFormat",
|
||||
StrId::STR_CAT_CLOCK),
|
||||
SettingInfo::Enum(StrId::STR_CLOCK_SIZE, &CrossPointSettings::clockSize,
|
||||
{StrId::STR_CLOCK_SIZE_SMALL, StrId::STR_CLOCK_SIZE_MEDIUM, StrId::STR_CLOCK_SIZE_LARGE},
|
||||
"clockSize", StrId::STR_CAT_CLOCK),
|
||||
SettingInfo::Enum(StrId::STR_TIMEZONE, &CrossPointSettings::timezone,
|
||||
{StrId::STR_TZ_UTC, StrId::STR_TZ_EASTERN, StrId::STR_TZ_CENTRAL, StrId::STR_TZ_MOUNTAIN,
|
||||
StrId::STR_TZ_PACIFIC, StrId::STR_TZ_ALASKA, StrId::STR_TZ_HAWAII, StrId::STR_TZ_CUSTOM},
|
||||
"timezone", StrId::STR_CAT_CLOCK),
|
||||
|
||||
// --- Reader ---
|
||||
SettingInfo::DynamicEnum(
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
|
||||
#include "Battery.h"
|
||||
@@ -192,19 +191,6 @@ void HomeActivity::freeCoverBuffer() {
|
||||
}
|
||||
|
||||
void HomeActivity::loop() {
|
||||
// Refresh the screen when the displayed minute changes (clock update)
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
const int currentMinute = t->tm_hour * 60 + t->tm_min;
|
||||
if (lastRenderedMinute >= 0 && currentMinute != lastRenderedMinute) {
|
||||
requestUpdate();
|
||||
}
|
||||
lastRenderedMinute = currentMinute;
|
||||
}
|
||||
}
|
||||
|
||||
const int menuCount = getMenuItemCount();
|
||||
|
||||
buttonNavigator.onNext([this, menuCount] {
|
||||
|
||||
@@ -16,7 +16,6 @@ class HomeActivity final : public Activity {
|
||||
bool recentsLoaded = false;
|
||||
bool firstRenderDone = false;
|
||||
bool hasOpdsUrl = false;
|
||||
int lastRenderedMinute = -1; // Track displayed minute for clock auto-update
|
||||
bool coverRendered = false; // Track if cover has been rendered once
|
||||
bool coverBufferStored = false; // Track if cover buffer is stored
|
||||
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
||||
|
||||
101
src/activities/settings/SetTimezoneOffsetActivity.cpp
Normal file
101
src/activities/settings/SetTimezoneOffsetActivity.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "SetTimezoneOffsetActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
void SetTimezoneOffsetActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
offsetHours = SETTINGS.timezoneOffsetHours;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void SetTimezoneOffsetActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void SetTimezoneOffsetActivity::loop() {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
SETTINGS.timezoneOffsetHours = offsetHours;
|
||||
SETTINGS.saveToFile();
|
||||
// Apply timezone immediately
|
||||
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
|
||||
tzset();
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up)) {
|
||||
if (offsetHours < 14) {
|
||||
offsetHours++;
|
||||
requestUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
|
||||
if (offsetHours > -12) {
|
||||
offsetHours--;
|
||||
requestUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SetTimezoneOffsetActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const int lineHeight12 = renderer.getLineHeight(UI_12_FONT_ID);
|
||||
|
||||
// Title
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 20, tr(STR_SET_UTC_OFFSET), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Format the offset string
|
||||
char offsetStr[16];
|
||||
if (offsetHours >= 0) {
|
||||
snprintf(offsetStr, sizeof(offsetStr), "UTC+%d", offsetHours);
|
||||
} else {
|
||||
snprintf(offsetStr, sizeof(offsetStr), "UTC%d", offsetHours);
|
||||
}
|
||||
|
||||
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, offsetStr);
|
||||
const int startX = (pageWidth - textWidth) / 2;
|
||||
const int valueY = 80;
|
||||
|
||||
// Draw selection highlight
|
||||
constexpr int highlightPad = 10;
|
||||
renderer.fillRoundedRect(startX - highlightPad, valueY - 4, textWidth + highlightPad * 2, lineHeight12 + 8, 6,
|
||||
Color::LightGray);
|
||||
|
||||
// Draw the offset text
|
||||
renderer.drawText(UI_12_FONT_ID, startX, valueY, offsetStr, true);
|
||||
|
||||
// Draw up/down arrows
|
||||
const int arrowX = pageWidth / 2;
|
||||
const int arrowUpY = valueY - 20;
|
||||
const int arrowDownY = valueY + lineHeight12 + 12;
|
||||
constexpr int arrowSize = 6;
|
||||
for (int row = 0; row < arrowSize; row++) {
|
||||
renderer.drawLine(arrowX - row, arrowUpY + row, arrowX + row, arrowUpY + row);
|
||||
}
|
||||
for (int row = 0; row < arrowSize; row++) {
|
||||
renderer.drawLine(arrowX - row, arrowDownY + arrowSize - 1 - row, arrowX + row, arrowDownY + arrowSize - 1 - row);
|
||||
}
|
||||
|
||||
// Button hints
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SAVE), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
21
src/activities/settings/SetTimezoneOffsetActivity.h
Normal file
21
src/activities/settings/SetTimezoneOffsetActivity.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/Activity.h"
|
||||
|
||||
class SetTimezoneOffsetActivity final : public Activity {
|
||||
public:
|
||||
explicit SetTimezoneOffsetActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: Activity("SetTZOffset", renderer, mappedInput), onBack(onBack) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
const std::function<void()> onBack;
|
||||
int8_t offsetHours = 0;
|
||||
};
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "ButtonRemapActivity.h"
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "ClearCacheActivity.h"
|
||||
@@ -12,19 +15,22 @@
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "SetTimeActivity.h"
|
||||
#include "SetTimezoneOffsetActivity.h"
|
||||
#include "SettingsList.h"
|
||||
#include "activities/network/WifiSelectionActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
const StrId SettingsActivity::categoryNames[categoryCount] = {StrId::STR_CAT_DISPLAY, StrId::STR_CAT_READER,
|
||||
StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM};
|
||||
StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM,
|
||||
StrId::STR_CAT_CLOCK};
|
||||
|
||||
void SettingsActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
|
||||
// Build per-category vectors from the shared settings list
|
||||
displaySettings.clear();
|
||||
clockSettings.clear();
|
||||
readerSettings.clear();
|
||||
controlsSettings.clear();
|
||||
systemSettings.clear();
|
||||
@@ -33,6 +39,8 @@ void SettingsActivity::onEnter() {
|
||||
if (setting.category == StrId::STR_NONE_OPT) continue;
|
||||
if (setting.category == StrId::STR_CAT_DISPLAY) {
|
||||
displaySettings.push_back(std::move(setting));
|
||||
} else if (setting.category == StrId::STR_CAT_CLOCK) {
|
||||
clockSettings.push_back(std::move(setting));
|
||||
} else if (setting.category == StrId::STR_CAT_READER) {
|
||||
readerSettings.push_back(std::move(setting));
|
||||
} else if (setting.category == StrId::STR_CAT_CONTROLS) {
|
||||
@@ -44,7 +52,7 @@ void SettingsActivity::onEnter() {
|
||||
}
|
||||
|
||||
// Append device-only ACTION items
|
||||
displaySettings.push_back(SettingInfo::Action(StrId::STR_SET_TIME, SettingAction::SetTime));
|
||||
rebuildClockActions();
|
||||
controlsSettings.insert(controlsSettings.begin(),
|
||||
SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network));
|
||||
@@ -136,6 +144,9 @@ void SettingsActivity::loop() {
|
||||
case 3:
|
||||
currentSettings = &systemSettings;
|
||||
break;
|
||||
case 4:
|
||||
currentSettings = &clockSettings;
|
||||
break;
|
||||
}
|
||||
settingsCount = static_cast<int>(currentSettings->size());
|
||||
}
|
||||
@@ -207,6 +218,9 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
case SettingAction::SetTime:
|
||||
enterSubActivity(new SetTimeActivity(renderer, mappedInput, onComplete));
|
||||
break;
|
||||
case SettingAction::SetTimezoneOffset:
|
||||
enterSubActivity(new SetTimezoneOffsetActivity(renderer, mappedInput, onComplete));
|
||||
break;
|
||||
case SettingAction::None:
|
||||
// Do nothing
|
||||
break;
|
||||
@@ -216,6 +230,37 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
}
|
||||
|
||||
SETTINGS.saveToFile();
|
||||
|
||||
// Apply timezone whenever settings change (idempotent, cheap)
|
||||
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
|
||||
tzset();
|
||||
|
||||
// Rebuild clock actions (show/hide "Set UTC Offset" based on timezone selection)
|
||||
rebuildClockActions();
|
||||
}
|
||||
|
||||
void SettingsActivity::rebuildClockActions() {
|
||||
// Remove any existing ACTION items from clockSettings (keep enum settings from getSettingsList)
|
||||
clockSettings.erase(std::remove_if(clockSettings.begin(), clockSettings.end(),
|
||||
[](const SettingInfo& s) { return s.type == SettingType::ACTION; }),
|
||||
clockSettings.end());
|
||||
|
||||
// Always add Set Time
|
||||
clockSettings.push_back(SettingInfo::Action(StrId::STR_SET_TIME, SettingAction::SetTime));
|
||||
|
||||
// Only add Set UTC Offset when timezone is set to Custom
|
||||
if (SETTINGS.timezone == CrossPointSettings::TZ_CUSTOM) {
|
||||
clockSettings.push_back(SettingInfo::Action(StrId::STR_SET_UTC_OFFSET, SettingAction::SetTimezoneOffset));
|
||||
}
|
||||
|
||||
// Update settingsCount if we're currently viewing the clock category
|
||||
if (currentSettings == &clockSettings) {
|
||||
settingsCount = static_cast<int>(clockSettings.size());
|
||||
// Clamp selection to avoid pointing past the end of the list
|
||||
if (selectedSettingIndex > settingsCount) {
|
||||
selectedSettingIndex = settingsCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
@@ -22,6 +22,7 @@ enum class SettingAction {
|
||||
CheckForUpdates,
|
||||
Language,
|
||||
SetTime,
|
||||
SetTimezoneOffset,
|
||||
};
|
||||
|
||||
struct SettingInfo {
|
||||
@@ -143,6 +144,7 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
|
||||
// Per-category settings derived from shared list + device-only actions
|
||||
std::vector<SettingInfo> displaySettings;
|
||||
std::vector<SettingInfo> clockSettings;
|
||||
std::vector<SettingInfo> readerSettings;
|
||||
std::vector<SettingInfo> controlsSettings;
|
||||
std::vector<SettingInfo> systemSettings;
|
||||
@@ -150,11 +152,12 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
static constexpr int categoryCount = 4;
|
||||
static constexpr int categoryCount = 5;
|
||||
static const StrId categoryNames[categoryCount];
|
||||
|
||||
void enterCategory(int categoryIndex);
|
||||
void toggleCurrentSetting();
|
||||
void rebuildClockActions();
|
||||
|
||||
public:
|
||||
explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
|
||||
@@ -263,19 +263,22 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
||||
showBatteryPercentage);
|
||||
|
||||
// Draw clock on the left side (symmetric with battery on the right)
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
if (SETTINGS.clockFormat != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
char timeBuf[16];
|
||||
if (SETTINGS.homeScreenClock == CrossPointSettings::CLOCK_24H) {
|
||||
if (SETTINGS.clockFormat == CrossPointSettings::CLOCK_24H) {
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", t->tm_hour, t->tm_min);
|
||||
} else {
|
||||
int hour12 = t->tm_hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
|
||||
}
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + 12, rect.y + 5, timeBuf, true);
|
||||
int clockFont = SMALL_FONT_ID;
|
||||
if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_MEDIUM) clockFont = UI_10_FONT_ID;
|
||||
else if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_LARGE) clockFont = UI_12_FONT_ID;
|
||||
renderer.drawText(clockFont, rect.x + 12, rect.y + 5, timeBuf, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,19 +116,22 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
||||
showBatteryPercentage);
|
||||
|
||||
// Draw clock on the left side (symmetric with battery on the right)
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
if (SETTINGS.clockFormat != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
char timeBuf[16];
|
||||
if (SETTINGS.homeScreenClock == CrossPointSettings::CLOCK_24H) {
|
||||
if (SETTINGS.clockFormat == CrossPointSettings::CLOCK_24H) {
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", t->tm_hour, t->tm_min);
|
||||
} else {
|
||||
int hour12 = t->tm_hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
|
||||
}
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + 12, rect.y + 5, timeBuf, true);
|
||||
int clockFont = SMALL_FONT_ID;
|
||||
if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_MEDIUM) clockFont = UI_10_FONT_ID;
|
||||
else if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_LARGE) clockFont = UI_12_FONT_ID;
|
||||
renderer.drawText(clockFont, rect.x + 12, rect.y + 5, timeBuf, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
src/main.cpp
20
src/main.cpp
@@ -10,6 +10,7 @@
|
||||
#include <SPI.h>
|
||||
#include <builtinFonts/all.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
@@ -325,6 +326,11 @@ void setup() {
|
||||
}
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
|
||||
// Apply saved timezone setting on boot
|
||||
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
|
||||
tzset();
|
||||
|
||||
I18N.loadSettings();
|
||||
KOREADER_STORE.loadFromFile();
|
||||
UITheme::getInstance().reload();
|
||||
@@ -441,6 +447,20 @@ void loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh screen when the displayed minute changes (clock in header)
|
||||
if (SETTINGS.clockFormat != CrossPointSettings::CLOCK_OFF && currentActivity) {
|
||||
static int lastRenderedMinute = -1;
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
const int currentMinute = t->tm_hour * 60 + t->tm_min;
|
||||
if (lastRenderedMinute >= 0 && currentMinute != lastRenderedMinute) {
|
||||
currentActivity->requestUpdate();
|
||||
}
|
||||
lastRenderedMinute = currentMinute;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned long activityStartTime = millis();
|
||||
if (currentActivity) {
|
||||
currentActivity->loop();
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
namespace TimeSync {
|
||||
|
||||
void startNtpSync() {
|
||||
@@ -12,6 +16,10 @@ void startNtpSync() {
|
||||
esp_sntp_stop();
|
||||
}
|
||||
|
||||
// Apply timezone so NTP-synced time is displayed correctly
|
||||
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
|
||||
tzset();
|
||||
|
||||
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
||||
esp_sntp_setservername(0, "pool.ntp.org");
|
||||
esp_sntp_init();
|
||||
|
||||
Reference in New Issue
Block a user