Skip to content

Commit

Permalink
Merge pull request #40 from bunju20/develop
Browse files Browse the repository at this point in the history
✨Feat: 문단 교정 페이지 생성, DB연동
  • Loading branch information
seochan99 authored Feb 20, 2024
2 parents 5f3dfdc + 3475ba4 commit dc10a13
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 52 deletions.
52 changes: 52 additions & 0 deletions lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';



class LearningSessionScreenViewModel extends GetxController {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;

final RxBool isLoading = false.obs; // 로딩 상태 관리
final RxList<Paragraph> paragraphs = <Paragraph>[].obs; // Paragraph 객체 리스트

// Firestore에서 paragraphs 컬렉션의 데이터를 가져오는 함수
Future<void> fetchParagraphs() async {
final uid = _auth.currentUser?.uid; // 사용자 UID 가져오기
try {
isLoading(true); // 로딩 시작
final QuerySnapshot paragraphSnapshot = await _firestore
.collection('paragraph') // 사용자의 paragraph 컬렉션에 접근
.limit(5) // 예제로 5개의 문서만 가져오기
.get();

final List<Paragraph> fetchedParagraphs = paragraphSnapshot.docs
.map((doc) {
// doc.data() 호출 결과를 Map<String, dynamic>으로 타입 캐스팅
final data = doc.data() as Map<String, dynamic>?;
// Null-safety를 고려하여, 필드에 접근하기 전에 null 체크
final title = data?['title'] as String? ?? ''; // title이 없으면 빈 문자열 할당
final text = data?['text'] as String? ?? ''; // text가 없으면 빈 문자열 할당
return Paragraph(title: title, text: text);
})
.toList();


paragraphs.value = fetchedParagraphs; // 상태 업데이트
} catch (e) {
print("Error fetching paragraphs: $e"); // 오류 처리
} finally {
isLoading(false); // 로딩 종료
}
}
}

// Firestore 문서로부터 생성되는 Paragraph 모델
class Paragraph {
final String title;
final String text;

Paragraph({required this.title, required this.text});
}
84 changes: 50 additions & 34 deletions lib/views/paragraph/analyze_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<AnalyzeScreen> {
late AnalyzeViewModel viewModel;

@override
void initState() {
super.initState();
viewModel = Get.put(AnalyzeViewModel()); // 여기서 viewModel을 등록
print(viewModel.userSenten);
}

@override
Widget build(BuildContext context) {
Expand All @@ -36,13 +43,13 @@ class _AnalyzeScreenState extends State<AnalyzeScreen> {
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: [
Expand All @@ -51,15 +58,14 @@ class _AnalyzeScreenState extends State<AnalyzeScreen> {
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을 전달합니다.
),

],
),
],
Expand All @@ -68,13 +74,12 @@ class _AnalyzeScreenState extends State<AnalyzeScreen> {
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: '홈으로',
),
),
),
Expand All @@ -83,7 +88,9 @@ class _AnalyzeScreenState extends State<AnalyzeScreen> {
}

class TextStylingWidget extends StatelessWidget {
final AnalyzeViewModel model = Get.put(AnalyzeViewModel());
final AnalyzeViewModel viewModel; // viewModel을 받기 위한 생성자 파라미터를 추가합니다.

TextStylingWidget({required this.viewModel}); // 생성자를 통해 viewModel을 초기화합니다.

@override
Widget build(BuildContext context) {
Expand All @@ -98,32 +105,46 @@ class TextStylingWidget extends StatelessWidget {
);
}


List<TextSpan> _buildTextSpans() {
List<TextSpan> spans = [];
int globalWordIndex = 0; // 전체 단어에 대한 인덱스를 추적합니다.
int globalWordIndex = 0;

for (int i = 0; i < model.userSenten.length; i++) {
final List<String> words = model.userSenten[i].split(' ');
for (int i = 0; i < viewModel.userSenten.length; i++) {
final List<String> words = viewModel.userSenten[i].split(' ');
List<TextSpan> 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;
Expand Down Expand Up @@ -166,21 +187,16 @@ class _TopText extends StatelessWidget {
text: TextSpan(
style: TextStyle(fontSize: 16, color: Colors.black),
children: <TextSpan>[
TextSpan(text: '문장의 빠르기가 빠르거나 느리면 밑줄이 표시됩니다. ex)'),
TextSpan(text: '문장이 빠르면 빨강, 느리면 보라색 밑줄로 표시됩니다.'),
// 예시에 적용할 스타일
TextSpan(
text: '강아지는 ',
style: TextStyle(decoration: TextDecoration.underline),
),
TextSpan(
text: '뛴다',
style: TextStyle(decoration: TextDecoration.underline),
),
//들여쓰기
],
),
),
],
),
);
}
}

}

