FinishedBookListScreenFlutter
에서는 사용자가 완독한 책 목록을 확인하고 감상평을 작성할 수 있는 화면을 제공할 수 있습니다. 이를 위해 Firebase
와 Cloud Firestore
를 사용하여 데이터를 관리합니다.
먼저, 앱 개발에 필요한 라이브러리와 위젯을 import합니다. Firebase와 Cloud Firestore를 사용하기 위해 cloud_firestore
패키지를, Firebase Authentication을 사용하기 위해 firebase_auth
패키지를 import합니다. 또한, 화면 구성을 위해 필요한 Flutter 위젯들을 import합니다.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:military_bookstore/database/get_database_Info.dart';
import 'package:military_bookstore/screen/review_screen.dart';
import 'package:military_bookstore/screen/search_bookTitle_screen.dart';
import 'package:military_bookstore/widget/appbar_widget.dart';
import 'package:military_bookstore/widget/drewer_widget.dart';
import 'package:military_bookstore/widget/nav_widget.dart';
FinishedBookListScreen 클래스는 StatelessWidget을 상속받아야 하며, 사용자의 완독한 책 목록을 보여주는 화면을 구현합니다. 코드에서는 build 메서드를 사용하여 화면을 구성합니다.
class FinishedBookListScreen extends StatelessWidget {
const FinishedBookListScreen({super.key});
Widget build(BuildContext context) {
// 화면 크기에 따른 비율 계산
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHeight = MediaQuery.of(context).size.height;
double widthRatio = deviceWidth / 375;
double heightRatio = deviceHeight / 812;
// Scaffold를 통해 화면 구성
return Scaffold(
appBar: appbar_widget(context), // 상단 앱 바
drawer: drewer_widget(context), // 좌측 드로어 메뉴
body: Center(
child: Container(
clipBehavior: Clip.antiAlias,
// 배경색상 및 그래디언트 설정
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment(-0.00, -1.00),
end: Alignment(0, 1),
colors: [Color(0xA545B0C5), Color(0xFF4580C5), Color(0xFF4580C5)],
),
),
child: Column(
children: [
nav_widget(context), // 네비게이션 바
// 완독한 책 목록 표시 컨테이너
Container(
// 완독한 책 목록 컨테이너
width: widthRatio * 350,
height: heightRatio * 515,
child: FutureBuilder(
future: getFinishedBookInfo(), // 완독한 책 정보 가져오기
builder: (BuildContext context,
AsyncSnapshot<Map<dynamic, dynamic>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// 데이터 로딩 중일 때 로딩 스피너 표시
return CircularProgressIndicator();
} else if (snapshot.hasError) {
// 에러 발생 시 에러 메시지 표시
return Text('Error: ${snapshot.error}');
} else {
// 완독한 책 목록을 보여주는 리스트뷰 생성
return SingleChildScrollView(
child: Column(
children: List.generate(
snapshot.data!['length'],
(index) => lstItem(
context,
snapshot.data!["Ids"],
index,
widthRatio,
heightRatio)),
),
);
}
},
),
)
],
),
),
),
);
}
// 각 완독한 책 항목을 표시하는 위젯
Widget lstItem(BuildContext context, Map<dynamic, dynamic> bookMap,
int index, widthRatio, heightRatio) {
List<dynamic> bookList = bookMap.keys.toList();
return GestureDetector(
onTap: () {
// 각 책을 클릭하면 해당 책의 감상평을 보여주는 화면으로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ReviewScreen(
review: bookMap[bookList[index]],
bookTitle: bookList[index],
),
),
);
},
child: Container(
margin: EdgeInsets.only(bottom: heightRatio * 20),
padding: EdgeInsets.symmetric(
vertical: heightRatio * 10, horizontal: widthRatio * 10),
decoration: ShapeDecoration(
color: Colors.white.withOpacity(0),
shape: RoundedRectangleBorder(
side: BorderSide(width: 2, color: Color(0xBFFFFFFF)),
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
margin: EdgeInsets.only(left: widthRatio * 5),
child: Row(
children: [
// 책 이미지 표시
FutureBuilder(
future: getBookImage(bookList[index]),
builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// 이미지 로딩 중일 때 로딩 스피너 표시
return CircularProgressIndicator();
} else if (snapshot.hasError) {
// 에러 발생 시 에러 메시지 표시
return Text('Error: ${snapshot.error}');
} else {
// 이미지 로딩이 완료되면 이미지 표시
return Container(
child: Image(
image: snapshot.data!.image,
width: widthRatio * 105,
height: widthRatio * 105 * 1.48,
fit: BoxFit.fill,
),
);
}
},
),
// 책 제목 및 감상평 표시
Container(
margin: EdgeInsets.only(left: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 책 제목
Container(
constraints: BoxConstraints(
maxWidth: 200.0,
),
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
bookList[index],
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xE5001F3F),
fontSize: 18,
fontFamily: 'GowunBatang',
fontWeight: FontWeight.bold,
height: 0,
letterSpacing: -0.40,
),
),
),
),
// 감상평 요약
Container(
width: widthRatio * 190,
height: heightRatio * 101,
child: Text(
bookMap[bookList[index]]!,
maxLines: 4,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color(0xE5001F3F),
fontFamily: 'GowunBatang',
fontWeight: FontWeight.bold,
height: 0,
letterSpacing: -0.40,
),
),
),
],
),
),
],
),
),
),
);
}
위에서 사용된 getFinishedBookInfo(), getBookImage(), getTopRankers(), getUserRank() 등의 함수들은 Firebase와 상호작용하여 데이터를 가져오거나 업데이트하는 기능을 담당합니다. 이들 함수는 각각 사용자의 완독한 책 정보를 가져오는 함수, 책의 이미지를 가져오는 함수, 상위 랭커 정보를 가져와 랭킹을 업데이트하는 함수, 사용자의 랭킹을 가져오는 함수입니다.
Future<Map<String, dynamic>> getFinishedBookInfo() async {
Map<String, dynamic> collectionInfo = {};
final User? user = FirebaseAuth.instance.currentUser;
FirebaseFirestore _firestore = FirebaseFirestore.instance;
///쿼리 실행결과는 QuerySnapshot, 실행 결과의 각 문서 객체는 QueryDocumentSnapshot
QuerySnapshot<Map<String, dynamic>> documentSnapshot = await _firestore
.collection('users')
.doc(user!.email)
.collection('finishedBookList')
.get();
collectionInfo['length'] = documentSnapshot.docs.length;
Map<String, dynamic> documentIds = Map.fromIterable(
documentSnapshot.docs,
key: (doc) => doc.id,
value: (doc) => doc["review"],
);
collectionInfo['Ids'] = documentIds;
return collectionInfo;
}
Future<int> getFinishedBookCount() async {
Map<String, dynamic> finishedBookInfo = await getFinishedBookInfo();
Map<String, dynamic> finishedBookList = finishedBookInfo['Ids'];
List<dynamic> reviews = finishedBookList.values.toList();
int count = 0;
for (var review in reviews) {
if (review.length > 199) {
count++;
}
}
return count;
}
// Future<List<Map<String, dynamic>>>
Future<void> getTopRankers() async {
/// Firestore에서 'users' 컬렉션의 문서들을 'bookCount' 필드를
/// 기준으로 내림차순으로 정렬하여 상위 10개의 문서 가져오기
QuerySnapshot<Map<String, dynamic>> querysnapshot = await FirebaseFirestore
.instance
.collection('users')
.orderBy('bookCount', descending: true)
.get();
final CollectionReference usersCollectionRef =
await FirebaseFirestore.instance
.collection('users');
// 상위 10개의 문서를 Map의 List로 변환
List<Map<String, dynamic>> topRankers = querysnapshot.docs.map((doc) =>
doc.data()).toList();
// 상위 10개의 문서의 순위 부여
for (int i = 0; i < topRankers.length; i++) {
await usersCollectionRef.doc(topRankers[i]["email"]).update({
"rank": i + 1
});
}
}
Future<int> getUserRank(context) async {
int userRank = -1;
final User? user =
FirebaseAuth.instance.currentUser;
if (user == null) {
Navigator.pushNamed(context, '/login');
return userRank;
}
else {
final DocumentSnapshot<Map<String, dynamic>> documentRef =
await FirebaseFirestore.instance
.collection('users')
.doc(user.email)
.get();
int userRank = documentRef.data()!['rank'];
return userRank;
}
}
위와 같이 FinishedBookListScreen
클래스를 구현하면 사용자는 완독한 책 목록을 확인하고, 각 책에 대한 감상평을 작성하거나 확인할 수 있습니다.
이와 같이 Flutter
와 Firebase
를 이용하여 완독한 책 목록을 효과적으로 관리하고 화면에 표시할 수 있습니다.