セリフ実績画面の実装

## 実装した機能

アラームを指定時間内に止めることによって、解放されるセリフを実績画面で確認できるようにする機能を追加しました。

未開放のセリフは「???」で表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676445/80824f23-32aa-c001-0c1e-8e8393b71968.png)

## コードの中身

アラームを止めることをトリガーに、
AlarmScreenからHistoryScreenへnavigationを利用して、
セリフのIDを送っている。


```react
//AlarmScreen.js
//再生するボイスファイルを定義
const voiceFiles = {
  1: require("../voice/voice1.mp3"),
  2: require("../voice/voice2.mp3"),
  3: require("../voice/voice3.mp3"),
  4: require("../voice/voice4.mp3"),
  5: require("../voice/voice5.mp3"),
  6: require("../voice/voice6.mp3"),
  7: require("../voice/voice7.mp3"),
  8: require("../voice/voice8.mp3"),
  9: require("../voice/voice9.mp3"),
  10: require("../voice/voice10.mp3"),
};

const AlarmScreen = ({ navigation }) => {
  const [randomIndex, setRandomIndex] = useState(null);

//諸々と省略


  const checkAlarms = async () => {
    for (const alarm of alarms) {
      const alarmTime = alarm.time;
      const currentTime = new Date();

      // アラームの設定された時間（時、分）が現在の時間と一致するかどうかを確認
      if (
        alarm.isActive &&
        alarmTime.getHours() === currentTime.getHours() &&
        alarmTime.getMinutes() === currentTime.getMinutes()
      ) {
        alarm.isActive = !alarm.isActive;
        setShowPopup(true); 
        
        // ランダムなインデックスを取得
        const newIndex = Math.floor(Math.random() * 10) + 1; 
        //newIndex-1(0始まりのため)をRandomIndexにセット
        setRandomIndex(newIndex - 1);
        // ランダムに選択された音声ファイルを再生
        playAlarmSound(voiceFiles[newIndex]);

        // 10秒ごとに音声を再生
        const intervalId = setInterval(() => {
          playAlarmSound(voiceFiles[newIndex]);
        }, 10000);

        // 1分後に音声再生を停止
        setTimeout(() => {
          clearInterval(intervalId);
          setShowPopup(false);
          stopAlarm();
        }, 60000);

        // stopAlarm() を呼ぶために intervalId を state に保存
        setAlarmIntervalId(intervalId);
      }
    }
  };

  const stopAlarm = async () => {
    if (sound) {
      clearInterval(alarmIntervalId);
      await sound.unloadAsync();
    
      // アラームが止まった時点の情報をHistoryScreenに送信
      navigation.navigate("History", {
        alarmStopped: true,
        //randomIndexを送る
        playedPhraseIndex: randomIndex,
      });
    }
};
  
```

```react
//HistoryScreen.js

import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet, FlatList } from "react-native";
import { Calendar } from "react-native-calendars";

const HistoryScreen = ({ route }) => {
  const [phrases, setPhrases] = useState([
    {
      id: 1,
      text: "おはようございます。今日も一緒に素敵な1日を迎えましょう。",
      achieved: false,
    },
    { id: 2, text: "おはよう。なんでそんなに笑ってるの？", achieved: false },
    {
      id: 3,
      text: "すごいね。今日も目覚ましを1回で止めてくれてありがとう。",
      achieved: false,
    },
    //省略
  ]);

  useEffect(() => {
    // アラーム停止で実績を更新
    if (route.params?.alarmStopped) {
      // セリフのindexを持って、updatePhraseAchievedを呼び出し
      //playedPhraseIndexは、AlarmScreenから渡された値
      updatePhraseAchieved(route.params?.playedPhraseIndex);
    }
  }, [route.params?.playedPhraseIndex]);

//Phraseリストを更新する
  const updatePhraseAchieved = (playedPhraseIndex) => {
    if (playedPhraseIndex !== null && playedPhraseIndex !== undefined) {
      const updatedPhrases = [...phrases];
      // playedPhraseIndexに対応するセリフが存在し、かつ未達成の場合
      if (
        updatedPhrases[playedPhraseIndex] &&
        !updatedPhrases[playedPhraseIndex].achieved
      ) {
        updatedPhrases[playedPhraseIndex].achieved = true;
        setPhrases(updatedPhrases);
      }
    }
  };

  const renderPhraseItem = ({ item }) => (
    <View style={styles.phraseItem}>
      <Text>{item.achieved ? item.text : "???"}</Text>
    </View>
  );

  return (
    <View style={styles.container}>

      {/* 下部：セリフ一覧 */}
      <View style={styles.phrasesContainer}>
        {phrases.length > 0 ? (
          <FlatList
            data={phrases}
            renderItem={renderPhraseItem}
            keyExtractor={(item) => item.id.toString()}
          />
        ) : (
          <Text>獲得したセリフがありません</Text>
        )}
      </View>
    </View>
  );
};
```

### useState
```react
const [state, setState] = useState(initialState);
```
- state は現在の状態を保持する変数
- setState は state の値を更新するための関数
- initialState は最初の状態を指定する。初回のレンダリング時に一度だけ実行される

```initialState```の部分に、
今回はセリフ群を初期値として設定することで、その後のアクションに応じてセリフ群のプロパティ部分を更新できるようにしました。

 ### const updated_list = [...list];
新しい配列を作成しています。
reactは元のオブジェクトの状態を変えるよりも、新しいオブジェクトを作成するほうが
奨励されているみたいです。

###  JavaScriptの条件（三項）演算子
```{item.achieved ? item.text : "???"}```で表現している部分です。
item.achieved が true の場合は item.text の値を返し、condition が false の場合は"???"を返します。