43 changes: 27 additions & 16 deletions lib/views/paragraph/create_script_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import 'package:earlips/viewModels/script/create_script_viewmodel.dart';
import 'package:get/get.dart';

class CreateScriptPage extends StatelessWidget {
const CreateScriptPage({super.key});
final String title;
final String text;
const CreateScriptPage({Key? key, required this.title, required this.text}) : super(key: key);


@override
Widget build(BuildContext context) {
Expand All @@ -14,7 +17,7 @@ class CreateScriptPage extends StatelessWidget {
child: Consumer<CreateScriptViewModel>(
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: const Text('문단교정'),
title: Text(title),
centerTitle: true,
actions: <Widget>[
TextButton(
Expand All @@ -37,22 +40,30 @@ class CreateScriptPage extends StatelessWidget {
children: [
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
controller: model.writedTextController,
expands: true,
maxLines: null,
decoration: InputDecoration(
hintText: '대본을 입력하세요...',
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
borderSide: BorderSide.none,
child: SingleChildScrollView(
child: Container(
child: Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
margin: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(20.0),
//가장자리 둥글게
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),

color: Colors.white,

),

child: Text(
text,
style: const TextStyle(fontSize: 16,
fontFamily: 'Pretendard',
fontWeight: FontWeight.bold
),
),
),
),
textAlignVertical: TextAlignVertical.top,
),
),
),
Expand Down
78 changes: 78 additions & 0 deletions lib/views/paragraph/learning_session_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:earlips/viewModels/Paragraph/learning_session_screen_viewmodel.dart';
import 'package:earlips/views/word/widget/blue_back_appbar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:earlips/views/paragraph/create_script_screen.dart';

class LearningSessionScreen extends StatefulWidget {
LearningSessionScreen({Key? key}) : super(key: key);

@override
State<LearningSessionScreen> createState() => _LearningSessionScreenState();
}

class _LearningSessionScreenState extends State<LearningSessionScreen> {
final viewModel = Get.put(
LearningSessionScreenViewModel()); // ViewModel 인스턴스 생성

@override
void initState() {
super.initState();
viewModel.fetchParagraphs(); // 화면이 로드될 때 Firestore에서 데이터 가져오기
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: BlueBackAppbar(title: "문단교정"),
),
body: Obx(() {
if (viewModel.isLoading.value) {
return Center(child: CircularProgressIndicator());
} else {
return ListView.separated(
padding: const EdgeInsets.fromLTRB(25, 20, 25, 20),
itemCount: viewModel.paragraphs.length,
itemBuilder: (context, index) {
var paragraph = viewModel.paragraphs[index];
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.15),
spreadRadius: 0.1,
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ListTile(
contentPadding: const EdgeInsets.all(20),
title: Text(
paragraph.title,
style: const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
onTap: () {
// title과 text만 다음 페이지로 전달
Get.to(() =>
CreateScriptPage(
title: paragraph.title, text: paragraph.text));
},
),
);
},
separatorBuilder: (context, index) => const SizedBox(height: 20),
);
}
}),
);
}
}

4 changes: 3 additions & 1 deletion lib/views/study/widget/study_main_body_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:earlips/views/word/word_screen.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:earlips/views/paragraph/create_script_screen.dart';
import 'package:earlips/views/paragraph/learning_session_screen.dart';

class StudyNainBodyWidget extends StatelessWidget {
const StudyNainBodyWidget({
Expand Down Expand Up @@ -77,7 +78,8 @@ class StudyNainBodyWidget extends StatelessWidget {
subtitle: "대본 입력 및 발음 테스트",
imagePath: "assets/images/study/4.png",
onTap: () {
Get.to(() => const CreateScriptPage());
Get.to(() => LearningSessionScreen(
));
},
imgSize: 85,
),
Expand Down
Loading

0 comments on commit dc10a13

Please sign in to comment.