リマインダー開発Ⅲ予約も目覚ましも
優しい音楽で
リマインダーが予定を正確に管理
安心して快適な朝を

image: リマインダー開発Ⅲ予約も目覚ましも優しい音楽で
Toplabo
Developing Reminder III

この記事は『リマインダーを開発するⅡ』の続きになります。先にそちらをご覧ください。

いよいよElectronを使って、予定をサウンドで通知する

 | 2025/10/02

今回の
内容

新たに通知機能とアプリのビルド機能を実装
  • これまでの構成と今後

    前回までで、Electron環境でカレンダーを表示し、予約を入れて保存し、次の起動時にも予約を残せるように構築しました。
    今回からは、いよいよ予約の時間前に通知が来るような機能を入れていきます。
    これまでは予約のタイトルと詳細だけだったので、新たに予約時間と何時間前に通知をよこすかの予告時間をデータに保存します。

    今回の目標

    以上を踏まえて、構成すると
    1. 予定時刻と通知時刻を保存・読み込みできるようにする
    2. 予定の時刻にサウンドで通知できるようにする
    になります。

    リマインダー2/エレクトロンの開発環境を構築する 

    これまでのソースコードは下記より。
    リマインダー2/エレクトロンの開発環境を構築するまでのコード:https://studio-happyvalley.com/wp/data/developing_reminder2.txt

時刻の
実装

予定の時刻と通知の時刻を保存・読み込みできるようにする
  • 予定時間などを設定するためには入力フォームを追加

    下記の様にindex.htmlにフォームを追加します。
    実際には、予約の削除や追加機能など必要ですが、今回は解説メインなので最低限の内容で進めます。

    <!-- 予定入力モーダル -->
    <div id="noteModal" style="display:none;">
      <h2 id="noteTitle">予約</h2>
    <input type="time"  id="noteInputTime" class="note-time" value="09:00">
    <input type="text" id="noteInputTitle" class="note-title" placeholder="タイトル(例:会議)" maxlength="100" >
    
    <!-- 通知タイミング入力を追加 -->
    <label class="notifi" for="noteInputAlert">通知タイミング</label>
    <input id="noteInputAlert" type="number" value="30" min="0" step="5">
    <small>分前(例: 30 → 30分前に通知)</small>
    <textarea id="noteInputBody" placeholder="詳細"></textarea>
    <input type="hidden" id="date">
    <button onclick="saveNoteToStorage()">保存</button>
    <button onclick="closeModal()">キャンセル</button>
    </div>
    https://studio-happyvalley.com/wp/wp-content/uploads/e2adf2a87ed62982bcac96ae82eff920.png

通知の
実装

