Add extension updates notification label
This commit is contained in:
parent
9b770027db
commit
4171b7efe7
6 changed files with 346 additions and 123 deletions
|
|
@ -234,5 +234,7 @@
|
|||
"email_adress":"Email Address",
|
||||
"password":"Password",
|
||||
"log_out_from":"Log out from {tracker}?",
|
||||
"log_out":"Log out"
|
||||
"log_out":"Log out",
|
||||
"update_pending":"Update pending",
|
||||
"update_all":"Update all"
|
||||
}
|
||||
|
|
@ -234,5 +234,7 @@
|
|||
"email_adress":"Adresse couriel",
|
||||
"password":"Mot de passe",
|
||||
"log_out_from":"Se déconnecter de {tracker} ?",
|
||||
"log_out":"Se déconnecter"
|
||||
"log_out":"Se déconnecter",
|
||||
"update_pending":"Mises à jour en attente",
|
||||
"update_all":"Tout mettre à jour"
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
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/source.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/extension_screen.dart';
|
||||
|
|
@ -123,8 +128,24 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
tabs: [
|
||||
Tab(text: l10n.manga_sources),
|
||||
Tab(text: l10n.anime_sources),
|
||||
Tab(text: l10n.manga_extensions),
|
||||
Tab(text: l10n.anime_extensions),
|
||||
Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.manga_extensions),
|
||||
const SizedBox(width: 8),
|
||||
_extensionUpdateNumbers(ref, true)
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.anime_extensions),
|
||||
const SizedBox(width: 8),
|
||||
_extensionUpdateNumbers(ref, false)
|
||||
],
|
||||
),
|
||||
),
|
||||
// Tab(text: l10n.migrate),
|
||||
],
|
||||
),
|
||||
|
|
@ -150,3 +171,42 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _extensionUpdateNumbers(WidgetRef ref, bool isManga) {
|
||||
return StreamBuilder(
|
||||
stream: isar.sources
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final entries = snapshot.data!
|
||||
.where((element) => ref.watch(showNSFWStateProvider)
|
||||
? true
|
||||
: element.isNsfw == false)
|
||||
.where((element) =>
|
||||
compareVersions(element.version!, element.versionLast!) < 0)
|
||||
.toList();
|
||||
return entries.isEmpty
|
||||
? Container()
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).focusColor),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(
|
||||
entries.length.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall!.color),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/providers/fetch_anime_sources.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/language.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/widgets/extension_list_tile_widget.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
|
|
@ -23,7 +24,7 @@ class ExtensionScreen extends ConsumerWidget {
|
|||
} else {
|
||||
ref.watch(fetchAnimeSourcesListProvider(id: null, reFresh: false));
|
||||
}
|
||||
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => isManga
|
||||
? ref.refresh(
|
||||
|
|
@ -32,59 +33,147 @@ class ExtensionScreen extends ConsumerWidget {
|
|||
fetchAnimeSourcesListProvider(id: null, reFresh: true).future),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: StreamBuilder(
|
||||
stream: query.isNotEmpty
|
||||
? isar.sources
|
||||
.filter()
|
||||
.nameContains(query.toLowerCase(), caseSensitive: false)
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true)
|
||||
: isar.sources
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final entries = snapshot.data!
|
||||
.where((element) => ref.watch(showNSFWStateProvider)
|
||||
? true
|
||||
: element.isNsfw == false)
|
||||
.toList();
|
||||
return GroupedListView<Source, String>(
|
||||
elements: entries,
|
||||
groupBy: (element) =>
|
||||
completeLanguageName(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),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
StreamBuilder(
|
||||
stream: query.isNotEmpty
|
||||
? isar.sources
|
||||
.filter()
|
||||
.nameContains(query.toLowerCase(),
|
||||
caseSensitive: false)
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true)
|
||||
: isar.sources
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final entries = snapshot.data!
|
||||
.where((element) => ref.watch(showNSFWStateProvider)
|
||||
? true
|
||||
: element.isNsfw == false)
|
||||
.where((element) =>
|
||||
compareVersions(
|
||||
element.version!, element.versionLast!) <
|
||||
0)
|
||||
.toList();
|
||||
return GroupedListView<Source, String>(
|
||||
elements: entries,
|
||||
groupBy: (element) => "",
|
||||
groupSeparatorBuilder: (_) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
l10n.update_pending,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 13),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
for (var source in entries) {
|
||||
source.isManga!
|
||||
? await ref.watch(
|
||||
fetchMangaSourcesListProvider(
|
||||
id: source.id,
|
||||
reFresh: true)
|
||||
.future)
|
||||
: await ref.watch(
|
||||
fetchAnimeSourcesListProvider(
|
||||
id: source.id,
|
||||
reFresh: true)
|
||||
.future);
|
||||
}
|
||||
},
|
||||
child: Text(l10n.update_all))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, Source element) {
|
||||
return ExtensionListTileWidget(
|
||||
source: element,
|
||||
);
|
||||
},
|
||||
groupComparator: (group1, group2) => group1.compareTo(group2),
|
||||
itemComparator: (item1, item2) =>
|
||||
item1.name!.compareTo(item2.name!),
|
||||
order: GroupedListOrder.ASC,
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
itemBuilder: (context, Source element) {
|
||||
return ExtensionListTileWidget(
|
||||
source: element,
|
||||
);
|
||||
},
|
||||
groupComparator: (group1, group2) =>
|
||||
group1.compareTo(group2),
|
||||
shrinkWrap: true,
|
||||
itemComparator: (item1, item2) =>
|
||||
item1.name!.compareTo(item2.name!),
|
||||
order: GroupedListOrder.ASC,
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: query.isNotEmpty
|
||||
? isar.sources
|
||||
.filter()
|
||||
.nameContains(query.toLowerCase(),
|
||||
caseSensitive: false)
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true)
|
||||
: isar.sources
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.isMangaEqualTo(isManga)
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final entries = snapshot.data!
|
||||
.where((element) => ref.watch(showNSFWStateProvider)
|
||||
? true
|
||||
: element.isNsfw == false)
|
||||
.where((element) =>
|
||||
element.version == element.versionLast!)
|
||||
.toList();
|
||||
return GroupedListView<Source, String>(
|
||||
elements: entries,
|
||||
groupBy: (element) =>
|
||||
completeLanguageName(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 ExtensionListTileWidget(
|
||||
source: element,
|
||||
);
|
||||
},
|
||||
groupComparator: (group1, group2) =>
|
||||
group1.compareTo(group2),
|
||||
itemComparator: (item1, item2) =>
|
||||
item1.name!.compareTo(item2.name!),
|
||||
shrinkWrap: true,
|
||||
order: GroupedListOrder.ASC,
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/providers/fetch_manga_sources.dart';
|
||||
import 'package:mangayomi/modules/more/about/providers/check_for_update.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/router/router.dart';
|
||||
import 'package:mangayomi/utils/colors.dart';
|
||||
|
|
@ -89,65 +94,82 @@ class MainScreen extends ConsumerWidget {
|
|||
_ => 100,
|
||||
},
|
||||
},
|
||||
child: NavigationRailTheme(
|
||||
data: NavigationRailThemeData(
|
||||
indicatorShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30)),
|
||||
),
|
||||
child: NavigationRail(
|
||||
labelType: NavigationRailLabelType.all,
|
||||
useIndicator: true,
|
||||
destinations: [
|
||||
NavigationRailDestination(
|
||||
selectedIcon:
|
||||
const Icon(Icons.collections_bookmark),
|
||||
icon: const Icon(
|
||||
Icons.collections_bookmark_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.manga))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon:
|
||||
const Icon(Icons.video_collection),
|
||||
icon: const Icon(
|
||||
Icons.video_collection_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.anime))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.history),
|
||||
icon: const Icon(Icons.history_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.history))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.explore),
|
||||
icon: const Icon(Icons.explore_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.browse))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.more_horiz),
|
||||
icon: const Icon(Icons.more_horiz_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.more))),
|
||||
],
|
||||
selectedIndex: currentIndex,
|
||||
onDestinationSelected: (newIndex) {
|
||||
if (newIndex == 0) {
|
||||
route.go('/MangaLibrary');
|
||||
} else if (newIndex == 1) {
|
||||
route.go('/AnimeLibrary');
|
||||
} else if (newIndex == 2) {
|
||||
route.go('/history');
|
||||
} else if (newIndex == 3) {
|
||||
route.go('/browse');
|
||||
} else if (newIndex == 4) {
|
||||
route.go('/more');
|
||||
}
|
||||
},
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
NavigationRailTheme(
|
||||
data: NavigationRailThemeData(
|
||||
indicatorShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30)),
|
||||
),
|
||||
child: NavigationRail(
|
||||
labelType: NavigationRailLabelType.all,
|
||||
useIndicator: true,
|
||||
destinations: [
|
||||
NavigationRailDestination(
|
||||
selectedIcon: const Icon(
|
||||
Icons.collections_bookmark),
|
||||
icon: const Icon(Icons
|
||||
.collections_bookmark_outlined),
|
||||
label: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.manga))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon:
|
||||
const Icon(Icons.video_collection),
|
||||
icon: const Icon(
|
||||
Icons.video_collection_outlined),
|
||||
label: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.anime))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.history),
|
||||
icon:
|
||||
const Icon(Icons.history_outlined),
|
||||
label: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.history))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.explore),
|
||||
icon:
|
||||
const Icon(Icons.explore_outlined),
|
||||
label: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.browse))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon:
|
||||
const Icon(Icons.more_horiz),
|
||||
icon: const Icon(
|
||||
Icons.more_horiz_outlined),
|
||||
label: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.more))),
|
||||
],
|
||||
selectedIndex: currentIndex,
|
||||
onDestinationSelected: (newIndex) {
|
||||
if (newIndex == 0) {
|
||||
route.go('/MangaLibrary');
|
||||
} else if (newIndex == 1) {
|
||||
route.go('/AnimeLibrary');
|
||||
} else if (newIndex == 2) {
|
||||
route.go('/history');
|
||||
} else if (newIndex == 3) {
|
||||
route.go('/browse');
|
||||
} else if (newIndex == 4) {
|
||||
route.go('/more');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 7,
|
||||
top: 210,
|
||||
child: _extensionUpdateTotalNumbers(ref)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: child)
|
||||
|
|
@ -197,10 +219,18 @@ class MainScreen extends ConsumerWidget {
|
|||
selectedIcon: const Icon(Icons.history),
|
||||
icon: const Icon(Icons.history_outlined),
|
||||
label: l10n.history),
|
||||
NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.explore),
|
||||
icon: const Icon(Icons.explore_outlined),
|
||||
label: l10n.browse),
|
||||
Stack(
|
||||
children: [
|
||||
NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.explore),
|
||||
icon: const Icon(Icons.explore_outlined),
|
||||
label: l10n.browse),
|
||||
Positioned(
|
||||
right: 14,
|
||||
top: 3,
|
||||
child: _extensionUpdateTotalNumbers(ref)),
|
||||
],
|
||||
),
|
||||
NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.more_horiz),
|
||||
icon: const Icon(Icons.more_horiz_outlined),
|
||||
|
|
@ -229,3 +259,42 @@ class MainScreen extends ConsumerWidget {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _extensionUpdateTotalNumbers(WidgetRef ref) {
|
||||
return StreamBuilder(
|
||||
stream: isar.sources
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.isActiveEqualTo(true)
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final entries = snapshot.data!
|
||||
.where((element) => ref.watch(showNSFWStateProvider)
|
||||
? true
|
||||
: element.isNsfw == false)
|
||||
.where((element) =>
|
||||
compareVersions(element.version!, element.versionLast!) < 0)
|
||||
.toList();
|
||||
return entries.isEmpty
|
||||
? Container()
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: const Color.fromARGB(255, 176, 46, 37)),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
|
||||
child: Text(
|
||||
entries.length.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).textTheme.bodySmall!.color),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,10 @@ class DownloadQueueScreen extends ConsumerWidget {
|
|||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 3),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).focusColor,
|
||||
radius: 10,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).focusColor),
|
||||
child: Text(
|
||||
allQueueLength.toString(),
|
||||
style: TextStyle(
|
||||
|
|
|
|||
Loading…
Reference in a new issue