Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
neal committed Nov 27, 2013
0 parents commit 65d6042
Show file tree
Hide file tree
Showing 22 changed files with 725 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
.lock-waf*
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Hacker News

**Introducing Hacker News for Pebble! Read the front page, new, and best stories on Hacker News now right on your wrist!**

![Home Menu](http://f.cl.ly/items/30431D041R1P1M1z1L05/hackernews1.png)
![Story List](http://f.cl.ly/items/1a2b0A1U1c2d411h1O1V/hackernews2.png)
![Story View](http://f.cl.ly/items/1z2Q3x0M0c3E1946023s/hackernews3.png)

It uses HNify API to get the stories and Clipped API to summarize them.

A comments view and settings (change text size) is in the works.

Long click select in the story list view refreshes the list (until i figure out a better way to use it).

Contributions are much welcome!

###### Thanks to @matthewtole for multi-timer! <small>(Good MenuLayer example)</small>
28 changes: 28 additions & 0 deletions appinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"uuid": "bbf7c879-a2c8-43df-8cbc-b258276698bf",
"shortName": "Hacker News",
"longName": "Hacker News",
"companyName": "Neal",
"versionCode": 1,
"versionLabel": "1.0.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"endpoint": 0,
"index": 1,
"title": 2,
"subtitle": 3,
"summary": 4
},
"resources": {
"media": [
{
"menuIcon": true,
"type": "png",
"name": "IMAGE_MENU_ICON",
"file": "images/icon.png"
}
]
}
}
4 changes: 4 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pebble build || { exit $?; }
if [ "$1" = "install" ]; then
pebble install --logs
fi
Binary file added resources/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions src/appmessage.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <pebble.h>
#include "appmessage.h"
#include "common.h"
#include "windows/storylist.h"

static void in_received_handler(DictionaryIterator *iter, void *context);
static void in_dropped_handler(AppMessageResult reason, void *context);
static void out_sent_handler(DictionaryIterator *sent, void *context);
static void out_failed_handler(DictionaryIterator *failed, AppMessageResult reason, void *context);

void appmessage_init(void) {
app_message_open(128 /* inbound_size */, 32 /* outbound_size */);
app_message_register_inbox_received(in_received_handler);
app_message_register_inbox_dropped(in_dropped_handler);
app_message_register_outbox_sent(out_sent_handler);
app_message_register_outbox_failed(out_failed_handler);
}

static void in_received_handler(DictionaryIterator *iter, void *context) {
Tuple *endpoint_tuple = dict_find(iter, HN_KEY_ENDPOINT);

if (endpoint_tuple) {
if (storylist_is_on_top() && storylist_current_endpoint() == endpoint_tuple->value->int16) {
storylist_in_received_handler(iter);
} else {
app_message_outbox_send();
}
}
}

static void in_dropped_handler(AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Incoming AppMessage from Pebble dropped, %d", reason);
}

static void out_sent_handler(DictionaryIterator *sent, void *context) {
// outgoing message was delivered
}

static void out_failed_handler(DictionaryIterator *failed, AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Failed to send AppMessage to Pebble");
}
3 changes: 3 additions & 0 deletions src/appmessage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

void appmessage_init(void);
19 changes: 19 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#define ENDPOINT_FRONTPAGE 0
#define ENDPOINT_NEWPOSTS 1
#define ENDPOINT_BESTPOSTS 2

typedef struct {
int index;
char title[24];
char subtitle[30];
} HNStory;

enum {
HN_KEY_ENDPOINT = 0x0,
HN_KEY_INDEX = 0x1,
HN_KEY_TITLE = 0x2,
HN_KEY_SUBTITLE = 0x3,
HN_KEY_SUMMARY = 0x4,
};
147 changes: 147 additions & 0 deletions src/js/pebble-js-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
var maxAppMessageBuffer = 100;
var maxAppMessageTries = 3;
var appMessageRetryTimeout = 3000;
var appMessageTimeout = 100;
var httpTimeout = 12000;
var appMessageQueue = [];
var stories = {};

var ENDPOINTS = {
'FRONTPAGE': 0,
'NEWPOSTS': 1,
'BESTPOSTS': 2
};

var API_URLS = {
[ENDPOINTS.FRONTPAGE]: 'http://hnify.herokuapp.com/get/top',
[ENDPOINTS.NEWPOSTS]: 'http://hnify.herokuapp.com/get/newest',
[ENDPOINTS.BESTPOSTS]: 'http://hnify.herokuapp.com/get/best',
'clipped': 'http://clipped.me/algorithm/clippedapi.php?url='
};

function sendAppMessage() {
if (appMessageQueue.length > 0) {
currentAppMessage = appMessageQueue[0];
currentAppMessage.numTries = currentAppMessage.numTries || 0;
currentAppMessage.transactionId = currentAppMessage.transactionId || -1;
if (currentAppMessage.numTries < maxAppMessageTries) {
console.log('Sending AppMessage to Pebble: ' + JSON.stringify(currentAppMessage.message));
Pebble.sendAppMessage(
currentAppMessage.message,
function(e) {
appMessageQueue.shift();
setTimeout(function() {
sendAppMessage();
}, appMessageTimeout);
}, function(e) {
console.log('Failed sending AppMessage for transactionId:' + e.data.transactionId + '. Error: ' + e.data.error.message);
appMessageQueue[0].transactionId = e.data.transactionId;
appMessageQueue[0].numTries++;
setTimeout(function() {
sendAppMessage();
}, appMessageRetryTimeout);
}
);
} else {
console.log('Failed sending AppMessage for transactionId:' + currentAppMessage.transactionId + '. Bailing. ' + JSON.stringify(currentAppMessage.message));
}
}
}

function hackernews(endpoint) {
var xhr = new XMLHttpRequest();
xhr.open('GET', API_URLS[endpoint], true);
xhr.timeout = httpTimeout;
xhr.onload = function(e) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.responseText) {
res = JSON.parse(xhr.responseText);
stories = res.stories;
stories.forEach(function (element, index, array) {
title = element.title.substring(0,23);
subtitle = element.points + ' points • ' + element.num_comments + ' comments';
appMessageQueue.push({'message': {'endpoint': endpoint, 'index': index, 'title': title, 'subtitle': subtitle}});
});
} else {
console.log('Invalid response received! ' + JSON.stringify(xhr));
appMessageQueue.push({'message': {'endpoint': endpoint, 'title': 'Invalid response!'}});
}
} else {
console.log('Request returned error code ' + xhr.status.toString());
appMessageQueue.push({'message': {'endpoint': endpoint, 'title': 'HTTP/1.1 ' + xhr.statusText}});
}
}
sendAppMessage();
}
xhr.ontimeout = function() {
console.log('HTTP request timed out');
appMessageQueue.push({'message': {'endpoint': endpoint, 'title': 'Request timed out!'}});
sendAppMessage();
};
xhr.onerror = function() {
console.log('HTTP request return error');
appMessageQueue.push({'message': {'endpoint': endpoint, 'title': 'Failed to connect!'}});
sendAppMessage();
};
xhr.send(null);
}

