manga library filter features
This commit is contained in:
parent
065f930472
commit
923660dcea
10 changed files with 219 additions and 43 deletions
|
|
@ -62,6 +62,7 @@ class _BrowseScreenState extends State<BrowseScreen>
|
|||
setState(() {
|
||||
_isSearch = false;
|
||||
});
|
||||
_textEditingController.clear();
|
||||
},
|
||||
controller: _textEditingController,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
lib/views/library/providers/state_providers.dart
Normal file
9
lib/views/library/providers/state_providers.dart
Normal 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',
|
||||
);
|
||||
|
|
@ -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!,
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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!)
|
||||
|
|
|
|||
|
|
@ -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")),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue