manga library filter features

This commit is contained in:
kodjodevf 2023-04-06 18:31:36 +01:00
parent 065f930472
commit 923660dcea
10 changed files with 219 additions and 43 deletions

View file

@ -62,6 +62,7 @@ class _BrowseScreenState extends State<BrowseScreen>
setState(() {
_isSearch = false;
});
_textEditingController.clear();
},
controller: _textEditingController,
)

View file

@ -44,7 +44,7 @@ class _GeneralScreenState extends ConsumerState<GeneralScreen> {
height: 20,
),
child: NavigationBar(
animationDuration: const Duration(seconds: 1),
animationDuration: const Duration(milliseconds: 500),
selectedIndex: currentIndex,
destinations: const [
NavigationDestination(
@ -68,7 +68,7 @@ class _GeneralScreenState extends ConsumerState<GeneralScreen> {
Icons.history,
),
icon: Icon(
Icons.history_outlined,
Icons.history_sharp,
),
label: "History"),
NavigationDestination(

View file

@ -55,6 +55,7 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen> {
setState(() {
_isSearch = false;
});
_textEditingController.clear();
},
controller: _textEditingController,
)
@ -79,10 +80,12 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen> {
valueListenable: ref.watch(hiveBoxMangaHistory).listenable(),
builder: (context, value, child) {
final entries = value.values.toList();
entriesData = entries;
final entriesHistory = _textEditingController.text.isNotEmpty
? entriesFilter
: entries;
if (entries.isNotEmpty) {
return GroupedListView<MangaHistoryModel, String>(
elements: entriesFilter.isNotEmpty ? entriesFilter : entries,
elements: entriesHistory,
groupBy: (element) => element.date.substring(0, 10),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(bottom: 8),

View file

@ -5,10 +5,13 @@ import 'package:hive_flutter/hive_flutter.dart';
import 'package:mangayomi/models/model_manga.dart';
import 'package:mangayomi/providers/hive_provider.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/media_query.dart';
import 'package:mangayomi/views/library/providers/state_providers.dart';
import 'package:mangayomi/views/library/search_text_form_field.dart';
import 'package:mangayomi/views/widgets/bottom_text_widget.dart';
import 'package:mangayomi/views/widgets/cover_view_widget.dart';
import 'package:mangayomi/views/widgets/gridview_widget.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class LibraryScreen extends ConsumerStatefulWidget {
const LibraryScreen({super.key});
@ -17,13 +20,16 @@ class LibraryScreen extends ConsumerStatefulWidget {
ConsumerState<LibraryScreen> createState() => _LibraryScreenState();
}
class _LibraryScreenState extends ConsumerState<LibraryScreen> {
class _LibraryScreenState extends ConsumerState<LibraryScreen>
with TickerProviderStateMixin {
bool isOk = false;
bool isSearch = false;
List<ModelManga> entries = [];
List<ModelManga> entriesFilter = [];
final _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
final reverse = ref.watch(reverseStateProvider);
return Scaffold(
appBar: AppBar(
elevation: 0,
@ -49,7 +55,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> {
setState(() {
isSearch = false;
});
}, controller: _textEditingController,
},
controller: _textEditingController,
)
: IconButton(
splashRadius: 20,
@ -57,11 +64,14 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> {
setState(() {
isSearch = true;
});
_textEditingController.clear();
},
icon: Icon(Icons.search, color: Theme.of(context).hintColor)),
IconButton(
splashRadius: 20,
onPressed: () {},
onPressed: () {
_showModalSort();
},
icon: Icon(Icons.filter_list_sharp,
color: Theme.of(context).hintColor)),
PopupMenuButton(
@ -90,8 +100,11 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> {
valueListenable: ref.watch(hiveBoxManga).listenable(),
builder: (context, value, child) {
entries = value.values.where((element) => element.favorite).toList();
final entriesManga =
_textEditingController.text.isNotEmpty ? entriesFilter : entries;
final entriesManga = _textEditingController.text.isNotEmpty
? entriesFilter
: reverse
? entries.reversed.toList()
: entries;
if (entries.isNotEmpty || entriesFilter.isNotEmpty) {
return GridViewWidget(
itemCount: entriesManga.length,
@ -117,11 +130,33 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> {
},
child: CoverViewWidget(
children: [
cachedNetworkImage(
imageUrl: entriesManga[index].imageUrl!,
width: 200,
height: 270,
fit: BoxFit.cover),
Stack(
children: [
cachedNetworkImage(
imageUrl: entriesManga[index].imageUrl!,
width: 200,
height: 270,
fit: BoxFit.cover),
Positioned(
top: 0,
left: 0,
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: Theme.of(context).cardColor),
child: Padding(
padding: const EdgeInsets.all(1),
child: Text(entriesManga[index]
.chapterDate!
.length
.toString()),
),
),
))
],
),
BottomTextWidget(text: entriesManga[index].name!)
],
),
@ -134,4 +169,87 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> {
),
);
}
_showModalSort() {
List<String> sortList = [
"Alphabetically",
"Total chapters",
"Latest chapter",
"Date added"
];
late TabController tabBarController;
showMaterialModalBottomSheet(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(5), topRight: Radius.circular(5))),
enableDrag: true,
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context) {
if (!isOk) {
tabBarController = TabController(length: 3, vsync: this);
tabBarController.animateTo(0);
}
return SizedBox(
height: mediaHeight(context, 0.4),
child: DefaultTabController(
length: 3,
child: Scaffold(
body: Column(
children: [
TabBar(
controller: tabBarController,
tabs: const [
Tab(text: "Filter"),
Tab(text: "Sort"),
Tab(text: "Display"),
],
),
Flexible(
child: TabBarView(
controller: tabBarController,
children: [
const Center(child: Text("soon")),
Consumer(builder: (context, ref, chil) {
final reverse =
ref.watch(reverseStateProvider);
final sortedValue =
ref.watch(sortedValueStateProvider);
return Column(
children: [
for (var i = 0; i < sortList.length; i++)
ListTile(
onTap: () {
ref
.read(reverseStateProvider
.notifier)
.state = !reverse;
ref
.read(sortedValueStateProvider
.notifier)
.state = sortList[i];
},
dense: true,
leading: sortedValue == sortList[i]
? Icon(reverse
? Icons.arrow_downward_sharp
: Icons.arrow_upward_sharp)
: const Icon(
Icons.arrow_upward_sharp,
color: Colors.transparent,
),
title: Text(sortList[i]),
),
],
);
}),
const Center(child: Text("soon"))
]),
),
],
),
)));
});
}
}

View file

@ -0,0 +1,9 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
final reverseStateProvider = StateProvider.autoDispose<bool>(
(ref) => false,
);
final sortedValueStateProvider = StateProvider.autoDispose<String>(
(ref) => 'Alphabetically',
);

View file

@ -299,14 +299,14 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView> {
child: Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 100),
width: MediaQuery.of(context).size.width,
height: 70,
width: MediaQuery.of(context).size.width * 0.9,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.modelManga!.name!,
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
style: const TextStyle(
fontSize: 18,
)),
widget.titleDescription!,
],
),

View file

@ -161,6 +161,13 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
),
Row(
children: [
const Icon(
FontAwesomeIcons.clock,
size: 12,
),
const SizedBox(
width: 4,
),
Text(widget.modelManga.status!),
const Text(''),
Text(widget.modelManga.source!)

View file

@ -175,7 +175,10 @@ class _MangaHomeImageCardState extends ConsumerState<MangaHomeImageCard>
height: 270,
),
),
BottomTextWidget(text: widget.name)
BottomTextWidget(
text: widget.name,
isLoading: true,
)
]),
error: (error, stackTrace) => const Center(child: Text("Error")),
);

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
class BottomTextWidget extends StatelessWidget {
final bool isLoading;
final String text;
const BottomTextWidget({super.key, required this.text});
const BottomTextWidget(
{super.key, required this.text, this.isLoading = false});
@override
Widget build(BuildContext context) {
@ -10,28 +12,59 @@ class BottomTextWidget extends StatelessWidget {
bottom: 0,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black.withOpacity(0.4)],
stops: const [0, 1],
),
),
child: Text(
text,
style: const TextStyle(
fontSize: 13.0,
color: Colors.white,
shadows: <Shadow>[
Shadow(offset: Offset(0.5, 0.9), blurRadius: 3.0)
],
),
textAlign: TextAlign.center,
),
),
child: isLoading
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(left: 5, bottom: 5),
child: Text(
text,
style: const TextStyle(
fontSize: 13.0,
color: Colors.white,
shadows: <Shadow>[
Shadow(offset: Offset(0.5, 0.9), blurRadius: 3.0)
],
),
textAlign: TextAlign.start,
),
),
],
)
: Container(
height: 70,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black.withOpacity(0.6)],
stops: const [0, 1],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(left: 5, bottom: 5),
child: Text(
text,
style: const TextStyle(
fontSize: 13.0,
color: Colors.white,
shadows: <Shadow>[
Shadow(offset: Offset(0.5, 0.9), blurRadius: 3.0)
],
),
textAlign: TextAlign.start,
),
),
],
),
),
);
}
}

View file

@ -3,12 +3,14 @@ import 'package:flutter/material.dart';
class GridViewWidget extends StatelessWidget {
final ScrollController? controller;
final int? itemCount;
final bool reverse;
final Widget? Function(BuildContext, int) itemBuilder;
const GridViewWidget(
{super.key,
this.controller,
required this.itemCount,
required this.itemBuilder});
required this.itemBuilder,
this.reverse = false});
@override
Widget build(BuildContext context) {