📘 hkob-astro-notion-blog

これまではてなブログにて情報発信をしていましたが、令和5年3月22日より、こちらでの情報発信を始めました。2019年以前の古い記事は過去の Middleman 時代のものなので、情報が古いです。記録のためだけに残しています。

Apple Watch による StartStopTask の作成

1. はじめに

研究室ではマルチモニタの環境になっているので、LaunchPad をセカンダリディスプレイに常時表示しておき、タスク開始・終了をボタンで実施しています。ただし、会議の場では Notion の議事録を取る MacBook Air と議題を確認する iPad mini しかありません。会議終了時に LaunchPad を表示して、タスク開始・終了ボタンを押すのをつい忘れてしまいます。タスク終了するだけであれば、Apple Watch でも簡単に実施できます。そのため、Apple Watch 専用アプリを作成しようと思います。ただそれだけだとつまらないので、タスク実施中でなければタスク開始も実施できるようにしてみようと思います。少し規模も大きくなるので、ショートカット開発手順を記録しながら、ブログ記事にしてみようと思います。

2. 作成ログ

ここからは作業記録を箇条書きでまとめておきます。

  • 新規にアプリを作成しました。名前は「StartStopTask」としておきます。Apple Watch 専用なので、「Apple Watch に表示」だけ入れておきます。
  • いつものインテグレーションキーとタスクデータベース ID を記録しておきます。インテグレーションキーは実行前に正式なものに差し替えます。
  • 次の今日の日付と現在の時刻の文字列を作成しておきます。前者は yyyy-MM-dd 形式、後者は時間まで含めた ISO8601 形式になります。
  • タスクを何も開始しておらず、Action にチェックが入っていない時にはメニューで一覧を表示します。それを表示するための辞書を hash という変数に用意しておきます。
  • まずは今日の未完了タスクを取得するために、Query database の payload を調査します。まず、NotionRubyMapping を使えるようにしておきます。irb 起動後に NotionRubyMapping をrequire・include し、インテグレーションキーを設定する初期化スクリプトを実行しておきます。以下のスクリプト実行画面において、アンダーライン部はコマンドの出力結果です。
    load "/Users/hkob/bin/include_nrm.rb"
    => true
  • データベースを取得し、各プロパティを用意しておきます。
    db = Database.find "2395e3ffb55e4a8abc1ba426243776e3"
    => NotionRubyMapping::Database-2395e3ffb55e4a8abc1ba426243776e3
    
    dps = db.properties
    => PropertyCache
    
    sp, dp = dps.values_at *%w[Status 日付]
    =>
    [#<NotionRubyMapping::StatusProperty:0x000000010404bff0
    ...
    
  • 日付が今日で未完了のタスクのみを時間順に抽出してみます。
    tasks = db.query_database(sp.filter_does_not_equal("Done").and(
    dp.filter_equals(Date.today)).ascending(dp))
    => NotionRubyMapping::List-
  • 中身を確認します(中身は伏せ字にしておきます)。
    tasks.map(&:title)
    => ["タスクAの名前", "タスクBの名前", "タスクCの名前"]
  • 正しく取得できているようなので、payload を確認します。
    print db.query_database(sp.filter_does_not_equal("Done").and(dp
    .filter_equals(Date.today)).ascending(dp), dry_run: true)
    #!/bin/sh
    curl -X POST 'https://api.notion.com/v1/databases/2395e3ffb55e4a8abc1ba426243776e3/query' \
      -H 'Notion-Version: 2022-06-28' \
      -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
      -H 'Content-Type: application/json' \
      --data '{"filter":{"and":[
    {"property":"Status","status":{"does_not_equal":"Done"}},
    {"and":[
    {"property":"日付","date":{"after":"2023-05-11T00:00:00+09:00"}},
    {"property":"日付","date":{"before":"2023-05-11T23:59:59+09:00"}}
    ]}]},
    "sorts":[{"property":"日付","direction":"ascending"}],
    "page_size":100}'=> nil
    描画がおかしくなるので、出力結果を改行しています。
  • このサンプル payload を参考にして、実際の payload を作成します。と言っても日付の部分を事前に作成した yyyy-MM-dd 形式の定数に置き換えているだけです。
  • この query_payload を使って、URL の内容を取得します。わかりやすいように定数には名前をつけています。
  • インテグレーションキーを正式なものとした上で、試しに実行すると以下のようにデータが取得できていることがわかります。
  • まず、Action が設定されていない時用にタイトルをキー、ページID を値とした辞書を作成します。結果は results 配列に入っているので一つずつ繰り返しながら作業します。繰り返し項目はタスクページなので、id とタイトルを取得して、辞書を更新しています。
  • ここから3つのパターンに分かれます。最初のパターンは作業中のタスクが存在する場合です。作業中のタスクとは、Action にチェックが入っており、Status が In progress の場合です。
    t = tasks.first
    => NotionRubyMapping::Page-9996a5ba0cae40cabc4bf19456e5cbb6
    
    page.properties["Status"].status["name"]
    => "In progress"
    
    page.properties["Action"].checkbox
    => false
  • タスクを完了させるために、状態が以下のように変化させます。
    • 終了時刻(ボタン) [Date property]: 空欄 → 現在時刻
    • Status [Status property]: In progressDone
    • Action [Checkbox property]: true → false
  • これを NotionRubyMapping で確認してみます。
    t.properties["終了時刻(ボタン)"].start_date = Time.now
    => 2023-05-11 12:42:43.099471 +0900
    
    t.properties["Status"].status = "Done"
    => "Done"
    
    t.properties["Action"].checkbox = false
    => false
    
    print t.save(dry_run: true)
    #!/bin/sh
    curl -X PATCH 'https://api.notion.com/v1/pages/9996a5ba0cae40cabc4bf19456e5cbb6' \
      -H 'Notion-Version: 2022-06-28' \
      -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
      -H 'Content-Type: application/json' \
      --data '{"properties":{"Status":{"status":{"name":"Done"},"type":"status"},
    "Action":{"checkbox":false,"type":"checkbox"},
    "終了時刻(ボタン)":{"type":"date","date":{
    "start":"2023-05-11T12:42:43+09:00",
    "end":null,"time_zone":null}}}}'=> nil
  • これらをふまえてショートカットを記述します。最初の checkbox の値はブール値なので、そのまま「もし」の中に記述できます(値としてブール値を選びます)。その後、status の名前が In progress の時に値を更新します。更新が完了したら戻り値を finished という変数に入れておきます。
  • 二番目のパターンは作業中のタスクがないが、Action だけが設定してある場合です。すなわち、Action が設定してあり Not started の場合ですが、これはその他の場合に相当します。
    • 日付 [Date property]: 空欄 → 現在時刻
    • Status [Status property]: Not startedIn progress
    • Action [Checkbox property]: true → true (変化なし)
  • 最後のパターンは Action が一つもチェックされていない時です。上記の処理で finished が設定されていない時に相当します。キーをメニューで表示した上で選択し、辞書から対応するページIDを取得します。payload は二番目の場合と同じになります。

3. テスト実行

最初にサンプルタスクが3つ用意されているとします。

コンプリケーションからショートカットアプリを起動すると、今回作成した「StartStopTask」アプリが存在しています。また、下は「声でタスク登録」アプリです。

タスクを実行すると、Action がついていたタスクが開始されました。

この状態で再度、Apple Watch から「StartStopTask」を実行すると、実行中のタスクが完了しました。

この状態だと全てのタスクに Action が設定されていない状態になります。この状態で再度 Apple Watch から「StartStopTask」を実行します。すると未完了のタスクがリストで表示されます。

ここで「タスク2」をタップしてみます。無事にタスク2が開始しました。

タスク2には Action が付いているので、再度の実行によりタスクが完了しました。

4. おわりに

Apple Watch でタスク完了できるようになったので、タスクの完了漏れがなくなりました。いちいち LaunchPad を開かなくていいのは非常に作業が捗りますね。今日やれそうな仕事を全部次のアクションに入れてさえおけば、全部 Apple Watch だけでタスク管理できるようになりそうです。