Skip to content

Commit

Permalink
Merge branch 'main' into feature/#6-create-calculationproblem
Browse files Browse the repository at this point in the history
  • Loading branch information
herring101 authored Jun 17, 2022
2 parents 9c8470d + e88aed2 commit 32fcc74
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 15 deletions.
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ android {
applicationId "com.example.alarm2022"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
minSdkVersion 21//flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
12 changes: 12 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,16 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>起動時に自動的に開始-->
<uses-permission android:name="android.permission.VIBRATE" />
<!--ここから音声認識-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<queries>
<intent>
<action android:name="android.speech.RecognitionService" />
</intent>
</queries>
<!--ここまで音声認識-->
</manifest>
31 changes: 17 additions & 14 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'model/ringing_alarm_model.dart';
import 'view/ringing_alarm_view.dart';
import 'view/CalculationProblem_s.dart';
import 'view/tongue_twister_view.dart';

void main() async {
setupRingingAlarm();
Expand All @@ -16,7 +16,6 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(

primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
Expand All @@ -34,16 +33,17 @@ class MyHomePage extends StatefulWidget {
}

class _MyHomePageState extends State<MyHomePage> {
bool? ttIsCleared;

@override
Widget build(BuildContext context) {
return Scaffold(
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
//RingingAlarmPageに飛ぶボタン
Expand All @@ -60,22 +60,25 @@ class _MyHomePageState extends State<MyHomePage> {
child: const Icon(Icons.alarm),
),
FloatingActionButton(
//CalculationProblemPageに飛ぶボタン
heroTag: 'calculation',

heroTag: 'tongue_twister_page_button',
onPressed: () async {
// "push"で新規画面に遷移
await Navigator.of(context).push(
ttIsCleared = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return const CalculationProblemPage(
title: 'Ringing Alarm Test Page');
return const TongueTwisterPage();
}),
);
setState(() {});
},
child: const Icon(Icons.calculate),
child: const Icon(Icons.mic),
),
Text(
"早口言葉:${ttIsCleared == null ? "未成功" : (ttIsCleared! ? "成功" : "失敗")}",
style: Theme.of(context).textTheme.headline6,
)
],
),
),
);
);
}
}
101 changes: 101 additions & 0 deletions lib/model/tongue_twister_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:speech_to_text/speech_to_text.dart' as stt;
import 'package:speech_to_text/speech_recognition_error.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:alarm2022/model/tongue_twister_questions.dart';

class TongueTwisterModel {
//音声認識の実体
stt.SpeechToText speech = stt.SpeechToText();

//表示用String
String lastWords = "";
String lastError = "";
String lastStatus = "";

//時間計測用
late DateTime? _startTime;
late DateTime? _endTime;
Duration speakDuration = const Duration();
//判定表示用
String message = "";
bool isDoneLastTime = false;

//お題。RtQuestionを参照
late TtQuestion question;

//音声認識開始
Future<void> speak() async {
//権限があるかなどを確認し初期化
//アプリ起動につき1回のみ行う
bool available = await speech.initialize(
onError: errorListener, onStatus: statusListener);
if (available) {
//音声認識できるなら計測時間初期化して開始
_startTime = null;
speech.listen(onResult: resultListener, localeId: question.language);
} else {
debugPrint("The user has denied the use of speech recognition.");
}
}

//音声認識終了。Androidでは一定時間話さなかったら自動的に終了するのであまり使われない
Future<void> stop() async {
speech.stop();
}

//音声認識の結果を受け取る
void resultListener(SpeechRecognitionResult result) {
_startTime ??= DateTime.now();
_endTime = DateTime.now();
//経過時間を測定
speakDuration = _endTime!.difference(_startTime!);
//認識結果を保存
lastWords = result.recognizedWords;
}

//エラー時
void errorListener(SpeechRecognitionError error) {
lastError = '${error.errorMsg} - ${error.permanent}';
}

//状態(String)を受け取る。
//notListening, listening, done の3種
void statusListener(String status) {
lastStatus = status;
}

//お題を変更。合わせて変数を初期化
void resetQuestion() {
lastWords = "";
speakDuration = const Duration();
message = "";
question = ttQuestions[Random().nextInt(ttQuestions.length)];
}

//音声認識が終了したか判断し評価を下す。終了判断はlastStatusの監視という力技
void doneCheck() {
//前回の状態を更新
bool last = isDoneLastTime;
isDoneLastTime = speech.lastStatus == "done";
//今回doneになったら終了と判断
if (last == false && isDoneLastTime) {
if (lastWords.isEmpty) return;
//判断部分
//文字表示だけでなく成功時のエフェクトなども作りたい
if (lastWords == question.pronounceQuestion) {
message = "一致";
return;
}
if (lastWords != question.pronounceQuestion) {
message = "違う";
return;
}
if (speakDuration.compareTo(question.limitTime) > 0) {
message = "遅い";
return;
}
}
}
}
58 changes: 58 additions & 0 deletions lib/model/tongue_twister_questions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';

