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: multi select books and update book type #313

Merged
merged 8 commits into from
Sep 16, 2023
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
5 changes: 4 additions & 1 deletion assets/translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,8 @@
"migration_v1_to_v2_retrigger_description": "Click if books are not showing up after update to v2.0.",
"dark_mode_style": "Dark mode style",
"dark_mode_natural": "Natural dark mode",
"dark_mode_amoled": "AMOLED dark mode"
"dark_mode_amoled": "AMOLED dark mode",
"selected": "Selected",
"change_book_type": "Change book type",
"update_successful_message": "Updated successfully!"
}
21 changes: 21 additions & 0 deletions lib/database/database_controler.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import 'dart:io';

import 'package:openreads/database/database_provider.dart';
import 'package:openreads/model/book.dart';
import 'package:sqflite/sqflite.dart';

import '../core/constants.dart/enums.dart';

class DatabaseController {
final dbClient = DatabaseProvider.dbProvider;

Expand Down Expand Up @@ -125,4 +129,21 @@ class DatabaseController {
final db = await dbClient.db;
return await db.delete("booksTable");
}

Future<List<Object?>> updateBookType(Set<int> ids, BookType bookType) async {
final db = await dbClient.db;
var batch = db.batch();

String bookTypeString = bookType == BookType.audiobook
? 'audiobook'
: bookType == BookType.ebook
? 'ebook'
: 'paper';

for (int id in ids) {
batch.update("booksTable", {"book_type": bookTypeString},
where: "id = ?", whereArgs: [id]);
}
return await batch.commit();
}
}
3 changes: 3 additions & 0 deletions lib/generated/locale_keys.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,7 @@ abstract class LocaleKeys {
static const dark_mode_style = 'dark_mode_style';
static const dark_mode_natural = 'dark_mode_natural';
static const dark_mode_amoled = 'dark_mode_amoled';
static const selected = 'selected';
static const change_book_type = 'change_book_type';
static const update_successful_message = 'update_successful_message';
}
7 changes: 7 additions & 0 deletions lib/logic/cubit/book_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:openreads/model/book.dart';
import 'package:openreads/resources/repository.dart';
import 'package:rxdart/rxdart.dart';

import '../../core/constants.dart/enums.dart';

class BookCubit extends Cubit {
final Repository repository = Repository();

Expand Down Expand Up @@ -115,6 +117,11 @@ class BookCubit extends Cubit {
getAllBooks();
}

updateBookType(Set<int> ids, BookType bookType) async {
repository.updateBookType(ids, bookType);
getAllBooks();
}

deleteBook(int id) async {
repository.deleteBook(id);
getAllBooksByStatus();
Expand Down
5 changes: 5 additions & 0 deletions lib/resources/repository.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:openreads/database/database_controler.dart';
import 'package:openreads/model/book.dart';

import '../core/constants.dart/enums.dart';


class Repository {
final DatabaseController dbController = DatabaseController();

Expand All @@ -20,6 +23,8 @@ class Repository {

Future updateBook(Book book) => dbController.updateBook(book);

Future updateBookType(Set<int> ids, BookType bookType) => dbController.updateBookType(ids, bookType);

Future deleteBook(int index) => dbController.deleteBook(index);

Future getBook(int index) => dbController.getBook(index);
Expand Down
95 changes: 91 additions & 4 deletions lib/ui/books_screen/books_screen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:openreads/core/constants.dart/enums.dart';
import 'package:openreads/core/themes/app_theme.dart';
import 'package:openreads/generated/locale_keys.g.dart';
Expand All @@ -16,6 +17,8 @@ import 'package:openreads/ui/search_page/search_page.dart';
import 'package:openreads/ui/settings_screen/settings_screen.dart';
import 'package:openreads/ui/statistics_screen/statistics_screen.dart';

import 'helper/multi_select_helper.dart';

class BooksScreen extends StatefulWidget {
const BooksScreen({Key? key}) : super(key: key);

Expand All @@ -26,6 +29,17 @@ class BooksScreen extends StatefulWidget {
class _BooksScreenState extends State<BooksScreen>
with AutomaticKeepAliveClientMixin {
late List<String> moreButtonOptions;
Set<int> selectedBookIds = {};

_onItemSelected(int id) {
setState(() {
if (selectedBookIds.contains(id)) {
selectedBookIds.remove(id);
} else {
selectedBookIds.add(id);
}
});
}

List<Book> _sortReadList({
required SetSortState state,
Expand Down Expand Up @@ -429,10 +443,23 @@ class _BooksScreenState extends State<BooksScreen>
if (state is SetThemeState) {
AppTheme.init(state, context);

return Scaffold(
appBar: _buildAppBar(context),
floatingActionButton: _buildFAB(context),
body: _buildScaffoldBody(),
return WillPopScope(
child: Scaffold(
appBar: selectedBookIds.isNotEmpty
? _buildMultiSelectAppBar(context)
: _buildAppBar(context),
floatingActionButton: selectedBookIds.isNotEmpty
? _buildMultiSelectFAB(state)
: _buildFAB(context),
body: _buildScaffoldBody(),
),
onWillPop: () {
if (selectedBookIds.isNotEmpty) {
_resetMultiselectMode();
return Future.value(false);
}
return Future.value(true);
},
);
} else {
return const SizedBox();
Expand All @@ -441,6 +468,54 @@ class _BooksScreenState extends State<BooksScreen>
);
}

AppBar _buildMultiSelectAppBar(BuildContext context) {
return AppBar(
title: Row(
children: [
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_resetMultiselectMode();
},
),
Text(
'${LocaleKeys.selected.tr()} ${selectedBookIds.length}',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
));
}

void _resetMultiselectMode() {
setState(() {
selectedBookIds = {};
});
}

Padding? _buildMultiSelectFAB(SetThemeState state) {
return selectedBookIds.isNotEmpty ? Padding(
padding: const EdgeInsets.only(bottom: 50),
child: SpeedDial(
spacing: 3,
dialRoot: (ctx, open, toggleChildren) {
return FloatingActionButton(
onPressed: toggleChildren, child: const Icon(Icons.create));
},
childPadding: const EdgeInsets.all(5),
spaceBetweenChildren: 4,
children: [
SpeedDialChild(
child: const Icon(Icons.menu_book_outlined),
backgroundColor:
Theme.of(context).colorScheme.secondaryContainer,
label: LocaleKeys.change_book_type.tr(),
onTap: () {
showEditBookTypeBottomSheet(context, selectedBookIds);
}),
],
)): null;
}

BlocBuilder<ThemeBloc, ThemeState> _buildScaffoldBody() {
return BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) {
Expand Down Expand Up @@ -656,6 +731,8 @@ class _BooksScreenState extends State<BooksScreen>
list: snapshot.data!,
),
listNumber: 2,
selectedBookIds: selectedBookIds,
onBookSelected: _onItemSelected,
);
} else {
return BooksList(
Expand All @@ -664,6 +741,8 @@ class _BooksScreenState extends State<BooksScreen>
list: snapshot.data!,
),
listNumber: 2,
selectedBookIds: selectedBookIds,
onBookSelected: _onItemSelected,
);
}
},
Expand Down Expand Up @@ -717,6 +796,8 @@ class _BooksScreenState extends State<BooksScreen>
list: snapshot.data!,
),
listNumber: 1,
selectedBookIds: selectedBookIds,
onBookSelected: _onItemSelected,
);
} else {
return BooksList(
Expand All @@ -725,6 +806,8 @@ class _BooksScreenState extends State<BooksScreen>
list: snapshot.data!,
),
listNumber: 1,
selectedBookIds: selectedBookIds,
onBookSelected: _onItemSelected,
);
}
},
Expand Down Expand Up @@ -778,6 +861,8 @@ class _BooksScreenState extends State<BooksScreen>
list: snapshot.data!,
),
listNumber: 0,
selectedBookIds: selectedBookIds,
onBookSelected: _onItemSelected,
);
} else {
return BooksList(
Expand All @@ -786,6 +871,8 @@ class _BooksScreenState extends State<BooksScreen>
list: snapshot.data!,
),
listNumber: 0,
selectedBookIds: selectedBookIds,
onBookSelected: _onItemSelected,
);
}
},
Expand Down
60 changes: 60 additions & 0 deletions lib/ui/books_screen/helper/multi_select_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import '../../../core/constants.dart/enums.dart';
import '../../../generated/locale_keys.g.dart';
import '../../../main.dart';
import '../../add_book_screen/widgets/book_type_dropdown.dart';