function clipped(url, endpoint) {
var xhr = new XMLHttpRequest();
xhr.open('GET', API_URLS.clipped + url, true);
xhr.timeout = httpTimeout;
xhr.onload = function(e) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.responseText) {
try {
res = JSON.parse(xhr.responseText);
title = res.title || '';
summary = res.summary.join(' ') || '';
for (var i = 0; i <= Math.floor(summary.length/maxAppMessageBuffer); i++) {
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary': summary.substring(i * maxAppMessageBuffer, i * maxAppMessageBuffer + maxAppMessageBuffer)}});
}
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary': true, 'title': title.substring(0,60)}});
} catch(e) {
console.log('Caught error: ' + JSON.stringify(e));
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary':true}});
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary':true, 'title': 'Unable to summarize! :('}});
}
} else {
console.log('Invalid response received! ' + JSON.stringify(xhr));
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary':true, 'title': 'Invalid response!'}});
}
} else {
console.log('Request returned error code ' + xhr.status.toString());
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary':true, 'title': 'HTTP/' + xhr.statusText}});
}
}
sendAppMessage();
}
xhr.ontimeout = function() {
console.log('HTTP request timed out');
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary':true, 'title': 'Request timed out!'}});
sendAppMessage();
};
xhr.onerror = function() {
console.log('HTTP request return error');
appMessageQueue.push({'message': {'endpoint': endpoint, 'summary':true, 'title': 'Failed to connect!'}});
sendAppMessage();
};
xhr.send(null);
}

Pebble.addEventListener('ready', function(e) {});

