refresh & search impl

This commit is contained in:
kodjodevf 2023-04-06 16:52:04 +01:00
parent 8a2dba2e0d
commit 065f930472
15 changed files with 308 additions and 253 deletions

View file

@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kodjodevf.mangayomi">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="mangayomi"
android:name="${applicationName}"

View file

@ -69,7 +69,6 @@ Future<GetMangaChapterUrlModel> getMangaChapterUrl(
"Referer": "https://www.mangahere.cc/",
"Cookie": "isAdult=1"
});
log("message");
var link = "http://www.mangahere.cc${modelManga.chapterUrl![index]}";
dom.Document htmll = dom.Document.html(response.body);
int? pagesNumber = -1;

View file

@ -7,7 +7,7 @@ part of 'get_manga_chapter_url.dart';
// **************************************************************************
String _$getMangaChapterUrlHash() =>
r'bd696a5ff424b0396cd67d08325945f6ac00093b';
r'38cf836814df00df1a3a269c0c2fc0c85debff78';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -29,9 +29,9 @@ lang(String lang) {
return 'tr';
} else if (lang == 'Polski') {
return 'pl';
} else if (lang == '中文') {
} else if (lang == '中文(Zhōngwén)') {
return 'zh';
} else if (lang == '(Hong Kong) 繁體中文') {
} else if (lang == '繁體中文(Hong Kong)') {
return 'zh-hk';
}
}
@ -68,9 +68,9 @@ completeLang(String lang) {
} else if (lang == 'pl') {
return 'Polski';
} else if (lang == 'zh') {
return '中文';
return '中文(Zhōngwén)';
} else if (lang == 'zh-hk') {
return '(Hong Kong) 繁體中文';
return '繁體中文(Hong Kong)';
}
}
@ -90,6 +90,6 @@ final List<String> language = [
'Polski',
'Türkçe',
'Deutsch',
'中文',
'(Hong Kong) 繁體中文'
'中文(Zhōngwén)',
'繁體中文(Hong Kong)'
];

View file

