0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

生活記録アプリを自作した

Last updated at Posted at 2025-01-29

概要

生活記録が欲しかったため、fitbitを使用したWebアプリケーションを開発した。
というより、fitbitを使うことが目的ではなく、ライフログアプリ(日常生活を記録するもの)を元に、睡眠だけ正確に計測する目的でfitbitを使用した。
また、LaravelでのPDF出力機能の実装が大変だったのでこの記事を書いた。

技術

領域 技術
フロントエンド Vue.js
バックエンド PHP(Laravel)
ライブラリ hammerjs 等など

機能

カレンダー

・スワイプでの描画対象期間移動
・イベントの新規作成、更新、削除機能
・移動用の小さいカレンダー

スマホのWebブラウザで本アプリを使用する際、画面の大きさの都合上、前週・翌週に移動するためのボタンを設置するスペースがない。
そのため、スワイプ動作で前週・翌週に遷移する機能が欲しかった。
これはhammerjsを使って実装した。
Screenshot from 2025-01-27 18-45-10.png

スワイプに近い動作の実装をする要素が縦方向のスクロールにできる場合、cssでtouch-actionを設定しないと、スワイプ動作が検知されなかった。
要するに

timelines.js
const swipe = (e) => {
    console.log('swipe');
    if (e.direction === 4) {
            //右方向
        } else if(e.direction === 2) {
            //左方向
        }
};

といった処理の場合、

style.css
.timelines{
    overflow-y: scroll;
    overflow-x: hidden;

    touch-action: pan-y; /* 必須*/
}

とする必要があるらしい。

fitbitアカウントでのログイン

fitbit APIを使って正確に睡眠時間を把握するためにこのWebアプリを開発に取り掛かった、という経緯が有ったためメールアドレスを使ったログインは実装していない。
Screenshot from 2025-01-29 12-13-29.png

fitbitAPIを利用した睡眠ログ取り込み

同一日の睡眠ログを重複して取り込む可能性があったため、ユーザと取り込み済み日付を格納するテーブルを用意し、同一睡眠ログを取り込めないように実装した。

habitの計測機能

画面上のボタンを押下することで、生活記録を計測を開始・終了・中止ができる。

睡眠時間帯をPDFに出力機能

これはChatGPTに適当に出力してもらったソースコードを調整して実装した。
あまりにも実装がめんどくさかったので、ソースコードを以下に記載。

HogeController.php
    public function getPDF(Request $request, Response $response)
    {
        //ftibti API実行クラス 各自用意する
        $client = $this->getFitbitClient();

        //開始期間と終了期間
        $start_date = Carbon::parse("2024-3-01")->toDateString();
        $end_date = Carbon::parse("2024-3-31")->toDateString();
        
        //apiを叩く
        $response = $client->fetchSleepLogByDateDateRange(
            $start_date, $end_date
        );
        //jsonに変換    
        $sleep_log = json_decode($response->getBody(), true);
    

        // すべての日付をリスト化(データがない日付も含める)
        $allDates = [];
        $period = new \DatePeriod(
            new \DateTime($start_date),
            new \DateInterval('P1D'),
            (new \DateTime($end_date))->modify('+1 day')
        );

        foreach ($period as $date) {
            $allDates[$date->format('Y-m-d')] = [];
        }

        // 睡眠データの処理
        $sleepRecords = [];

        foreach ($sleep_log['sleep'] as $sleep) {
            $startTime = strtotime($sleep['startTime']);
            $endTime = strtotime($sleep['endTime']);
        
            $startDate = date('Y-m-d', $startTime);
            $endDate = date('Y-m-d', $endTime);
        
            $startIndex = (date('H', $startTime) * 6) + (int)(date('i', $startTime) / 10);
            $endIndex = (date('H', $endTime) * 6) + (int)(date('i', $endTime) / 10);
        
            if ($startDate != $endDate) {
                // 前日分
                if (isset($sleepRecords[$startDate])) {
                    $sleepRecords[$startDate][] = ['start' => $startIndex, 'end' => 144];
                } else {
                    $sleepRecords[$startDate] = [['start' => $startIndex, 'end' => 144]];
                }
        
                // 翌日分
                if (isset($sleepRecords[$endDate])) {
                    $sleepRecords[$endDate][] = ['start' => 0, 'end' => $endIndex];
                } else {
                    $sleepRecords[$endDate] = [['start' => 0, 'end' => $endIndex]];
                }
            } else {
                // 同じ日付内
                if (isset($sleepRecords[$startDate])) {
                    $sleepRecords[$startDate][] = ['start' => $startIndex, 'end' => $endIndex];
                } else {
                    $sleepRecords[$startDate] = [['start' => $startIndex, 'end' => $endIndex]];
                }
            }
        }

        // すべての日付を統合
        foreach ($sleepRecords as $date => $records) {
            $allDates[$date] = $records;
        }

        // PDF作成(横向き)
        $pdf = new TCPDF('L', 'mm', 'A4');
        $pdf->SetAutoPageBreak(true);
        $pdf->AddPage();
        $pdf->SetFont('Helvetica', '', 10);

        // タイトル
        $pdf->Cell(0, 10, "Fitbit Sleep Logs ({$start_date} - {$end_date})", 0, 1, 'C');
        
        // 時間軸(10分単位)
        $pdf->SetFillColor(200, 200, 200);
        $pdf->Cell(12, 5, 'Date', 1, 0, 'C', true);
        for ($hour = 0; $hour < 24; $hour++) {
            $pdf->Cell(1.81 * 6, 5, $hour, 1, 0, 'C', true);
        }
        $pdf->Ln();

        // 睡眠データを描画
        foreach ($allDates as $date => $records) {
            $pdf->Cell(12, 5, Carbon::parse($date)->format('m-d'), 1, 0, 'C');
        
            for ($index = 0; $index < 144; $index++) {
                $fillColor = [255, 255, 255]; // デフォルトは白
        
                foreach ($records as $record) {
                    if ($index >= $record['start'] && $index < $record['end']) {
                        $fillColor = [0, 0, 255]; // 青色
                        break;
                    }
                }
        
                $pdf->SetFillColor($fillColor[0], $fillColor[1], $fillColor[2]);
                $pdf->Cell(1.81, 5, '', 1, 0, 'C', true);
            }
            $pdf->Ln();
        }
        
        return response($pdf->Output('sleep_chart.pdf', 'S'))->header('Content-Type', 'application/pdf');
    }

出力されたPDFは以下のようになった。
Screenshot from 2025-01-30 07-52-27.png

終わりに

デザインが壊滅的である。UIライブラリの導入をする必要があると感じた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?