Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨Feat: 문단 교정 페이지 생성, DB연동 #40

Merged
merged 5 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading