Skip to content

Commit

Permalink
feat(flashcard): updated widget usability across different pages
Browse files Browse the repository at this point in the history
feat(dictionary): updated dictionary system to use `firebase_ui_firestore`
  • Loading branch information
SethCohen committed Mar 30, 2023
1 parent 8a6737b commit 323fb0d
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 157 deletions.
44 changes: 0 additions & 44 deletions src/lib/common/utils/data_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'package:firebase_auth/firebase_auth.dart';
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/flashcard/flashcard_model.dart';
import '../../features/review/review_model.dart';

Expand All @@ -12,12 +11,8 @@ const pageSize = 5;

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

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

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

Future<void> loadReviews() async {
try {
Expand Down Expand Up @@ -121,43 +116,4 @@ class DataProvider extends ChangeNotifier {
_reviews.remove(deckTitle);
notifyListeners();
}

Future<void> loadDictionary() async {
final dictionary = await FirebaseFirestore.instance
.collectionGroup('cards')
.where('type', isEqualTo: 'immutable')
.limit(pageSize)
.get();

_dictionary = dictionary.docs
.map((doc) => DictionaryModel.fromMap(doc.id, doc.data()))
.toList();

_lastCardDoc = dictionary.docs.last;

notifyListeners();
}

Future<void> loadMoreDictionary() async {
final cards = await FirebaseFirestore.instance
.collectionGroup('cards')
.where('type', isEqualTo: 'immutable')
.startAfterDocument(_lastCardDoc)
.limit(pageSize)
.get();

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

_dictionary = _dictionary
..addAll(cards.docs
.map((doc) => DictionaryModel.fromMap(doc.id, doc.data()))
.toList());

_lastCardDoc = cards.docs.last;

notifyListeners();
}
}
24 changes: 0 additions & 24 deletions src/lib/features/dictionary/dictionary_model.dart

This file was deleted.

83 changes: 42 additions & 41 deletions src/lib/features/dictionary/dictionary_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:asl/features/flashcard/flashcard_model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../common/utils/data_provider.dart';
import '../flashcard/flashcard.dart';

class DictionaryPage extends StatefulWidget {
const DictionaryPage({super.key});
Expand All @@ -10,50 +12,49 @@ class DictionaryPage extends StatefulWidget {
}

class _DictionaryPageState extends State<DictionaryPage> {
final ScrollController _scrollController = ScrollController();

@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}

@override
Widget build(BuildContext context) {
final cards = context.watch<DataProvider>().dictionary;
final flashcardsQuery = FirebaseFirestore.instance
.collectionGroup('cards')
.where('type', isEqualTo: 'immutable')
.orderBy('title')
.withConverter<FlashcardModel>(
fromFirestore: (snapshot, _) =>
FlashcardModel.fromMap(snapshot.data()!),
toFirestore: (flashcard, _) => flashcard.toMap(),
);

// TODO add search bar
// TODO restyle with better loading indicator and better card design
return Scrollbar(
thumbVisibility: true,
controller: _scrollController,
child: ListView.builder(
controller: _scrollController,
itemCount: cards.length + 1,
itemBuilder: (context, index) {
if (index == cards.length) {
return SizedBox(
height: MediaQuery.of(context).size.height,
child: const Center(child: CircularProgressIndicator()),
);
}

return Card(
child: ListTile(
title: Text(cards[index].title),
subtitle: Text(cards[index].instructions),
leading: Image.network(cards[index].image),
),
);
}),
return FirestoreQueryBuilder<FlashcardModel>(
query: flashcardsQuery,
builder: (context, snapshot, _) {
if (snapshot.isFetching) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
debugPrint('error ${snapshot.error}');
return Text('error ${snapshot.error}');
}

return Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(
snapshot.docs.length,
(index) {
if (snapshot.hasMore && index + 1 == snapshot.docs.length) {
snapshot.fetchMore();
}

return Flashcard(
card: snapshot.docs[index].data(),
type: CardType.dictionary,
);
},
),
);
},
);
}

void _scrollListener() {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
context.read<DataProvider>().loadMoreDictionary();
}
}
}
52 changes: 29 additions & 23 deletions src/lib/features/flashcard/flashcard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import 'flashcard_model.dart';
import '../../common/utils/data_provider.dart';
import '../../common/widgets/custom_iconbutton.dart';

enum CardType { review, dictionary, lesson }

class Flashcard extends StatefulWidget {
const Flashcard({
super.key,
required this.handleIndex,
this.handleIndex,
required this.card,
required this.isReview,
required this.type,
});

final void Function() handleIndex;
final void Function()? handleIndex;
final FlashcardModel card;
final bool isReview;
final CardType type;

@override
State<Flashcard> createState() => _FlashcardState();
Expand Down Expand Up @@ -43,13 +45,14 @@ class _FlashcardState extends State<Flashcard> {
Stack(
children: [
_buildImage(),
if (!_isImageBlurred && !isEmptyInstructions)
if ((!_isImageBlurred || widget.type == CardType.dictionary) &&
!isEmptyInstructions)
_buildInstructionsPopup(context),
],
),
// TODO media controls implementation
_buildMediaControls(),
_buildFlashcardButtons(),
if (widget.type != CardType.dictionary) _buildFlashcardButtons(),
],
),
),
Expand All @@ -67,13 +70,6 @@ class _FlashcardState extends State<Flashcard> {
),
);

Widget _buildDifficultyButton(String text, int quality, Color color) =>
TextButton(
style: TextButton.styleFrom(foregroundColor: color),
onPressed: () => _handleButtonPress(widget.card, quality),
child: Text(text),
);

Widget _buildMediaControls() => Row(
mainAxisSize: MainAxisSize.min,
children: [
Expand Down Expand Up @@ -106,17 +102,15 @@ class _FlashcardState extends State<Flashcard> {
);

Widget _buildImage() => ClipRRect(
child: ImageFiltered(
enabled: _isImageBlurred,
imageFilter: ImageFilter.blur(sigmaX: 48, sigmaY: 48),
child: Image.network(widget.card.image)),
child: widget.type == CardType.dictionary
? Image.network(widget.card.image)
: ImageFiltered(
enabled: _isImageBlurred,
imageFilter: ImageFilter.blur(sigmaX: 48, sigmaY: 48),
child: Image.network(widget.card.image),
),
);

void _handleButtonPress(FlashcardModel flashcard, int quality) {
context.read<DataProvider>().updateCardProgress(flashcard, quality);
widget.handleIndex();
}

Widget _buildFlashcardButtons() {
if (_isImageBlurred) {
return TextButton(
Expand All @@ -125,7 +119,7 @@ class _FlashcardState extends State<Flashcard> {
);
}

if (widget.isReview) {
if (widget.type == CardType.review) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Expand All @@ -142,6 +136,18 @@ class _FlashcardState extends State<Flashcard> {
);
}

Widget _buildDifficultyButton(String text, int quality, Color color) =>
TextButton(
style: TextButton.styleFrom(foregroundColor: color),
onPressed: () => _handleButtonPress(widget.card, quality),
child: Text(text),
);

void _handleButtonPress(FlashcardModel flashcard, int quality) {
context.read<DataProvider>().updateCardProgress(flashcard, quality);
widget.handleIndex!();
}

Widget _buildInstructions(height, width) => Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
Expand Down
39 changes: 24 additions & 15 deletions src/lib/features/flashcard/flashcard_model.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
class FlashcardModel {
String deckTitle;
String cardId;
String deckId;
String? deckTitle;
String? cardId;
String? deckId;
String title;
String instructions;
String image;

FlashcardModel(
{required this.deckTitle,
required this.cardId,
required this.deckId,
required this.title,
required this.instructions,
required this.image});
FlashcardModel({
this.deckTitle,
this.cardId,
this.deckId,
required this.title,
required this.instructions,
required this.image,
});

factory FlashcardModel.fromMap(
Map<String, dynamic> data,
String cardId,
String deckId,
String deckTitle,
) {
Map<String, dynamic> data, [
String? cardId,
String? deckId,
String? deckTitle,
]) {
return FlashcardModel(
deckTitle: deckTitle,
cardId: cardId,
Expand All @@ -29,4 +30,12 @@ class FlashcardModel {
image: data['image'],
);
}

Map<String, dynamic> toMap() {
return {
'title': title,
'instructions': instructions,
'image': image,
};
}
}
6 changes: 0 additions & 6 deletions src/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../common/utils/data_provider.dart';
import '../../common/widgets/custom_tab.dart';
import '../creator/creator_page.dart';
import '../dictionary/dictionary_page.dart';
Expand All @@ -19,14 +17,10 @@ class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();

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

@override
Widget build(BuildContext context) {
// TODO restyle Tabs to have proper on hover and on select icon colours

return SafeArea(
child: DefaultTabController(
length: 5,
Expand Down
10 changes: 7 additions & 3 deletions src/lib/features/lesson/lesson_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ class _LessonDetailsState extends State<LessonDetails> {

List<Widget> _getFlashcards(List<QueryDocumentSnapshot> cards) => cards
.map((card) => Flashcard(
card: FlashcardModel.fromMap(card.data() as Map<String, dynamic>,
card.id, widget.lessonId, widget.lessonData.title),
card: FlashcardModel.fromMap(
card.data() as Map<String, dynamic>,
card.id,
widget.lessonId,
widget.lessonData.title,
),
handleIndex: _handleIndex,
isReview: false,
type: CardType.lesson,
))
.toList();

Expand Down
Loading

0 comments on commit 323fb0d

Please sign in to comment.