List<String> bookTypes = [
LocaleKeys.book_type_paper.tr(),
LocaleKeys.book_type_ebook.tr(),
LocaleKeys.book_type_audiobook.tr(),
];

showEditBookTypeBottomSheet(BuildContext context, Set<int> selectedBookIds) {
return showModalBottomSheet(
context: context,
builder: (context) {
return Padding(
padding:
const EdgeInsets.only(left: 20, right: 20, top: 20, bottom: 40),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
LocaleKeys.change_book_type.tr(),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
BookTypeDropdown(
bookType: BookType.paper,
bookTypes: bookTypes,
changeBookType: (bookType) {
if (bookType == null) return;
_updateBooks(bookType, selectedBookIds);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(LocaleKeys.update_successful_message.tr())));
},
),
],
),
);
});
}

void _updateBooks(String bookType, Set<int> selectedIds) async {
BookType selectedBookType = BookType.paper;
if (bookType == bookTypes[0]) {
selectedBookType = BookType.paper;
} else if (bookType == bookTypes[1]) {
selectedBookType = BookType.ebook;
} else if (bookType == bookTypes[2]) {
selectedBookType = BookType.audiobook;
} else {
selectedBookType = BookType.paper;
}
bookCubit.updateBookType(selectedIds, selectedBookType);
}
6 changes: 6 additions & 0 deletions lib/ui/books_screen/widgets/book_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ class BookCard extends StatelessWidget {
required this.onPressed,
required this.heroTag,
required this.addBottomPadding,
this.onLongPressed,
this.cardColor,
}) : super(key: key);

final Book book;
final String heroTag;
final bool addBottomPadding;
final Function() onPressed;
final Function()? onLongPressed;
final Color? cardColor;

Widget _buildSortAttribute() {
return BlocBuilder<SortBloc, SortState>(
Expand Down Expand Up @@ -156,6 +160,7 @@ class BookCard extends StatelessWidget {
return Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, addBottomPadding ? 90 : 5),
child: Card(
color: cardColor,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(color: dividerColor, width: 1),
Expand All @@ -165,6 +170,7 @@ class BookCard extends StatelessWidget {
borderRadius: BorderRadius.circular(cornerRadius),
child: InkWell(
onTap: onPressed,
onLongPress: onLongPressed,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: Row(
Expand Down
3 changes: 3 additions & 0 deletions lib/ui/books_screen/widgets/book_grid_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ class BookGridCard extends StatelessWidget {
required this.onPressed,
required this.heroTag,
required this.addBottomPadding,
this.onLongPressed,
}) : super(key: key);

final Book book;
final String heroTag;
final bool addBottomPadding;
final Function() onPressed;
final Function()? onLongPressed;

@override
Widget build(BuildContext context) {
return InkWell(
onTap: onPressed,
onLongPress: onLongPressed,
child: book.cover != null
? ClipRRect(
borderRadius: BorderRadius.circular(3),
Expand Down
Loading
Loading