С наращиванием функциональности и увеличением сложности приложения возникает необходимость дать пользователю возможность самому настраивать необходимые ему параметры. Приложение в свою очередь должно уметь сохранять эти настройки и предоставить пользователю интерфейс для управления ими. Какие средства и возможности для этого дали нам разработчики Pebble?
Документация у проекта 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 для выполнения кода при открытии страницы:
<!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(); }
Автор: tmnhy