diff --git a/android/app/build.gradle b/android/app/build.gradle index 1c8f9b8..bd095f4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -66,3 +66,5 @@ flutter { } dependencies {} + +apply plugin: 'com.google.gms.google-services' diff --git a/lib/viewModels/script/analyze_viewmodel.dart b/lib/viewModels/script/analyze_viewmodel.dart index f2f831b..40ab365 100644 --- a/lib/viewModels/script/analyze_viewmodel.dart +++ b/lib/viewModels/script/analyze_viewmodel.dart @@ -1,26 +1,29 @@ import 'package:flutter/foundation.dart'; - +import 'package:flutter_sound/flutter_sound.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; // JSON 데이터를 다루기 위해 필요 class AnalyzeViewModel with ChangeNotifier { + List userWord = []; + List userSenten = []; + List wrongWordIndexes = []; + List wrongFastIndexes = []; - final List userWord = [ - "가", "나", "다", "어쩌구", "저쩌구", "입니다", "나는", "매일", "조깅을", "합니다", - "플러터로", "앱", "개발을", "배우고", "있어요", "이것은", "더미", "텍스트입니다", - "데이터를", "시각화하는", "것은", "중요합니다" - ]; - final List userSenten = [ - "가 나 다 어쩌구 저쩌구 입니다.", - "나는 매일 조깅을 합니다.", - "플러터로 앱 개발을 배우고 있어요.", - "이것은 더미 텍스트입니다.", - "데이터를 시각화하는 것은 중요합니다.", - ]; - final List wrongWordIndexes = [2, 14]; // "다", "앱"을 가리킴 - final List wrongFastIndexes = [1, 3]; // 두 번째와 네 번째 문장을 가리킴 + void updateData(Map data) { + userWord = List.from(data['user_word'] as List? ?? []); + userSenten = List.from(data['user_sentence'] as List? ?? []); + wrongWordIndexes = List.from(data['wrong'] as List? ?? []); + wrongFastIndexes = (data['speed'] as List? ?? []).map((e) { - // ViewModel 초기화 - AnalyzeViewModel() { - // 필요한 초기화 로직 추가 + if (e is double) { + return e; + } else if (e is int) { + return e.toDouble(); + } else { + // 로그 출력 또는 오류 처리 + print("Warning: Invalid type in 'speed' list, defaulting to 0.0"); + return 0.0; // 기본값 + } + }).toList(); + notifyListeners(); // 데이터가 업데이트되면 리스너들에게 알립니다. } - -// 여기에 필요한 기능을 추가하세요. } diff --git a/lib/viewModels/script/create_script_viewmodel.dart b/lib/viewModels/script/create_script_viewmodel.dart index d7d17b1..cda49c2 100644 --- a/lib/viewModels/script/create_script_viewmodel.dart +++ b/lib/viewModels/script/create_script_viewmodel.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:speech_to_text/speech_to_text.dart' as stt; @@ -8,6 +10,8 @@ import 'dart:async'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:earlips/views/script/analyze_screen.dart'; +import 'package:earlips/viewModels/script/analyze_viewmodel.dart'; + class CreateScriptViewModel extends ChangeNotifier { bool isRecording = false; @@ -28,6 +32,7 @@ class CreateScriptViewModel extends ChangeNotifier { } void _init() async { + Get.put(AnalyzeViewModel()); await requestPermission(); speechToText.initialize( onError: (val) => print('Error: $val'), @@ -73,26 +78,33 @@ class CreateScriptViewModel extends ChangeNotifier { Future sendTextAndAudio() async { - String url = 'https://heheds.free.beeceptor.com'; + String url = 'https://962554f7-5348-4141-a3df-1a50c06b79b5-00-15gg2xcmiy7a0.sisko.replit.dev/upload'; String textToSend = writedTextController.text; - if (audioFilePath == null) { + if (audioFilePath.isEmpty) { print('Audio file is not available.'); return; } try { var request = http.MultipartRequest('POST', Uri.parse(url)) - ..fields['text'] = textToSend // 텍스트 데이터 추가 - ..files.add(await http.MultipartFile.fromPath( - 'audio', // 서버에서 음성 파일을 식별하는 필드명 - audioFilePath, - )); + ..fields['text'] = textToSend + ..files.add(await http.MultipartFile.fromPath('audio', audioFilePath)); var response = await request.send(); if (response.statusCode == 200) { - print('Data and audio sent successfully'); + final respStr = await response.stream.bytesToString(); + final jsonResponse = json.decode(respStr); + print('Server response: $respStr'); + + if (jsonResponse != null && jsonResponse['data'] != null) { + final analyzeViewModel = Get.find(); + analyzeViewModel.updateData(jsonResponse['data']); + Get.to(() => AnalyzeScreen()); // AnalyzeScreen으로 이동 + } else { + print('Received null or invalid data from the server.'); + } } else { - print('Failed to send data and audio'); + print('Failed to send data and audio. Status code: ${response.statusCode}'); } } catch (e) { print(e.toString()); @@ -100,6 +112,8 @@ class CreateScriptViewModel extends ChangeNotifier { } + + void _handleStatus(String status) { if (handDone) return; if (status == 'done') { @@ -149,11 +163,9 @@ class CreateScriptViewModel extends ChangeNotifier { notifyListeners(); } - void complete() { + void complete() async { print("완료버튼 눌렀습니다."); - sendTextAndAudio(); // 텍스트와 오디오 파일 전송 - //스크린을 이동 analyze_screen으로 이동하는 코드 - Get.to(() => AnalyzeScreen()); + await sendTextAndAudio(); // 비동기 호출로 수정 } diff --git a/lib/views/script/analyze_screen.dart b/lib/views/script/analyze_screen.dart index d53608a..f806d8e 100644 --- a/lib/views/script/analyze_screen.dart +++ b/lib/views/script/analyze_screen.dart @@ -8,14 +8,21 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:earlips/viewModels/script/analyze_viewmodel.dart'; import '../../utilities/app_routes.dart'; - - class AnalyzeScreen extends StatefulWidget { + AnalyzeScreen({Key? key}) : super(key: key); @override _AnalyzeScreenState createState() => _AnalyzeScreenState(); } class _AnalyzeScreenState extends State { + late AnalyzeViewModel viewModel; + + @override + void initState() { + super.initState(); + viewModel = Get.put(AnalyzeViewModel()); // 여기서 viewModel을 등록 + print(viewModel.userSenten); + } @override Widget build(BuildContext context) { @@ -36,13 +43,13 @@ class _AnalyzeScreenState extends State { width: Get.width - 40, height: Get.height * 0.2, margin: EdgeInsets.all(20.0), - padding: EdgeInsets.all(10.0), // 내부 여백을 추가합니다. + padding: EdgeInsets.all(10.0), decoration: BoxDecoration( - color: Colors.white, // 배경색을 지정합니다. - borderRadius: BorderRadius.circular(15.0), // 테두리 둥글기를 지정합니다. - border: Border.all(color: Colors.white), // 테두리 색상을 지정합니다. 필요에 따라 변경 가능합니다. + color: Colors.white, + borderRadius: BorderRadius.circular(15.0), + border: Border.all(color: Colors.white), ), - child: _TopText(), + child: _TopText(), // 이 부분은 상태를 표시하지 않으므로 그대로 유지합니다. ), Stack( children: [ @@ -51,15 +58,14 @@ class _AnalyzeScreenState extends State { width: Get.width - 40, height: Get.height * 0.5, margin: EdgeInsets.all(20.0), - padding: EdgeInsets.all(10.0), // 내부 여백을 추가합니다. + padding: EdgeInsets.all(10.0), decoration: BoxDecoration( - color: Colors.white, // 배경색을 지정합니다. - borderRadius: BorderRadius.circular(15.0), // 테두리 둥글기를 지정합니다. - border: Border.all(color: Colors.white), // 테두리 색상을 지정합니다. 필요에 따라 변경 가능합니다. + color: Colors.white, + borderRadius: BorderRadius.circular(15.0), + border: Border.all(color: Colors.white), ), - child: TextStylingWidget(), + child: TextStylingWidget(viewModel: viewModel), // viewModel을 전달합니다. ), - ], ), ], @@ -68,13 +74,12 @@ class _AnalyzeScreenState extends State { floatingActionButton: Container( width: Get.width, alignment: Alignment.bottomCenter, - child: FloatingActionButton( onPressed: () { Get.toNamed(Routes.HOME); }, - child: Icon(Icons.home), // 홈 아이콘 사용 - tooltip: '홈으로', // 롱 프레스 시 표시되는 텍스트 + child: Icon(Icons.home), + tooltip: '홈으로', ), ), ), @@ -83,7 +88,9 @@ class _AnalyzeScreenState extends State { } class TextStylingWidget extends StatelessWidget { - final AnalyzeViewModel model = Get.put(AnalyzeViewModel()); + final AnalyzeViewModel viewModel; // viewModel을 받기 위한 생성자 파라미터를 추가합니다. + + TextStylingWidget({required this.viewModel}); // 생성자를 통해 viewModel을 초기화합니다. @override Widget build(BuildContext context) { @@ -98,32 +105,46 @@ class TextStylingWidget extends StatelessWidget { ); } + List _buildTextSpans() { List spans = []; - int globalWordIndex = 0; // 전체 단어에 대한 인덱스를 추적합니다. + int globalWordIndex = 0; - for (int i = 0; i < model.userSenten.length; i++) { - final List words = model.userSenten[i].split(' '); + for (int i = 0; i < viewModel.userSenten.length; i++) { + final List words = viewModel.userSenten[i].split(' '); List wordSpans = []; for (String word in words) { - final bool isWrongWord = model.wrongWordIndexes.contains(globalWordIndex); + final bool isWrongWord = viewModel.wrongWordIndexes.contains(globalWordIndex); wordSpans.add(TextSpan( text: "$word ", style: TextStyle( color: isWrongWord ? Colors.red : Colors.black, ), )); - globalWordIndex++; // 각 단어를 처리할 때마다 전체 단어 인덱스를 증가시킵니다. + globalWordIndex++; + } + + // `wrongFastIndexes`의 값에 따라 밑줄 색상을 결정합니다. + Color underlineColor = Colors.white; // 기본값은 투명색입니다. + if (viewModel.wrongFastIndexes[i] < 1) { + underlineColor = Colors.purple; // 1미만인 경우 보라색 밑줄 + } else if (viewModel.wrongFastIndexes[i] > 1) { + underlineColor = Colors.red; // 1초과인 경우 빨간색 밑줄 } spans.add(TextSpan( children: wordSpans, style: TextStyle( - decoration: model.wrongFastIndexes.contains(i) ? TextDecoration.underline : TextDecoration.none, + decoration: viewModel.wrongFastIndexes[i] != 0 ? TextDecoration.underline : TextDecoration.none, + decorationColor: underlineColor, // 밑줄 색상을 지정합니다. + decorationStyle: TextDecorationStyle.solid, + decorationThickness: 3.0, + //밑줄을 밑으로 내리기 위한 값 + ), )); - spans.add(TextSpan(text: "\n")); // 문장 사이에 줄바꿈 추가 + spans.add(TextSpan(text: "\n")); } return spans; @@ -166,16 +187,9 @@ class _TopText extends StatelessWidget { text: TextSpan( style: TextStyle(fontSize: 16, color: Colors.black), children: [ - TextSpan(text: '문장의 빠르기가 빠르거나 느리면 밑줄이 표시됩니다. ex)'), + TextSpan(text: '문장이 빠르면 빨강, 느리면 보라색 밑줄로 표시됩니다.'), // 예시에 적용할 스타일 - TextSpan( - text: '강아지는 ', - style: TextStyle(decoration: TextDecoration.underline), - ), - TextSpan( - text: '뛴다', - style: TextStyle(decoration: TextDecoration.underline), - ), + //들여쓰기 ], ), ), @@ -183,4 +197,6 @@ class _TopText extends StatelessWidget { ), ); } -} \ No newline at end of file + +} +