見出し画像

ブルアカの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を文字列としてぶち込み、クラウドストレージの代替にしてしまう、というものです。
リマインダーでの表示は酷いもんですが、動作はとても改善されました。

更新日時が新しい方からjsonを読み込みたいので、AP用とカフェ用の両方にjsonを記述してる

以降の説明では旧バージョンの使いまわしがありますが、内容的に問題ないのでお許しください。

使い方(変化なし)

  • 現在のAPを入力すれば、APの上限到達時刻をセット

  • 何も入力せずにOKを押せば、カフェの上限到達時刻をセット

  • 負の数値を入力すれば、現在の状態を表示

AP上限時刻セットのイメージ
カフェ上限時刻セットのイメージ
iPadも対応(ただし表示が少し崩れる)

仕様

  • APかカフェがMAXになる前にリマインダーによる通知が来る(何分前に通知するか設定可能)

  • ウィジェットに現在のAP、カフェの状態、上限に到達する時刻を表示

  • 同一のApple IDであれば複数端末で自動同期(iPhone・iPad関係なし)

  • ブルアカをインストールしてない端末でも利用可能

  • ウィジェットの自動更新タイミングは制御不可(タップで強制更新は可能)

導入方法

必要なアプリ

iOSショートカット(純正)
リマインダー(純正) ※ウィジェット機能&通知機能が要らなければ不要
Scriptable ※ウィジェット機能が要らなければ不要

手順

リマインダーの追加、リマインダーによる通知、ウィジェットの表示 を行うための手順を書いていきます。
リマインダーと通知が不要で、ただ単にウィジェット表示だけをしたい場合は、米印のところに注目してもらえれば問題なくできると思います。(不要な設定をしないだけです)

iOSショートカットの設定

ショートカットの入手
 ショートカット:ブルアカAP管理 リマインダー同期 をリンクか下記QRコードかファイルから開き、アプリに追加する。

「ショートカットを設定」で追加できます。

初期設定
ここでの設定は後からでも変更可能です。計算に使用する数値を入力します。

設定画面その1

AP自然回復 :6分に1回復するという意味。全員同じなので変更不要。
AP上限値  :APの最大値。自分の数値を入力。
カフェ回復  :1時間あたりのAP回復量(実は小数まで桁が設定されている)
カフェ上限値 :カフェの最大保管量

https://bluearchive.wikiru.jp/?%E3%82%AB%E3%83%95%E3%82%A7#Lank より
設定画面その2

言及していないものはプログラムでの定義として利用しているので変更すると壊れます。

リマインダーリスト名:リマインダー通知用にリマインダーを作成するリストの名前。これと同様の名前のリマインダーリストを作成する必要がある。
※リマインダー不使用の場合は無視して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()
}
右下の実行ボタンを押して
ウィジェットが表示されればOK
名前の変更は長押しからRename

ウィジェットの追加
ホーム画面の空白を長押しorアプリ長押し-ホーム画面を編集
+からScriptabeのウィジェットを追加

小サイズを選択

アプリのインストール直後は、ウィジェットとして追加可能なアプリ一覧にない場合があります。再起動したら表示されました。

ウィジェットを長押し-ウィジェットを編集
Scriptで作成したスクリプトを選択
Open AppはRun Scriptに変更
Parameterには1を入力
ウィジェットの設定完了

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に連動しています。

このウィジェットはiOSの純正バッテリーウィジェットと同じ色になるように設定しているのですが、バーの色である緑色(67CE67)が絶妙に設定したとおりに表示されません。黒とかグレーは大丈夫なんですが、なんかずれます。理由もさっぱりです。

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は最新のはずなんですが…

感想・不具合報告・カスタム報告 お待ちしてます

いいなと思ったら応援しよう!