[WIP] - Basic SimpleAppMessage integration for ints.

This commit is contained in:
Keegan
2016-10-25 03:00:43 -07:00
parent 62f0337198
commit 6e6440c9fb
19 changed files with 496 additions and 62 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# We ignore everying except for what is actually used by the pebble tool.
# We ignore everything except for what is actually used by the pebble tool.
/**/*
!/dist.zip
!/src/**/*
+33
View File
@@ -0,0 +1,33 @@
cmake_minimum_required (VERSION 3.2)
project (test-project)
set(CMAKE_C_FLAGS "-std=c11 -mthumb -ffunction-sections -g -fno-diagnostics-show-caret -D_REENT_SMALL=1 -Wall -Wextra -Werror -Wpointer-arith -Wno-unused-parameter -Wno-missing-field-initializers -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=unused-parameter -Wno-error=unused-but-set-variable -Wno-packed-bitfield-compat -mcpu=cortex-m3 -Os -Werror=return-type")
file(GLOB_RECURSE src "src/c/*")
file(GLOB_RECURSE include "include/*")
file(GLOB_RECURSE build "build/basalt/*.c")
set(SOURCES
${src}
${build}
${include}
)
set(INCLUDES
~/Library/Application\ Support/Pebble\ SDK/SDKs/current/sdk-core/pebble/basalt/include
./node_modules/pebble-events/dist/include
./node_modules/@smallstoneapps/linked-list/dist/include
./node_modules/@keegan-stoneware/simple-dict/dist/include
./node_modules/@keegan-stoneware/simple-app-message/dist/include
./build/include
./build
./include
)
add_definitions(-DPEBBLE)
add_definitions(-DPBL_COLOR)
include_directories(${INCLUDES})
add_executable(app ${SOURCES})
+6 -1
View File
@@ -1,4 +1,9 @@
# Clay
# Clay - Experimental
Welcome to the experimental branch of Clay. Expect this branch to be buggy, with incorrect docs, and with APIs that often change. DO NOT use this branch for your own projets but feel free to provide feedback via the Clay channel at http://discord.gg/aRUAYFN
---
Clay is a JavaScript library that makes it easy to add offline configuration pages to your Pebble apps. All you need to get started is a couple lines of JavaScript and a JSON file; no servers or HTML required.
Clay will by default automatically handle the 'showConfiguration' and 'webviewclosed' events traditionally implemented by developers to relay configuration settings to the watch side of the app. This step is not required when using Clay, since each config item is given the same `messageKey` as defined in `package.json` (or PebbleKit JS Message Keys on CloudPebble), and is automatically transmitted once the configuration page is submitted by the user. Developers can override this behavior by [handling the events manually](#handling-the-showconfiguration-and-webviewclosed-events-manually).
+10
View File
@@ -0,0 +1,10 @@
# Ignore build generated files
build/
dist/
dist.zip
# Ignore waf lock file
.lock-waf*
# Ignore installed node modules
node_modules/
+30
View File
@@ -0,0 +1,30 @@
cmake_minimum_required (VERSION 3.2)
project (test-project)
set(CMAKE_C_FLAGS "-std=c11 -mthumb -ffunction-sections -g -fno-diagnostics-show-caret -D_REENT_SMALL=1 -Wall -Wextra -Werror -Wpointer-arith -Wno-unused-parameter -Wno-missing-field-initializers -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=unused-parameter -Wno-error=unused-but-set-variable -Wno-packed-bitfield-compat -mcpu=cortex-m3 -Os -Werror=return-type")
file(GLOB_RECURSE src "src/*")
file(GLOB_RECURSE build "build/basalt/*.c")
set(SOURCES
${src}
${build}
)
set(INCLUDES
~/Library/Application\ Support/Pebble\ SDK/SDKs/current/sdk-core/pebble/basalt/include
./node_modules/pebble-events/dist/include
./node_modules/@keegan-stoneware/simple-dict/dist/include
./node_modules/pebble-clay/dist/include
./build/include
./build
./include
)
add_definitions(-DPEBBLE)
add_definitions(-DPBL_COLOR)
include_directories(${INCLUDES})
add_executable(app ${SOURCES})
+34
View File
@@ -0,0 +1,34 @@
{
"name": "clay-test",
"author": "MakeAwesomeHappen",
"version": "1.0.0",
"keywords": [
"pebble-app"
],
"scripts": {
"build": "./scripts/build.sh"
},
"private": true,
"dependencies": {
"pebble-clay": "../"
},
"pebble": {
"displayName": "clay-test",
"uuid": "3fff53d2-1b59-48d7-8187-0c2f3674c32d",
"sdkVersion": "3",
"enableMultiJS": true,
"targetPlatforms": [
"aplite",
"basalt",
"chalk",
"diorite"
],
"watchapp": {
"watchface": false
},
"messageKeys": [],
"resources": {
"media": []
}
}
}
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
test_app_folder_name="clay-test"
if [ "$(basename $(pwd))" != "$test_app_folder_name" ]; then
echo "Must be executed from $test_app_folder_name folder"
exit
fi
pebble clean && cd .. && npm run pebble-build || { cd $test_app_folder_name && exit; } && cd $test_app_folder_name && pebble build
+69
View File
@@ -0,0 +1,69 @@
#include <inttypes.h>
#include <pebble.h>
#include <pebble-clay/clay.h>
#define CLAY_INBOX_SIZE (64)
static Window *s_window;
static int s_background_color;
static void draw_background(Layer *layer, GContext *ctx) {
graphics_context_set_fill_color(ctx, GColorFromHEX(s_background_color));
graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone);
}
static void prv_update_layer(Layer *layer, GContext *ctx) {
draw_background(layer, ctx);
}
static void prv_window_unload(Window *window) {
window_destroy(window);
}
static void prv_window_load(Window *window) {
layer_set_update_proc(window_get_root_layer(s_window), prv_update_layer);
}
static void prv_clay_updated_handler(void *context) {
int value_int;
if (clay_get_int("test_int", &value_int)) {
s_background_color = value_int;
}
layer_mark_dirty(window_get_root_layer(s_window));
}
static void prv_init(void) {
const ClayCallbacks clay_callbacks = (ClayCallbacks) {
.settings_updated = prv_clay_updated_handler,
};
clay_register_callbacks(&clay_callbacks, NULL);
clay_init(CLAY_INBOX_SIZE);
s_window = window_create();
window_set_window_handlers(s_window, (WindowHandlers) {
.load = prv_window_load,
.unload = prv_window_unload,
});
window_stack_push(s_window, true);
}
int main(void) {
prv_init();
app_event_loop();
}
//int s_background_color = 0xffffff;
//#define TEXT_SIZE 20;
//char s_text [TEXT_SIZE];
//
//void clay_update_handler(void *ctx) {
// clay_get_int("background_color", &s_background_color);
// clay_get_string("background_color", &s_text, TEXT_SIZE - 1);
//}
+19
View File
@@ -0,0 +1,19 @@
'use strict';
var Clay = require('pebble-clay');
var clay = new Clay({
config: [
{
label: 'test',
type: 'color',
messageKey: 'test_int'
},
{
type: 'submit',
defaultValue: 'submit'
}
],
closedCallback: function(response) {
console.log('closedcallback: arguments' + JSON.stringify(arguments));
}
});
+59
View File
@@ -0,0 +1,59 @@
#
# This file is the default set of rules to compile a Pebble application.
#
# Feel free to customize this to your needs.
#
import os.path
import shutil
import waflib
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def distclean(ctx):
node_modules_dir = ctx.path.find_node('node_modules')
if node_modules_dir:
shutil.rmtree(node_modules_dir.abspath())
waflib.Scripting.distclean(ctx)
def configure(ctx):
"""
This method is used to configure your build. ctx.load(`pebble_sdk`) automatically configures
a build for each valid platform in `targetPlatforms`. Platform-specific configuration: add your
change after calling ctx.load('pebble_sdk') and make sure to set the correct environment first.
Universal configuration: add your change prior to calling ctx.load('pebble_sdk').
"""
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
cached_env = ctx.env
for platform in ctx.env.TARGET_PLATFORMS:
ctx.env = ctx.all_envs[platform]
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), target=app_elf)
if build_worker:
worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), target=worker_elf)
else:
binaries.append({'platform': platform, 'app_elf': app_elf})
ctx.env = cached_env
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries,
js=ctx.path.ant_glob(['src/js/**/*.js', 'src/js/**/*.json']),
js_entry_file='src/js/app.js')
+7
View File
@@ -12,6 +12,13 @@ module.exports = [
"type": "text",
"defaultValue": "This is config 2"
},
{
type: 'thing',
defaultValue: {
en_US: 'foo',
fr_FR: 'bar'
}
},
{
"type": "section",
"items": [
+1 -1
View File
@@ -41,7 +41,7 @@ module.exports = [
"messageKey": "email",
"defaultValue": "",
"label": "Input Field",
"description": "This is a description for the input component. " +
"description": "{{i18n.foo}} This is a description for the input component. " +
"You can add <strong>html</strong> in here too.",
"attributes": {
"placeholder": "Placeholder set with attributes"
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <@keegan-stoneware/simple-app-message/simple-app-message.h>
typedef void (*ClayUpdatedCallback)(void *context);
typedef struct ClayCallbacks {
ClayUpdatedCallback settings_updated;
} ClayCallbacks;
void clay_init(uint32_t inbox_size);
void clay_remove(const char *key);
void clay_clear();
void clay_set_bool(const char *key, bool value);
void clay_set_data(const char *key, const void *data, size_t n);
void clay_set_int(const char *key, int value);
void clay_set_string(const char *key, const char *value);
bool clay_get_bool(const char *key, bool *value_out);
bool clay_get_data(const char *key, void *value_out, size_t n);
bool clay_get_int(const char *key, int *value_out);
bool clay_get_string(const char *key, char *value_out, size_t n);
bool clay_register_callbacks(const ClayCallbacks *callbacks, void *context);
+57 -58
View File
@@ -5,34 +5,30 @@ var toSource = require('tosource');
var standardComponents = require('./src/scripts/components');
var deepcopy = require('deepcopy/build/deepcopy.min');
var version = require('./package.json').version;
var messageKeys = require('message_keys');
var simpleAppMessage = require('@keegan-stoneware/simple-app-message');
var utils = require('./src/scripts/lib/utils');
/**
* @param {Array} config - the Clay config
* @param {function} [customFn] - Custom code to run from the config page. Will run
* with the ClayConfig instance as context
* @param {Object} [options] - Additional options to pass to Clay
* @param {boolean} [options.autoHandleEvents=true] - If false, Clay will not
* automatically handle the 'showConfiguration' and 'webviewclosed' events
* @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
* be available as `clayConfig.meta.userData`
* @param {Object} options - Clay options
* @param {Array} [options.config] - The config
* @param {function} [options.openCallback]
* @param {function} [options.closedCallback]
* @param {Object} [options.locales]
* @param {function} [options.customFunction] - Custom code to run from the config
* page. Will run with the ClayConfig instance as context
* @constructor
*/
function Clay(config, customFn, options) {
function Clay(options) {
var self = this;
if (!Array.isArray(config)) {
throw new Error('config must be an Array');
if (!options) {
utils.throw('You must define options');
}
if (customFn && typeof customFn !== 'function') {
throw new Error('customFn must be a function or "null"');
}
options = deepcopy(options);
options = options || {};
self.config = deepcopy(config);
self.customFn = customFn || function() {};
self.config = options.config;
self.customFn = options.customFn || function() {};
self.components = {};
self.meta = {
activeWatchInfo: null,
@@ -57,30 +53,30 @@ function Clay(config, customFn, options) {
}
// Let Clay handle all the magic
if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {
Pebble.addEventListener('showConfiguration', function() {
_populateMeta();
Pebble.openURL(self.generateUrl());
});
Pebble.addEventListener('showConfiguration', function() {
_populateMeta();
Pebble.openURL(self.generateUrl());
Pebble.addEventListener('webviewclosed', function(e) {
if (!e || !e.response) { return; }
var settings = self.getSettings(e.response);
if (options.closedCallback) {
options.closedCallback(settings);
}
simpleAppMessage.send('CLAY', settings, function(response) {
if (response.error) {
utils.log('Failed to send config data!');
utils.log(response.error);
} else {
utils.log('Sent config data to Pebble');
}
});
Pebble.addEventListener('webviewclosed', function(e) {
if (!e || !e.response) { return; }
// Send settings to Pebble watchapp
Pebble.sendAppMessage(self.getSettings(e.response), function() {
console.log('Sent config data to Pebble');
}, function(error) {
console.log('Failed to send config data!');
console.log(JSON.stringify(error));
});
});
} else if (typeof Pebble !== 'undefined') {
Pebble.addEventListener('ready', function() {
_populateMeta();
});
}
});
/**
* If this function returns true then the callback will be executed
@@ -195,20 +191,21 @@ Clay.prototype.getSettings = function(response, convert) {
try {
settings = JSON.parse(response);
} catch (e) {
throw new Error('The provided response was not valid JSON');
utils.error('The provided response was not valid JSON');
}
// @todo do something with the persist flag here
// flatten the settings for localStorage
var settingsStorage = {};
Object.keys(settings).forEach(function(key) {
if (typeof settings[key] === 'object' && settings[key]) {
settingsStorage[key] = settings[key].value;
} else {
settingsStorage[key] = settings[key];
}
});
localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
// var settingsStorage = {};
// Object.keys(settings).forEach(function(key) {
// if (typeof settings[key] === 'object' && settings[key]) {
// settingsStorage[key] = settings[key].value;
// } else {
// settingsStorage[key] = settings[key];
// }
// });
//
// localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
};
@@ -321,13 +318,15 @@ Clay.prepareSettingsForAppMessage = function(settings) {
var result = {};
Object.keys(flatSettings).forEach(function(key) {
var messageKey = messageKeys[key];
var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
// var messageKey = messageKeys[key];
// var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
// settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
//
// settingArr.forEach(function(setting, index) {
// result[messageKey + index] = setting;
// });
settingArr.forEach(function(setting, index) {
result[messageKey + index] = setting;
});
result[key] = Clay.prepareForAppMessage(flatSettings[key]);
});
// validate the settings
+3 -1
View File
@@ -87,5 +87,7 @@
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.7.0"
},
"dependencies": {}
"dependencies": {
"@keegan-stoneware/simple-app-message": "^1.0.0"
}
}
+91
View File
@@ -0,0 +1,91 @@
#include "clay.h"
#include "hash.h"
#define SIMPLE_APP_MESSAGE_NAMESPACE ("CLAY")
static ClayCallbacks s_callbacks;
static void *s_context;
static bool prv_store_settings(const char *key, SimpleDictDataType type, const void *data,
size_t data_size, void *context) {
uint32_t persist_key = hash((uint8_t *)key, strlen(key));
switch (type) {
case SimpleDictDataType_Raw:
persist_write_data(persist_key, data, data_size);
return true;
case SimpleDictDataType_Bool: {
const bool value = *((bool *)data);
persist_write_bool(persist_key, value);
return true;
}
case SimpleDictDataType_Int: {
const int value = *((int *)data);
APP_LOG(APP_LOG_LEVEL_INFO, "CLAY: writing %d - %d to storage", (int)persist_key, value);
persist_write_int(persist_key, value);
return true;
}
case SimpleDictDataType_String: {
const char *value = data;
persist_write_string(persist_key, value);
return true;
}
case SimpleDictDataTypeCount:
break;
}
APP_LOG(APP_LOG_LEVEL_ERROR, "Unexpected type %d", type);
return false;
}
static void prv_simple_app_message_received_callback(const SimpleDict *message, void *context) {
if (!message) {
return;
}
APP_LOG(APP_LOG_LEVEL_INFO, "CLAY: Received SimpleAppMessage");
simple_dict_foreach(message, prv_store_settings, NULL);
s_callbacks.settings_updated(context);
}
bool clay_register_callbacks(const ClayCallbacks *callbacks, void *context) {
s_callbacks = *callbacks;
s_context = context;
return true;
}
void clay_init(uint32_t inbox_size) {
const SimpleAppMessageCallbacks simple_app_message_callbacks = (SimpleAppMessageCallbacks) {
.message_received = prv_simple_app_message_received_callback,
};
const bool register_success = simple_app_message_register_callbacks(SIMPLE_APP_MESSAGE_NAMESPACE,
&simple_app_message_callbacks,
s_context);
if (!register_success) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to register callbacks for namespace %s", SIMPLE_APP_MESSAGE_NAMESPACE);
return;
}
const bool request_inbox_size_success = simple_app_message_request_inbox_size(inbox_size);
if (!request_inbox_size_success) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to request inbox size of %d", (int)inbox_size);
return;
}
simple_app_message_open();
}
bool clay_get_int(const char *key, int *value_out) {
uint32_t persist_key = hash((uint8_t *)key, strlen(key));
if (persist_exists(persist_key)) {
int value = persist_read_int(persist_key);
APP_LOG(APP_LOG_LEVEL_INFO, "CLAY: reading %d - %d from storage", (int)persist_key, value);
memcpy(value_out, &value, sizeof(&value));
return true;
}
return false;
}
+21
View File
@@ -0,0 +1,21 @@
#include "hash.h"
#include <stdint.h>
// Based on DJB2 Hash
uint32_t hash(const uint8_t *bytes, const uint32_t length) {
uint32_t hash = 5381;
if (length == 0) {
return hash;
}
uint8_t c;
const uint8_t *last_byte = bytes + length;
while (bytes != last_byte) {
c = *bytes;
hash = ((hash << 5) + hash) + c;
bytes++;
}
return hash;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
uint32_t hash(const uint8_t *bytes, const uint32_t length);
+8
View File
@@ -133,3 +133,11 @@ module.exports.includesCapability = function(activeWatchInfo, capabilities) {
return result.indexOf(false) === -1;
};
module.exports.log = function(message) {
console.log('Clay: ' + JSON.stringify(message));
};
module.exports.throw = function(error) {
throw new Error('Clay: '+ error)
};