@ -1,8 +1,12 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/source/source_model.dart';
import 'package:mangayomi/views/browse/extension/extension_screen.dart';
import 'package:mangayomi/views/browse/migrate_screen.dart';
import 'package:mangayomi/views/browse/sources_screen.dart';
import 'package:mangayomi/views/library/search_text_form_field.dart';
class BrowseScreen extends StatefulWidget {
const BrowseScreen({super.key});
@ -25,6 +29,10 @@ class _BrowseScreenState extends State<BrowseScreen>
super.initState();
}
List<SourceModel> entries = [];
List<SourceModel> entriesFilter = [];
final _textEditingController = TextEditingController();
bool _isSearch = false;
@override
Widget build(BuildContext context) {
return DefaultTabController(
@ -39,15 +47,40 @@ class _BrowseScreenState extends State<BrowseScreen>
style: TextStyle(color: Theme.of(context).hintColor),
),
actions: [
if (_tabBarController.index != 2)
IconButton(
splashRadius: 20,
onPressed: () {},
icon: Icon(
_tabBarController.index == 0
? Icons.travel_explore_rounded
: Icons.search_rounded,
color: Theme.of(context).hintColor)),
_isSearch
? SeachFormTextField(
onChanged: (value) {
setState(() {
entriesFilter = entries
.where((element) => element.sourceName
.toLowerCase()
.contains(value.toLowerCase()))
.toList();
});
},
onPressed: () {
setState(() {
_isSearch = false;
});
},
controller: _textEditingController,
)
: _tabBarController.index != 2
? IconButton(
splashRadius: 20,
onPressed: () {
if (_tabBarController.index == 1) {
setState(() {
_isSearch = true;
});
}
},
icon: Icon(
_tabBarController.index == 0
? Icons.travel_explore_rounded
: Icons.search_rounded,
color: Theme.of(context).hintColor))
: Container(),
IconButton(
splashRadius: 20,
onPressed: () {
@ -74,10 +107,15 @@ class _BrowseScreenState extends State<BrowseScreen>
],
),
),
body: TabBarView(controller: _tabBarController, children: const [
SourcesScreen(),
ExtensionScreen(),
MigrateScreen()
body: TabBarView(controller: _tabBarController, children: [
const SourcesScreen(),
ExtensionScreen(
entriesData: (val) {
entries = val as List<SourceModel>;
},
entriesFilter: entriesFilter,
),
const MigrateScreen()
]),
),
);

View file

@ -12,6 +12,7 @@ class ExtensionsLang extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
language.sort((a, b) => a.compareTo(b));
return Scaffold(
appBar: AppBar(
title: const Text("Extensions"),

View file

@ -8,76 +8,64 @@ import 'package:mangayomi/utils/lang.dart';
import 'package:mangayomi/views/browse/extension/refresh_filter_data.dart';
import 'package:mangayomi/views/browse/extension/widgets/extension_list_tile_widget.dart';
class ExtensionScreen extends ConsumerWidget {
const ExtensionScreen({super.key});
final Function(dynamic) entriesData;
final List<SourceModel> entriesFilter;
const ExtensionScreen(
{required this.entriesData, required this.entriesFilter, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final refreshFilter = ref.watch(refreshFilterDataProvider);
return refreshFilter.when(
data: (data) {
return Padding(
padding: const EdgeInsets.only(top: 10),
child: ValueListenableBuilder<Box<SourceModel>>(
valueListenable:
ref.watch(hiveBoxMangaSourceProvider).listenable(),
builder: (context, value, child) {
final entries = value.values.toList();
return GroupedListView<SourceModel, String>(
elements: entries,
groupBy: (element) =>
completeLang(element.lang.toLowerCase()),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 12),
),
],
ref.watch(refreshFilterDataProvider);
return Padding(
padding: const EdgeInsets.only(top: 10),
child: ValueListenableBuilder<Box<SourceModel>>(
valueListenable: ref.watch(hiveBoxMangaSourceProvider).listenable(),
builder: (context, value, child) {
final entries = value.values.toList();
entriesData(entries);
return GroupedListView<SourceModel, String>(
elements: entriesFilter.isNotEmpty ? entriesFilter : entries,
groupBy: (element) => completeLang(element.lang.toLowerCase()),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 12),
),
),
itemBuilder: (context, SourceModel element) {
final source =
value.get("${element.sourceName}${element.lang}")!;
return ExtensionListTileWidget(
lang: value
.get("${element.sourceName}${element.lang}")!
.lang,
onChanged: (val) {
value.put(
"${element.sourceName}${element.lang}",
SourceModel(
sourceName: element.sourceName,
url: element.url,
lang: element.lang,
typeSource: element.typeSource,
isAdded: val,
logoUrl: element.logoUrl));
},
sourceName: source.sourceName,
value: source.isAdded,
logoUrl: source.logoUrl,
);
],
),
),
itemBuilder: (context, SourceModel element) {
final source =
value.get("${element.sourceName}${element.lang}")!;
return ExtensionListTileWidget(
lang: value.get("${element.sourceName}${element.lang}")!.lang,
onChanged: (val) {
value.put(
"${element.sourceName}${element.lang}",
SourceModel(
sourceName: element.sourceName,
url: element.url,
lang: element.lang,
typeSource: element.typeSource,
isAdded: val,
logoUrl: element.logoUrl));
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName.compareTo(item2.sourceName),
order: GroupedListOrder.ASC,
sourceName: source.sourceName,
value: source.isAdded,
logoUrl: source.logoUrl,
);
}),
);
},
error: (error, stackTrace) {
return Container();
},
loading: () {
return Container();
},
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName.compareTo(item2.sourceName),
order: GroupedListOrder.ASC,
);
}),
);
}
}

View file

@ -5,7 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'refresh_filter_data.g.dart';
@riverpod
Future<bool> refreshFilterData(RefreshFilterDataRef ref) async {
refreshFilterData(RefreshFilterDataRef ref) async {
final lf = ref
.watch(hiveBoxMangaFilterProvider)
.get("language_filter", defaultValue: []);
@ -69,5 +69,4 @@ Future<bool> refreshFilterData(RefreshFilterDataRef ref) async {
}
}
}
return true;
}

View file

