Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weather info #20

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/FitbitBuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,18 @@ jobs:
- name: Run Build
run: npx fitbit-build --if-present

- name: Copy App
run: cp build/app.fba build/app.sdk4.fba

- name: Upload artifact
uses: actions/upload-artifact@v2
if: ${{ success() }}
with:
path: build/app.fba
path: build/app.sdk4.fba
if-no-files-found: error

build-sdk5:
name: Build SDK5
build-sdk6:
name: Build SDK6
runs-on: ubuntu-latest

steps:
Expand All @@ -56,8 +59,8 @@ jobs:
- name: Setup node
uses: actions/setup-node@v1

- name: Select SDK 5
run: cp package.sdk5.json package.json
- name: Select SDK 6
run: cp package.sdk6.json package.json

- name: Checkout Fitbit SDK
run: npm add --also=dev @fitbit/sdk
Expand All @@ -68,9 +71,12 @@ jobs:
- name: Run Build
run: npx fitbit-build --if-present

- name: Copy App
run: cp build/app.fba build/app.sdk6.fba

- name: Upload artifact
uses: actions/upload-artifact@v2
if: ${{ success() }}
with:
path: build/app.fba
path: build/app.sdk6.fba
if-no-files-found: error
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Version 1.6.0

### Added
- Added weather info
- Added show battery settings

### Changed
- Updated from SDK5 to SDK6

## Version 1.5.2

