Closed16

Fitbit で画像を表示するアプリを作る

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

このスクラップについて

このスクラップでは Fitbit で画像を表示するアプリを作る過程を記録する。

せっかくなので TypeScript を使って開発してみる。

https://blog.chick-p.work/blog/fitbit-typescript/

https://github.com/SergioMorchon/fitbit-sdk-types

また Fitbit Studio が使えなくなったみたいなのでコマンドラインでプロジェクトを作成する方法などについても学んでいきたい。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

fitbit アプリ作成

コマンド
npx create-fitbit-app fitbit-images

色々と質問されるのでそれぞれ下記のように回答した。

? What type of application should be created? app
? What should the name of this application be? Fitbit Images
? Should this application contain a companion component? No
? Which platforms should this application be built for? Fitbit Versa 3, Fitbit 
Sense
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ビルド

コマンド
fitbit$ build

色々と警告が表示された。

コンソール出力
[11:11:45][warn][build] package.json Tiles available only for native components. Skipping tile configuration!
[11:11:45][warn][companion] This project is being built without a companion component. Create a file named companion/index.ts or companion/index.js to add a companion component to your project.
[11:11:45][warn][settings] This project is being built without a settings component. Create a file named settings/index.tsx, settings/index.ts, settings/index.jsx or settings/index.js to add a settings component to your project.
[11:11:45][info][app] Building app for Fitbit Versa 3
[11:11:45][warn][app] There is no app icon present in this project. To set an app icon, add a 80x80 PNG file named resources/icon.png to your project.
[11:11:45][info][app] Building app for Fitbit Sense
[11:11:45][warn][app] There is no app icon present in this project. To set an app icon, add a 80x80 PNG file named resources/icon.png to your project.
[11:11:45][info][build] App UUID: 4a6abd61-2ea5-433f-92fe-3ed704ea1eee, BuildID: 0x06b4b296a85fd867
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

別の画像を表示してみる

うつ病の男性のイラスト

https://www.irasutoya.com/2014/01/blog-post_8791.html

import document from "document";

const el = {
  image: document.getElementById("image") as ImageElement,
};

let counter = 0;

setInterval(() => {
  counter += 1;

  if (counter % 3 === 0) {
    el.image.href = "pose_genki01_boy.png";
  } else if (counter % 3 === 1) {
    el.image.href = "utsu_man.png";
  } else {
    el.image.href = "";
  }
}, 1000);


1 秒おきに画像が切り替わるようになった。


href に "" を代入することで画像を非表示にもできるようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

タイルリストを表示する

https://dev.fitbit.com/build/guides/user-interface/svg-components/views/#tile-list

resources/widget.defs
<svg>
  <defs>
    <link rel="stylesheet" href="styles.css" />
    <link rel="import" href="/mnt/sysassets/system_widget.defs" />

    <link rel="import" href="/mnt/sysassets/widgets/baseview_widget.defs" />
    <link rel="import" href="/mnt/sysassets/widgets/scrollbar.defs" />
    <link rel="import" href="/mnt/sysassets/widgets/tile_list_widget.defs" />

    <symbol id="my-item" href="#tile-list-item" class="list-item">
      <text id="text" />
      <rect class="line" />
    </symbol>
  </defs>
</svg>
resources/index.view
<svg viewport-fill="gray">
  <image x="68" y="68" width="200" height="200" href="" id="image" />
  <use id="myList" href="#tile-list" class="horizontal-pad">
    <use href="#my-item">
      <set href="#text" attributeName="text-buffer" to="Text Item 1" />
    </use>
    <use href="#my-item">
      <set href="#text" attributeName="text-buffer" to="Text Item 2" />
    </use>
    <use href="#my-item">
      <set href="#text" attributeName="text-buffer" to="Text Item 3" />
    </use>
  </use>
</svg>
resources/styles.css
.defaultText {
  font-size: 32;
  font-family: System-Regular;
  font-weight: regular;
  text-length: 32;
}

#background {
  width: 100%;
  height: 100%;
  fill: white;
}

.list-item {
  height: 112;
}

.list-item text {
  fill: "white";
  x: 50%;
  text-anchor: middle;
  font-size: 24;
}

.line {
  height: 6;
  width: 100%;
  fill: #222222;
  y: 100%-6;
}


タイルリストが表示された。

CSS 以外は何をやっているのかはよく理解していない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

タイルリストのクリックイベント

https://dev.fitbit.com/build/guides/user-interface/javascript/#events

resources/index.view
<svg viewport-fill="gray">
  <image x="68" y="68" width="200" height="200" href="" id="image" />
  <use id="myList" href="#tile-list" class="horizontal-pad" pointer-events="visible">
    <use href="#my-item">
      <set href="#text" attributeName="text-buffer" to="Text Item 1" />
    </use>
    <use href="#my-item">
      <set href="#text" attributeName="text-buffer" to="Text Item 2" />
    </use>
    <use href="#my-item">
      <set href="#text" attributeName="text-buffer" to="Text Item 3" />
    </use>
  </use>
</svg>

onclick を使えるようにするには pointer-events="visible" をセットする必要がある。

https://stackoverflow.com/questions/53934887/document-onclick-is-not-firing-on-fitbit-studio

app/index.ts
import document from "document";

const noImage = "";
const positiveImage = "pose_genki01_boy.png";
const negativeImage = "utsu_man.png";

const el = {
  image: document.getElementById("image") as ImageElement | null,
  tileList: document.getElementById("myList"),
};

if (!el.image) {
  throw new Error("!el.image");
}

if (!el.tileList) {
  throw new Error("!el.tileList");
}

el.tileList.onclick = () => {
  console.log("here");
};

これでタップするとコンソールに console.log と表示されるようになった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

切り替えられるようにする

resources/styles.css(追加分)
.hidden {
  display: none;
}
resources/index.view(一部)
  <image x="68" y="68" width="200" height="200" href="pose_genki01_boy.png" id="image" class="hidden" pointer-events="visible" />
app/index.ts
import document from "document";

type State = {
  mode: "tile" | "image";
};

const state: State = {
  mode: "tile",
};

const el = {
  image: document.getElementById("image") as ImageElement,
  tileList: document.getElementById("myList") as TileList,
};

if (!el.image) {
  throw new Error("!el.image");
}

if (!el.tileList) {
  throw new Error("!el.tileList");
}

el.image.onclick = () => {
  state.mode = "tile";
  updateDisplay();
};

el.tileList.onclick = () => {
  state.mode = "image";
  updateDisplay();
};

function updateDisplay() {
  if (state.mode === "tile") {
    el.tileList.class = "";
    el.image.class = "hidden";
  } else if (state.mode === "image") {
    el.tileList.class = "hidden";
    el.image.class = "";
  }
}


無事に切り替えられるようになった。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

おわりに

比較的スムーズにやりたいことを終えれて良かった。

Fitbit アプリ開発は情報が少なくて結構大変。

TypeScript は今回初めて使ったけどとても便利だった。

今後 Fitbit アプリを開発する機会があったら TypeScript を使おうと思う。

このスクラップは3ヶ月前にクローズされました