С наращиванием функциональности и увеличением сложности приложения возникает необходимость дать пользователю возможность самому настраивать необходимые ему параметры. Приложение в свою очередь должно уметь сохранять эти настройки и предоставить пользователю интерфейс для управления ими. Какие средства и возможности для этого дали нам разработчики Pebble?
Документация у проекта Pebble хорошая и целью изложенного не является её дублирования. Это попытка собрать то, что касается хранения данных и возможностей по конфигурированию приложения в одном месте. Далее присутствуют краткие выдержки из документации и немного кода в виде примера проекта.
Разработчики предусмотрели следующие варианты хранить данные приложения:
PebbleKit JavaScript позволяет отобразить на экране смартфона окно конфигурации (web-приложение) размещенное и доступное по определенному разработчиком URL'у. [3]
После завершения работы пользователя с web-приложением, оно вызывает особый URL «pebblejs://close» и передает в мобильное приложение необходимые данные.
Для полноценной работы приложения с настройками необходимо реализовать следующее:
Обо всем по порядку, добавим в простое приложение, которое показывает на экране время, следующие опции:
Настройки будем хранить не отдельными параметрами, а в структуре.
Данные, по рекомендации из документации, будем упаковывать, так как компилятор каждое поле структуры выравнивает по границе 32 bit.
Также зададим значения по умолчанию — вибрировать при смене статуса bluetooth соединения и молчать при смене минут:
Чтение и запись данных будет выглядеть следующим образом:
Задаем обработчики событий, подписываемся и отписываемся от событий, добавляем чтение из хранилища при старте приложения и запись при завершении его работы:
Теперь, когда приложение умеет сохранять состояние своих настроек, переходим к организации хранилища на телефоне.
Для отображения значка шестеренки по клику на который вызывается окошко с настройками необходимо включить соотвествующую опцию в appinfo.json:
Нажатие на шестеренку генерирует событие showConfiguration, в WebView загружается страничка указанная в обработчике pebble-js-app.js:
Для примера простой интерфейс из двух чекбоксов и кнопки «Submit», их даже не принципиально «оборачивать» в форму.
Сначала считываем значения из localStorage и в соответствии с этим расставляем галочки на чекбоксах, если параметр отсутствует в хранилище инициализируем его значением по умолчанию:
Отслеживаем нажатие кнопки и отправляем в приложение выбор пользователя, попутно сохраняя новые значения в хранилище:
Полный код страничики, не обошлось без jquery для выполнения кода при открытии страницы:
Закрытие окна-странички порождает событие webviewclosed обработчик которого необходимо прописать в pebble-js-app.js:
Подошли к тому моменту, когда показали пользователю всё что хотели, он сделал свой выбор и дело за малым, сохранить этот выбор в приложении на часах.
Как сообщает нам документация, AppMessage API реализует push-ориентированный протокол обмена. Либо часы, либо смартфон может инициировать передачу сообщения. Обработка принимающей стороной осуществляется callback'ами.
Каждое сообщение — словарь, который содержит список пар key-value.
Чтобы и часы и смартфон «понимали», чем они обмениваются, необходимо описать параметры этого словаря минимум в двух местах, в основном Си файле:
и в appinfo.json:
Регистрируем обратный вызов и инициализируем AppMessage:
И завершающий штрих, описываем callback для входящих сообщений:
В итоге, приложение при первом запуске/установке инициализирует persistent storage, умеет читать и сохранять данные, имеет собственное страничку для настройки параметров.
Полный код проекта на Bitbucket.
[1] Pebble Developers // Persisting App Data
[2] Pebble Developers // Extending App Capabilities // Storing Data
[3] Pebble Developers // App Configuration
[4] Pebble SDK 2.0 Tutorial #8: Android App Integration | try { work(); } finally { code(); }
[5] Pebble SDK 2.0 Tutorial #9: App Configuration | try { work(); } finally { code(); }
Документация у проекта Pebble хорошая и целью изложенного не является её дублирования. Это попытка собрать то, что касается хранения данных и возможностей по конфигурированию приложения в одном месте. Далее присутствуют краткие выдержки из документации и немного кода в виде примера проекта.
Хранение данных
Разработчики предусмотрели следующие варианты хранить данные приложения:
- Хранение данных приложения на самом устройстве.
Для этого есть отдельное для каждого приложения key-value хранилище. Размер хранилища до 256 байт, поддерживает хранение целых чисел, строк и массивов байт. Отличительная особенность — данные сохраняются на самих часах, не требуется сопряжение с приложением-компаньоном, как следствие — скорость работы и энергосбережение. [1] - Хранение данных на телефоне в приложении-компаньоне.
В официальном мобильном приложении PebbleKit JavaScript реализует API на основе объекта localStorage. [2]
Настройка приложения
PebbleKit JavaScript позволяет отобразить на экране смартфона окно конфигурации (web-приложение) размещенное и доступное по определенному разработчиком URL'у. [3]
После завершения работы пользователя с web-приложением, оно вызывает особый URL «pebblejs://close» и передает в мобильное приложение необходимые данные.
Реализация
Для полноценной работы приложения с настройками необходимо реализовать следующее:
- persistent storage — организовать «локальное» хранилище настроек на часах, необходимо для автономной работы приложения;
- local storage — организовать «локальное» хранилище настроек в официальном мобильном приложении, понадобится для связи окна конфигурации с приложением на часах;
- сделать web-страничку с интерфейсом управления настройками;
- организовать передачу новых настроек из мобильного приложения для сохранения на часах.
Обо всем по порядку, добавим в простое приложение, которое показывает на экране время, следующие опции:
- «отбивать» каждую минуту вибросигналом;
- сигнализировать вибросигналом о потере и восстановлении bluetooth-соединения.
Persistent storage
Настройки будем хранить не отдельными параметрами, а в структуре.
Данные, по рекомендации из документации, будем упаковывать, так как компилятор каждое поле структуры выравнивает по границе 32 bit.
Также зададим значения по умолчанию — вибрировать при смене статуса bluetooth соединения и молчать при смене минут:
#define STORAGE_KEY 99 typedef struct persist { bool vibe_bt; bool vibe_min; } __attribute__((__packed__)) persist; persist settings = { .vibe_bt = true, .vibe_min = false };
Чтение и запись данных будет выглядеть следующим образом:
/*...*/ persist_read_data(STORAGE_KEY, &settings, sizeof(settings)); /*...*/ persist_write_data(STORAGE_KEY, &settings, sizeof(settings));
Задаем обработчики событий, подписываемся и отписываемся от событий, добавляем чтение из хранилища при старте приложения и запись при завершении его работы:
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { /*...*/ if ((units_changed & MINUTE_UNIT) && (settings.vibe_min)) { vibes_double_pulse(); }; } void bt_handler(bool connected) { if (settings.vibe_bt) { vibes_long_pulse(); }; } static void init(void) { if (persist_exists(STORAGE_KEY)) { persist_read_data(STORAGE_KEY, &settings, sizeof(settings)); } else { persist_write_data(STORAGE_KEY, &settings, sizeof(settings)); }; /*...*/ window_stack_push(window, animated); tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); bluetooth_connection_service_subscribe(bt_handler); } static void deinit(void) { /*...*/ persist_write_data(STORAGE_KEY, &settings, sizeof(settings)); tick_timer_service_unsubscribe(); bluetooth_connection_service_unsubscribe(); }
Теперь, когда приложение умеет сохранять состояние своих настроек, переходим к организации хранилища на телефоне.
Local storage и web-интерфейс
Для отображения значка шестеренки по клику на который вызывается окошко с настройками необходимо включить соотвествующую опцию в appinfo.json:
{ /* ... */ "capabilities": [ "configurable" ], /* ... */ }
Нажатие на шестеренку генерирует событие showConfiguration, в WebView загружается страничка указанная в обработчике pebble-js-app.js:
Pebble.addEventListener('showConfiguration', function(e) {Pebble.openURL("http://domain.tld/index.html"); })
Для примера простой интерфейс из двух чекбоксов и кнопки «Submit», их даже не принципиально «оборачивать» в форму.
Сначала считываем значения из localStorage и в соответствии с этим расставляем галочки на чекбоксах, если параметр отсутствует в хранилище инициализируем его значением по умолчанию:
<script> $().ready(function() { var vibe_bt = parseInt(localStorage.getItem("vibe_bt")); var vibe_min = parseInt(localStorage.getItem("vibe_min")); if (isNaN(vibe_bt)) { vibe_bt = 1; }; if (vibe_bt) { $('#vibe_bt').prop('checked', true); } else { $('#vibe_bt').prop('checked', false); }; if (isNaN(vibe_min)) { vibe_min = 0; }; if (vibe_min) { $('#vibe_min').prop('checked', true); } else { $('#vibe_min').prop('checked', false); }; }); /*...*/ </script>
Отслеживаем нажатие кнопки и отправляем в приложение выбор пользователя, попутно сохраняя новые значения в хранилище:
<script> /*...*/ var submitButton = document.getElementById("b_submit"); submitButton.addEventListener("click", function() { localStorage.setItem("vibe_bt", $('#vibe_bt').prop('checked') ? 1 : 0); localStorage.setItem("vibe_min", $('#vibe_min').prop('checked') ? 1 : 0); var result = { vibe_bt: $('#vibe_bt').prop('checked') ? 1 : 0, vibe_min: $('#vibe_min').prop('checked') ? 1 : 0, }; var location = "pebblejs://close#"+JSON.stringify(result); document.location = location; }, false); </script>
Полный код страничики, не обошлось без jquery для выполнения кода при открытии страницы:
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="js/jquery/jquery.min.js"></script> <title>Configuration window</title> <style> </style> </head> <body> <div> <div> <input id="vibe_bt" type="checkbox" checked /> <span>vibe bluetooth on/off</span> </div> <div> <input id="vibe_min" type="checkbox" checked /> <span>vibe every minute</span> </div> <div> <input id="b_submit" type="submit" value="Save" /> </div> </div> <script> $().ready(function() { var vibe_bt = parseInt(localStorage.getItem("vibe_bt")); var vibe_min = parseInt(localStorage.getItem("vibe_min")); if (isNaN(vibe_bt)) { vibe_bt = 1; }; if (vibe_bt) { $('#vibe_bt').prop('checked', true); } else { $('#vibe_bt').prop('checked', false); }; if (isNaN(vibe_min)) { vibe_min = 0; }; if (vibe_min) { $('#vibe_min').prop('checked', true); } else { $('#vibe_min').prop('checked', false); }; }); var submitButton = document.getElementById("b_submit"); submitButton.addEventListener("click", function() { localStorage.setItem("vibe_bt", $('#vibe_bt').prop('checked') ? 1 : 0); localStorage.setItem("vibe_min", $('#vibe_min').prop('checked') ? 1 : 0); var result = { VIBE_BT: $('#vibe_bt').prop('checked') ? 1 : 0, VIBE_MIN: $('#vibe_min').prop('checked') ? 1 : 0, }; var location = "pebblejs://close#"+JSON.stringify(result); document.location = location; }, false); </script> </body> </html>
Закрытие окна-странички порождает событие webviewclosed обработчик которого необходимо прописать в pebble-js-app.js:
Pebble.addEventListener('webviewclosed', function(e) { Pebble.sendAppMessage(JSON.parse(e.response), function(e) {}, function(e) {}); })
Подошли к тому моменту, когда показали пользователю всё что хотели, он сделал свой выбор и дело за малым, сохранить этот выбор в приложении на часах.
AppMessage API
Как сообщает нам документация, AppMessage API реализует push-ориентированный протокол обмена. Либо часы, либо смартфон может инициировать передачу сообщения. Обработка принимающей стороной осуществляется callback'ами.
Каждое сообщение — словарь, который содержит список пар key-value.
Чтобы и часы и смартфон «понимали», чем они обмениваются, необходимо описать параметры этого словаря минимум в двух местах, в основном Си файле:
#define VIBE_BT 1 #define VIBE_MIN 2
и в appinfo.json:
{ /* ... */ "appKeys": { "VIBE_BT": 1, "VIBE_MIN": 2, } /* ... */ }
Регистрируем обратный вызов и инициализируем AppMessage:
static void init(void) { /*...*/ app_message_register_inbox_received(in_received_handler); const uint32_t inbound_size = 128; const uint32_t outbound_size = 128; app_message_open(inbound_size, outbound_size); /*...*/ }
И завершающий штрих, описываем callback для входящих сообщений:
void in_received_handler(DictionaryIterator *received, void *context) { Tuple *vibe_bt_tuple = dict_find(received, VIBE_BT); Tuple *vibe_min_tuple = dict_find(received, VIBE_MIN); settings.vibe_bt = (bool)vibe_bt_tuple->value->int16; settings.vibe_min = (bool)vibe_min_tuple->value->int16; }
В итоге, приложение при первом запуске/установке инициализирует persistent storage, умеет читать и сохранять данные, имеет собственное страничку для настройки параметров.
Полный код проекта на Bitbucket.
[1] Pebble Developers // Persisting App Data
[2] Pebble Developers // Extending App Capabilities // Storing Data
[3] Pebble Developers // App Configuration
[4] Pebble SDK 2.0 Tutorial #8: Android App Integration | try { work(); } finally { code(); }
[5] Pebble SDK 2.0 Tutorial #9: App Configuration | try { work(); } finally { code(); }