Pebble.addEventListener('appmessage', function(e) {
console.log('AppMessage received from Pebble: ' + JSON.stringify(e.payload));
if (e.payload.summary) {
clipped(stories[e.payload.index].link, e.payload.endpoint);
} else if (typeof(e.payload.endpoint) != 'undefined') {
hackernews(e.payload.endpoint);
} else {
appMessageQueue = [];
}
});

55 changes: 55 additions & 0 deletions src/libs/pebble-assist.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/***
* Pebble Assist
* Copyright (C) 2013 Matthew Tole
***/

#pragma once

#ifndef MENU_CELL_BASIC_CELL_HEIGHT
#define MENU_CELL_BASIC_CELL_HEIGHT 44
#endif

#ifndef PEBBLE_HEIGHT
#define PEBBLE_HEIGHT 168
#endif

#ifndef PEBBLE_WIDTH
#define PEBBLE_WIDTH 144
#endif

#ifndef STATUS_HEIGHT
#define STATUS_HEIGHT 16
#endif

#define layer_add_to_window(layer, window) layer_add_child(window_get_root_layer(window), layer)
#define text_layer_add_to_window(layer, window) layer_add_child(window_get_root_layer(window), text_layer_get_layer(layer))
#define bitmap_layer_add_to_window(layer, window) layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(layer))
#define inverter_layer_add_to_window(layer, window) layer_add_child(window_get_root_layer(window), inverter_layer_get_layer(layer))
#define menu_layer_add_to_window(layer, window) layer_add_child(window_get_root_layer(window), menu_layer_get_layer(layer))
#define simple_menu_layer_add_to_window(layer, window) layer_add_child(window_get_root_layer(window), simple_menu_layer_get_layer(layer))
#define text_layer_set_system_font(layer, font) text_layer_set_font(layer, fonts_get_system_font(font))
#define text_layer_set_colours(layer, foreground, background) text_layer_set_text_color(layer, foreground); text_layer_set_background_color(layer, background)
#define text_layer_set_colors(layer, foreground, background) text_layer_set_text_color(layer, foreground); text_layer_set_background_color(layer, background)
#define layer_create_fullscreen(window) layer_create(layer_get_bounds(window_get_root_layer(window)));
#define text_layer_create_fullscreen(window) text_layer_create(layer_get_bounds(window_get_root_layer(window)));
#define menu_layer_create_fullscreen(window) menu_layer_create(layer_get_bounds(window_get_root_layer(window)));
#define action_bar_layer_create_and_add(action_bar, window) action_bar = action_bar_layer_create(); action_bar_layer_add_to_window(action_bar, window)

#define window_destroy_safe(window) if (window != NULL) { window_destroy(window); }
#define number_window_destroy_safe(window) if (window != NULL ) { number_window_destroy(window); }
#define text_layer_destroy_safe(layer) if (layer != NULL) { text_layer_destroy(layer); }
#define bitmap_layer_destroy_safe(layer) if (layer != NULL) { bitmap_layer_destroy(layer); }
#define layer_destroy_safe(layer) if (layer != NULL) { layer_destroy(layer); }
#define menu_layer_destroy_safe(layer) if (layer != NULL) { menu_layer_destroy(layer); }
#define simple_menu_layer_destroy_safe(layer) if (layer != NULL) { simple_menu_layer_destroy(layer); }
#define action_bar_layer_destroy_safe(layer) if (layer != NULL) { action_bar_layer_destroy(layer); }
#define scroll_layer_destroy_safe(layer) if (layer != NULL) { scroll_layer_destroy(layer); }
#define app_timer_cancel_safe(timer) if (timer != NULL) { app_timer_cancel(timer); timer = NULL; }

#define persist_read_int_safe(key, value) if (persist_exists(key)) { return persist_read_int(key); } else { return value; }

#define fonts_load_resource_font(resource) fonts_load_custom_font(resource_get_handle(resource))

#define action_bar_layer_clear_icons(action_bar) action_bar_layer_clear_icon(action_bar, BUTTON_ID_SELECT); action_bar_layer_clear_icon(action_bar, BUTTON_ID_DOWN); action_bar_layer_clear_icon(action_bar, BUTTON_ID_UP)

#define menu_layer_reload_data_and_mark_dirty(layer) menu_layer_reload_data(layer); layer_mark_dirty(menu_layer_get_layer(layer));
Loading

0 comments on commit 65d6042

Please sign in to comment.