通知の仕組み
  • 通知の方法はOSのネイティブ通知機能を使う

    Electronにはブラウザと同じ Notification API がありそれを使って通知するようにします。
    例えば:
    new Notification("予定のお知らせ", { body: "歯医者の時間が近いです!" });

    これをレンダプロセスへ記述することでOS標準の通知が発生します。
    また、通知がデスクトップの右下からスライドする仕様に関しては、インストールしないと機能しません。
    環境によってはそれも出ない可能性はありますが最終的にはアクションセンターに必ず通知が表示されます。

    私の場合も通知時のサウンドもタスクトレイの右横からスライドもインストールして初めて起動したのでこれが通常のケースなのかなと思います。

    ただ、通知は見逃しやすいので今回はサウンドと、アイコンに赤丸表示に変化という機能をつけることで、気づきやすくしました。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig31.pngアクションセンターの通知https://studio-happyvalley.com/wp/wp-content/uploads/fig34.png通知の設定。『通知時にサウンド・・・』にチェック。
    機能の要件定義

    繰り返し通知も可能ですが、今回は一回だけとします。
    あと、通知の設定は最終的にはOS側で設定する必要があります。
    アクションセンターのパレットの右端に『通知の設定』メニューがありますが、ここをクリックで設定します。
    これもインストールする前はelectronの名前で表示されていると思います。

    実装の流れ

    予定データ(date, time, alertMinutes) を元に「通知予定時刻」を計算
    setInterval で1分おきにチェック。
    現在時刻が「通知予定時刻」を過ぎたら通知
    一度通知した予定は、再通知しない
    また、アプリとしてインストールする前だと通知をOSが受け取らない可能性が高いので、サウンドの設定をし音を鳴らすようにします。あとのことを考えるとwavファイルがいいと思います。本来は1~2秒の短い通知音がいいとは思いますが、朝の目覚めなどに利用する時とかは好きなポップかクラシック音楽なんかでもいいかもしれませんね。

    なんにしてもほとんどの不具合はインストールすれば解消します。 

    まず日付のフォートを桁揃えに変更

    内部的には計算のしやすさから桁をそろえてませんが、日付の比較などは ISO 8601形式(国際標準の日付表記)でおこなうため、remminderのみ、桁ぞろえで使用します。

    function formatDateToISO(dateStr) {
    	// "2025-9-26" → "2025-09-26"
    	const [y, m, d] = dateStr.split("-").map(Number);
    	const mm = String(m).padStart(2, "0");
    	const dd = String(d).padStart(2, "0");
    	return `${y}-${mm}-${dd}`;
    }
    通知機能のコード

    通知はレンダラープロセスのsetIntervalで1分置きに通知時刻と比較し超えていたら、new Notification().show() で通知するという流れとなります。
    通知が済んだ予約はnotifiedに格納していきます。
    また、予約を編集する都度、setIntervalも更新する必要があるので、再起動時前の処理をリセットするようにしてあります。

    let reminderTimer= null;
    let notified = {}; // 通知済みフラグ
    function startReminder() {
    	if (reminderTimer) {
    		clearInterval(reminderTimer); // すでに動いていたらリセット
    	}
    
    	// 通知チェック間隔(ミリ秒)
    	const CHECK_INTERVAL = 60 * 1000; // 1分ごと
    
    	reminderTimer = setInterval(() => {
    		const notes = getAllNotes();
    		const now = new Date();
    
    		for (const dateStr in notes) {
    			const note = notes[dateStr];
    			if (!note.alertMinutes) continue;
    			if(note.status == 'done') continue;
    			//日付をISOフォーマットに変換
    			const isoDate = formatDateToISO(dateStr);
    
    			// UIのtime欄
    			const target = new Date(`${isoDate}T${note.time || "09:00"}:00`);
    			const alertTime = new Date(target.getTime() - note.alertMinutes * 60 * 1000);
    
    			const key = `${dateStr}-${note.title}`;
    			if (notified[key]) continue;
    
    			console.log("SetInterval起動");
    			if (now >= alertTime && now < target) {
    			console.log("通知発生", alertTime);
    				new Notification([`[${dateStr}/${note.time}: ${note.title}]\n${note.alertMinutes}分前のお知らせ`, 
    					`${note.title}\n${note.body}`
    				]);
    				  // trayアイコン変更のリクエストをメインに送る
    				ipcRenderer.send("reminder-alert");
    				
    				// サウンド
    				try {
    				  const audio = new Audio("./sounds/Ring10.wav");
    				  audio.play();
    				} catch (e) { /* ignore */ }
    
    				notified[key] = true;
    			}
    		}
    	}, CHECK_INTERVAL);
    }
    
    startReminderを呼び出すタイミング

    今はindex.html のロード完了後に通知用関数startReminderを呼び出します。

    document.addEventListener("DOMContentLoaded", () => {
      startReminder(i.currentUserKey);
    });
    
    main.js

    メインプロセスの処理ではデスクトップのタスクトレイにアイコンを表示させ、右クリックするとメニューが表示されるようにします。

    Electronのタスクトレイ関連:https://www.electronjs.org/ja/docs/latest/api/tray

    const { app, BrowserWindow, Tray, Menu, ipcMain } = require("electron");
    const path = require("path");
    
    // 通知ポップアップに必須!
    app.setAppUserModelId("com.shv.reminder");
    
    function createWindow() {
      const win = new BrowserWindow({
        width: 1000,
        height: 800,
    	icon: path.join(__dirname, "icon.ico"),
        webPreferences: {
    		nodeIntegration: true, 
    		contextIsolation: false, 
       }
      });
    
      win.loadFile('index.html'); // ←これまで作ったカレンダーがそのまま使える!
      
      tray = new Tray(path.join(__dirname, "icon.ico"));//
      const contextMenu = Menu.buildFromTemplate([
        { label: '開く', click: () => win.show() },
        { label: '終了', click: () => app.quit() }
      ]);
      tray.setToolTip('SHV Reminder');
      tray.setContextMenu(contextMenu);
      
      // ウィンドウに戻ったら通常アイコンへ
      win.on("focus", () => {
        tray.setImage(path.join(__dirname, "icon.ico"));
      });
    
    // アプリを開いたら通常アイコンに戻す
    app.on("browser-window-focus", () => {
      if (tray) {
        tray.setImage(__dirname + '/icon.ico');
      }
    });
    
    }
    ipcMain.on("reminder-alert", () => {
      if (tray) {
        tray.setImage(path.join(__dirname, "icon-alert.ico"));
      }
    });
    
    app.whenReady().then(createWindow);
    
    app.on('window-all-closed', () => {
      if (process.platform !== 'darwin') app.quit();
    });
    https://studio-happyvalley.com/wp/wp-content/uploads/fig33.png
    アイコン

    まず、アイコン画像がないとビルドできないので、必須です。
    必要な画像はサイズ256ⅹ256以上のICOファイル2点となります。
    通常表示のアイコンと、通知が来た時の赤丸付きアイコンです。
    別に赤丸でなくてもアイコンの色自体が変わるなどでもいいと思います。
    ファイル名は任意で構いませんが、icon.ico, icon-alert.icoで問題ないかと思います。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig37.png通常アイコンhttps://studio-happyvalley.com/wp/wp-content/uploads/fig36.png通知時の赤丸付きアイコン