### Added
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![](https://img.shields.io/badge/Fitbit%20App%20Gallery-%2300B0B9?style=flat&logo=fitbit&logoColor=white)](https://gallery.fitbit.com/details/ae441b73-2660-407f-b796-a98d1d0583a0)
![languages](https://img.shields.io/badge/languages-JavaScript%20|%20CSS-blue)
![platform](https://img.shields.io/badge/platforms-Ionic%20|%20Versa%20|%20Versa%202%20|%20Versa%20Lite%20|%20Versa%203%20|%20Sense-silver)
[![version](https://img.shields.io/badge/version-%201.5.2-blue)](https://github.com/smirko-dev/fitbit-clockface/blob/master/CHANGELOG.md)
[![version](https://img.shields.io/badge/version-%201.6.0-blue)](https://github.com/smirko-dev/fitbit-clockface/blob/master/CHANGELOG.md)
[![](https://img.shields.io/badge/license-MIT-blue)](https://github.com/smirko-dev/fitbit-clockface/blob/master/LICENSE)
[![FitbitBuild Actions Status](https://github.com/smirko-dev/fitbit-clockface/workflows/FitbitBuild/badge.svg)](https://github.com/smirko-dev/fitbit-clockface/actions)
[![CodeQL Actions Status](https://github.com/smirko-dev/fitbit-clockface/workflows/CodeQL/badge.svg)](https://github.com/smirko-dev/fitbit-clockface/actions)
Expand All @@ -16,15 +16,15 @@ It comes with
- languages: de-DE, en-US
- battery icon (Ionic: and status in percentage)
- next calendar event of the current day
- user activity in case of no events (selectable in settings menu)
- user activity or weather in case of no events (selectable in settings menu)
- six color schemes (selectable in settings menu)
- touch to hide calendar event temporarily

Find the latest published version in the [Fitbit gallery](https://gallery.fitbit.com/details/ae441b73-2660-407f-b796-a98d1d0583a0).

The clockface is based on https://github.com/Fitbit/sdk-calendar-clock/ and https://github.com/Fitbit/sdk-weather-clock.

Icons are from https://materialdesignicons.com/ ([Apache license version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html)) and from https://github.com/Fitbit/sdk-design-assets.
Icons are from https://materialdesignicons.com/ ([Apache license version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html)) and from https://github.com/Fitbit/sdk-design-assets ([Design Asset License](https://github.com/Fitbit/sdk-design-assets/blob/master/LICENCE.txt)).

## Screenshots

Expand All @@ -37,7 +37,7 @@ Choose SDK version
| SDK | Device |
|-----|-----------------------------------|
| 4 | Versa, Versa Lite, Versa 2, Ionic |
| 5 | Versa 3, Sense |
| 6 | Versa 3, Sense |

```
cp package.sdkX.json package.json
Expand Down
19 changes: 11 additions & 8 deletions app/appointment.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { inbox } from "file-transfer";
import { readFileSync } from "fs";

import { dataFile, dataType } from "../common/constants";
import { calendarFile, calendarType } from "../common/constants";
import { toEpochSec } from "../common/utils";

let data;
let handleCalendarUpdatedCallback;
let handleCallback;

export function initialize(callback) {
handleCalendarUpdatedCallback = callback;
handleCallback = callback;
data = loadData();
inbox.addEventListener("newfile", fileHandler);
fileHandler();
Expand Down Expand Up @@ -43,14 +43,17 @@ function fileHandler() {
let fileName;
do {
fileName = inbox.nextFile();
data = loadData();
updatedData();
if (fileName === calendarFile) {
console.log('Load ' + fileName);
data = loadData();
updatedData();
}
} while (fileName);
}

function loadData() {
try {
return readFileSync(`/private/data/${dataFile}`, dataType);
return readFileSync(`/private/data/${calendarFile}`, calendarType);
} catch (ex) {
console.error(`Appointment: loadData() failed. ${ex}`);
return;
Expand All @@ -66,7 +69,7 @@ function existsData() {
}

function updatedData() {
if (typeof handleCalendarUpdatedCallback === "function" && existsData()) {
handleCalendarUpdatedCallback();
if (typeof handleCallback === "function" && existsData()) {
handleCallback();
}
}
164 changes: 105 additions & 59 deletions app/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { me } from "appbit";
import { me as appbit } from "appbit";
import document from "document";
import { battery } from "power";
import { display } from "display";
import { today } from 'user-activity';
import { me as device } from "device";
import { units } from "user-settings";
import * as fs from "fs";
import * as appointment from "./appointment";
import * as weather from "./weather";
import * as clock from "./clock";
import * as messaging from "messaging";
import { fromEpochSec, timeString } from "../common/utils";
Expand All @@ -19,40 +21,55 @@ const appointmentsLabel = document.getElementById("appointmentsLabel");
const batteryImage = document.getElementById("batteryImage");
const batteryLabel = document.getElementById("batteryLabel");

const activityIcon = document.getElementById("activityIcon");
const activityLabel = document.getElementById("activityLabel");

//TODO: let activityIntervalID = 0;
const infoIcon = document.getElementById("infoIcon");
const infoLabel = document.getElementById("infoLabel");

const INVISIBLE = 0.0;
const VISIBLE = 0.8;

// Show battery label just for Ionic
if (device.modelId != 27 ) {
batteryLabel.style.opacity = INVISIBLE;
}
else {
batteryLabel.style.opacity = VISIBLE;
}
let WeatherIcon = 'thermometer.png';
let WeatherValue = 'N/A';

let ShowBattery = true;

// Register for the unload event
me.onunload = saveSettings;
appbit.onunload = saveSettings;

// Load settings at startup
let settings = loadSettings();
applySettings(settings.activity, settings.color);
applySettings(settings.info, settings.color, settings.battery);

// Apply and store settings
function applySettings(activity, color) {
if (typeof activity !== 'undefined') {
activityIcon.image = `${activity}.png`;
settings.activity = activity;
function applySettings(info, color, battery) {
//DEBUG console.log(`[applySettings] info=${info}, color=${color}`);
if (typeof info !== 'undefined') {
if (info !== 'weather') {
infoIcon.image = `${info}.png`;
}
settings.info = info;
}
if (typeof color !== 'undefined') {
hourLabel.style.fill = color;
activityIcon.style.fill = color;
infoIcon.style.fill = color;
settings.color = color;
}
if (typeof battery !== 'undefined') {
settings.battery = battery;
ShowBattery = battery;
if (ShowBattery) {
batteryImage.style.opacity = VISIBLE;
if (device.modelId != 27 ) {
batteryLabel.style.opacity = INVISIBLE;
}
else {
batteryLabel.style.opacity = VISIBLE;
}
}
else {
batteryLabel.style.opacity = INVISIBLE;
batteryImage.style.opacity = INVISIBLE;
}
}
}

// Load settings
Expand All @@ -63,7 +80,8 @@ function loadSettings() {
catch (ex) {
// Default values
return {
activity: "steps",
info: "steps",
battery: true,
color: "#2490DD"
};
}
Expand All @@ -76,11 +94,14 @@ function saveSettings() {

// Update settings
messaging.peerSocket.onmessage = (evt) => {
if (evt.data.key === "activity") {
applySettings(evt.data.value, settings.color);
if (evt.data.key === "info") {
applySettings(evt.data.value, settings.color, settings.battery);
}
else if (evt.data.key === "color") {
applySettings(settings.activity, evt.data.value);
applySettings(settings.info, evt.data.value, settings.battery);
}
else if (evt.data.key === "battery") {
applySettings(settings.info, settings.color, evt.data.value);
}
renderAppointment();
}
Expand All @@ -98,10 +119,26 @@ battery.onchange = (evt) => {
renderBattery();
}

appointment.initialize(() => {
// Update appointment with new data
renderAppointment();
});
if (appbit.permissions.granted('access_calendar')) {
appointment.initialize(() => {
renderAppointment();
});
}
else {
console.warn('Missing permission: access_calendar');
}

if (appbit.permissions.granted('access_location')) {
weather.initialize(data => {
//DEBUG console.log(`Weather: ${data.icon} - ${data.temperature} ${data.unit} in ${data.location}`);
data = units.temperature === "F" ? toFahrenheit(data) : data;
WeatherValue = `${data.temperature}\u00B0 ${units.temperature}`;
WeatherIcon = `${data.icon}`;
});
}
else {
console.warn('Missing permission: access_location');
}

display.addEventListener("change", () => {
if (display.on) {
Expand All @@ -110,57 +147,66 @@ display.addEventListener("change", () => {
renderBattery();
}
else {
// Stop updating activity info
hideActivity();
// Stop updating info
hideInfo();
}
});

// Hide event when touched
appointmentsLabel.addEventListener("mousedown", () => {
showActivity();
updateActivity();
showInfo();
updateInfo();
})

function renderAppointment() {
// Upate the appointment <text> element
let event = appointment.next();
if (event) {
const date = fromEpochSec(event.startDate);
appointmentsLabel.text = timeString(date) + " " + event.title;
hideActivity();
}
else {
showActivity();
updateActivity();
}
if (appbit.permissions.granted('access_calendar')) {
let event = appointment.next();
if (event) {
const date = fromEpochSec(event.startDate);
appointmentsLabel.text = timeString(date) + " " + event.title;
hideInfo();
return;
}
}
showInfo();
updateInfo();
}

function hideActivity() {
activityIcon.style.opacity = INVISIBLE;
activityLabel.style.opacity = INVISIBLE;
function hideInfo() {
infoIcon.style.opacity = INVISIBLE;
infoLabel.style.opacity = INVISIBLE;
appointmentsLabel.style.opacity = VISIBLE;
//TODO: clearInterval(activityIntervalID);
}

function showActivity() {
activityIcon.style.opacity = VISIBLE;
activityLabel.style.opacity = VISIBLE;
function showInfo() {
infoIcon.style.opacity = VISIBLE;
infoLabel.style.opacity = VISIBLE;
appointmentsLabel.style.opacity = INVISIBLE;
//TODO: activityIntervalID = setInterval(updateActivity, 1500);
}

function updateActivity() {
if (settings.activity === 'distance') {
activityLabel.text = today.adjusted.distance;
}
else if (settings.activity === 'floors') {
activityLabel.text = today.adjusted.elevationGain;
}
else if (settings.activity === 'calories') {
activityLabel.text = today.adjusted.calories;
function updateInfo() {
//DEBUG console.log(`[updateInfo] info=${settings.info}`);
if (settings.info === 'weather') {
infoLabel.text = WeatherValue;
infoIcon.image = WeatherIcon;
}
else if (appbit.permissions.granted('access_activity')) {
if (settings.info === 'distance') {
infoLabel.text = today.adjusted.distance;
}
else if (settings.info === 'floors') {
infoLabel.text = today.adjusted.elevationGain;
}
else if (settings.info === 'calories') {
infoLabel.text = today.adjusted.calories;
}
else {
infoLabel.text = today.adjusted.steps;
}
}
else {
activityLabel.text = today.adjusted.steps;
infoLabel.text = 'N/A';
}
}

Expand Down
Loading