diff --git a/README.md b/README.md index 3ac84c9..004d8d0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# Countdown App для Zepp OS +# Countdown Timer для Zepp OS -Современное и настраиваемое приложение обратного отсчета для умных часов на базе Zepp OS. Позволяет отслеживать время до любого важного события. +Универсальное приложение обратного отсчёта для умных часов на базе Zepp OS 3.0. Отслеживайте время до любого важного события — дня рождения, Нового года, дедлайна, поездки и многого другого. ![MIT License](https://img.shields.io/badge/License-MIT-blue.svg) +![Zepp OS](https://img.shields.io/badge/Zepp_OS-3.0-green.svg) +![Version](https://img.shields.io/badge/Version-2.0.0-blue.svg) ## 🖼️ Скриншоты @@ -12,25 +14,43 @@ ## ✨ Возможности -- **Точный обратный отсчет**: Отсчитывает дни, часы, минуты и секунды до указанной даты. -- **Современный дизайн**: Чистый и минималистичный интерфейс с белыми цифрами на черном фоне. -- **Настраиваемая дата**: Легко установите любую целевую дату и время через интуитивное меню настроек. -- **Сохранение состояния**: Приложение запоминает установленную дату даже после закрытия. -- **Цветовая индикация**: Цифры меняют цвет на красный, когда до события остается менее 5 минут. -- **Open Source**: Проект полностью открыт для доработок и улучшений. +- **Точный обратный отсчёт**: Дни, часы, минуты и секунды до указанной даты в реальном времени. +- **Название события**: Выбирайте тип события — День рождения, Новый год, Дедлайн, Экзамен, Свадьба и другие. +- **Быстрые пресеты**: Установка цели в одно касание — через 1 час, 1 день, 1 неделю или на Новый год. +- **Адаптивный дизайн**: Автоматическая адаптация под размер экрана любого устройства. +- **Цветовая индикация**: Зелёный → оранжевый → красный по мере приближения события. +- **Уведомление о завершении**: Toast-уведомление при истечении времени. +- **Многоязычность**: Поддержка русского и английского языков. +- **Сохранение состояния**: Приложение запоминает настройки после закрытия. + +## ⌚ Поддерживаемые устройства + +### Круглые экраны +| Размер экрана | Устройства | +| :---: | :--- | +| 480×480 | Amazfit GTR 4, GTR 3 Pro, T-Rex Ultra, Falcon | +| 466×466 | Amazfit GTR 3 | +| 454×454 | Amazfit GTR 4 Mini, GTR Mini | +| 416×416 | Amazfit T-Rex 2, T-Rex Pro | + +### Квадратные экраны +| Размер экрана | Устройства | +| :---: | :--- | +| 390×450 | Amazfit GTS 4, Active | +| 336×384 | Amazfit GTS 4 Mini, Bip 5 | ## 🛠️ Установка и настройка ### Требования -- [Zeus CLI](https://developer.zepp.com/docs/zeus/cli/) - инструмент для разработки под Zepp OS. +- [Zeus CLI](https://developer.zepp.com/docs/zeus/cli/) — инструмент для разработки под Zepp OS. - Node.js (версия 14 или выше). ### Установка 1. **Клонируйте репозиторий:** ```bash - git clone https://github.com/your-username/countdown-app.git + git clone https://github.com/DmitryKolyadin/countdown-app.git cd countdown-app ``` @@ -43,8 +63,6 @@ ### Запуск в эмуляторе -Для запуска приложения в эмуляторе с автоматической перезагрузкой при изменениях в коде выполните: - ```bash zeus dev ``` @@ -61,42 +79,59 @@ zeus dev zeus preview ``` -3. **Отсканируйте QR-код** в приложении Zepp на вашем смартфоне, чтобы установить приложение на часы. +3. **Отсканируйте QR-код** в приложении Zepp на смартфоне. ### Сборка проекта -Для сборки установочного пакета `.zab` выполните: - ```bash zeus build ``` -Готовый файл будет находиться в папке `dist/`. +Готовый файл `.zab` будет в папке `dist/`. ## 📂 Структура проекта ``` countdown-app/ -├── LICENSE # Файл лицензии MIT ├── app.js # Главный файл приложения -├── app.json # Конфигурация приложения +├── app.json # Конфигурация (устройства, разрешения, i18n) ├── package.json # Зависимости и скрипты -├── README.md # Документация ├── page/ -│ └── index.js # Код главного экрана (таймер) -└── setting/ - └── index.js # Код экрана настроек +│ ├── index.js # Главный экран (таймер обратного отсчёта) +│ └── i18n/ +│ ├── en-US.po # Английская локализация +│ └── ru-RU.po # Русская локализация +├── setting/ +│ ├── index.js # Экран настроек (событие, дата, пресеты) +│ └── i18n/ +│ ├── en-US.po # Английская локализация +│ └── ru-RU.po # Русская локализация +├── app-side/ +│ ├── index.js # Фоновый сервис +│ └── i18n/ +│ ├── en-US.po +│ └── ru-RU.po +├── utils/ # Утилиты (дата/время) +├── constants/ # Константы приложения +├── assets/ # Иконки приложения +└── docs/ # Скриншоты ``` -## 🤝 Участие в разработке +## 🆕 Что нового в версии 2.0 -Мы приветствуем любой вклад в развитие проекта! Если у вас есть идеи по улучшению или вы нашли ошибку, пожалуйста, следуйте этим шагам: +- **Поддержка 6 типов экранов** — круглые (480, 466, 454, 416) и квадратные (390, 336) +- **Название события** — выбор из 10 предустановленных категорий +- **Быстрые пресеты** — +1 час, +1 день, +1 неделя, Новый год +- **Русский язык** — полная локализация интерфейса +- **Адаптивная вёрстка** — UI масштабируется под любой экран +- **Трёхуровневая индикация** — зелёный/оранжевый/красный статус +- **Уведомление о завершении** — Toast-уведомление при достижении цели +- **Загрузка сохранённых настроек** — настройки загружаются при открытии + +## 🤝 Участие в разработке -1. Сделайте форк репозитория. -2. Создайте новую ветку (`git checkout -b feature/your-feature`). -3. Внесите свои изменения. -4. Создайте Pull Request. +Мы приветствуем любой вклад! Создайте форк, внесите изменения и отправьте Pull Request. ## 📄 Лицензия -Этот проект распространяется под лицензией MIT. Подробности смотрите в файле [LICENSE](LICENSE). +Проект распространяется под лицензией MIT. Подробности в файле [LICENSE](LICENSE). diff --git a/app-side/i18n/en-US.po b/app-side/i18n/en-US.po index d1f8a1d..3a49127 100644 --- a/app-side/i18n/en-US.po +++ b/app-side/i18n/en-US.po @@ -2,7 +2,4 @@ msgid "app_started" msgstr "CountdownApp Started" msgid "app_destroyed" -msgstr "CountdownApp Destroyed" - -msgid "example" -msgstr "This is an example in app-side" \ No newline at end of file +msgstr "CountdownApp Destroyed" \ No newline at end of file diff --git a/app-side/i18n/ru-RU.po b/app-side/i18n/ru-RU.po new file mode 100644 index 0000000..59e9835 --- /dev/null +++ b/app-side/i18n/ru-RU.po @@ -0,0 +1,5 @@ +msgid "app_started" +msgstr "CountdownApp запущен" + +msgid "app_destroyed" +msgstr "CountdownApp завершён" diff --git a/app-side/index.js b/app-side/index.js index 2e0c9d2..9c1c8a4 100644 --- a/app-side/index.js +++ b/app-side/index.js @@ -1,191 +1,13 @@ -import { gettext } from 'i18n' - AppSideService({ - globalData: { - targetTimestamp: null, - missionCode: 'OPERATION COUNTDOWN', - agentId: null, - lastSync: null - }, - onInit() { - console.log('[SPY-SYSTEM] CountdownApp initialized') - this.initializeMission() - this.loadSettings() + console.log('CountdownApp service initialized') }, onRun() { - console.log('[SPY-SYSTEM] Mission commenced') - this.syncMissionData() + console.log('CountdownApp service running') }, onDestroy() { - console.log('[SPY-SYSTEM] Mission terminated') - this.saveSettings() - }, - - initializeMission() { - // Генерируем ID агента если его нет - if (!this.globalData.agentId) { - this.globalData.agentId = this.generateAgentId() - } - - // Генерируем код миссии - this.globalData.missionCode = this.generateMissionCode() - - console.log(`[SPY-SYSTEM] Agent ${this.globalData.agentId} assigned to ${this.globalData.missionCode}`) - }, - - generateAgentId() { - const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - const numbers = '0123456789' - - let agentId = 'AGENT-' - - // Добавляем 2 буквы - for (let i = 0; i < 2; i++) { - agentId += letters[Math.floor(Math.random() * letters.length)] - } - - agentId += '-' - - // Добавляем 3 цифры - for (let i = 0; i < 3; i++) { - agentId += numbers[Math.floor(Math.random() * numbers.length)] - } - - return agentId - }, - - generateMissionCode() { - const operations = [ - 'OPERATION COUNTDOWN', - 'MISSION CRITICAL', - 'PROJECT OMEGA', - 'ALPHA ZERO', - 'CODE CRIMSON', - 'OPERATION PHOENIX' - ] - - return operations[Math.floor(Math.random() * operations.length)] - }, - - syncMissionData() { - this.globalData.lastSync = Date.now() - console.log('[SPY-SYSTEM] Mission data synchronized') - }, - - loadSettings() { - // Загружаем сохраненные настройки из локального хранилища - try { - const savedTimestamp = this.getStorageSync('targetTimestamp') - const savedAgentId = this.getStorageSync('agentId') - const savedMissionCode = this.getStorageSync('missionCode') - - if (savedTimestamp) { - this.globalData.targetTimestamp = parseInt(savedTimestamp) - console.log('[SPY-SYSTEM] Target timestamp loaded') - } - - if (savedAgentId) { - this.globalData.agentId = savedAgentId - } - - if (savedMissionCode) { - this.globalData.missionCode = savedMissionCode - } - - } catch (error) { - console.log('[SPY-SYSTEM] Error loading mission data:', error) - } - }, - - saveSettings() { - // Сохраняем настройки в локальное хранилище - try { - if (this.globalData.targetTimestamp) { - this.setStorageSync('targetTimestamp', this.globalData.targetTimestamp.toString()) - } - - if (this.globalData.agentId) { - this.setStorageSync('agentId', this.globalData.agentId) - } - - if (this.globalData.missionCode) { - this.setStorageSync('missionCode', this.globalData.missionCode) - } - - console.log('[SPY-SYSTEM] Mission data saved') - - } catch (error) { - console.log('[SPY-SYSTEM] Error saving mission data:', error) - } - }, - - getGlobalData(key) { - return this.globalData[key] - }, - - setGlobalData(key, value) { - this.globalData[key] = value - - // Автоматически сохраняем критичные данные - if (key === 'targetTimestamp') { - try { - this.setStorageSync('targetTimestamp', value.toString()) - console.log('[SPY-SYSTEM] Target updated and saved') - } catch (error) { - console.log('[SPY-SYSTEM] Error auto-saving target:', error) - } - } - }, - - // Проверка статуса миссии - getMissionStatus() { - if (!this.globalData.targetTimestamp) { - return 'STANDBY' - } - - const now = Date.now() - const diff = this.globalData.targetTimestamp - now - const minutes = Math.floor(diff / (1000 * 60)) - - if (diff <= 0) { - return 'COMPLETE' - } else if (minutes <= 5) { - return 'CRITICAL' - } else if (minutes <= 60) { - return 'WARNING' - } else { - return 'ACTIVE' - } - }, - - // Генерация отчета о миссии - generateMissionReport() { - const status = this.getMissionStatus() - const timeRemaining = this.globalData.targetTimestamp ? - this.globalData.targetTimestamp - Date.now() : 0 - - return { - agentId: this.globalData.agentId, - missionCode: this.globalData.missionCode, - status: status, - timeRemaining: timeRemaining, - lastSync: this.globalData.lastSync, - timestamp: Date.now() - } - }, - - // Заглушки для методов хранилища (в реальном приложении используйте API Zepp OS) - getStorageSync(key) { - // В реальном приложении здесь будет вызов API хранилища - // Например: hmFS.SysProGetString(key) - return null - }, - - setStorageSync(key, value) { - // В реальном приложении здесь будет вызов API хранилища - // Например: hmFS.SysProSetString(key, value) + console.log('CountdownApp service destroyed') } }) diff --git a/app.js b/app.js index 351b85b..0f959dc 100644 --- a/app.js +++ b/app.js @@ -1,32 +1,14 @@ App({ globalData: { - targetTimestamp: null + targetTimestamp: null, + eventKey: null }, - onCreate(options) { + onCreate() { console.log('CountdownApp created') - this.loadGlobalSettings() }, - onDestroy(options) { + onDestroy() { console.log('CountdownApp destroyed') - this.saveGlobalSettings() - }, - - loadGlobalSettings() { - // Загружаем глобальные настройки - // В реальном приложении можно использовать персистентное хранилище - }, - - saveGlobalSettings() { - // Сохраняем глобальные настройки - }, - - getGlobalData(key) { - return this.globalData[key] - }, - - setGlobalData(key, value) { - this.globalData[key] = value } }) \ No newline at end of file diff --git a/app.json b/app.json index eba5a67..a39cd34 100644 --- a/app.json +++ b/app.json @@ -5,12 +5,12 @@ "appName": "CountdownApp", "appType": "app", "version": { - "code": 1, - "name": "1.0.0" + "code": 2, + "name": "2.0.0" }, "icon": "icon.png", "vender": "zepp", - "description": "Countdown timer application" + "description": "Countdown timer - track time to your important events" }, "permissions": ["zos.permission.READ_WRITE_DATA"], "runtime": { @@ -31,16 +31,21 @@ } }, "platforms": [ - { - "st": "r", - "dw": 480 - } + { "st": "r", "dw": 480 }, + { "st": "r", "dw": 466 }, + { "st": "r", "dw": 454 }, + { "st": "r", "dw": 416 }, + { "st": "s", "dw": 390 }, + { "st": "s", "dw": 336 } ] } }, "i18n": { "en-US": { - "appName": "CountdownApp" + "appName": "Countdown Timer" + }, + "ru-RU": { + "appName": "Таймер обратного отсчёта" } }, "defaultLanguage": "en-US" diff --git a/package.json b/package.json index af52aea..081c0de 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "countdown-app", - "version": "1.0.0", - "description": "Countdown timer app for Zepp OS", + "version": "2.0.0", + "description": "Countdown timer for Zepp OS - track time to important events", "main": "app.js", "scripts": { "dev": "zeus dev", diff --git a/page/i18n/en-US.po b/page/i18n/en-US.po index 860c00c..6f25b7f 100644 --- a/page/i18n/en-US.po +++ b/page/i18n/en-US.po @@ -1,41 +1,62 @@ -msgid "countdown_title" -msgstr ">>> TARGET COUNTDOWN <<<" +msgid "countdown" +msgstr "Countdown" -msgid "mission_code" -msgstr "OPERATION COUNTDOWN" +msgid "birthday" +msgstr "Birthday" -msgid "days" -msgstr "DAYS" +msgid "newYear" +msgstr "New Year" + +msgid "holiday" +msgstr "Holiday" + +msgid "meeting" +msgstr "Meeting" + +msgid "deadline" +msgstr "Deadline" + +msgid "anniversary" +msgstr "Anniversary" -msgid "hours" -msgstr "HOURS" +msgid "trip" +msgstr "Trip" -msgid "minutes" -msgstr "MINS" +msgid "exam" +msgstr "Exam" + +msgid "wedding" +msgstr "Wedding" + +msgid "timeLeft" +msgstr "Time remaining" + +msgid "days" +msgstr "DAYS" -msgid "seconds" -msgstr "SECS" +msgid "hours" +msgstr "HRS" -msgid "set_target" -msgstr "[ SET TARGET ]" +msgid "mins" +msgstr "MIN" -msgid "mission_active" -msgstr "[STATUS: MISSION ACTIVE]" +msgid "secs" +msgstr "SEC" -msgid "mission_critical" -msgstr "[STATUS: CRITICAL TIME]" +msgid "settings" +msgstr "Settings" -msgid "mission_warning" -msgstr "[STATUS: WARNING ZONE]" +msgid "almostThere" +msgstr "Almost there!" -msgid "mission_complete" -msgstr "[STATUS: MISSION COMPLETE]" +msgid "lessThanHour" +msgstr "Less than 1 hour" -msgid "time_up" -msgstr "MISSION ACCOMPLISHED" +msgid "daysRemaining" +msgstr "days left" -msgid "reset" -msgstr "RESET" +msgid "hoursRemaining" +msgstr "hours left" -msgid "example" -msgstr "This is an example in device" \ No newline at end of file +msgid "timesUp" +msgstr "Time's up!" \ No newline at end of file diff --git a/page/i18n/ru-RU.po b/page/i18n/ru-RU.po new file mode 100644 index 0000000..dc722d9 --- /dev/null +++ b/page/i18n/ru-RU.po @@ -0,0 +1,62 @@ +msgid "countdown" +msgstr "Отсчёт" + +msgid "birthday" +msgstr "День рождения" + +msgid "newYear" +msgstr "Новый год" + +msgid "holiday" +msgstr "Праздник" + +msgid "meeting" +msgstr "Встреча" + +msgid "deadline" +msgstr "Дедлайн" + +msgid "anniversary" +msgstr "Юбилей" + +msgid "trip" +msgstr "Поездка" + +msgid "exam" +msgstr "Экзамен" + +msgid "wedding" +msgstr "Свадьба" + +msgid "timeLeft" +msgstr "Осталось" + +msgid "days" +msgstr "ДНИ" + +msgid "hours" +msgstr "ЧАС" + +msgid "mins" +msgstr "МИН" + +msgid "secs" +msgstr "СЕК" + +msgid "settings" +msgstr "Настройки" + +msgid "almostThere" +msgstr "Почти!" + +msgid "lessThanHour" +msgstr "Менее 1 часа" + +msgid "daysRemaining" +msgstr "дн. осталось" + +msgid "hoursRemaining" +msgstr "ч. осталось" + +msgid "timesUp" +msgstr "Время вышло!" diff --git a/page/index.js b/page/index.js index f0b8055..6c258b9 100644 --- a/page/index.js +++ b/page/index.js @@ -1,282 +1,227 @@ import { getText } from '@zos/i18n' -import { createWidget, widget, prop, align, text_style, event } from '@zos/ui' -import { px } from '@zos/utils' +import { createWidget, widget, prop, align, event } from '@zos/ui' import { push } from '@zos/router' import { readFileSync, statSync } from '@zos/fs' +import { getDeviceInfo } from '@zos/device' +import { showToast } from '@zos/interaction' + +const deviceInfo = getDeviceInfo() +const W = deviceInfo.width +const H = deviceInfo.height Page({ state: { targetTimestamp: null, + eventName: '', intervalId: null, - days: 0, - hours: 0, - minutes: 0, - seconds: 0, - isCritical: false + isExpired: false }, onInit() { - console.log('Spy Countdown Mission Initialized') this.loadSettings() }, build() { - console.log('Building spy interface...') - - // Создаем простой интерфейс - this.createSimpleInterface() - - // Запускаем таймер + this.createInterface() this.startCountdown() }, loadSettings() { try { - const filePath = 'countdown_target.json' - const { size } = statSync({ path: filePath }) - + const { size } = statSync({ path: 'countdown_target.json' }) if (size > 0) { - const data = readFileSync({ path: filePath, options: { encoding: 'utf8' } }) + const data = readFileSync({ + path: 'countdown_target.json', + options: { encoding: 'utf8' } + }) const settings = JSON.parse(data) if (settings && settings.target) { this.state.targetTimestamp = settings.target - console.log('Loaded target from file:', new Date(this.state.targetTimestamp).toString()) + this.state.eventName = settings.eventKey + ? getText(settings.eventKey) + : getText('countdown') return } } - } catch (error) { - console.log('Settings file not found or invalid, using default.') + } catch (e) { + console.log('No settings found, using defaults') } - - // Устанавливаем время по умолчанию, если загрузка не удалась this.setDefaultTarget() }, setDefaultTarget() { - // Устанавливаем время через час для демонстрации - const futureTime = new Date() - futureTime.setHours(futureTime.getHours() + 1) - this.state.targetTimestamp = futureTime.getTime() - console.log('Target set to:', futureTime.toString()) + const future = new Date() + future.setHours(future.getHours() + 1) + this.state.targetTimestamp = future.getTime() + this.state.eventName = getText('countdown') }, - createSimpleInterface() { - try { - // Фон - createWidget(widget.FILL_RECT, { - x: 0, - y: 0, - w: 480, - h: 490, - color: 0x000000 - }) - - // Заголовок - createWidget(widget.TEXT, { - x: 0, - y: 60, - w: 480, - h: 50, - text: 'ОСТАЛОСЬ', - text_size: 36, - color: 0xffffff, - align_h: align.CENTER_H - }) - - // Статус - // this.statusWidget = createWidget(widget.TEXT, { - // x: 0, - // y: 120, - // w: 480, - // h: 30, - // text: 'MISSION ACTIVE', - // text_size: 20, - // color: 0xffffff, - // align_h: align.CENTER_H - // }) - - const yPos = 155 - const labelYPos = yPos + 100 - const valueHeight = 90 - const labelHeight = 30 - const valueTextSize = 80 - const labelTextSize = 18 - const valueColor = 0xffffff - const labelColor = 0xaaaaaa - const componentWidth = 100 - const separatorWidth = 20 - const totalWidth = (componentWidth * 4) + (separatorWidth * 3) - const startX = (480 - totalWidth) / 2 - - // Дни - this.daysValue = createWidget(widget.TEXT, { - x: startX, - y: yPos, - w: componentWidth, - h: valueHeight, - text: '00', - text_size: valueTextSize, - color: valueColor, - align_h: align.CENTER_H - }) - createWidget(widget.TEXT, { - x: startX, - y: labelYPos, - w: componentWidth, - h: labelHeight, - text: 'DAYS', - text_size: labelTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Разделитель - let currentX = startX + componentWidth - createWidget(widget.TEXT, { - x: currentX, - y: yPos - 10, - w: separatorWidth, - h: valueHeight, - text: ':', - text_size: valueTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Часы - currentX += separatorWidth - this.hoursValue = createWidget(widget.TEXT, { - x: currentX, - y: yPos, - w: componentWidth, - h: valueHeight, - text: '00', - text_size: valueTextSize, - color: valueColor, - align_h: align.CENTER_H - }) - createWidget(widget.TEXT, { - x: currentX, - y: labelYPos, - w: componentWidth, - h: labelHeight, - text: 'HOURS', - text_size: labelTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Разделитель - currentX += componentWidth - createWidget(widget.TEXT, { - x: currentX, - y: yPos - 10, - w: separatorWidth, - h: valueHeight, - text: ':', - text_size: valueTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Минуты - currentX += separatorWidth - this.minutesValue = createWidget(widget.TEXT, { - x: currentX, - y: yPos, - w: componentWidth, - h: valueHeight, - text: '00', - text_size: valueTextSize, - color: valueColor, - align_h: align.CENTER_H - }) - createWidget(widget.TEXT, { - x: currentX, - y: labelYPos, - w: componentWidth, - h: labelHeight, - text: 'MINS', - text_size: labelTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Разделитель - currentX += componentWidth - createWidget(widget.TEXT, { - x: currentX, - y: yPos - 10, - w: separatorWidth, - h: valueHeight, - text: ':', - text_size: valueTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Секунды - currentX += separatorWidth - this.secondsValue = createWidget(widget.TEXT, { - x: currentX, - y: yPos, - w: componentWidth, - h: valueHeight, - text: '00', - text_size: valueTextSize, - color: valueColor, - align_h: align.CENTER_H - }) - createWidget(widget.TEXT, { - x: currentX, - y: labelYPos, - w: componentWidth, - h: labelHeight, - text: 'SECS', - text_size: labelTextSize, - color: labelColor, - align_h: align.CENTER_H - }) - - // Кнопка настройки цели - const targetBtn = createWidget(widget.BUTTON, { - x: (480 - 220) / 2, - y: 400, - w: 220, - h: 55, - text: 'SET TARGET', - normal_color: 0x1c1c1e, - press_color: 0x333333, - color: 0xffffff, - text_size: 22, - radius: 28 - }) - - targetBtn.addEventListener(event.CLICK_UP, () => { - console.log('Opening settings...') - push({ url: 'setting/index' }) - }) - - console.log('Interface created successfully') - - } catch (error) { - console.error('Error creating interface:', error) - } + createInterface() { + createWidget(widget.FILL_RECT, { + x: 0, + y: 0, + w: W, + h: H, + color: 0x000000 + }) + + this.eventWidget = createWidget(widget.TEXT, { + x: 0, + y: Math.floor(H * 0.10), + w: W, + h: Math.floor(H * 0.10), + text: this.state.eventName || getText('countdown'), + text_size: Math.floor(W * 0.065), + color: 0x00b4ff, + align_h: align.CENTER_H + }) + + createWidget(widget.TEXT, { + x: 0, + y: Math.floor(H * 0.20), + w: W, + h: Math.floor(H * 0.06), + text: getText('timeLeft'), + text_size: Math.floor(W * 0.035), + color: 0x888888, + align_h: align.CENTER_H + }) + + const yPos = Math.floor(H * 0.30) + const valueH = Math.floor(H * 0.18) + const labelY = yPos + valueH - Math.floor(H * 0.02) + const labelH = Math.floor(H * 0.06) + const valFont = Math.floor(W * 0.15) + const lblFont = Math.floor(W * 0.032) + const compW = Math.floor(W * 0.19) + const sepW = Math.floor(W * 0.04) + const totalW = compW * 4 + sepW * 3 + const startX = Math.floor((W - totalW) / 2) + + this.daysValue = this.createTimeBlock(startX, yPos, compW, valueH, valFont, '00') + this.createLabel(startX, labelY, compW, labelH, lblFont, getText('days')) + + let cx = startX + compW + this.createSep(cx, yPos, sepW, valueH, valFont) + + cx += sepW + this.hoursValue = this.createTimeBlock(cx, yPos, compW, valueH, valFont, '00') + this.createLabel(cx, labelY, compW, labelH, lblFont, getText('hours')) + + cx += compW + this.createSep(cx, yPos, sepW, valueH, valFont) + + cx += sepW + this.minutesValue = this.createTimeBlock(cx, yPos, compW, valueH, valFont, '00') + this.createLabel(cx, labelY, compW, labelH, lblFont, getText('mins')) + + cx += compW + this.createSep(cx, yPos, sepW, valueH, valFont) + + cx += sepW + this.secondsValue = this.createTimeBlock(cx, yPos, compW, valueH, valFont, '00') + this.createLabel(cx, labelY, compW, labelH, lblFont, getText('secs')) + + this.statusWidget = createWidget(widget.TEXT, { + x: 0, + y: Math.floor(H * 0.58), + w: W, + h: Math.floor(H * 0.08), + text: '', + text_size: Math.floor(W * 0.04), + color: 0x00cc66, + align_h: align.CENTER_H + }) + + this.targetDateWidget = createWidget(widget.TEXT, { + x: 0, + y: Math.floor(H * 0.66), + w: W, + h: Math.floor(H * 0.06), + text: this.formatTargetDate(), + text_size: Math.floor(W * 0.030), + color: 0x666666, + align_h: align.CENTER_H + }) + + const btnW = Math.floor(W * 0.46) + const btnH = Math.floor(H * 0.11) + const settingsBtn = createWidget(widget.BUTTON, { + x: Math.floor((W - btnW) / 2), + y: Math.floor(H * 0.80), + w: btnW, + h: btnH, + text: getText('settings'), + normal_color: 0x1c1c1e, + press_color: 0x333333, + color: 0xffffff, + text_size: Math.floor(W * 0.045), + radius: Math.floor(btnH / 2) + }) + + settingsBtn.addEventListener(event.CLICK_UP, () => { + push({ url: 'setting/index' }) + }) + }, + + createTimeBlock(x, y, w, h, fontSize, text) { + return createWidget(widget.TEXT, { + x: x, + y: y, + w: w, + h: h, + text: text, + text_size: fontSize, + color: 0xffffff, + align_h: align.CENTER_H + }) + }, + + createLabel(x, y, w, h, fontSize, text) { + createWidget(widget.TEXT, { + x: x, + y: y, + w: w, + h: h, + text: text, + text_size: fontSize, + color: 0x888888, + align_h: align.CENTER_H + }) + }, + + createSep(x, y, w, h, fontSize) { + createWidget(widget.TEXT, { + x: x, + y: y - Math.floor(h * 0.05), + w: w, + h: h, + text: ':', + text_size: fontSize, + color: 0x444444, + align_h: align.CENTER_H + }) + }, + + formatTargetDate() { + if (!this.state.targetTimestamp) return '' + const d = new Date(this.state.targetTimestamp) + const dd = d.getDate().toString().padStart(2, '0') + const mm = (d.getMonth() + 1).toString().padStart(2, '0') + const yyyy = d.getFullYear() + const hh = d.getHours().toString().padStart(2, '0') + const min = d.getMinutes().toString().padStart(2, '0') + return dd + '.' + mm + '.' + yyyy + ' ' + hh + ':' + min }, startCountdown() { - console.log('Starting countdown...') - if (this.state.intervalId) { clearInterval(this.state.intervalId) } - + this.updateCountdown() this.state.intervalId = setInterval(() => { this.updateCountdown() }, 1000) - - // Сразу обновляем при запуске - this.updateCountdown() }, updateCountdown() { @@ -285,93 +230,109 @@ Page({ return } - const now = Date.now() - const diff = this.state.targetTimestamp - now + const diff = this.state.targetTimestamp - Date.now() if (diff <= 0) { this.setTime(0, 0, 0, 0) - this.missionComplete() + this.onExpired() return } - const days = Math.floor(diff / (1000 * 60 * 60 * 24)) - const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) - const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) - const seconds = Math.floor((diff % (1000 * 60)) / 1000) + const days = Math.floor(diff / 86400000) + const hours = Math.floor((diff % 86400000) / 3600000) + const minutes = Math.floor((diff % 3600000) / 60000) + const seconds = Math.floor((diff % 60000) / 1000) this.setTime(days, hours, minutes, seconds) - this.updateMissionStatus(diff) + this.updateStatus(diff) }, - setTime(days, hours, minutes, seconds) { + setTime(d, h, m, s) { try { - if (this.daysValue) { - this.daysValue.setProperty(prop.TEXT, days.toString().padStart(2, '0')) - } - if (this.hoursValue) { - this.hoursValue.setProperty(prop.TEXT, hours.toString().padStart(2, '0')) - } - if (this.minutesValue) { - this.minutesValue.setProperty(prop.TEXT, minutes.toString().padStart(2, '0')) - } - if (this.secondsValue) { - this.secondsValue.setProperty(prop.TEXT, seconds.toString().padStart(2, '0')) - } - } catch (error) { - console.error('Error updating time:', error) + if (this.daysValue) this.daysValue.setProperty(prop.TEXT, d.toString().padStart(2, '0')) + if (this.hoursValue) this.hoursValue.setProperty(prop.TEXT, h.toString().padStart(2, '0')) + if (this.minutesValue) this.minutesValue.setProperty(prop.TEXT, m.toString().padStart(2, '0')) + if (this.secondsValue) this.secondsValue.setProperty(prop.TEXT, s.toString().padStart(2, '0')) + } catch (e) { + console.log('Error updating time:', e) } }, - updateMissionStatus(timeDiff) { - const totalMinutes = Math.floor(timeDiff / (1000 * 60)) - + updateStatus(diff) { + const mins = Math.floor(diff / 60000) + const hours = Math.floor(diff / 3600000) + const days = Math.floor(diff / 86400000) + try { - if (totalMinutes <= 5 && totalMinutes > 0) { - this.state.isCritical = true + if (mins <= 5) { + const c = { color: 0xff3b30 } + if (this.daysValue) this.daysValue.setProperty(prop.MORE, c) + if (this.hoursValue) this.hoursValue.setProperty(prop.MORE, c) + if (this.minutesValue) this.minutesValue.setProperty(prop.MORE, c) + if (this.secondsValue) this.secondsValue.setProperty(prop.MORE, c) if (this.statusWidget) { - this.statusWidget.setProperty(prop.TEXT, 'CRITICAL TIME') - this.statusWidget.setProperty(prop.MORE, { color: 0xff0000 }) + this.statusWidget.setProperty(prop.TEXT, getText('almostThere')) + this.statusWidget.setProperty(prop.MORE, { color: 0xff3b30 }) } - - // Меняем цвет всех значений на красный - const criticalColor = { color: 0xff0000 } - if (this.daysValue) this.daysValue.setProperty(prop.MORE, criticalColor) - if (this.hoursValue) this.hoursValue.setProperty(prop.MORE, criticalColor) - if (this.minutesValue) this.minutesValue.setProperty(prop.MORE, criticalColor) - if (this.secondsValue) this.secondsValue.setProperty(prop.MORE, criticalColor) - - } else if (this.state.isCritical) { // Сбрасываем, если время больше не критическое - this.state.isCritical = false + } else if (hours < 1) { + const c = { color: 0xff9500 } + if (this.daysValue) this.daysValue.setProperty(prop.MORE, c) + if (this.hoursValue) this.hoursValue.setProperty(prop.MORE, c) + if (this.minutesValue) this.minutesValue.setProperty(prop.MORE, c) + if (this.secondsValue) this.secondsValue.setProperty(prop.MORE, c) if (this.statusWidget) { - this.statusWidget.setProperty(prop.TEXT, 'MISSION ACTIVE') - this.statusWidget.setProperty(prop.MORE, { color: 0xffffff }) + this.statusWidget.setProperty(prop.TEXT, getText('lessThanHour')) + this.statusWidget.setProperty(prop.MORE, { color: 0xff9500 }) + } + } else { + const c = { color: 0xffffff } + if (this.daysValue) this.daysValue.setProperty(prop.MORE, c) + if (this.hoursValue) this.hoursValue.setProperty(prop.MORE, c) + if (this.minutesValue) this.minutesValue.setProperty(prop.MORE, c) + if (this.secondsValue) this.secondsValue.setProperty(prop.MORE, c) + if (this.statusWidget) { + if (days > 0) { + this.statusWidget.setProperty(prop.TEXT, days + ' ' + getText('daysRemaining')) + } else { + this.statusWidget.setProperty(prop.TEXT, hours + ' ' + getText('hoursRemaining')) + } + this.statusWidget.setProperty(prop.MORE, { color: 0x00cc66 }) } - const normalColor = { color: 0xffffff } - if (this.daysValue) this.daysValue.setProperty(prop.MORE, normalColor) - if (this.hoursValue) this.hoursValue.setProperty(prop.MORE, normalColor) - if (this.minutesValue) this.minutesValue.setProperty(prop.MORE, normalColor) - if (this.secondsValue) this.secondsValue.setProperty(prop.MORE, normalColor) } - } catch (error) { - console.error('Error updating status:', error) + } catch (e) { + console.log('Error updating status:', e) } }, - missionComplete() { + onExpired() { + if (this.state.isExpired) return + this.state.isExpired = true + try { - this.state.isCritical = false + const c = { color: 0x00b4ff } + if (this.daysValue) this.daysValue.setProperty(prop.MORE, c) + if (this.hoursValue) this.hoursValue.setProperty(prop.MORE, c) + if (this.minutesValue) this.minutesValue.setProperty(prop.MORE, c) + if (this.secondsValue) this.secondsValue.setProperty(prop.MORE, c) if (this.statusWidget) { - this.statusWidget.setProperty(prop.TEXT, 'MISSION COMPLETE') - this.statusWidget.setProperty(prop.MORE, { color: 0x00ffff }) + this.statusWidget.setProperty(prop.TEXT, getText('timesUp')) + this.statusWidget.setProperty(prop.MORE, { color: 0x00b4ff }) + } + if (this.eventWidget) { + this.eventWidget.setProperty(prop.MORE, { color: 0x00b4ff }) } - console.log('Mission Complete!') - } catch (error) { - console.error('Error in mission complete:', error) + } catch (e) { + console.log('Error updating expired state:', e) + } + + try { + showToast({ content: getText('timesUp') }) + } catch (e) { + console.log('Toast notification unavailable') } }, onDestroy() { - console.log('Destroying countdown page') if (this.state.intervalId) { clearInterval(this.state.intervalId) } diff --git a/page/index.r.layout.js b/page/index.r.layout.js index 661be52..6b67ca4 100644 --- a/page/index.r.layout.js +++ b/page/index.r.layout.js @@ -1,78 +1,52 @@ -import { px } from "@zos/utils"; +import { getDeviceInfo } from '@zos/device' -// Стили для круглых экранов (480x480) -export const SCREEN_WIDTH = px(480); -export const SCREEN_HEIGHT = px(480); +const deviceInfo = getDeviceInfo() +const W = deviceInfo.width +const H = deviceInfo.height -// Заголовок -export const TITLE_STYLE = { - x: px(0), - y: px(50), - w: px(480), - h: px(60), - text_size: px(32), - color: 0xffffff, -}; - -// Контейнеры для значений таймера -export const TIMER_CONTAINER = { - width: px(240), - height: px(110) -}; - -// Позиции для блоков таймера -export const DAYS_POSITION = { - x: px(0), - y: px(130) -}; - -export const HOURS_POSITION = { - x: px(240), - y: px(130) -}; +export const SCREEN_WIDTH = W +export const SCREEN_HEIGHT = H -export const MINUTES_POSITION = { - x: px(0), - y: px(250) -}; - -export const SECONDS_POSITION = { - x: px(240), - y: px(250) -}; - -// Стили для подписей -export const LABEL_STYLE = { - w: px(240), - h: px(40), - text_size: px(20), - color: 0xcccccc -}; - -// Стили для значений -export const VALUE_STYLE = { - w: px(240), - h: px(60), - text_size: px(48), - color: 0x00ff00 -}; +export const TITLE_STYLE = { + x: 0, + y: Math.floor(H * 0.10), + w: W, + h: Math.floor(H * 0.10), + text_size: Math.floor(W * 0.065), + color: 0x00b4ff +} + +export const TIMER_VALUE_STYLE = { + w: Math.floor(W * 0.19), + h: Math.floor(H * 0.18), + text_size: Math.floor(W * 0.15), + color: 0xffffff +} + +export const TIMER_LABEL_STYLE = { + w: Math.floor(W * 0.19), + h: Math.floor(H * 0.06), + text_size: Math.floor(W * 0.032), + color: 0x888888 +} -// Кнопка настроек export const SETTINGS_BUTTON = { - x: px(140), - y: px(380), - w: px(200), - h: px(60), - radius: px(30) -}; + x: Math.floor((W - Math.floor(W * 0.46)) / 2), + y: Math.floor(H * 0.80), + w: Math.floor(W * 0.46), + h: Math.floor(H * 0.11), + radius: Math.floor(Math.floor(H * 0.11) / 2) +} -// Цвета export const COLORS = { WHITE: 0xffffff, - GREEN: 0x00ff00, - GRAY: 0xcccccc, - RED: 0xff0000, - BLUE: 0x0066cc, - DARK_GRAY: 0x222222, - MEDIUM_GRAY: 0x444444 -}; + ACCENT: 0x00b4ff, + GRAY: 0x888888, + DARK_GRAY: 0x444444, + RED: 0xff3b30, + ORANGE: 0xff9500, + GREEN: 0x00cc66, + BACKGROUND: 0x000000, + BUTTON: 0x1c1c1e, + BUTTON_PRESSED: 0x333333 +} diff --git a/setting/i18n/en-US.po b/setting/i18n/en-US.po index fcba706..884dda9 100644 --- a/setting/i18n/en-US.po +++ b/setting/i18n/en-US.po @@ -1,50 +1,53 @@ -msgid "setting_title" -msgstr ">>> TARGET PARAMETERS <<<" +msgid "setTarget" +msgstr "Set Target" -msgid "mission_config" -msgstr "[MISSION CONFIGURATION MODE]" +msgid "eventName" +msgstr "Event" -msgid "target_datetime" -msgstr "TARGET DATE/TIME" +msgid "save" +msgstr "SAVE" -msgid "quick_presets" -msgstr "QUICK PRESETS:" +msgid "presets" +msgstr "Quick Set" -msgid "deploy" -msgstr "[ DEPLOY ]" +msgid "plusHour" +msgstr "+1 Hour" -msgid "abort" -msgstr "ABORT" +msgid "plusDay" +msgstr "+1 Day" -msgid "reset" -msgstr "RESET" +msgid "plusWeek" +msgstr "+1 Week" -msgid "target_deployed" -msgstr "[TARGET DEPLOYED]" +msgid "nextNewYear" +msgstr "New Year" -msgid "plus_hour" -msgstr "+1 HOUR" +msgid "countdown" +msgstr "Countdown" -msgid "plus_day" -msgstr "+1 DAY" +msgid "birthday" +msgstr "Birthday" -msgid "plus_week" -msgstr "+1 WEEK" +msgid "newYear" +msgstr "New Year" -msgid "new_year" -msgstr "NEW YEAR" +msgid "holiday" +msgstr "Holiday" -msgid "select_date" -msgstr "Select Date" +msgid "meeting" +msgstr "Meeting" -msgid "select_time" -msgstr "Select Time" +msgid "deadline" +msgstr "Deadline" -msgid "save_settings" -msgstr "Save Settings" +msgid "anniversary" +msgstr "Anniversary" -msgid "settings_saved" -msgstr "Settings Saved!" +msgid "trip" +msgstr "Trip" -msgid "example" -msgstr "This is an example in app-side" \ No newline at end of file +msgid "exam" +msgstr "Exam" + +msgid "wedding" +msgstr "Wedding" \ No newline at end of file diff --git a/setting/i18n/ru-RU.po b/setting/i18n/ru-RU.po new file mode 100644 index 0000000..4b6e0d5 --- /dev/null +++ b/setting/i18n/ru-RU.po @@ -0,0 +1,53 @@ +msgid "setTarget" +msgstr "Установить цель" + +msgid "eventName" +msgstr "Событие" + +msgid "save" +msgstr "СОХРАНИТЬ" + +msgid "presets" +msgstr "Быстрый выбор" + +msgid "plusHour" +msgstr "+1 Час" + +msgid "plusDay" +msgstr "+1 День" + +msgid "plusWeek" +msgstr "+1 Неделя" + +msgid "nextNewYear" +msgstr "Новый год" + +msgid "countdown" +msgstr "Отсчёт" + +msgid "birthday" +msgstr "День рождения" + +msgid "newYear" +msgstr "Новый год" + +msgid "holiday" +msgstr "Праздник" + +msgid "meeting" +msgstr "Встреча" + +msgid "deadline" +msgstr "Дедлайн" + +msgid "anniversary" +msgstr "Юбилей" + +msgid "trip" +msgstr "Поездка" + +msgid "exam" +msgstr "Экзамен" + +msgid "wedding" +msgstr "Свадьба" diff --git a/setting/index.js b/setting/index.js index 87a6b0f..5ec7959 100644 --- a/setting/index.js +++ b/setting/index.js @@ -1,305 +1,395 @@ import { getText } from '@zos/i18n' -import { createWidget, widget, prop, align, text_style, event } from '@zos/ui' -import { px } from '@zos/utils' +import { createWidget, widget, prop, align, event } from '@zos/ui' import { back } from '@zos/router' -import { writeFileSync } from '@zos/fs' +import { writeFileSync, readFileSync, statSync } from '@zos/fs' +import { getDeviceInfo } from '@zos/device' + +const deviceInfo = getDeviceInfo() +const W = deviceInfo.width +const H = deviceInfo.height + +const EVENT_KEYS = [ + 'countdown', 'birthday', 'newYear', 'holiday', 'meeting', + 'deadline', 'anniversary', 'trip', 'exam', 'wedding' +] Page({ state: { - selectedYear: 2025, - selectedMonth: 8, - selectedDay: 6, + eventIndex: 0, + selectedYear: 2026, + selectedMonth: 1, + selectedDay: 1, selectedHour: 12, - selectedMinute: 0, - targetTimestamp: null + selectedMinute: 0 }, build() { - console.log('Building settings page...') - this.loadCurrentSettings() this.createSettingsInterface() }, loadCurrentSettings() { - // Устанавливаем значения по умолчанию (завтра в 12:00) + try { + const { size } = statSync({ path: 'countdown_target.json' }) + if (size > 0) { + const data = readFileSync({ + path: 'countdown_target.json', + options: { encoding: 'utf8' } + }) + const settings = JSON.parse(data) + if (settings && settings.target) { + const d = new Date(settings.target) + this.state.selectedYear = d.getFullYear() + this.state.selectedMonth = d.getMonth() + 1 + this.state.selectedDay = d.getDate() + this.state.selectedHour = d.getHours() + this.state.selectedMinute = d.getMinutes() + if (settings.eventKey) { + const idx = EVENT_KEYS.indexOf(settings.eventKey) + if (idx >= 0) this.state.eventIndex = idx + } + return + } + } + } catch (e) { + console.log('No saved settings, using defaults') + } + const tomorrow = new Date() tomorrow.setDate(tomorrow.getDate() + 1) tomorrow.setHours(12, 0, 0, 0) - this.state.selectedYear = tomorrow.getFullYear() this.state.selectedMonth = tomorrow.getMonth() + 1 this.state.selectedDay = tomorrow.getDate() - this.state.selectedHour = tomorrow.getHours() - this.state.selectedMinute = tomorrow.getMinutes() - - console.log('Settings loaded:', this.state) + this.state.selectedHour = 12 + this.state.selectedMinute = 0 }, createSettingsInterface() { - try { - // Фон - createWidget(widget.FILL_RECT, { - x: 0, - y: 0, - w: 480, - h: 490, - color: 0x000000 - }) + const contentH = Math.max(H, Math.floor(W * 1.55)) - // Заголовок - createWidget(widget.TEXT, { - x: 0, - y: 40, - w: 480, - h: 40, - text: 'SET TARGET DATE', - text_size: 32, - color: 0xffffff, - align_h: align.CENTER_H - }) + createWidget(widget.FILL_RECT, { + x: 0, + y: 0, + w: W, + h: contentH, + color: 0x000000 + }) - const startY = 120 - const stepY = 50 + createWidget(widget.TEXT, { + x: 0, + y: Math.floor(W * 0.07), + w: W, + h: Math.floor(W * 0.08), + text: getText('setTarget'), + text_size: Math.floor(W * 0.065), + color: 0xffffff, + align_h: align.CENTER_H + }) - // Год - this.createDateField('YEAR', this.state.selectedYear, startY, (delta) => { - this.state.selectedYear = Math.max(new Date().getFullYear(), Math.min(2035, this.state.selectedYear + delta)) - this.updateDisplay() - }) + let y = Math.floor(W * 0.18) - // Месяц - this.createDateField('MONTH', this.state.selectedMonth, startY + stepY, (delta) => { - this.state.selectedMonth += delta - if (this.state.selectedMonth > 12) this.state.selectedMonth = 1 - if (this.state.selectedMonth < 1) this.state.selectedMonth = 12 - this.updateDisplay() - }) + createWidget(widget.TEXT, { + x: Math.floor(W * 0.08), + y: y, + w: Math.floor(W * 0.22), + h: Math.floor(W * 0.07), + text: getText('eventName'), + text_size: Math.floor(W * 0.040), + color: 0x888888, + align_h: align.LEFT + }) - // День - this.createDateField('DAY', this.state.selectedDay, startY + stepY * 2, (delta) => { - const daysInMonth = new Date(this.state.selectedYear, this.state.selectedMonth, 0).getDate() - this.state.selectedDay += delta - if (this.state.selectedDay > daysInMonth) this.state.selectedDay = 1 - if (this.state.selectedDay < 1) this.state.selectedDay = daysInMonth - this.updateDisplay() - }) + const evBtnSize = Math.floor(W * 0.09) + const evNameW = Math.floor(W * 0.32) + const evBtnStartX = Math.floor(W * 0.34) - // Час - this.createDateField('HOUR', this.state.selectedHour, startY + stepY * 3, (delta) => { - this.state.selectedHour += delta - if (this.state.selectedHour > 23) this.state.selectedHour = 0 - if (this.state.selectedHour < 0) this.state.selectedHour = 23 - this.updateDisplay() - }) + const prevEventBtn = createWidget(widget.BUTTON, { + x: evBtnStartX, + y: y - Math.floor(W * 0.01), + w: evBtnSize, + h: evBtnSize, + text: '<', + normal_color: 0x1c1c1e, + press_color: 0x333333, + color: 0xffffff, + text_size: Math.floor(W * 0.042), + radius: Math.floor(evBtnSize / 2) + }) - // Минута - this.createDateField('MINUTE', this.state.selectedMinute, startY + stepY * 4, (delta) => { - this.state.selectedMinute += delta - if (this.state.selectedMinute >= 60) this.state.selectedMinute = 0 - if (this.state.selectedMinute < 0) this.state.selectedMinute = 59 - this.updateDisplay() - }) + this.eventNameWidget = createWidget(widget.TEXT, { + x: evBtnStartX + evBtnSize + Math.floor(W * 0.01), + y: y, + w: evNameW, + h: Math.floor(W * 0.07), + text: getText(EVENT_KEYS[this.state.eventIndex]), + text_size: Math.floor(W * 0.040), + color: 0x00b4ff, + align_h: align.CENTER_H + }) - // Кнопки управления - this.createControlButtons() + const nextEventBtn = createWidget(widget.BUTTON, { + x: evBtnStartX + evBtnSize + Math.floor(W * 0.02) + evNameW, + y: y - Math.floor(W * 0.01), + w: evBtnSize, + h: evBtnSize, + text: '>', + normal_color: 0x1c1c1e, + press_color: 0x333333, + color: 0xffffff, + text_size: Math.floor(W * 0.042), + radius: Math.floor(evBtnSize / 2) + }) + + prevEventBtn.addEventListener(event.CLICK_UP, () => { + this.state.eventIndex = (this.state.eventIndex - 1 + EVENT_KEYS.length) % EVENT_KEYS.length + this.eventNameWidget.setProperty(prop.TEXT, getText(EVENT_KEYS[this.state.eventIndex])) + }) - // Обновляем отображение + nextEventBtn.addEventListener(event.CLICK_UP, () => { + this.state.eventIndex = (this.state.eventIndex + 1) % EVENT_KEYS.length + this.eventNameWidget.setProperty(prop.TEXT, getText(EVENT_KEYS[this.state.eventIndex])) + }) + + y = Math.floor(W * 0.30) + const stepY = Math.floor(W * 0.105) + + this.createDateField('YEAR', this.state.selectedYear, y, (delta) => { + this.state.selectedYear = Math.max( + new Date().getFullYear(), + Math.min(2035, this.state.selectedYear + delta) + ) + this.updateDisplay() + }) + + this.createDateField('MONTH', this.state.selectedMonth, y + stepY, (delta) => { + this.state.selectedMonth += delta + if (this.state.selectedMonth > 12) this.state.selectedMonth = 1 + if (this.state.selectedMonth < 1) this.state.selectedMonth = 12 this.updateDisplay() + }) - console.log('Settings interface created') + this.createDateField('DAY', this.state.selectedDay, y + stepY * 2, (delta) => { + const daysInMonth = new Date(this.state.selectedYear, this.state.selectedMonth, 0).getDate() + this.state.selectedDay += delta + if (this.state.selectedDay > daysInMonth) this.state.selectedDay = 1 + if (this.state.selectedDay < 1) this.state.selectedDay = daysInMonth + this.updateDisplay() + }) - } catch (error) { - console.error('Error creating settings interface:', error) - } + this.createDateField('HOUR', this.state.selectedHour, y + stepY * 3, (delta) => { + this.state.selectedHour += delta + if (this.state.selectedHour > 23) this.state.selectedHour = 0 + if (this.state.selectedHour < 0) this.state.selectedHour = 23 + this.updateDisplay() + }) + + this.createDateField('MIN', this.state.selectedMinute, y + stepY * 4, (delta) => { + this.state.selectedMinute += delta + if (this.state.selectedMinute >= 60) this.state.selectedMinute = 0 + if (this.state.selectedMinute < 0) this.state.selectedMinute = 59 + this.updateDisplay() + }) + + const presetsY = y + stepY * 5 + Math.floor(W * 0.02) + + createWidget(widget.TEXT, { + x: 0, + y: presetsY, + w: W, + h: Math.floor(W * 0.06), + text: getText('presets'), + text_size: Math.floor(W * 0.035), + color: 0x888888, + align_h: align.CENTER_H + }) + + const presetBtnW = Math.floor(W * 0.40) + const presetBtnH = Math.floor(W * 0.085) + const presetGap = Math.floor(W * 0.04) + const presetStartX = Math.floor((W - presetBtnW * 2 - presetGap) / 2) + const presetY1 = presetsY + Math.floor(W * 0.07) + const presetY2 = presetY1 + presetBtnH + Math.floor(W * 0.02) + + this.createPresetBtn(presetStartX, presetY1, presetBtnW, presetBtnH, + getText('plusHour'), () => this.applyPreset(1, 'hour')) + this.createPresetBtn(presetStartX + presetBtnW + presetGap, presetY1, presetBtnW, presetBtnH, + getText('plusDay'), () => this.applyPreset(1, 'day')) + this.createPresetBtn(presetStartX, presetY2, presetBtnW, presetBtnH, + getText('plusWeek'), () => this.applyPreset(7, 'day')) + this.createPresetBtn(presetStartX + presetBtnW + presetGap, presetY2, presetBtnW, presetBtnH, + getText('nextNewYear'), () => this.applyNewYear()) + + const saveBtnW = Math.floor(W * 0.50) + const saveBtnH = Math.floor(W * 0.11) + const saveY = presetY2 + presetBtnH + Math.floor(W * 0.05) + + const saveBtn = createWidget(widget.BUTTON, { + x: Math.floor((W - saveBtnW) / 2), + y: saveY, + w: saveBtnW, + h: saveBtnH, + text: getText('save'), + normal_color: 0x004400, + press_color: 0x006600, + color: 0xffffff, + text_size: Math.floor(W * 0.045), + radius: Math.floor(saveBtnH / 2) + }) + + saveBtn.addEventListener(event.CLICK_UP, () => { + this.saveSettings() + }) + + this.updateDisplay() }, createDateField(label, value, y, onAdjust) { - const labelWidth = 120 - const valueWidth = 80 - const buttonSize = 44 - const startX = 40 + const labelWidth = Math.floor(W * 0.24) + const valueWidth = Math.floor(W * 0.17) + const buttonSize = Math.floor(W * 0.09) + const startX = Math.floor(W * 0.08) + const fontSize = Math.floor(W * 0.042) + const valFontSize = Math.floor(W * 0.052) - // Подпись createWidget(widget.TEXT, { x: startX, y: y, w: labelWidth, - h: 30, + h: Math.floor(W * 0.07), text: label, - text_size: 22, + text_size: fontSize, color: 0xaaaaaa, align_h: align.LEFT }) - // Кнопка - const downBtn = createWidget(widget.BUTTON, { - x: startX + labelWidth + 20, - y: y - 7, + x: startX + labelWidth + Math.floor(W * 0.02), + y: y - Math.floor(W * 0.01), w: buttonSize, h: buttonSize, - text: '-', + text: '−', normal_color: 0x1c1c1e, press_color: 0x333333, color: 0xffffff, - text_size: 24, - radius: buttonSize / 2 + text_size: fontSize, + radius: Math.floor(buttonSize / 2) }) - // Значение const valueWidget = createWidget(widget.TEXT, { - x: startX + labelWidth + 20 + buttonSize + 10, + x: startX + labelWidth + Math.floor(W * 0.03) + buttonSize, y: y, w: valueWidth, - h: 30, + h: Math.floor(W * 0.07), text: value.toString().padStart(2, '0'), - text_size: 28, + text_size: valFontSize, color: 0xffffff, align_h: align.CENTER_H }) - // Кнопка + const upBtn = createWidget(widget.BUTTON, { - x: startX + labelWidth + 20 + buttonSize + 10 + valueWidth + 10, - y: y - 7, + x: startX + labelWidth + Math.floor(W * 0.04) + buttonSize + valueWidth, + y: y - Math.floor(W * 0.01), w: buttonSize, h: buttonSize, text: '+', normal_color: 0x1c1c1e, press_color: 0x333333, color: 0xffffff, - text_size: 24, - radius: buttonSize / 2 - }) - - upBtn.addEventListener(event.CLICK_UP, () => { - onAdjust(1) + text_size: fontSize, + radius: Math.floor(buttonSize / 2) }) - downBtn.addEventListener(event.CLICK_UP, () => { - onAdjust(-1) - }) + upBtn.addEventListener(event.CLICK_UP, () => onAdjust(1)) + downBtn.addEventListener(event.CLICK_UP, () => onAdjust(-1)) - // Сохраняем ссылку на виджет значения - switch(label) { - case 'YEAR': - this.yearWidget = valueWidget - break - case 'MONTH': - this.monthWidget = valueWidget - break - case 'DAY': - this.dayWidget = valueWidget - break - case 'HOUR': - this.hourWidget = valueWidget - break - case 'MINUTE': - this.minuteWidget = valueWidget - break + switch (label) { + case 'YEAR': this.yearWidget = valueWidget; break + case 'MONTH': this.monthWidget = valueWidget; break + case 'DAY': this.dayWidget = valueWidget; break + case 'HOUR': this.hourWidget = valueWidget; break + case 'MIN': this.minuteWidget = valueWidget; break } }, - createControlButtons() { - // Кнопка применения - const applyBtn = createWidget(widget.BUTTON, { - x: (480 - 220) / 2, - y: 400, - w: 220, - h: 55, - text: 'SAVE', - normal_color: 0x004400, - press_color: 0x006600, - color: 0xffffff, - text_size: 22, - radius: 28 - }) - - applyBtn.addEventListener(event.CLICK_UP, () => { - this.saveSettings() + createPresetBtn(x, y, w, h, text, onClick) { + const btn = createWidget(widget.BUTTON, { + x: x, + y: y, + w: w, + h: h, + text: text, + normal_color: 0x1a1a2e, + press_color: 0x2a2a4e, + color: 0x00b4ff, + text_size: Math.floor(W * 0.032), + radius: Math.floor(h / 2) }) + btn.addEventListener(event.CLICK_UP, onClick) }, - setPreset(amount, unit) { - const now = new Date() + applyPreset(amount, unit) { const target = new Date() - - switch(unit) { - case 'hour': - target.setHours(target.getHours() + amount) - break - case 'day': - target.setDate(target.getDate() + amount) - break + if (unit === 'hour') { + target.setHours(target.getHours() + amount) + } else if (unit === 'day') { + target.setDate(target.getDate() + amount) } - this.state.selectedYear = target.getFullYear() this.state.selectedMonth = target.getMonth() + 1 this.state.selectedDay = target.getDate() this.state.selectedHour = target.getHours() this.state.selectedMinute = target.getMinutes() - this.updateDisplay() - console.log('Preset applied:', unit, amount) + }, + + applyNewYear() { + const year = new Date().getFullYear() + 1 + this.state.selectedYear = year + this.state.selectedMonth = 1 + this.state.selectedDay = 1 + this.state.selectedHour = 0 + this.state.selectedMinute = 0 + this.updateDisplay() }, updateDisplay() { try { - if (this.yearWidget) { - this.yearWidget.setProperty(prop.TEXT, this.state.selectedYear.toString()) - } - if (this.monthWidget) { - this.monthWidget.setProperty(prop.TEXT, this.state.selectedMonth.toString().padStart(2, '0')) - } - if (this.dayWidget) { - this.dayWidget.setProperty(prop.TEXT, this.state.selectedDay.toString().padStart(2, '0')) - } - if (this.hourWidget) { - this.hourWidget.setProperty(prop.TEXT, this.state.selectedHour.toString().padStart(2, '0')) - } - if (this.minuteWidget) { - this.minuteWidget.setProperty(prop.TEXT, this.state.selectedMinute.toString().padStart(2, '0')) - } - } catch (error) { - console.error('Error updating display:', error) + if (this.yearWidget) this.yearWidget.setProperty(prop.TEXT, this.state.selectedYear.toString()) + if (this.monthWidget) this.monthWidget.setProperty(prop.TEXT, this.state.selectedMonth.toString().padStart(2, '0')) + if (this.dayWidget) this.dayWidget.setProperty(prop.TEXT, this.state.selectedDay.toString().padStart(2, '0')) + if (this.hourWidget) this.hourWidget.setProperty(prop.TEXT, this.state.selectedHour.toString().padStart(2, '0')) + if (this.minuteWidget) this.minuteWidget.setProperty(prop.TEXT, this.state.selectedMinute.toString().padStart(2, '0')) + } catch (e) { + console.log('Error updating display:', e) } }, - calculateTargetTimestamp() { - const target = new Date( - this.state.selectedYear, - this.state.selectedMonth - 1, - this.state.selectedDay, - this.state.selectedHour, - this.state.selectedMinute, - 0 - ) - this.state.targetTimestamp = target.getTime() - console.log('Target timestamp calculated:', this.state.targetTimestamp, target.toString()) - }, - saveSettings() { try { - this.calculateTargetTimestamp() - - if (this.state.targetTimestamp) { - const filePath = 'countdown_target.json' - const data = JSON.stringify({ target: this.state.targetTimestamp }) - - writeFileSync({ - path: filePath, - data: data, - options: { - encoding: 'utf8' - } - }) - - console.log('Settings saved successfully to file!') - - back() - } - } catch (error) { - console.error('Error saving settings:', error) + const target = new Date( + this.state.selectedYear, + this.state.selectedMonth - 1, + this.state.selectedDay, + this.state.selectedHour, + this.state.selectedMinute, + 0 + ) + + const data = JSON.stringify({ + target: target.getTime(), + eventKey: EVENT_KEYS[this.state.eventIndex] + }) + + writeFileSync({ + path: 'countdown_target.json', + data: data, + options: { encoding: 'utf8' } + }) + + back() + } catch (e) { + console.error('Error saving settings:', e) } } }) \ No newline at end of file