@ -6,11 +6,11 @@ part of 'refresh_filter_data.dart';
// RiverpodGenerator
// **************************************************************************
String _$refreshFilterDataHash() => r'0a4c4949efbe333131bef6b2e52ee94d81f06036';
String _$refreshFilterDataHash() => r'78e543cb9e5bffa4a5960abeb9d7b2dd4cb23e30';
/// See also [refreshFilterData].
@ProviderFor(refreshFilterData)
final refreshFilterDataProvider = AutoDisposeFutureProvider<bool>.internal(
final refreshFilterDataProvider = AutoDisposeProvider<dynamic>.internal(
refreshFilterData,
name: r'refreshFilterDataProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@ -20,5 +20,5 @@ final refreshFilterDataProvider = AutoDisposeFutureProvider<bool>.internal(
allTransitiveDependencies: null,
);
typedef RefreshFilterDataRef = AutoDisposeFutureProviderRef<bool>;
typedef RefreshFilterDataRef = AutoDisposeProviderRef<dynamic>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View file

@ -15,108 +15,96 @@ class SourcesScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final refreshFilter = ref.watch(refreshFilterDataProvider);
ref.watch(refreshFilterDataProvider);
return refreshFilter.when(
data: (data) {
return Padding(
padding: const EdgeInsets.only(top: 10),
child: ValueListenableBuilder<Box<SourceModel>>(
valueListenable:
ref.watch(hiveBoxMangaSourceProvider).listenable(),
builder: (context, value, child) {
final entries = value.values
.where((element) => element.isAdded == true)
.toList();
if (entries.isEmpty) {
return const Center(child: Text("Empty"));
}
return GroupedListView<SourceModel, String>(
elements: entries,
groupBy: (element) =>
completeLang(element.lang.toLowerCase()),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 12),
),
],
return Padding(
padding: const EdgeInsets.only(top: 10),
child: ValueListenableBuilder<Box<SourceModel>>(
valueListenable: ref.watch(hiveBoxMangaSourceProvider).listenable(),
builder: (context, value, child) {
final entries = value.values
.where((element) => element.isAdded == true)
.toList();
if (entries.isEmpty) {
return const Center(child: Text("Empty"));
}
return GroupedListView<SourceModel, String>(
elements: entries,
groupBy: (element) => completeLang(element.lang.toLowerCase()),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 12),
),
),
itemBuilder: (context, SourceModel element) {
final source =
value.get("${element.sourceName}${element.lang}")!;
return ListTile(
onTap: () {
context.push('/mangaHome',
extra: MangaType(
isFullData: element.isFullData,
lang: element.lang,
source: element.sourceName));
},
leading: Container(
height: 37,
width: 37,
decoration: BoxDecoration(
color: Theme.of(context)
.secondaryHeaderColor
.withOpacity(0.5),
borderRadius: BorderRadius.circular(5)),
child: element.logoUrl.isEmpty
? const Icon(Icons.source_outlined)
: CachedNetworkImage(
imageUrl: element.logoUrl,
fit: BoxFit.contain,
],
),
),
itemBuilder: (context, SourceModel element) {
final source =
value.get("${element.sourceName}${element.lang}")!;
return ListTile(
onTap: () {
context.push('/mangaHome',
extra: MangaType(
isFullData: element.isFullData,
lang: element.lang,
source: element.sourceName));
},
leading: Container(
height: 37,
width: 37,
decoration: BoxDecoration(
color: Theme.of(context)
.secondaryHeaderColor
.withOpacity(0.5),
borderRadius: BorderRadius.circular(5)),
child: element.logoUrl.isEmpty
? const Icon(Icons.source_outlined)
: CachedNetworkImage(
imageUrl: element.logoUrl,
fit: BoxFit.contain,
width: 37,
height: 37,
errorWidget: (context, url, error) {
return const SizedBox(
width: 37,
height: 37,
errorWidget: (context, url, error) {
return const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.source_outlined),
),
);
},
),
),
subtitle: Text(
completeLang(source.lang.toLowerCase()),
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 12),
),
title: Text(source.sourceName),
trailing: SizedBox(
width: 110,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
Icon(
Icons.push_pin_outlined,
color: Colors.black,
)
],
)),
);
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName.compareTo(item2.sourceName),
order: GroupedListOrder.ASC,
child: Center(
child: Icon(Icons.source_outlined),
),
);
},
),
),
subtitle: Text(
completeLang(source.lang.toLowerCase()),
style: const TextStyle(
fontWeight: FontWeight.w300, fontSize: 12),
),
title: Text(source.sourceName),
trailing: SizedBox(
width: 110,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
Icon(
Icons.push_pin_outlined,
color: Colors.black,
)
],
)),
);
}),
);
},
error: (error, stackTrace) {
return Container();
},
loading: () {
return Container();
},
},
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator: (item1, item2) =>
item1.sourceName.compareTo(item2.sourceName),
order: GroupedListOrder.ASC,
);
}),
);
}
}

