diff --git a/lib/models/settings.dart b/lib/models/settings.dart index b0ea833..2ec8eec 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -94,7 +94,7 @@ class Settings { this.dateFormat = "M/d/y", this.relativeTimesTamps = 2, this.flexSchemeColorIndex = 2, - this.themeIsDark = true, + this.themeIsDark = false, this.incognitoMode = false, this.chapterPageUrlsList, this.showPagesNumber = true, diff --git a/lib/models/source.dart b/lib/models/source.dart index d98a82c..6603c83 100644 --- a/lib/models/source.dart +++ b/lib/models/source.dart @@ -16,6 +16,8 @@ class Source { bool? isAdded; + bool? isPinned; + bool? isNsfw; @enumerated @@ -27,6 +29,8 @@ class Source { bool? isCloudflare; + bool? lastUsed; + String? dateFormat; String? dateFormatLocale; @@ -47,6 +51,8 @@ class Source { this.isNsfw = false, this.isFullData = false, this.isCloudflare = false, + this.isPinned = false, + this.lastUsed = false, this.apiUrl = "", }); } diff --git a/lib/models/source.g.dart b/lib/models/source.g.dart index de6c3af..5914fb3 100644 --- a/lib/models/source.g.dart +++ b/lib/models/source.g.dart @@ -62,23 +62,33 @@ const SourceSchema = CollectionSchema( name: r'isNsfw', type: IsarType.bool, ), - r'lang': PropertySchema( + r'isPinned': PropertySchema( id: 9, + name: r'isPinned', + type: IsarType.bool, + ), + r'lang': PropertySchema( + id: 10, name: r'lang', type: IsarType.string, ), + r'lastUsed': PropertySchema( + id: 11, + name: r'lastUsed', + type: IsarType.bool, + ), r'logoUrl': PropertySchema( - id: 10, + id: 12, name: r'logoUrl', type: IsarType.string, ), r'sourceName': PropertySchema( - id: 11, + id: 13, name: r'sourceName', type: IsarType.string, ), r'typeSource': PropertySchema( - id: 12, + id: 14, name: r'typeSource', type: IsarType.byte, enumMap: _SourcetypeSourceEnumValueMap, @@ -164,10 +174,12 @@ void _sourceSerialize( writer.writeBool(offsets[6], object.isCloudflare); writer.writeBool(offsets[7], object.isFullData); writer.writeBool(offsets[8], object.isNsfw); - writer.writeString(offsets[9], object.lang); - writer.writeString(offsets[10], object.logoUrl); - writer.writeString(offsets[11], object.sourceName); - writer.writeByte(offsets[12], object.typeSource.index); + writer.writeBool(offsets[9], object.isPinned); + writer.writeString(offsets[10], object.lang); + writer.writeBool(offsets[11], object.lastUsed); + writer.writeString(offsets[12], object.logoUrl); + writer.writeString(offsets[13], object.sourceName); + writer.writeByte(offsets[14], object.typeSource.index); } Source _sourceDeserialize( @@ -187,11 +199,13 @@ Source _sourceDeserialize( isCloudflare: reader.readBoolOrNull(offsets[6]), isFullData: reader.readBoolOrNull(offsets[7]), isNsfw: reader.readBoolOrNull(offsets[8]), - lang: reader.readStringOrNull(offsets[9]), - logoUrl: reader.readStringOrNull(offsets[10]), - sourceName: reader.readStringOrNull(offsets[11]), + isPinned: reader.readBoolOrNull(offsets[9]), + lang: reader.readStringOrNull(offsets[10]), + lastUsed: reader.readBoolOrNull(offsets[11]), + logoUrl: reader.readStringOrNull(offsets[12]), + sourceName: reader.readStringOrNull(offsets[13]), typeSource: - _SourcetypeSourceValueEnumMap[reader.readByteOrNull(offsets[12])] ?? + _SourcetypeSourceValueEnumMap[reader.readByteOrNull(offsets[14])] ?? TypeSource.single, ); return object; @@ -223,12 +237,16 @@ P _sourceDeserializeProp

( case 8: return (reader.readBoolOrNull(offset)) as P; case 9: - return (reader.readStringOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 10: return (reader.readStringOrNull(offset)) as P; case 11: - return (reader.readStringOrNull(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 12: + return (reader.readStringOrNull(offset)) as P; + case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: return (_SourcetypeSourceValueEnumMap[reader.readByteOrNull(offset)] ?? TypeSource.single) as P; default: @@ -1128,6 +1146,32 @@ extension SourceQueryFilter on QueryBuilder { }); } + QueryBuilder isPinnedIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'isPinned', + )); + }); + } + + QueryBuilder isPinnedIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'isPinned', + )); + }); + } + + QueryBuilder isPinnedEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isPinned', + value: value, + )); + }); + } + QueryBuilder langIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( @@ -1273,6 +1317,32 @@ extension SourceQueryFilter on QueryBuilder { }); } + QueryBuilder lastUsedIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'lastUsed', + )); + }); + } + + QueryBuilder lastUsedIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'lastUsed', + )); + }); + } + + QueryBuilder lastUsedEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'lastUsed', + value: value, + )); + }); + } + QueryBuilder logoUrlIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( @@ -1732,6 +1802,18 @@ extension SourceQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByIsPinned() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPinned', Sort.asc); + }); + } + + QueryBuilder sortByIsPinnedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPinned', Sort.desc); + }); + } + QueryBuilder sortByLang() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'lang', Sort.asc); @@ -1744,6 +1826,18 @@ extension SourceQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByLastUsed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastUsed', Sort.asc); + }); + } + + QueryBuilder sortByLastUsedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastUsed', Sort.desc); + }); + } + QueryBuilder sortByLogoUrl() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'logoUrl', Sort.asc); @@ -1902,6 +1996,18 @@ extension SourceQuerySortThenBy on QueryBuilder { }); } + QueryBuilder thenByIsPinned() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPinned', Sort.asc); + }); + } + + QueryBuilder thenByIsPinnedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPinned', Sort.desc); + }); + } + QueryBuilder thenByLang() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'lang', Sort.asc); @@ -1914,6 +2020,18 @@ extension SourceQuerySortThenBy on QueryBuilder { }); } + QueryBuilder thenByLastUsed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastUsed', Sort.asc); + }); + } + + QueryBuilder thenByLastUsedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastUsed', Sort.desc); + }); + } + QueryBuilder thenByLogoUrl() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'logoUrl', Sort.asc); @@ -2011,6 +2129,12 @@ extension SourceQueryWhereDistinct on QueryBuilder { }); } + QueryBuilder distinctByIsPinned() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isPinned'); + }); + } + QueryBuilder distinctByLang( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -2018,6 +2142,12 @@ extension SourceQueryWhereDistinct on QueryBuilder { }); } + QueryBuilder distinctByLastUsed() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'lastUsed'); + }); + } + QueryBuilder distinctByLogoUrl( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -2100,12 +2230,24 @@ extension SourceQueryProperty on QueryBuilder { }); } + QueryBuilder isPinnedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isPinned'); + }); + } + QueryBuilder langProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'lang'); }); } + QueryBuilder lastUsedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'lastUsed'); + }); + } + QueryBuilder logoUrlProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'logoUrl'); diff --git a/lib/modules/browse/browse_screen.dart b/lib/modules/browse/browse_screen.dart index c1c0f52..ca0cc41 100644 --- a/lib/modules/browse/browse_screen.dart +++ b/lib/modules/browse/browse_screen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:mangayomi/modules/browse/extension/providers/refresh_source_list_data.dart'; import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/models/source.dart'; import 'package:mangayomi/modules/browse/extension/extension_screen.dart'; -import 'package:mangayomi/modules/browse/extension/refresh_source_list_data.dart'; import 'package:mangayomi/modules/browse/migrate_screen.dart'; import 'package:mangayomi/modules/browse/sources/sources_screen.dart'; import 'package:mangayomi/modules/library/search_text_form_field.dart'; diff --git a/lib/modules/browse/extension/extension_screen.dart b/lib/modules/browse/extension/extension_screen.dart index f07ecaf..fad423c 100644 --- a/lib/modules/browse/extension/extension_screen.dart +++ b/lib/modules/browse/extension/extension_screen.dart @@ -43,7 +43,7 @@ class ExtensionScreen extends ConsumerWidget { Text( groupByValue, style: const TextStyle( - fontWeight: FontWeight.w300, fontSize: 12), + fontWeight: FontWeight.bold, fontSize: 13), ), ], ), diff --git a/lib/modules/browse/extension/refresh_source_list_data.dart b/lib/modules/browse/extension/providers/refresh_source_list_data.dart similarity index 97% rename from lib/modules/browse/extension/refresh_source_list_data.dart rename to lib/modules/browse/extension/providers/refresh_source_list_data.dart index 4a9c7c9..b0bded4 100644 --- a/lib/modules/browse/extension/refresh_source_list_data.dart +++ b/lib/modules/browse/extension/providers/refresh_source_list_data.dart @@ -28,6 +28,7 @@ refreshSourceListData(RefreshSourceListDataRef ref) { ..typeSource = source.typeSource ..isFullData = source.isFullData ..lang = source.lang + ..isNsfw = source.isNsfw ..sourceName = source.sourceName); } } diff --git a/lib/modules/browse/extension/refresh_source_list_data.g.dart b/lib/modules/browse/extension/providers/refresh_source_list_data.g.dart similarity index 95% rename from lib/modules/browse/extension/refresh_source_list_data.g.dart rename to lib/modules/browse/extension/providers/refresh_source_list_data.g.dart index e7960d1..03df1be 100644 --- a/lib/modules/browse/extension/refresh_source_list_data.g.dart +++ b/lib/modules/browse/extension/providers/refresh_source_list_data.g.dart @@ -7,7 +7,7 @@ part of 'refresh_source_list_data.dart'; // ************************************************************************** String _$refreshSourceListDataHash() => - r'f77838cebad50f030d2038d07584199b4509f407'; + r'c6ab103b0519d79afc8530ab7027a988203de5d3'; /// See also [refreshSourceListData]. @ProviderFor(refreshSourceListData) diff --git a/lib/modules/browse/global_search/global_search_screen.dart b/lib/modules/browse/global_search/global_search_screen.dart index fbc94f4..c70a067 100644 --- a/lib/modules/browse/global_search/global_search_screen.dart +++ b/lib/modules/browse/global_search/global_search_screen.dart @@ -3,12 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:isar/isar.dart'; import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/services/get_manga_detail.dart'; import 'package:mangayomi/services/search_manga.dart'; import 'package:mangayomi/models/source.dart'; import 'package:mangayomi/sources/service.dart'; import 'package:mangayomi/sources/source_list.dart'; import 'package:mangayomi/utils/cached_network.dart'; +import 'package:mangayomi/utils/colors.dart'; import 'package:mangayomi/utils/headers.dart'; import 'package:mangayomi/utils/lang.dart'; import 'package:mangayomi/modules/library/search_text_form_field.dart'; @@ -31,7 +33,7 @@ class _GlobalSearchScreenState extends ConsumerState { @override Widget build(BuildContext context) { final sourceList = ref.watch(onlyIncludePinnedSourceStateProvider) - ? isar.sources.filter().isAddedEqualTo(true).findAllSync() + ? isar.sources.filter().isPinnedEqualTo(true).findAllSync() : sourcesList; return Scaffold( appBar: AppBar( @@ -62,7 +64,7 @@ class _GlobalSearchScreenState extends ConsumerState { children: [ for (var i = 0; i < sourceList.length; i++) SizedBox( - height: 230, + height: 260, child: SourceSearchScreen( query: query, source: sourceList[i], @@ -91,7 +93,7 @@ class SourceSearchScreen extends ConsumerWidget { .watch(searchMangaProvider(source: source.sourceName!, query: query)); return Scaffold( body: SizedBox( - height: 240, + height: 260, child: Column( children: [ ListTile( @@ -175,43 +177,93 @@ class _MangaGlobalImageCardState extends ConsumerState pushToMangaReaderDetail( context: context, getManga: data, lang: widget.lang); }, - child: SizedBox( - width: 90, - child: Column(children: [ - cachedNetworkImage( - headers: ref.watch(headersProvider(source: data.source!)), - imageUrl: data.imageUrl!, - width: 80, - height: 120, - fit: BoxFit.fill), - BottomTextWidget( - fontSize: 12.0, - text: widget.manga.name!, - isLoading: true, - isComfortableGrid: true, - ) - ]), - ), + child: StreamBuilder( + stream: isar.mangas + .filter() + .langEqualTo(widget.lang) + .nameEqualTo(data.name) + .sourceEqualTo(data.source) + .favoriteEqualTo(true) + .watch(fireImmediately: true), + builder: (context, snapshot) { + return Padding( + padding: const EdgeInsets.only(left: 10), + child: Stack( + children: [ + SizedBox( + width: 100, + child: Column(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(2), + child: cachedNetworkImage( + headers: ref.watch( + headersProvider(source: data.source!)), + imageUrl: data.imageUrl!, + width: 100, + height: 140, + fit: BoxFit.fill), + ), + BottomTextWidget( + fontSize: 12.0, + text: widget.manga.name!, + isLoading: true, + isComfortableGrid: true, + ) + ]), + ), + Container( + width: 100, + height: 140, + color: snapshot.hasData && snapshot.data!.isNotEmpty + ? Colors.black.withOpacity(0.7) + : null, + ), + if (snapshot.hasData && snapshot.data!.isNotEmpty) + Positioned( + top: 0, + left: 0, + child: Padding( + padding: const EdgeInsets.all(4), + child: Container( + decoration: BoxDecoration( + color: primaryColor(context), + borderRadius: BorderRadius.circular(5)), + child: const Padding( + padding: EdgeInsets.all(2), + child: Text( + "In library", + style: TextStyle(fontSize: 10), + ), + ), + ), + )), + ], + ), + ); + }), ); }, - loading: () => SizedBox( - width: 60, - child: Column(children: [ - ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Container( - color: Theme.of(context).cardColor, - width: 80, - height: 120, + loading: () => Padding( + padding: const EdgeInsets.only(left: 10), + child: SizedBox( + width: 100, + child: Column(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Container( + color: Theme.of(context).cardColor, + width: 100, + height: 140, + ), ), - ), - BottomTextWidget( - fontSize: 12.0, - text: widget.manga.name!, - isLoading: true, - isComfortableGrid: true, - ) - ]), + BottomTextWidget( + fontSize: 12.0, + text: widget.manga.name!, + isLoading: true, + isComfortableGrid: true, + ) + ]), + ), ), error: (error, stackTrace) => Center(child: Text(error.toString())), ); diff --git a/lib/modules/browse/sources/sources_screen.dart b/lib/modules/browse/sources/sources_screen.dart index e14ba33..c343de8 100644 --- a/lib/modules/browse/sources/sources_screen.dart +++ b/lib/modules/browse/sources/sources_screen.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:grouped_list/grouped_list.dart'; import 'package:isar/isar.dart'; import 'package:mangayomi/main.dart'; -import 'package:mangayomi/models/manga_type.dart'; import 'package:mangayomi/models/source.dart'; +import 'package:mangayomi/modules/browse/sources/widgets/source_list_tile.dart'; import 'package:mangayomi/utils/lang.dart'; import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart'; @@ -15,124 +14,156 @@ class SourcesScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return Padding( - padding: const EdgeInsets.only(top: 10), - child: StreamBuilder( - stream: isar.sources - .filter() - .idIsNotNull() - .isAddedEqualTo(true) - .and() - .isActiveEqualTo(true) - .watch(fireImmediately: true), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center(child: Text("Empty")); - } - final entries = snapshot.data! - .where((element) => ref.watch(showNSFWStateProvider) - ? true - : element.isNsfw == false) - .toList(); - return GroupedListView( - 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, Source element) { - 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( - // httpHeaders: ref.watch( - // headersProvider(source: element.sourceName!)), - // imageUrl: element.logoUrl!, - // fit: BoxFit.contain, - // width: 37, - // height: 37, - // errorWidget: (context, url, error) { - // return const SizedBox( - // width: 37, - // height: 37, - // child: Center( - // child: Icon(Icons.source_outlined), - // ), - // ); - // }, - // ), - ), - subtitle: Row( - children: [ - Text( - completeLang(element.lang!.toLowerCase()), - style: const TextStyle( - fontWeight: FontWeight.w300, fontSize: 12), - ), - if (element.isNsfw!) - Row( + padding: const EdgeInsets.only(top: 10), + child: SingleChildScrollView( + child: Column( + children: [ + StreamBuilder( + stream: isar.sources + .filter() + .idIsNotNull() + .isAddedEqualTo(true) + .and() + .isActiveEqualTo(true) + .and() + .lastUsedEqualTo(true) + .watch(fireImmediately: true), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: Text("")); + } + final entries = snapshot.data! + .where((element) => ref.watch(showNSFWStateProvider) + ? true + : element.isNsfw == false) + .toList(); + return GroupedListView( + elements: entries, + groupBy: (element) => "", + groupSeparatorBuilder: (String groupByValue) => + const Padding( + padding: EdgeInsets.only(left: 12), + child: Row( children: [ - const SizedBox( - width: 2, - ), Text( - "18+", + "Last used", style: TextStyle( - fontWeight: FontWeight.w300, - fontSize: 10, - color: Colors.redAccent - .withBlue(5) - .withOpacity(0.8)), + fontWeight: FontWeight.bold, fontSize: 13), ), ], - ) - ], - ), - title: Text(element.sourceName!), - trailing: const SizedBox( - width: 110, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - 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, - ); - }), - ); + ), + ), + itemBuilder: (context, Source element) { + return SourceListTile( + source: element, + ); + }, + shrinkWrap: true, + groupComparator: (group1, group2) => + group1.compareTo(group2), + itemComparator: (item1, item2) => + item1.sourceName!.compareTo(item2.sourceName!), + order: GroupedListOrder.ASC, + ); + }), + StreamBuilder( + stream: isar.sources + .filter() + .idIsNotNull() + .isAddedEqualTo(true) + .and() + .isActiveEqualTo(true) + .and() + .isPinnedEqualTo(true) + .watch(fireImmediately: true), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: Text("")); + } + final entries = snapshot.data! + .where((element) => ref.watch(showNSFWStateProvider) + ? true + : element.isNsfw == false) + .toList(); + return GroupedListView( + elements: entries, + groupBy: (element) => "", + groupSeparatorBuilder: (String groupByValue) => + const Padding( + padding: EdgeInsets.only(left: 12), + child: Row( + children: [ + Text( + "Pinned", + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 13), + ), + ], + ), + ), + itemBuilder: (context, Source element) { + return SourceListTile( + source: element, + ); + }, + shrinkWrap: true, + groupComparator: (group1, group2) => + group1.compareTo(group2), + itemComparator: (item1, item2) => + item1.sourceName!.compareTo(item2.sourceName!), + order: GroupedListOrder.ASC, + ); + }), + StreamBuilder( + stream: isar.sources + .filter() + .idIsNotNull() + .isAddedEqualTo(true) + .and() + .isActiveEqualTo(true) + .and() + .isPinnedEqualTo(false) + .watch(fireImmediately: true), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: Text("Empty")); + } + final entries = snapshot.data! + .where((element) => ref.watch(showNSFWStateProvider) + ? true + : element.isNsfw == false) + .toList(); + return GroupedListView( + 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.bold, fontSize: 13), + ), + ], + ), + ), + itemBuilder: (context, Source element) { + return SourceListTile( + source: element, + ); + }, + shrinkWrap: true, + groupComparator: (group1, group2) => + group1.compareTo(group2), + itemComparator: (item1, item2) => + item1.sourceName!.compareTo(item2.sourceName!), + order: GroupedListOrder.ASC, + ); + }), + ], + ), + )); } } diff --git a/lib/modules/browse/sources/widgets/source_list_tile.dart b/lib/modules/browse/sources/widgets/source_list_tile.dart new file mode 100644 index 0000000..6c52546 --- /dev/null +++ b/lib/modules/browse/sources/widgets/source_list_tile.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:isar/isar.dart'; +import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/manga_type.dart'; +import 'package:mangayomi/models/source.dart'; +import 'package:mangayomi/utils/colors.dart'; +import 'package:mangayomi/utils/lang.dart'; + +class SourceListTile extends StatelessWidget { + final Source source; + const SourceListTile({super.key, required this.source}); + + @override + Widget build(BuildContext context) { + return ListTile( + onTap: () { + final sources = isar.sources.filter().idIsNotNull().findAllSync(); + isar.writeTxnSync(() { + for (var src in sources) { + isar.sources + .putSync(src..lastUsed = src.id == source.id ? true : false); + } + }); + + context.push('/mangaHome', + extra: MangaType( + isFullData: source.isFullData, + lang: source.lang, + source: source.sourceName)); + }, + leading: Container( + height: 37, + width: 37, + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(5)), + child: + // source.logoUrl!.isEmpty + // ? + const Icon(Icons.source_outlined) + // : CachedNetworkImage( + // httpHeaders: ref.watch( + // headersProvider(source: source.sourceName!)), + // imageUrl: source.logoUrl!, + // fit: BoxFit.contain, + // width: 37, + // height: 37, + // errorWidget: (context, url, error) { + // return const SizedBox( + // width: 37, + // height: 37, + // child: Center( + // child: Icon(Icons.source_outlined), + // ), + // ); + // }, + // ), + ), + subtitle: Row( + children: [ + Text( + completeLang(source.lang!.toLowerCase()), + style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 12), + ), + if (source.isNsfw!) + Row( + children: [ + const SizedBox( + width: 2, + ), + Text( + "18+", + style: TextStyle( + fontWeight: FontWeight.w300, + fontSize: 10, + color: Colors.redAccent.withBlue(5).withOpacity(0.8)), + ), + ], + ) + ], + ), + title: Text(source.sourceName!), + trailing: IconButton( + onPressed: () { + isar.writeTxnSync(() => + isar.sources.putSync(source..isPinned = !source.isPinned!)); + }, + icon: Icon( + Icons.push_pin_outlined, + color: source.isPinned! ? primaryColor(context) : null, + )), + ); + } +} diff --git a/lib/modules/more/settings/appearance/widgets/dark_mode_button.dart b/lib/modules/more/settings/appearance/widgets/dark_mode_button.dart index 4bd3df0..52fbee2 100644 --- a/lib/modules/more/settings/appearance/widgets/dark_mode_button.dart +++ b/lib/modules/more/settings/appearance/widgets/dark_mode_button.dart @@ -16,13 +16,13 @@ class DarkModeButton extends ConsumerStatefulWidget { class _DarkModeButtonState extends ConsumerState { @override Widget build(BuildContext context) { - bool isLight = ref.watch(themeModeStateProvider); + bool isDark = ref.watch(themeModeStateProvider); return SwitchListTile( onChanged: (value) { if (value) { - ref.read(themeModeStateProvider.notifier).setLightTheme(); - } else { ref.read(themeModeStateProvider.notifier).setDarkTheme(); + } else { + ref.read(themeModeStateProvider.notifier).setLightTheme(); } }, title: const Text( @@ -30,10 +30,10 @@ class _DarkModeButtonState extends ConsumerState { style: TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( - isLight ? 'Off' : 'On', + !isDark ? 'Off' : 'On', style: TextStyle(fontSize: 11, color: secondaryColor(context)), ), - value: isLight, + value: !isDark, ); } } diff --git a/lib/modules/widgets/manga_image_card_widget.dart b/lib/modules/widgets/manga_image_card_widget.dart index a9811f4..5c3f4b7 100644 --- a/lib/modules/widgets/manga_image_card_widget.dart +++ b/lib/modules/widgets/manga_image_card_widget.dart @@ -8,6 +8,7 @@ import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/sources/service.dart'; import 'package:mangayomi/utils/cached_network.dart'; +import 'package:mangayomi/utils/colors.dart'; import 'package:mangayomi/utils/headers.dart'; import 'package:mangayomi/modules/widgets/bottom_text_widget.dart'; import 'package:mangayomi/modules/widgets/cover_view_widget.dart'; @@ -30,16 +31,50 @@ class MangaImageCardWidget extends ConsumerWidget { pushToMangaReaderDetail( context: context, getManga: getMangaDetail!, lang: lang); }, - child: CoverViewWidget(children: [ - cachedNetworkImage( - headers: - ref.watch(headersProvider(source: getMangaDetail!.source!)), - imageUrl: getMangaDetail!.imageUrl!, - width: 200, - height: 270, - fit: BoxFit.cover), - BottomTextWidget(text: getMangaDetail!.name!) - ]), + child: StreamBuilder( + stream: isar.mangas + .filter() + .langEqualTo(lang) + .nameEqualTo(getMangaDetail!.name) + .sourceEqualTo(getMangaDetail!.source) + .favoriteEqualTo(true) + .watch(fireImmediately: true), + builder: (context, snapshot) { + return CoverViewWidget(children: [ + cachedNetworkImage( + headers: ref + .watch(headersProvider(source: getMangaDetail!.source!)), + imageUrl: getMangaDetail!.imageUrl!, + width: 200, + height: 270, + fit: BoxFit.cover), + Container( + color: snapshot.hasData && snapshot.data!.isNotEmpty + ? Colors.black.withOpacity(0.7) + : null, + ), + if (snapshot.hasData && snapshot.data!.isNotEmpty) + Positioned( + top: 0, + left: 0, + child: Padding( + padding: const EdgeInsets.all(4), + child: Container( + decoration: BoxDecoration( + color: primaryColor(context), + borderRadius: BorderRadius.circular(5)), + child: const Padding( + padding: EdgeInsets.all(2), + child: Text( + "In library", + style: TextStyle(fontSize: 12), + ), + ), + ), + )), + BottomTextWidget(text: getMangaDetail!.name!) + ]); + }), ); } } diff --git a/lib/services/http_service/cloudflare/cloudflare_bypass.dart b/lib/services/http_service/cloudflare/cloudflare_bypass.dart index 1a193d7..655706e 100644 --- a/lib/services/http_service/cloudflare/cloudflare_bypass.dart +++ b/lib/services/http_service/cloudflare/cloudflare_bypass.dart @@ -151,7 +151,7 @@ Future cloudflareBypassHtml(CloudflareBypassHtmlRef ref, } return false; }); - await Future.delayed(Duration(seconds: 10)); + await Future.delayed(const Duration(seconds: 10)); html = await controller.evaluateJavascript( source: "window.document.getElementsByTagName('html')[0].outerHTML;"); diff --git a/lib/sources/multisrc/madara/madara_source_list.dart b/lib/sources/multisrc/madara/madara_source_list.dart index 46d1651..fe5f315 100644 --- a/lib/sources/multisrc/madara/madara_source_list.dart +++ b/lib/sources/multisrc/madara/madara_source_list.dart @@ -84,19 +84,11 @@ List _madaraSourcesList = [ dateFormat: "MMMM dd, yyyy", dateFormatLocale: "tr", ), - Source( - sourceName: "Comictoon", - baseUrl: "https://comictoonthaith-new.com", - lang: "th", - typeSource: TypeSource.madara, - logoUrl: '', - dateFormat: "MMMMM dd, yyyy", - dateFormatLocale: "th", - ), + Source( sourceName: "CookieToon", baseUrl: "https://cookietoon.online", - lang: "pt-BR", + lang: "pt-br", typeSource: TypeSource.madara, logoUrl: '', dateFormat: "dd/MM/yyyy", @@ -105,7 +97,7 @@ List _madaraSourcesList = [ Source( sourceName: "Drope Scan", baseUrl: "https://dropescan.com", - lang: "pt-BR", + lang: "pt-br", typeSource: TypeSource.madara, logoUrl: '', dateFormat: "dd/MM/yyyy", @@ -123,7 +115,7 @@ List _madaraSourcesList = [ Source( sourceName: "Final Scans", baseUrl: "https://finalscans.com", - lang: "pt-BR", + lang: "pt-br", typeSource: TypeSource.madara, logoUrl: '', isNsfw: true, @@ -190,7 +182,7 @@ List _madaraSourcesList = [ Source( sourceName: "Kami Sama Explorer", baseUrl: "https://leitor.kamisama.com.br", - lang: "pt-BR", + lang: "pt-br", typeSource: TypeSource.madara, logoUrl: '', dateFormat: "dd 'de' MMMM 'de' yyyy", diff --git a/lib/sources/multisrc/mmrcms/src/mmrcms.dart b/lib/sources/multisrc/mmrcms/src/mmrcms.dart index 43775ea..ad23049 100644 --- a/lib/sources/multisrc/mmrcms/src/mmrcms.dart +++ b/lib/sources/multisrc/mmrcms/src/mmrcms.dart @@ -1,6 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:html/dom.dart'; import 'package:mangayomi/models/chapter.dart'; diff --git a/lib/sources/src/all/comick/comick_source_list.dart b/lib/sources/src/all/comick/comick_source_list.dart index a5bd042..d1bfdae 100644 --- a/lib/sources/src/all/comick/comick_source_list.dart +++ b/lib/sources/src/all/comick/comick_source_list.dart @@ -4,7 +4,7 @@ const logoUrl = 'https://comick.app/_next/image?url=%2Fstatic%2Ficons%2Funicorn-64.png&w=144&q=75'; const apiUrl = 'https://api.comick.fun'; const baseUrl = 'https://comick.app'; - +const isNsfw = true; List get comickSourcesList => _comickSourcesList; List _comickSourcesList = [ Source( @@ -15,6 +15,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -24,6 +25,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -33,6 +35,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -42,6 +45,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -51,6 +55,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -60,6 +65,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -69,6 +75,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -78,6 +85,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -87,6 +95,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -96,6 +105,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -105,6 +115,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -114,6 +125,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -123,6 +135,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -132,6 +145,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -141,6 +155,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -150,6 +165,7 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), Source( sourceName: 'Comick', @@ -159,5 +175,6 @@ List _comickSourcesList = [ typeSource: TypeSource.comick, logoUrl: logoUrl, dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'", + isNsfw: isNsfw, dateFormatLocale: "en"), ]; diff --git a/lib/sources/src/all/comick/src/comick.dart b/lib/sources/src/all/comick/src/comick.dart index 8198d76..68735f6 100644 --- a/lib/sources/src/all/comick/src/comick.dart +++ b/lib/sources/src/all/comick/src/comick.dart @@ -103,18 +103,18 @@ class Comick extends MangaYomiServices { required AutoDisposeFutureProviderRef ref}) async { final response = await ref.watch(httpGetProvider( url: - 'https://api.comick.fun/search?q=${query.trim()}&tachiyomi=true&page=1', + '${getMangaAPIUrl(source)}/v1.0/search?q=${query.trim()}&tachiyomi=true&limit=50&page=1', source: source, resDom: false) .future) as String?; - var popularManga = jsonDecode(response!) as List; - var popularMangaList = - popularManga.map((e) => MangaSearchModelComick.fromJson(e)).toList(); + var search = jsonDecode(response!) as List; + var searchList = + search.map((e) => MangaSearchModelComick.fromJson(e)).toList(); - for (var popular in popularMangaList) { - url.add("/comic/${popular.slug}"); - name.add(popular.title); - image.add(popular.coverUrl); + for (var search in searchList) { + url.add("/comic/${search.hid}#"); + name.add(search.title); + image.add(search.coverUrl); } return mangaRes(); diff --git a/lib/sources/src/all/comick/src/model/search_manga_cimick.dart b/lib/sources/src/all/comick/src/model/search_manga_cimick.dart index 7b0b308..102cc3c 100644 --- a/lib/sources/src/all/comick/src/model/search_manga_cimick.dart +++ b/lib/sources/src/all/comick/src/model/search_manga_cimick.dart @@ -32,6 +32,7 @@ class MangaSearchModelComick { title = json['title']; slug = json['slug']; + hid = json['hid']; coverUrl = json['cover_url']; } diff --git a/lib/sources/src/fr/mangakawaii/src/mangakawaii.dart b/lib/sources/src/fr/mangakawaii/src/mangakawaii.dart index 686793f..8d2ac49 100644 --- a/lib/sources/src/fr/mangakawaii/src/mangakawaii.dart +++ b/lib/sources/src/fr/mangakawaii/src/mangakawaii.dart @@ -212,10 +212,12 @@ class MangaKawaii extends MangaYomiServices { } return pageUrls; } - + @override - Future> getLatestUpdatesManga({required String source, required int page, required AutoDisposeFutureProviderRef ref}) { - // TODO: implement getLatestUpdatesManga + Future> getLatestUpdatesManga( + {required String source, + required int page, + required AutoDisposeFutureProviderRef ref}) { throw UnimplementedError(); } }