Skip to content

Commit

Permalink
Merge pull request #313 from vbh/mulit-select
Browse files Browse the repository at this point in the history
feat: multi select books and update book type
  • Loading branch information
mateusz-bak authored Sep 16, 2023
2 parents a334571 + c688d16 commit 898a958
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 133 deletions.
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

0 comments on commit 898a958

Please sign in to comment.