ブルアカのAP&カフェ確認ウィジェットを改良した(iOSショートカット×Scriptable)
まえがき
これの改良です。
何が気に食わなかったかというと、iPhoneとiPadの同期タイミングです。
遅くて遅くてイライラしてだめでした。
Scriptableウィジェットはタップした瞬間に更新されるのに、元のデータが同期されてないんじゃ意味ありません。
iCloud Drive内のShortcutsフォルダ内にAPとカフェが溢れる時刻を記述した bluearchive_widget.json を保存し、Scriptableのウィジェットから読み込むという方法で動作させていたんですが、どうやらiCloud Driveのバックグラウンド同期はWindowsにおけるOneDriveと違ってガンガン読み込んでくれないらしく、iPhoneで更新後iPadにいつまで経っても反映されない現象が多発しました。
iCloud Driveの仕様 参考 ↓
そこで、他のツールでも利用しているGoogle スプレッドシートをクラウドメモとして利用し、オフライン時のみ bluearchive_widget.json から情報を読む、というプログラムに作り替えてみたんですが(1ヶ月前)、POSTの処理に少し時間がかかり、あんまりスマートじゃないなと使っていて思いました。
function doPost(e) {
var params = e.postData.contents;// POSTされたデータを取得
var range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('A1'); //書き込み先
range.setValue(params);
}
↑ GASでこれ書くだけで動くようにするまで結構頑張ったんですけどね…
A1セルにJSONを丸ごと放り込むアイデアでかなり簡単に実現できました。
もう使い所はありませんが。
最終的なアイデアとしては、純正リマインダーの端末間同期が爆速(数秒)なので、AP溢れタイミング通知として使っていたリマインダーのメモ欄にJSONを文字列としてぶち込み、クラウドストレージの代替にしてしまう、というものです。
リマインダーでの表示は酷いもんですが、動作はとても改善されました。
以降の説明では旧バージョンの使いまわしがありますが、内容的に問題ないのでお許しください。
使い方(変化なし)
現在のAPを入力すれば、APの上限到達時刻をセット
何も入力せずにOKを押せば、カフェの上限到達時刻をセット
負の数値を入力すれば、現在の状態を表示
仕様
APかカフェがMAXになる前にリマインダーによる通知が来る(何分前に通知するか設定可能)
ウィジェットに現在のAP、カフェの状態、上限に到達する時刻を表示
同一のApple IDであれば複数端末で自動同期(iPhone・iPad関係なし)
ブルアカをインストールしてない端末でも利用可能
ウィジェットの自動更新タイミングは制御不可(タップで強制更新は可能)
導入方法
必要なアプリ
iOSショートカット(純正)
リマインダー(純正) ※ウィジェット機能&通知機能が要らなければ不要
Scriptable ※ウィジェット機能が要らなければ不要
手順
リマインダーの追加、リマインダーによる通知、ウィジェットの表示 を行うための手順を書いていきます。
リマインダーと通知が不要で、ただ単にウィジェット表示だけをしたい場合は、米印のところに注目してもらえれば問題なくできると思います。(不要な設定をしないだけです)
iOSショートカットの設定
ショートカットの入手
ショートカット:ブルアカAP管理 リマインダー同期 をリンクか下記QRコードかファイルから開き、アプリに追加する。
初期設定
ここでの設定は後からでも変更可能です。計算に使用する数値を入力します。
AP自然回復 :6分に1回復するという意味。全員同じなので変更不要。
AP上限値 :APの最大値。自分の数値を入力。
カフェ回復 :1時間あたりのAP回復量(実は小数まで桁が設定されている)
カフェ上限値 :カフェの最大保管量
言及していないものはプログラムでの定義として利用しているので変更すると壊れます。
リマインダーリスト名:リマインダー通知用にリマインダーを作成するリストの名前。これと同様の名前のリマインダーリストを作成する必要がある。
※リマインダー不使用の場合は無視してOK
AP・カフェ通知時間:上限に到達する何分前に通知するか。(例:「5」と入力すれば、上限到達5分前に通知がセットされる。
リマインダー通知OFF:リマインダー通知を不使用の場合には真に変更。(ブール値なので真偽の選択式)
実行後表示:APの入力かカフェのリセット後にアラート機能で状態を表示する。↓下図参照。通知みたいで煩わしいので偽(OFF)のままで良いと思う。
初期設定後に内容を変更したい場合は下記の通りに。
リマインダーの設定
リマインダーのリスト作成
※リマインダー不使用の場合はスキップ
リマインダーアプリを開き、リスト画面右下の[リストを追加]からリストを作成します。
ここで、リマインダーリスト名と同じ名前を設定します。デフォルトは「スタミナ管理」です。
リマインダーの通知がオンになっていない場合はiOSの設定から変更します。
Scriptableの前に
リマインダーの初期作成
リマインダーリストにAP用リマインダーとカフェ用リマインダーを作成しておきます。
先程のiOSショートカットを実行すると、リマインダーがあれば更新・なければ作成を自動で行ってくれます。
APは適当な数値(正の数)を入力して完了・カフェリセットは空のまま完了です。
この記事の最初にあった使い方のGIFの通りです。
Scriptableの設定
Scriptの追加
これ↑をダウンロードしてScriptableで開くか、
右上の+をタップして新規スクリプトを作成し、下記コードをコピペ
const bgColor = "#222222";//ウィジェットの背景色
const width = 110;//プログレスバーの横幅
const height = 8;//プログレスバーの太さ
const barColor = "#67CE67";//プログレスバーのバー部分の色
const backColor = "#48484A";//プログレスバーの背景色
const textColor = "#FFFFFF"//テキストの色
const lastloadedColor = "#FFFFFF";//最終更新時刻の文字の色
//const lastloadedColor = "#222222";//ウィジェットの背景色と一致させれば隠せる
const argsParam = args.widgetParameter;
const calTest = await Calendar.findOrCreateForReminders("スタミナ管理");
let reminders = await Reminder.all([calTest]);
let item;
//更新日時が後のリマインダーのメモに格納されているJSONをitemに格納
if (new Date(JSON.parse(reminders[0].notes)["更新日時"]) >= new Date(JSON.parse(reminders[1].notes)["更新日時"])){
item = JSON.parse(reminders[0].notes);
}else{
item = JSON.parse(reminders[1].notes);
}
//itemのカフェ上限到達時刻を新しい方(時刻がより後の方)で上書き
if (new Date(JSON.parse(reminders[0].notes)["カフェ上限到達時刻"]) >= new Date(JSON.parse(reminders[1].notes)["カフェ上限到達時刻"])){
item["カフェ上限到達時刻"] = JSON.parse(reminders[0].notes)["カフェ上限到達時刻"];
}else{
item["カフェ上限到達時刻"] = JSON.parse(reminders[1].notes)["カフェ上限到達時刻"];
}
const format = new DateFormatter();
format.dateFormat = "HH:mm";
let widget = new ListWidget();
const time = new Date();
const AP_limit_time = new Date(item["AP上限到達時刻"]);
const Cafe_limit_time = new Date(item["カフェ上限到達時刻"]);
widget.addSpacer(10);
const AP_text_stack = widget.addStack();
AP_text_stack.bottomAlignContent();
const AP_title = AP_text_stack.addText(" AP");
AP_title.font = Font.systemFont(16);
AP_title.textColor = new Color(textColor);
AP_title.leftAlignText();
AP_text_stack.addSpacer();
const AP_curr = AP_calc();
const AP_val = AP_text_stack.addText(AP_curr + "/" + item["AP上限値"]);
AP_val.font = Font.systemFont(10);
AP_val.textColor = new Color(textColor);
AP_val.rightAlignText();
const AP_progressBar = widget.addImage(createProgress(item["AP上限値"], AP_curr));
AP_progressBar.imageSize = new Size(width, height);
AP_progressBar.centerAlignImage();
const AP_limit = widget.addText("MAX " + format.string(AP_limit_time));
AP_limit.font = Font.systemFont(10);
AP_limit.textColor = new Color(textColor);
AP_limit.rightAlignText();
widget.addSpacer(20);
const Cafe_text_stack = widget.addStack();
Cafe_text_stack.bottomAlignContent();
const Cafe_title = Cafe_text_stack.addText(" Cafe");
Cafe_title.font = Font.systemFont(16);
Cafe_title.textColor = new Color(textColor);
Cafe_title.leftAlignText();
Cafe_text_stack.addSpacer();
const Cafe_curr = Cafe_calc();
const Cafe_val = Cafe_text_stack.addText(Cafe_curr + "/" + item["カフェ上限値"]);
Cafe_val.font = Font.systemFont(10);
Cafe_val.textColor = new Color(textColor);
Cafe_val.rightAlignText();
const Cafe_progressBar = widget.addImage(createProgress(item["カフェ上限値"], Cafe_curr));
Cafe_progressBar.imageSize = new Size(width, height);
Cafe_progressBar.centerAlignImage();
const Cafe_limit = widget.addText("MAX " + format.string(Cafe_limit_time));
Cafe_limit.font = Font.systemFont(10);
Cafe_limit.textColor = new Color(textColor);
Cafe_limit.rightAlignText();
widget.addSpacer(20);
const lastloaded = widget.addText(" Last Loaded " + format.string(time));
lastloaded.font = Font.systemFont(8);
lastloaded.textColor = new Color(lastloadedColor);
lastloaded.centerAlignText();
widget.backgroundColor = new Color(bgColor);
Script.setWidget(widget);
widget.presentSmall();
Script.complete();
if (argsParam == 1){
App.close();
}
function AP_calc(){
let diff = AP_limit_time.getTime() - time.getTime();
let diff_AP = Math.ceil((diff / (60 * 1000))/item["AP自然回復"]);
if ( diff_AP >= 0){
return item["AP上限値"] - diff_AP;
}else{
return item["AP上限値"];
}
}
function Cafe_calc(){
let diff = Cafe_limit_time.getTime() - time.getTime();
if (23 - diff / (60 * 60 * 1000) <= 23){
return Math.floor(Math.ceil(23 - diff / (60 * 60 * 1000)) * item["カフェ回復"]);
}else if(23 - diff / (60 * 60 * 1000) > 23){
return item["カフェ上限値"];
}else{
return 0;
}
}
// Function was borrowed from the Time Progress script available in
// the Scriptable Gallery https://scriptable.app/gallery/time-progress
function createProgress(total, current_val){
const context = new DrawContext()
context.size = new Size(width, height)
context.opaque = false
context.respectScreenScale = true
context.setFillColor(new Color(backColor))
const base = new Path()
base.addRoundedRect(new Rect(0, 0, width, height), height/2, height)
context.addPath(base)
context.fillPath()
context.setFillColor(new Color(barColor))
const fill = new Path()
fill.addRoundedRect(new Rect(0, 0, width * current_val/total, height), height/2, height)
context.addPath(fill)
context.fillPath()
return context.getImage()
}
ウィジェットの追加
ホーム画面の空白を長押しorアプリ長押し-ホーム画面を編集
+からScriptabeのウィジェットを追加
アプリのインストール直後は、ウィジェットとして追加可能なアプリ一覧にない場合があります。再起動したら表示されました。
iOSにおけるサードパーティ製ウィジェットの自動更新は頻度を制御することができないのですが、タップしたらスクリプトが実行されるようにすることで、表示の強制更新ができるようになります。Scriptableも起動するのでスマートな方法ではないのですが仕方ありません。今後のApple次第です。
アプリを経由せずに更新できればめちゃくちゃ良いんですけどね。
Parameterに1を指定することで、アプリ内での実行ではウィジェット画面の完成形を表示し、ウィジェットからの実行では実行後即ホーム画面に戻る、というように、起動した場面の違いによって動作の使い分けができるようになっています。
ウィジェット上での数値の変動はAPの6分に1回復か、カフェの1時間にいくらかの回復しかないので、現在の自動更新の頻度でも問題なく使えます。
見ていた感じでは20分くらい更新されないことがありますが、それで深刻な状況に陥ることは基本的にないでしょう。
スペック的に現代では厳しくなってきたiPhone7で大丈夫なので、ウィジェット置ける端末なら大丈夫なんじゃないでしょうか。何らかの理由で長期間更新されていない場合でも気づけるように、Last Loadedに時刻を表示してあります。
おまけ
カスタマイズ
JavaScriptを知らないまま突貫で作ったのであまり自由度はありませんが、若干のウィジェットのカスタマイズが可能です。
プログラムの先頭部分を書き換えることで変更できますが、iPhoneでの表示がうまくいくように微調整を行ったあとなので、色以外の変更はおすすめしません。
const bgColor = "#222222";//ウィジェットの背景色
const width = 110;//プログレスバーの横幅
const height = 8;//プログレスバーの太さ
const barColor = "#67CE67";//プログレスバーのバー部分の色
const backColor = "#48484A";//プログレスバーの背景色
const textColor = "#FFFFFF"//テキストの色
const lastloadedColor = "#FFFFFF";//最終更新時刻の文字の色
//const lastloadedColor = "#222222";//ウィジェットの背景色と一致させれば隠せる
プログレスバーの色はbarColorとbackColorに連動しています。
Last Loaded Timeの表示が要らない場合、プログラムでの実装が面倒だったため、文字色を背景と同化させることで解決できるようにしてあります。
const lastloadedColor = "#FFFFFF";//最終更新時刻の文字の色
//const lastloadedColor = "#222222";//ウィジェットの背景色と一致させれば隠せる
これを
//const lastloadedColor = "#FFFFFF";//最終更新時刻の文字の色
const lastloadedColor = "#222222";//ウィジェットの背景色と一致させれば隠せる
こうです。
僕は使っていないので詳細は知らないのですが、中サイズウィジェット(横長のやつ)にも表示自体はできるようです。バーの長さを調整すればいい感じになるのか、テキストの位置まで全部見直す必要があるのかはわかりませんが、ホーム画面の配置にこだわりのある方はぜひ挑戦してみてください。
iOSショートカットの便利な使い方
ショートカットの起動は
アプリ内でアイコンをタップ
URLスキームで指定して実行
オートメーションによる起動(背面タップとか)
別ショートカットからショートカット起動
ウィジェットに追加してアイコンをタップ
ホーム画面に追加してアイコンをタップ
などの方法がありますが、上2つ以外はアプリを起動せずに直接実行できるため、早くて快適です。
また、このショートカットは外部からの入力を受け付けています。これは、カフェと現在の状態確認を別ショートカット経由でワンタップ起動をするためです。
何も入力せずにOKを押せば、カフェの上限到達時刻をセット
負の数値を入力すれば、現在の状態を表示
APの入力はこれ以上手間を削れないとして、カフェの操作数は限界まで削ってはいますが2タップ必要です。確認も同様ですね。これを、カフェ専用の別ショートカットを作ることで、ワンタップでの処理が可能になります。
テキストを空にすれば値が無いことになり、カフェのリセットができます。
-1でなくとも負の数であれば別になんでもいいのですが、現在の数値の確認ができます。
正の数を入れればAPの現在値としてセットが可能ですが、日々のAP消費で決まって同じ数値になる、ということはほぼ無いので実用性はないです。
所感・愚痴
iPhoneとiPad間のiOSショートカットの同期の問題なのか、編集したのが巻き戻ったり質問による設定項目が全部飛んだり散々です。
OSは最新のはずなんですが…