View file

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -10,25 +12,60 @@ 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/date.dart';
import 'package:mangayomi/views/library/search_text_form_field.dart';
class HistoryScreen extends ConsumerWidget {
class HistoryScreen extends ConsumerStatefulWidget {
const HistoryScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<HistoryScreen> createState() => _HistoryScreenState();
}
class _HistoryScreenState extends ConsumerState<HistoryScreen> {
final _textEditingController = TextEditingController();
bool _isSearch = false;
List<MangaHistoryModel> entriesData = [];
List<MangaHistoryModel> entriesFilter = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
title: Text(
'History',
style: TextStyle(color: Theme.of(context).hintColor),
),
title: _isSearch
? null
: Text(
'History',
style: TextStyle(color: Theme.of(context).hintColor),
),
actions: [
IconButton(
splashRadius: 20,
onPressed: () {},
icon: Icon(Icons.search, color: Theme.of(context).hintColor)),
_isSearch
? SeachFormTextField(
onChanged: (value) {
log(value.toString());
setState(() {
entriesFilter = entriesData
.where((element) => element.modelManga.name!
.toLowerCase()
.contains(value.toLowerCase()))
.toList();
});
},
onPressed: () {
setState(() {
_isSearch = false;
});
},
controller: _textEditingController,
)
: IconButton(
splashRadius: 20,
onPressed: () {
setState(() {
_isSearch = true;
});
},
icon: Icon(Icons.search, color: Theme.of(context).hintColor)),
IconButton(
splashRadius: 20,
onPressed: () {},
@ -42,9 +79,10 @@ class HistoryScreen extends ConsumerWidget {
valueListenable: ref.watch(hiveBoxMangaHistory).listenable(),
builder: (context, value, child) {
final entries = value.values.toList();
entriesData = entries;
if (entries.isNotEmpty) {
return GroupedListView<MangaHistoryModel, String>(
elements: entries,
elements: entriesFilter.isNotEmpty ? entriesFilter : entries,
groupBy: (element) => element.date.substring(0, 10),
groupSeparatorBuilder: (String groupByValue) => Padding(
padding: const EdgeInsets.only(bottom: 8),

View file

@ -5,6 +5,7 @@ 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/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';
@ -35,41 +36,20 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> {
),
actions: [
isSearch
? Flexible(
child: TextFormField(
style: const TextStyle(fontFamily: 'Lato'),
controller: _textEditingController,
keyboardType: TextInputType.text,
onChanged: (value) {
setState(() {
entriesFilter = entries
.where((element) =>
element.name!.toLowerCase().contains(value))
.toList();
});
},
decoration: InputDecoration(
hintText: 'Seach...',
filled: true,
fillColor: Colors.transparent,
prefixIcon: IconButton(
onPressed: () {
setState(() {
isSearch = false;
});
},
icon: const Icon(
Icons.arrow_back,
)),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide.none,
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide.none,
),
border: const OutlineInputBorder(
borderSide: BorderSide.none)),
),
? SeachFormTextField(
onChanged: (value) {
setState(() {
entriesFilter = entries
.where((element) =>
element.name!.toLowerCase().contains(value))
.toList();
});
},
onPressed: () {
setState(() {
isSearch = false;
});
}, controller: _textEditingController,
)
: IconButton(
splashRadius: 20,

View file

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
class SeachFormTextField extends StatelessWidget {
final Function(String)? onChanged;
final VoidCallback onPressed;
final TextEditingController controller;
const SeachFormTextField(
{super.key,
required this.onChanged,
required this.onPressed,
required this.controller});
@override
Widget build(BuildContext context) {
return Flexible(
child: TextFormField(
autofocus: true,
controller: controller,
keyboardType: TextInputType.text,
onChanged: onChanged,
decoration: InputDecoration(
isDense: true,
hintText: 'Search...',
filled: true,
fillColor: Colors.transparent,
prefixIcon: IconButton(
onPressed: onPressed,
icon: const Icon(
Icons.arrow_back,
)),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide.none,
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide.none,
),
border: const OutlineInputBorder(borderSide: BorderSide.none)),
),
);
}
}

View file

@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
class SeachWi extends StatefulWidget {
const SeachWi({super.key});
@override
State<SeachWi> createState() => _SeachWiState();
}
class _SeachWiState extends State<SeachWi> {
@override
Widget build(BuildContext context) {
return PreferredSize(
child: AppBar(
title: Row(
children: [],
),
),
preferredSize: Size.fromHeight(AppBar().preferredSize.height));
}
}

View file

@ -33,6 +33,9 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
),
body: getManga.when(
data: (data) {
if (data.url.isEmpty) {
return const Center(child: Text("No result"));
}
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {