Skip to content

Commit

Permalink
feat: re implemented favorites feature (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
JideGuru committed May 28, 2023
1 parent c54d8a0 commit 2950da1
Show file tree
Hide file tree
Showing 16 changed files with 696 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ mixin _$BookDetailsState {
required TResult Function() loadFailure,
}) =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started,
Expand All @@ -42,7 +41,6 @@ mixin _$BookDetailsState {
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(BookDetailsStateStarted value) started,
Expand All @@ -52,7 +50,6 @@ mixin _$BookDetailsState {
required TResult Function(BookDetailsStateLoadFailure value) loadFailure,
}) =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(BookDetailsStateStarted value)? started,
Expand Down Expand Up @@ -84,9 +81,9 @@ class _$BookDetailsStateCopyWithImpl<$Res, $Val extends BookDetailsState>
implements $BookDetailsStateCopyWith<$Res> {
_$BookDetailsStateCopyWithImpl(this._value, this._then);

// ignore: unused_field
// ignore: unused_field
final $Val _value;
// ignore: unused_field
// ignore: unused_field
final $Res Function($Val) _then;
}

Expand Down
112 changes: 67 additions & 45 deletions lib/src/features/book_details/screens/book_details_screen.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ebook_app/src/features/common/data/notifiers/favorites/favorites_state_notifier.dart';
import 'package:flutter_ebook_app/src/features/common/widgets/error_widget.dart';
import 'package:flutter_ebook_app/src/features/common/widgets/modal_dialogs/download_alert.dart';
import 'package:flutter_ebook_app/src/features/common/widgets/loading_widget.dart';
Expand All @@ -14,7 +15,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iridium_reader_widget/views/viewers/epub_screen.dart';
import 'package:share/share.dart';

class BookDetailsScreen extends StatelessWidget {
class BookDetailsScreen extends ConsumerStatefulWidget {
final Entry entry;
final String imgTag;
final String titleTag;
Expand All @@ -28,20 +29,53 @@ class BookDetailsScreen extends StatelessWidget {
required this.authorTag,
});

@override
ConsumerState<BookDetailsScreen> createState() => _BookDetailsScreenState();
}

class _BookDetailsScreenState extends ConsumerState<BookDetailsScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(favoritesStateNotifierProvider.notifier).listen();
ref.read(downloadsStateNotifierProvider.notifier).listen();
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () async {
/// TODO(jideguru): reimplement favorites
},
icon: Icon(
Feather.heart,
color: Theme.of(context).iconTheme.color,
),
),
ref.watch(favoritesStateNotifierProvider).maybeWhen(
orElse: () => const SizedBox.shrink(),
listening: (favorites) {
final favorited = favorites.indexWhere(
(element) => element.id!.t == widget.entry.id!.t,
) !=
-1;
return IconButton(
onPressed: () async {
if (favorited) {
ref
.watch(favoritesStateNotifierProvider.notifier)
.deleteBook(widget.entry.id!.t);
} else {
ref
.watch(favoritesStateNotifierProvider.notifier)
.addBook(widget.entry, widget.entry.id!.t);
}
},
icon: Icon(
favorited ? Icons.favorite : Feather.heart,
color: favorited
? Colors.red
: Theme.of(context).iconTheme.color,
),
);
},
),
IconButton(
onPressed: () => _share(),
icon: const Icon(Feather.share),
Expand All @@ -53,24 +87,25 @@ class BookDetailsScreen extends StatelessWidget {
children: [
const SizedBox(height: 10.0),
_BookDescriptionSection(
entry: entry,
authorTag: authorTag,
imgTag: imgTag,
titleTag: titleTag,
entry: widget.entry,
authorTag: widget.authorTag,
imgTag: widget.imgTag,
titleTag: widget.titleTag,
),
const SizedBox(height: 30.0),
const _SectionTitle(title: 'Book Description'),
const _Divider(),
const SizedBox(height: 10.0),
DescriptionTextWidget(text: '${entry.summary!.t}'),
DescriptionTextWidget(text: '${widget.entry.summary!.t}'),
const SizedBox(height: 30.0),
const _SectionTitle(
title: 'More from Author',
),
const _Divider(),
const SizedBox(height: 10.0),
_MoreBooksFromAuthor(
authorUrl: entry.author!.uri!.t!.replaceAll(r'\&lang=en', ''),
authorUrl:
widget.entry.author!.uri!.t!.replaceAll(r'\&lang=en', ''),
),
const SizedBox(height: 30.0),
],
Expand All @@ -79,8 +114,8 @@ class BookDetailsScreen extends StatelessWidget {
}

void _share() {
Share.share('${entry.title!.t} by ${entry.author!.name!.t}'
'Read/Download ${entry.title!.t} from ${entry.link![3].href}.');
Share.share('${widget.entry.title!.t} by ${widget.entry.author!.name!.t}'
'Read/Download ${widget.entry.title!.t} from ${widget.entry.link![3].href}.');
}
}

Expand Down Expand Up @@ -227,42 +262,29 @@ class _CategoryChips extends StatelessWidget {
}
}

class _DownloadButton extends ConsumerStatefulWidget {
class _DownloadButton extends ConsumerWidget {
final Entry entry;

const _DownloadButton({required this.entry});

@override
ConsumerState<_DownloadButton> createState() => _DownloadButtonState();
}

class _DownloadButtonState extends ConsumerState<_DownloadButton> {
String get id => widget.entry.id!.t.toString();

@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(downloadsStateNotifierProvider.notifier).listen();
});
}
String get id => entry.id!.t.toString();

@override
Widget build(BuildContext context) {
String id = widget.entry.id!.t.toString();
Widget build(BuildContext context, WidgetRef ref) {
String id = entry.id!.t.toString();
return ref.watch(downloadsStateNotifierProvider).maybeWhen(
orElse: () {
return _downloadButton();
return _downloadButton(context);
},
listening: (books) {
final bookIsDownloaded =
books.indexWhere((element) => element['id'] == id) != -1;
if (!bookIsDownloaded) {
return _downloadButton();
return _downloadButton(context);
}
final book = books.firstWhere((element) => element['id'] == id);
return TextButton(
onPressed: () => openBook(book['path']),
onPressed: () => openBook(book['path'], context),
child: Text(
'Read Book'.toUpperCase(),
style: TextStyle(
Expand All @@ -276,14 +298,14 @@ class _DownloadButtonState extends ConsumerState<_DownloadButton> {
);
}

Widget _downloadButton() => TextButton(
Widget _downloadButton(BuildContext context) => TextButton(
onPressed: () {
DownloadAlert.show(
context: context,
url: widget.entry.link![3].href!,
name: widget.entry.title!.t ?? '',
image: '${widget.entry.link![1].href}',
id: widget.entry.id!.t.toString(),
url: entry.link![3].href!,
name: entry.title!.t ?? '',
image: '${entry.link![1].href}',
id: entry.id!.t.toString(),
);
},
child: Text(
Expand All @@ -296,7 +318,7 @@ class _DownloadButtonState extends ConsumerState<_DownloadButton> {
),
);

Future<void> openBook(String path) async {
Future<void> openBook(String path, BuildContext context) async {
MyRouter.pushPage(context, EpubScreen.fromPath(filePath: path));
}
}
Expand Down Expand Up @@ -347,7 +369,7 @@ class _MoreBooksFromAuthorState extends ConsumerState<_MoreBooksFromAuthor> {
Widget build(BuildContext context) {
return ref.watch(bookDetailsStateNotifierProvider).maybeWhen(
orElse: () => const SizedBox.shrink(),
loadInProgress: () => LoadingWidget(),
loadInProgress: () => const LoadingWidget(),
loadSuccess: (related) {
if (related.feed!.entry == null || related.feed!.entry!.isEmpty) {
return const Text('Empty');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:flutter_ebook_app/src/features/common/data/models/category_feed.dart';

abstract class FavoritesLocalDataSource {
const FavoritesLocalDataSource();

Future<void> addBook(Entry book, String id);

Future<void> deleteBook(String id);

Stream<List<Entry>> favoritesListStream();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:flutter_ebook_app/src/features/common/data/data_sources/favorites/favorites_local_data_source.dart';
import 'package:flutter_ebook_app/src/features/common/data/models/category_feed.dart';
import 'package:flutter_ebook_app/src/features/common/data/providers/database_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sembast/sembast.dart';

class FavoritesLocalDataSourceImpl implements FavoritesLocalDataSource {
final Database _database;
final StoreRef<String, Map<String, dynamic>> _store;

const FavoritesLocalDataSourceImpl({
required Database database,
required StoreRef<String, Map<String, dynamic>> store,
}) : _database = database,
_store = store;

@override
Future<void> addBook(Entry book, String id) async {
await _store.record(id).put(_database, book.toJson());
}
@override
Future<void> deleteBook(String id) async {
await _store.record(id).delete(_database);
}

@override
Stream<List<Entry>> favoritesListStream() {
return _store
.query()
.onSnapshots(_database)
.map<List<Entry>>(
(records) => records
.map<Entry>((record) => Entry.fromJson(record.value))
.toList(),
);
}
}

final favoritesLocalDataSourceProvider =
Provider.autoDispose<FavoritesLocalDataSource>(
(ref) {
final database = ref.watch(favoritesDatabaseProvider);
final store = ref.watch(storeRefProvider);
return FavoritesLocalDataSourceImpl(database: database, store: store);
},
);
2 changes: 1 addition & 1 deletion lib/src/features/common/data/models/category_feed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class Entry {
summary = json['summary'] != null ? Id.fromJson(json['summary']) : null;
if (json['category'] != null) {
String? t = json['category'].runtimeType.toString();
if (t == 'List<dynamic>' || t == '_GrowableList<dynamic>') {
if (t.toLowerCase().contains('list')) {
category = <Category>[];
json['category'].forEach((v) {
category!.add(Category.fromJson(v));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ mixin _$DownloadsState {
required TResult Function(List<Map<String, dynamic>> downloads) listening,
}) =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started,
Expand All @@ -36,14 +35,12 @@ mixin _$DownloadsState {
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(DownloadsStateStarted value) started,
required TResult Function(DownloadsStateLoadListening value) listening,
}) =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(DownloadsStateStarted value)? started,
Expand Down Expand Up @@ -71,9 +68,9 @@ class _$DownloadsStateCopyWithImpl<$Res, $Val extends DownloadsState>
implements $DownloadsStateCopyWith<$Res> {
_$DownloadsStateCopyWithImpl(this._value, this._then);

// ignore: unused_field
// ignore: unused_field
final $Val _value;
// ignore: unused_field
// ignore: unused_field
final $Res Function($Val) _then;
}

Expand Down Expand Up @@ -223,7 +220,7 @@ class _$DownloadsStateLoadListening implements DownloadsStateLoadListening {
@override
List<Map<String, dynamic>> get downloads {
if (_downloads is EqualUnmodifiableListView) return _downloads;
// ignore: implicit_dynamic_type
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_downloads);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of 'favorites_state_notifier.dart';

@freezed
abstract class FavoritesState with _$FavoritesState {
const factory FavoritesState.started() = FavoritesStateStarted;

const factory FavoritesState.listening({
required List<Entry> favorites,
}) = FavoritesStateLoadListening;
}
Loading

0 comments on commit 2950da1

Please sign in to comment.