Skip to content

Commit

Permalink
feat: updated lesson pagination to use new firebase_ui_firestore de…
Browse files Browse the repository at this point in the history
…pendency
  • Loading branch information
SethCohen committed Mar 28, 2023
1 parent 28a7fd1 commit 8a6737b
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 176 deletions.
72 changes: 0 additions & 72 deletions src/lib/common/utils/data_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,88 +4,21 @@ import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:spaced_repetition/sm.dart';
import '../../features/dictionary/dictionary_model.dart';
import '../../features/lesson/lesson_model.dart';
import '../../features/flashcard/flashcard_model.dart';
import '../../features/review/review_model.dart';

final currentUser = FirebaseAuth.instance.currentUser!;
const pageSize = 5;

class DataProvider extends ChangeNotifier {
List<LessonModel> _lessons = [];
Map<String, List<ReviewModel>> _reviews = {};
List<DictionaryModel> _dictionary = [];

late DocumentSnapshot<Map<String, dynamic>> _lastDeckDoc;
late DocumentSnapshot<Map<String, dynamic>> _lastCardDoc;

List<LessonModel> get lessons => _lessons;
Map<String, List<ReviewModel>> get reviews => _reviews;
List<DictionaryModel> get dictionary => _dictionary;

Future<void> loadLessons() async {
final lessons = await FirebaseFirestore.instance
.collection('decks')
.orderBy('title')
.limit(pageSize)
.get();

_lessons = await Future.wait(lessons.docs.map((doc) async {
// Gets the progress of the current user for each deck
final userProgress = await FirebaseFirestore.instance
.collection('users')
.doc(currentUser.uid)
.collection('deckProgress')
.doc(doc.id)
.get();

// If the user has not started the lesson, the cardCount will be 0
return userProgress.exists
? LessonModel.fromMap(doc.id, userProgress['cardCount'], doc.data())
: LessonModel.fromMap(doc.id, 0, doc.data());
}).toList());

_lastDeckDoc = lessons.docs.last;

notifyListeners();
}

Future<List<LessonModel>> loadMoreLessons() async {
final decks = await FirebaseFirestore.instance
.collection('decks')
.orderBy('name')
.startAfterDocument(_lastDeckDoc)
.limit(pageSize)
.get();

// If there are no more decks to fetch, return the current list of lessons
if (decks.docs.isEmpty) {
return _lessons;
}

// Gets the progress of the current user for each deck
final currentUser = FirebaseAuth.instance.currentUser!;
final deckProgressDocs = await FirebaseFirestore.instance
.collection('users')
.doc(currentUser.uid)
.collection('deckProgress')
.where(FieldPath.documentId,
whereIn: decks.docs.map((doc) => doc.id).toList())
.get();
_lessons = [];
for (final deck in decks.docs) {
final deckProgress = deckProgressDocs.docs
.firstWhere((doc) => doc.id == deck.id)['cardCount'];

// Adds the deck to the list of lessons
_lessons.add(LessonModel.fromMap(deck.id, deckProgress, deck.data()));
}

notifyListeners();

return _lessons;
}

Future<void> loadReviews() async {
try {
final reviews = await FirebaseFirestore.instance
Expand Down Expand Up @@ -177,11 +110,6 @@ class DataProvider extends ChangeNotifier {
.doc(flashcard.deckId);
batch.set(deckRec, {'cardCount': FieldValue.increment(1)},
SetOptions(merge: true));
_lessons = _lessons
.map((lesson) => lesson.id == flashcard.deckId
? lesson.copyWith(cardsCompleted: lesson.cardsCompleted + 1)
: lesson)
.toList();

await batch.commit();

Expand Down
4 changes: 3 additions & 1 deletion src/lib/common/utils/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ Route<dynamic> generateRoute(RouteSettings settings) {
} else if (settings.name!.contains("lesson")) {
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
builder: (_) => Lesson(lesson: args['lesson']), settings: settings);
builder: (_) => LessonDetails(
lessonId: args['lessonId'], lessonData: args['lessonData']),
settings: settings);
} else if (settings.name!.contains("review")) {
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
Expand Down
1 change: 0 additions & 1 deletion src/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class _HomePageState extends State<HomePage> {
void initState() {
super.initState();

context.read<DataProvider>().loadLessons();
context.read<DataProvider>().loadDictionary();
}

Expand Down
29 changes: 16 additions & 13 deletions src/lib/features/lesson/lesson_details_page.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'lesson_model.dart';
import '../flashcard/flashcard_model.dart';
import '../flashcard/flashcard.dart';
import 'lessons_list_page.dart';

class Lesson extends StatefulWidget {
const Lesson({super.key, required this.lesson});
final LessonModel lesson;
class LessonDetails extends StatefulWidget {
const LessonDetails(
{super.key, required this.lessonId, required this.lessonData});
final String lessonId;
final Lesson lessonData;

@override
State<Lesson> createState() => _LessonState();
State<LessonDetails> createState() => _LessonDetailsState();
}

class _LessonState extends State<Lesson> {
class _LessonDetailsState extends State<LessonDetails> {
int _currentCardIndex = 0;

@override
Expand All @@ -25,7 +27,7 @@ class _LessonState extends State<Lesson> {
},
child: Scaffold(
appBar: AppBar(
title: Text(widget.lesson.title),
title: Text(widget.lessonData.title),
),
body: FutureBuilder<QuerySnapshot>(
future: _getCardsFuture(),
Expand All @@ -45,12 +47,12 @@ class _LessonState extends State<Lesson> {

Future<QuerySnapshot> _getCardsFuture() => FirebaseFirestore.instance
.collection("decks")
.doc(widget.lesson.id)
.doc(widget.lessonId)
.collection('cards')
.get();

void _handleIndex() => setState(() {
bool isCompleted = _currentCardIndex == widget.lesson.cardsTotal - 1;
bool isCompleted = _currentCardIndex == widget.lessonData.cardCount - 1;
if (isCompleted) {
Navigator.pop(context);
} else {
Expand All @@ -61,7 +63,7 @@ class _LessonState extends State<Lesson> {
List<Widget> _getFlashcards(List<QueryDocumentSnapshot> cards) => cards
.map((card) => Flashcard(
card: FlashcardModel.fromMap(card.data() as Map<String, dynamic>,
card.id, widget.lesson.id, widget.lesson.title),
card.id, widget.lessonId, widget.lessonData.title),
handleIndex: _handleIndex,
isReview: false,
))
Expand All @@ -80,14 +82,15 @@ class _LessonState extends State<Lesson> {
return Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.centerRight,
child: Text('${_currentCardIndex + 1} / ${widget.lesson.cardsTotal}'));
child:
Text('${_currentCardIndex + 1} / ${widget.lessonData.cardCount}'));
}

Widget _buildProgressBarIndicator() {
return TweenAnimationBuilder(
tween: Tween<double>(
begin: _currentCardIndex / widget.lesson.cardsTotal,
end: (_currentCardIndex + 1) / widget.lesson.cardsTotal),
begin: _currentCardIndex / widget.lessonData.cardCount,
end: (_currentCardIndex + 1) / widget.lessonData.cardCount),
duration: const Duration(milliseconds: 1000),
builder: (context, double value, child) =>
LinearProgressIndicator(value: value));
Expand Down
49 changes: 0 additions & 49 deletions src/lib/features/lesson/lesson_model.dart

This file was deleted.

125 changes: 85 additions & 40 deletions src/lib/features/lesson/lessons_list_page.dart
Original file line number Diff line number Diff line change
@@ -1,42 +1,77 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'lesson_model.dart';
import '../../common/utils/data_provider.dart';

class LessonsPage extends StatefulWidget {
const LessonsPage({super.key});
class Lesson {
Lesson(
{required this.cardCount,
required this.title,
required this.description});

@override
State<LessonsPage> createState() => _LessonsPageState();
}
Lesson.fromMap(Map<String, Object?> data)
: this(
cardCount: data['cardCount'] as int,
title: data['title'] as String,
description: data['description'] as String,
);

class _LessonsPageState extends State<LessonsPage> {
final ScrollController _scrollController = ScrollController();
final int cardCount;
final String title;
final String description;

@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
Map<String, Object?> toMap() {
return {
'cardCount': cardCount,
'title': title,
'description': description,
};
}
}

class LessonsPage extends StatelessWidget {
const LessonsPage({super.key});

@override
Widget build(BuildContext context) {
final decks = context.watch<DataProvider>().lessons;
final decksQuery = FirebaseFirestore.instance
.collection('decks')
.orderBy('title')
.withConverter<Lesson>(
fromFirestore: (snapshot, _) => Lesson.fromMap(snapshot.data()!),
toFirestore: (deck, _) => deck.toMap(),
);
return FirestoreListView<Lesson>(
query: decksQuery,
itemBuilder: (context, deckSnapshot) {
Lesson deckData = deckSnapshot.data();
String deckId = deckSnapshot.id;

return FutureBuilder<DocumentSnapshot>(
future: _getUserDeckProgress(deckId),
builder: (context, snapshot) {
int userDeckCardCount = 0;

if (snapshot.hasData && snapshot.data!.exists) {
userDeckCardCount = snapshot.data!['cardCount'];
}

// TODO fix loading more lessons
// TODO add loading indicator
return ListView.builder(
controller: _scrollController,
itemCount: decks.length,
itemBuilder: (context, index) => _buildListItem(decks[index]));
return _buildListItem(context, deckId, deckData, userDeckCardCount);
},
);
},
);
}

void _scrollListener() {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
context.read<DataProvider>().loadMoreLessons();
}
Future<DocumentSnapshot> _getUserDeckProgress(deckId) {
final currentuser = FirebaseAuth.instance.currentUser!;

return FirebaseFirestore.instance
.collection('users')
.doc(currentuser.uid)
.collection('deckProgress')
.doc(deckId)
.get();
}

Color _lessonColour(cardsCompleted, cardsTotal) =>
Expand All @@ -46,21 +81,31 @@ class _LessonsPageState extends State<LessonsPage> {
? Colors.orange
: Colors.black38;

Widget _buildListItem(LessonModel deck) => Card(
Widget _buildListItem(BuildContext context, String deckId, Lesson deck,
int cardsCompleted) =>
Card(
child: ListTile(
title: Text(deck.title),
trailing: _buildTrailing(deck),
onTap: () => deck.navigateToLesson(context)),
subtitle: Text(deck.description),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
Text(
'${deck.cardCount - cardsCompleted}',
style: TextStyle(
color: _lessonColour(cardsCompleted, deck.cardCount)),
),
Icon(Icons.check_circle,
color: _lessonColour(cardsCompleted, deck.cardCount)),
]),
onTap: () => _navigateToLesson(context, deckId, deck)),
);

Widget _buildTrailing(LessonModel deck) =>
Row(mainAxisSize: MainAxisSize.min, children: [
Text(
'${deck.cardsTotal - deck.cardsCompleted}',
style: TextStyle(
color: _lessonColour(deck.cardsCompleted, deck.cardsTotal)),
),
Icon(Icons.check_circle,
color: _lessonColour(deck.cardsCompleted, deck.cardsTotal)),
]);
void _navigateToLesson(BuildContext context, String deckId, Lesson deck) =>
Navigator.pushNamed(
context,
'/${deck.title.toLowerCase().replaceAll(' ', '_')}',
arguments: {
'lessonId': deckId,
'lessonData': deck,
},
);
}

0 comments on commit 8a6737b

Please sign in to comment.