class TtQuestion {
///お題
TtQuestion(
this.displayQuestion,
this.limitSecond, {
this.pronounce,
this.english = false,
});

///お題の文
final String displayQuestion;

///制限時間
///- 初期化専用。limitTimeを使うこと。
final double limitSecond;

///音声認識結果がお題の分と違ってしまうとき(変換、句読点など)の認識される文
///- 初期化専用。pronounceQuestionを使うこと。
final String? pronounce;

///英語で認識
final bool english;

///制限時間(参照用)
Duration get limitTime {
return Duration(seconds: limitSecond.toInt()) +
Duration(
milliseconds: ((limitSecond - limitSecond.toInt()) * 1000).toInt());
}

///音声認識結果がお題の分と違ってしまうとき(変換、句読点など)の認識される文
String get pronounceQuestion {
return pronounce ?? displayQuestion;
}

///認識する言語
String? get language {
return english ? const Locale('en').toLanguageTag() : null;
}
}

///問題文一覧
List<TtQuestion> ttQuestions = [
TtQuestion("生麦生米生卵", 2.5),
TtQuestion("隣の客はよく柿食う客だ", 2.5),
TtQuestion("スモモも桃も桃のうち", 2.5, pronounce: "すもももももももものうち"),
TtQuestion("東京特許許可局長今日急遽休暇許可拒否", 4),
TtQuestion("I scream, you scream, we all scream for ice cream!", 3.5,
pronounce: "I scream you scream we all scream for ice cream",
english: true),
TtQuestion(
"パブロ・ディエゴ・ホセ・フランシスコ・デ・パウラ・ホアン・ネポムセーノ・チプリアーノ・デ・ラ・サンティシマ・トリニダード・ルイス・ピカソ",
9,
pronounce: "パブロディエゴホセフランシスコデパウラホアンネポムセーノチプリアーニデラサンティシマトリニダードルイスピカソ",
),
];
100 changes: 100 additions & 0 deletions lib/view/tongue_twister_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:alarm2022/model/tongue_twister_model.dart';

class TongueTwisterPage extends StatefulWidget {
const TongueTwisterPage({Key? key, this.title}) : super(key: key);
final String? title;
@override
// ignore: library_private_types_in_public_api
_TongueTwisterPageState createState() => _TongueTwisterPageState();
}

class _TongueTwisterPageState extends State<TongueTwisterPage> {
//modelの方の実体
TongueTwisterModel ttm = TongueTwisterModel();

@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}

@override
void initState() {
super.initState();
ttm.resetQuestion();
//表示更新用タイマー
Timer.periodic(const Duration(milliseconds: 100), (Timer clockTimer) {
setState(() {
ttm.doneCheck();
});
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Tongue Twister"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'お題',
style: Theme.of(context).textTheme.headline5,
),
Text(
ttm.question.displayQuestion,
style: Theme.of(context).textTheme.headline5,
),
Text(
'認識',
style: Theme.of(context).textTheme.headline5,
),
Text(
ttm.lastWords,
style: Theme.of(context).textTheme.headline5,
),
Text(
'ステータス : ${ttm.speech.lastStatus}',
style: Theme.of(context).textTheme.headline5,
),
Text(
'時間 : ${ttm.speakDuration.inMilliseconds ~/ 100 / 10} s',
style: Theme.of(context).textTheme.headline6,
),
Text(
'評価:${ttm.message}',
style: Theme.of(context).textTheme.headline6,
),
],
),
),
floatingActionButton:
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
FloatingActionButton(
heroTag: "tt_back",
onPressed: () {
Navigator.of(context).pop(ttm.message == "一致");
},
child: const Icon(Icons.arrow_back)),
FloatingActionButton(
heroTag: "tt_changeQuestion",
onPressed: ttm.resetQuestion,
child: const Icon(Icons.cached)),
FloatingActionButton(
heroTag: "tt_play",
onPressed: ttm.speak,
child: const Icon(Icons.play_arrow)),
FloatingActionButton(
heroTag: "tt_stop",
onPressed: ttm.stop,
child: const Icon(Icons.stop))
]),
);
}
}
Loading

0 comments on commit 32fcc74

Please sign in to comment.