通知の 確認

通知の確認
  • 確実に通知が発生して、タスクアイコンが赤丸に変わるか

    powerShellでnpm run start と入力してアプリを起動します。
    予定編集モーダルを開いて、適切な予定と通知時刻を設定し、正しく通知が発生するか確認します。正常に動作すれば下記の様にアイコンが変化するはずです。音楽もなると思いますが、環境によってはインストールしないとならないかもしれません。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig32.png

次回の
告知

通知をサウンドで送れるリマインダーの完成
  • エレクトロンの開発環境でリマインダーのサンプルを完成

    機能としてはただ、カレンダーを表示し、予約を一日に一回だけ、通知も一回だけの超シンプルなリマインダーですが、やっと今回で完成させました。使いようによっては朝の目覚めを優雅に管弦楽の音楽で起きるというようなことも可能なのではと思います。
    さて、いよいよ次回で、OSにインストールできるアプリをビルドする方法を解説します。
    データの保存ディレクトリの共有やレンダラプロセスとメインプロセスの設定などかなり細かな内容になります。WEBと違って、OSに依存する部分がかなりあるため大変ですが、苦労の後には新しい世界が待っていますよ。

    ここまでのファイル一覧:https://studio-happyvalley.com/wp/data/developing_reminder3.txt

補足
  • 開発ツールはcntrl + shift + i で表示されます。

    開発ツールはcntrl + shift + i で表示されます。また、cntrl + rでリロード、ツールのapplicationタブクリックでメニューが表示され、中のStorage -> Local storage で保存中のデータを見ることができます。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig38.png
    powerShellのコマンド

    リマインダーを終了したい場合は、cntrl + c で終了です。
    さらに上下方向の矢印キーで直前のコマンドを順番に呼び出せます。

この記事の続きは『リマインダーを開発するⅣ』でご覧になれます。

『リマインダー開発Ⅲ.予約も目覚ましも優しい音楽で』関連のお薦め

このサイトで紹介しているコード、プログラムなどは個人の学習目的で作成されたものであり、いかなる保証も行いません。
利用はすべて自己責任でお願いします。
ただし、このページで紹介しているプログラムやビジュアルなどはご依頼いただければ実装を賜ります。
お問い合わせはこちら