はじめに
現在のアプリトップ画面は下記。
2023年8月時点の、まだプロトタイプ状態だったミーアの静止画が表示されているのみ。
より、ユーザーに毎日アプリを開いて楽しんでもらえるように、下記実装を追加する。
- ミーアの静止画をGIFアニメーションにする
- GIFアニメーションに加えて、「本日のミーアの一言」を表示
- GIFアニメーションと一言フレーズを日替わりで表示して、毎日の変化をユーザーに楽しんでもらう。
GIF画像とテキストの用意
まず、表示したいGIF画像とテキストのリストを準備する必要がある。
GIF画像に関しては、ミーアではさまざまな目の表情の画像を用意しており、下記の記事で、目の画像をアップロードすればそのまま動作検証とGIFのダウンロードできる関数を作成したので、こちらを利用。
こんな感じでgif画像をいくつか作成してみた。
gif画像をpubspec.yamlに記載
Flutterプロジェクトで画像やその他のアセットを使用する際には、それらをpubspec.yaml
に記載してFlutterに知らせる必要がある。これにより、ビルドプロセス中にFlutterがアセットをアプリのアセットバンドルに含めることができる。
assets/images
配下にGIF画像を配置して、それをpubspec.yaml
に記載する。
pubspec.yaml
にアセットセクションを追加し、画像のパスを記載。フォルダ内のすべての画像を一度に追加したい場合は、ディレクトリのパスを記述し、末尾に/
を付けて、フォルダ内のすべてのファイルを含めるようにする。
flutter:
assets:
- assets/images/
もしくは、特定のファイルだけを明示的に追加する場合は、ファイル名を個別に記載する。
flutter:
assets:
- assets/images/specific_image.gif
- assets/images/another_image.gif
pubspec.yaml
に記載した後は、flutter pub get
を実行して変更を有効にする。これで、Image.asset
などのウィジェットを使用してアセットを読み込むことができる。今回は、多くのgif画像を取り込むので、imagesディレクトリをassetsとして指定。
日替わりのメッセージとGIFを取得する関数を実装
画像とテキストを扱う関数を別ファイルに分け、それをトップ画面から呼び出すようにする。daily_message_service.dart
という新しいファイルを作成し、その中に日替わりのメッセージとGIFを取得する関数を実装する。
daily_message_service.dart
はアプリケーションのビジネスロジックを担当するサービスクラスになるので、services
ディレクトリに記載。
lib/
└── services/
└── daily_message_service.dart
選択されたアイテムの保存と読み込み
- 選択されたGIFとテキストのインデックスをローカルストレージ(例:
SharedPreferences
)に保存し、同じ日にアプリが再度開かれたときに同じアイテムを表示できるようにする。 - Dartの
DateTime
クラスを使用して現在の日付を取得し、Random
クラスでその日付をシード値として使用。これにより、同じ日にアプリを開いた場合は常に同じアイテムが選ばれる。
日付の変更を検出
- ユーザーがアプリを開いたときに現在の日付をチェックし、最後に保存された日付と異なる場合は新しいアイテムを選択して保存する。
// lib/services/daily_message_service.dart
import 'dart:math';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DailyMessageService {
Future<Map<String, String>> getDailyMessageAndGif() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var now = DateTime.now();
var formatter = DateFormat('yyyyMMdd');
String formattedDate = formatter.format(now);
String lastPickedDate = prefs.getString('last_picked_date') ?? "";
Map<String, String> result = {
'gif': 'default.gif',
'message': 'デフォルトメッセージ'
};
if (formattedDate != lastPickedDate) {
// 日付が変わったら新しいメッセージとGIFを選ぶ
var rng = Random(now.millisecondsSinceEpoch);
List<String> gifs = [
'mia1.gif',
'mia2.gif'
]; // assets/imagesからの相対パス(つまりファイル名)を記述
List<String> messages = [
'ふぅ〜。思考停止',
'リラックスタイム、最高だね',
'「なんか面白い話して」って残酷すぎない?',
'今日は、パンケーキ食べちゃおーっと',
'この本読み始めたら止まらない、寝不足必至だな',
'ご注文の料理を持ってきましたニャン',
'健康診断の結果見るの、怖いー'
];
result['gif'] = gifs[rng.nextInt(gifs.length)];
result['message'] = messages[rng.nextInt(messages.length)];
// 選んだメッセージとGIFを保存
prefs.setString('last_picked_date', formattedDate);
prefs.setString('daily_gif', result['gif']);
prefs.setString('daily_message', result['message']);
print('Stored GIF: ${result['gif']}');
print('Stored Message: ${result['message']}');
} else {
// 同じ日なら保存されたメッセージとGIFを使用
result['gif'] = prefs.getString('daily_gif') ?? 'default.gif';
result['message'] = prefs.getString('daily_message') ?? 'デフォルトメッセージ';
}
return result;
}
}
ホーム画面で日替わり表示関数を呼び出す
// lib/screens/home/home_tab.dart
import 'daily_message_service.dart';
class _HomeTabState extends ConsumerState<HomeTab> {
// ... (その他の状態変数は省略) ...
String _dailyGif = 'default.gif';
String _dailyMessage = 'デフォルトメッセージ';
@override
void initState() {
super.initState();
_loadDailyMessage();
}
void _loadDailyMessage() async {
DailyMessageService service = DailyMessageService();
var dailyInfo = await service.getDailyMessageAndGif();
setState(() {
_dailyGif = dailyInfo['gif'] ?? 'default.gif';
_dailyMessage = dailyInfo['message'] ?? 'デフォルトメッセージ';
});
}
@override
Widget build(BuildContext context) {
// ... (ビルドメソッドのコードは省略) ...
return Align(
alignment: Alignment.center,
child: Column(
children: [
InkWell(
onTap: () async {
final message = await grpcService.sayHello('Clocky');
debugPrint('message: ${message.message}');
},
child: Image.asset('assets/images/$_dailyGif',
width: 250, height: 250),
),
Spacing.h8(),
Text(
_dailyMessage,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
// ... (その他のウィジェットは省略) ...
],
),
);
}
}
動作確認
トップ画面を開くと、下記のように無事、デフォルトのGIF画像とテキストが表示された。
次に、日付を変えたときに、GIFアニメーションとフレーズが変わるかを検証。
今回は簡易的に、トップ画面に「1日進める」ボタンを設置して、そのボタンが押されたときに日付を変更し、それに応じてデータを更新するようにする。
simulatedDateProvider
という名のStateProvider
を用いて、シミュレーションされている現在の日付を管理する。ボタンが押されたときに、StateProvider
の値を更新し、_loadDailyMessage
メソッドを呼び出し、DailyMessageService
を使用して新しい日付に基づいてデータを取得する。
DailyMessageService
内でgetCurrentDate
をオーバーライドし、プロバイダーから読み取った新しい日付を使用してサービスがデータを正しく取得できる。
// lib/screens/home/home_tab.dart
final simulatedDateProvider =
StateProvider<DateTime>((ref) => DateTime.now()); //日付シミュレーション用
class _HomeTabState extends ConsumerState<HomeTab> {
@override
void initState() {
super.initState();
final user = ref.read(userProvider);
volume = user?.volume ?? 50;
_loadDailyMessage();
}
void _advanceDay() {
// 現在のシミュレートされた日付を1日進める
ref.read(simulatedDateProvider.notifier).update((state) => state.add(Duration(days: 1)));
// 新しい日付でデータを再取得する
_loadDailyMessage();
}
void _loadDailyMessage() {
DailyMessageService service = DailyMessageService();
var date = ref.read(simulatedDateProvider);
service.getCurrentDate = () => date; // 日付をServiceに渡す
service.getDailyMessageAndGif().then((result) {
// 結果に基づいてUIを更新する
setState(() {
// 画像やメッセージを更新
});
});
}
Widget build(BuildContext context) {
final simulatedDate = ref.watch(simulatedDateProvider);
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
// ボタンが押された時の処理
_advanceDay();
},
child: Text('1日進める')
),
// その他のウィジェット...
],
),
),
);
}
}
flutter: Stored GIF: mia1.gif
flutter: Stored Message: 健康診断の結果見るの、怖いー
flutter: Stored GIF: mia1.gif
flutter: Stored Message: 健康診断の結果見るの、怖いー
flutter: Stored GIF: mia1.gif
flutter: Stored Message: 朝の体重計、いつもドキドキ
flutter: Stored GIF: mia2.gif
flutter: Stored Message: こわい夢見ちゃった〜
この画面で、音量調整のスライダーの下にある「1日進める」ボタンをクリックすると、local storageに変更後のGIFアニメーションとテキストが表示され、UIにも反映された。
この改修で、だいぶインタラクティブな、ホーム画面になったと思う。
コメント