見出し画像

【ミーア】待ち時間退屈対策:音声ファイルダウンロード中の文言をランダムに表示する

ゲームに学ぶロード画面の設計(ユーザーを退屈させない仕組み)

現在、猫型おしゃべりロボット「ミーア」ちゃんで、アプリで性格や方言を切り替えたときに、話す音声ファイルをESP32にダウロードしている。

音声ファイルが100以上あるので、ダウンロード完了までに1分くらいかかり、ダウンロード進捗のインジケーターは1%単位で動的に表示しているものの、メッセージは「完了まで一分くらい待っててね。」で固定表示にしている。

この、ユーザーへの待ち時間を対策したいと思い、そういえば、Nintendo Switchでゼルダの伝説をプレイしていた時に、ローディング中に、技や小ネタ集をランダムテキスト表示していて飽きさせない工夫をしていたなと思い、それを踏襲することにした。

詳細は、ゲームに学ぶロード画面の設計(ユーザーを退屈させない仕組み)というタイトルで、下記記事に記載されている。


現状のコード解説

現状のアプリ側のflutterのコードは下記。

import 'package:clocky_app/widgets/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:clocky_app/grpc/notification.pbenum.dart';
import 'package:clocky_app/services/grpc_service.dart';

OverlayEntry? currentOverlayEntry;

void showOverlay(BuildContext context, double progress, String message) {
  // 既存のオーバーレイがあれば削除
  currentOverlayEntry?.remove();
  currentOverlayEntry = null;

  currentOverlayEntry = OverlayEntry(
    builder: (context) => Stack(children: <Widget>[
      ModalBarrier(
        color: Colors.grey.withOpacity(0.5),
        dismissible: false,
      ),
      Center(
        child: Material(
          color: Colors.transparent, // 透明な背景色
          child: Container(
            padding: const EdgeInsets.all(32),
            color: Colors.white,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                const Text("完了まで一分くらい待っててね。"),
                Spacing.h16(),
                LinearProgressIndicator(
                  value: progress / 100,
                  backgroundColor: Colors.grey[200],
                  valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
                ),
                Spacing.h16(),
                Text("${message}: ${(progress).toStringAsFixed(0)}%")
              ],
            ),
          ),
        ),
      ),
    ]),
  );

  // オーバーレイを表示
  Overlay.of(context).insert(currentOverlayEntry!);
}

void removeOverlay() {
  currentOverlayEntry?.remove();
  currentOverlayEntry = null;
}


class NotificationWidget extends ConsumerWidget {
  final Widget child;

  const NotificationWidget({super.key, required this.child});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final notification = ref.watch(notificationStreamProvider);

    notification.whenData((response) {
      Future.delayed(Duration.zero, () {
        double progress = response.deviceStatus.downloadProgress.progress;
        DownloadStatus status = response.deviceStatus.downloadProgress.status;

        switch (status) {
          case DownloadStatus.DOWNLOAD_STATUS_DOWNLOADING:
            // オーバーレイを表示
            showOverlay(context, progress, "ダウンロード中");
            break;
          case DownloadStatus.DOWNLOAD_STATUS_COMPLETE:
            removeOverlay();
            break;
          case DownloadStatus.DOWNLOAD_STATUS_FAILED:
            removeOverlay();
            break;
          case DownloadStatus.DOWNLOAD_STATUS_INIT:
            // 初期状態の処理
            break;
        }
      });
    });

    return child;
  }
}

final notificationStreamProvider = StreamProvider<StreamResponse>((ref) {
  final grpcService = ref.watch(grpcServiceProvider);

  final user = ref.read(userProvider);
  if (user?.uid == null) {
    return const Stream.empty();
  }
  return grpcService.listenNotifications(user!.id!);
});

続きは、こちらで記載しています。


この記事が参加している募集

#仕事について話そう

109,984件

よろしければサポートお願いします!いただいたサポートはクリエイターとしての活動費に使わせていただきます!