Manage trackers

This commit is contained in:
kodjomoustapha 2023-11-28 17:40:52 +01:00
parent 8c846edbf5
commit eaca422612
17 changed files with 492 additions and 122 deletions

View file

@ -261,6 +261,7 @@
"restore_backup_warning_title":"Restoring a backup will overwrite all existing data.\n\nContinue restoring?",
"services":"Services",
"tracking_warning_info":"One-way sync yo update the chapter progress in tracking services. Set up tracking for individual entries from their tracking button.",
"use_page_tap_zones":"Use page tap zones"
"use_page_tap_zones":"Use page tap zones" ,
"manage_trackers":"Manage trackers"
}

View file

@ -261,5 +261,6 @@
"restore_backup_warning_title":"La restauration d'une sauvegarde écrasera toutes les données existantes.\n\nContinuer la restauration ?",
"services":"Services",
"tracking_warning_info":"Synchronisation à sens unique pour mettre à jour la progression du chapitre dans les services de suivi. Configurez le suivi des entrées individuelles à partir de leur boutton de suivi.",
"use_page_tap_zones":"Utiliser les zones tactiles"
"use_page_tap_zones":"Utiliser les zones tactiles",
"manage_trackers":"Gérer les suivis"
}

View file

@ -31,6 +31,8 @@ class Track {
String? trackingUrl;
bool? isManga;
Track(
{this.id = Isar.autoIncrement,
this.libraryId,
@ -44,7 +46,8 @@ class Track {
required this.status,
this.startedReadingDate,
this.finishedReadingDate,
this.trackingUrl});
this.trackingUrl,
this.isManga});
Track.fromJson(Map<String, dynamic> json) {
finishedReadingDate = json['finishedReadingDate'];
id = json['id'];
@ -59,6 +62,7 @@ class Track {
title = json['title'];
totalChapter = json['totalChapter'];
trackingUrl = json['trackingUrl'];
isManga = json['isManga'];
}
Map<String, dynamic> toJson() {
@ -76,6 +80,7 @@ class Track {
data['title'] = title;
data['totalChapter'] = totalChapter;
data['trackingUrl'] = trackingUrl;
data['isManga'] = isManga;
return data;
}
}

View file

@ -22,59 +22,64 @@ const TrackSchema = CollectionSchema(
name: r'finishedReadingDate',
type: IsarType.long,
),
r'lastChapterRead': PropertySchema(
r'isManga': PropertySchema(
id: 1,
name: r'isManga',
type: IsarType.bool,
),
r'lastChapterRead': PropertySchema(
id: 2,
name: r'lastChapterRead',
type: IsarType.long,
),
r'libraryId': PropertySchema(
id: 2,
id: 3,
name: r'libraryId',
type: IsarType.long,
),
r'mangaId': PropertySchema(
id: 3,
id: 4,
name: r'mangaId',
type: IsarType.long,
),
r'mediaId': PropertySchema(
id: 4,
id: 5,
name: r'mediaId',
type: IsarType.long,
),
r'score': PropertySchema(
id: 5,
id: 6,
name: r'score',
type: IsarType.long,
),
r'startedReadingDate': PropertySchema(
id: 6,
id: 7,
name: r'startedReadingDate',
type: IsarType.long,
),
r'status': PropertySchema(
id: 7,
id: 8,
name: r'status',
type: IsarType.byte,
enumMap: _TrackstatusEnumValueMap,
),
r'syncId': PropertySchema(
id: 8,
id: 9,
name: r'syncId',
type: IsarType.long,
),
r'title': PropertySchema(
id: 9,
id: 10,
name: r'title',
type: IsarType.string,
),
r'totalChapter': PropertySchema(
id: 10,
id: 11,
name: r'totalChapter',
type: IsarType.long,
),
r'trackingUrl': PropertySchema(
id: 11,
id: 12,
name: r'trackingUrl',
type: IsarType.string,
)
@ -121,17 +126,18 @@ void _trackSerialize(
Map<Type, List<int>> allOffsets,
) {
writer.writeLong(offsets[0], object.finishedReadingDate);
writer.writeLong(offsets[1], object.lastChapterRead);
writer.writeLong(offsets[2], object.libraryId);
writer.writeLong(offsets[3], object.mangaId);
writer.writeLong(offsets[4], object.mediaId);
writer.writeLong(offsets[5], object.score);
writer.writeLong(offsets[6], object.startedReadingDate);
writer.writeByte(offsets[7], object.status.index);
writer.writeLong(offsets[8], object.syncId);
writer.writeString(offsets[9], object.title);
writer.writeLong(offsets[10], object.totalChapter);
writer.writeString(offsets[11], object.trackingUrl);
writer.writeBool(offsets[1], object.isManga);
writer.writeLong(offsets[2], object.lastChapterRead);
writer.writeLong(offsets[3], object.libraryId);
writer.writeLong(offsets[4], object.mangaId);
writer.writeLong(offsets[5], object.mediaId);
writer.writeLong(offsets[6], object.score);
writer.writeLong(offsets[7], object.startedReadingDate);
writer.writeByte(offsets[8], object.status.index);
writer.writeLong(offsets[9], object.syncId);
writer.writeString(offsets[10], object.title);
writer.writeLong(offsets[11], object.totalChapter);
writer.writeString(offsets[12], object.trackingUrl);
}
Track _trackDeserialize(
@ -143,18 +149,19 @@ Track _trackDeserialize(
final object = Track(
finishedReadingDate: reader.readLongOrNull(offsets[0]),
id: id,
lastChapterRead: reader.readLongOrNull(offsets[1]),
libraryId: reader.readLongOrNull(offsets[2]),
mangaId: reader.readLongOrNull(offsets[3]),
mediaId: reader.readLongOrNull(offsets[4]),
score: reader.readLongOrNull(offsets[5]),
startedReadingDate: reader.readLongOrNull(offsets[6]),
status: _TrackstatusValueEnumMap[reader.readByteOrNull(offsets[7])] ??
isManga: reader.readBoolOrNull(offsets[1]),
lastChapterRead: reader.readLongOrNull(offsets[2]),
libraryId: reader.readLongOrNull(offsets[3]),
mangaId: reader.readLongOrNull(offsets[4]),
mediaId: reader.readLongOrNull(offsets[5]),
score: reader.readLongOrNull(offsets[6]),
startedReadingDate: reader.readLongOrNull(offsets[7]),
status: _TrackstatusValueEnumMap[reader.readByteOrNull(offsets[8])] ??
TrackStatus.reading,
syncId: reader.readLongOrNull(offsets[8]),
title: reader.readStringOrNull(offsets[9]),
totalChapter: reader.readLongOrNull(offsets[10]),
trackingUrl: reader.readStringOrNull(offsets[11]),
syncId: reader.readLongOrNull(offsets[9]),
title: reader.readStringOrNull(offsets[10]),
totalChapter: reader.readLongOrNull(offsets[11]),
trackingUrl: reader.readStringOrNull(offsets[12]),
);
return object;
}
@ -169,7 +176,7 @@ P _trackDeserializeProp<P>(
case 0:
return (reader.readLongOrNull(offset)) as P;
case 1:
return (reader.readLongOrNull(offset)) as P;
return (reader.readBoolOrNull(offset)) as P;
case 2:
return (reader.readLongOrNull(offset)) as P;
case 3:
@ -181,15 +188,17 @@ P _trackDeserializeProp<P>(
case 6:
return (reader.readLongOrNull(offset)) as P;
case 7:
return (reader.readLongOrNull(offset)) as P;
case 8:
return (_TrackstatusValueEnumMap[reader.readByteOrNull(offset)] ??
TrackStatus.reading) as P;
case 8:
return (reader.readLongOrNull(offset)) as P;
case 9:
return (reader.readStringOrNull(offset)) as P;
case 10:
return (reader.readLongOrNull(offset)) as P;
case 10:
return (reader.readStringOrNull(offset)) as P;
case 11:
return (reader.readLongOrNull(offset)) as P;
case 12:
return (reader.readStringOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@ -447,6 +456,32 @@ extension TrackQueryFilter on QueryBuilder<Track, Track, QFilterCondition> {
});
}
QueryBuilder<Track, Track, QAfterFilterCondition> isMangaIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'isManga',
));
});
}
QueryBuilder<Track, Track, QAfterFilterCondition> isMangaIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'isManga',
));
});
}
QueryBuilder<Track, Track, QAfterFilterCondition> isMangaEqualTo(
bool? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'isManga',
value: value,
));
});
}
QueryBuilder<Track, Track, QAfterFilterCondition> lastChapterReadIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@ -1358,6 +1393,18 @@ extension TrackQuerySortBy on QueryBuilder<Track, Track, QSortBy> {
});
}
QueryBuilder<Track, Track, QAfterSortBy> sortByIsManga() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isManga', Sort.asc);
});
}
QueryBuilder<Track, Track, QAfterSortBy> sortByIsMangaDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isManga', Sort.desc);
});
}
QueryBuilder<Track, Track, QAfterSortBy> sortByLastChapterRead() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'lastChapterRead', Sort.asc);
@ -1516,6 +1563,18 @@ extension TrackQuerySortThenBy on QueryBuilder<Track, Track, QSortThenBy> {
});
}
QueryBuilder<Track, Track, QAfterSortBy> thenByIsManga() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isManga', Sort.asc);
});
}
QueryBuilder<Track, Track, QAfterSortBy> thenByIsMangaDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isManga', Sort.desc);
});
}
QueryBuilder<Track, Track, QAfterSortBy> thenByLastChapterRead() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'lastChapterRead', Sort.asc);
@ -1656,6 +1715,12 @@ extension TrackQueryWhereDistinct on QueryBuilder<Track, Track, QDistinct> {
});
}
QueryBuilder<Track, Track, QDistinct> distinctByIsManga() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isManga');
});
}
QueryBuilder<Track, Track, QDistinct> distinctByLastChapterRead() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'lastChapterRead');
@ -1738,6 +1803,12 @@ extension TrackQueryProperty on QueryBuilder<Track, Track, QQueryProperty> {
});
}
QueryBuilder<Track, bool?, QQueryOperations> isMangaProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isManga');
});
}
QueryBuilder<Track, int?, QQueryOperations> lastChapterReadProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'lastChapterRead');

View file

@ -3,6 +3,8 @@ import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/track.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'migration.g.dart';
@ -20,25 +22,32 @@ Future<void> migration(MigrationRef ref) async {
.idIsNotNull()
.isMangaIsNull()
.findAllSync();
final tracks =
isar.tracks.filter().idIsNotNull().isMangaIsNull().findAllSync();
isar.writeTxnSync(() {
//mangaId in chapter
for (var chapter in chapters) {
final mangaId = chapter.manga.value!.id;
final mangaId = chapter.manga.value?.id;
isar.chapters.putSync(chapter..mangaId = mangaId);
}
//mangaId in Download
for (var download in downloads) {
final mangaId = download.chapter.value!.manga.value!.id;
final mangaId = download.chapter.value?.manga.value?.id;
isar.downloads.putSync(download..mangaId = mangaId);
}
//chapterId and isManga in History
for (var history in histories) {
final chapterId = history.chapter.value!.id;
final isManga = history.chapter.value!.manga.value!.isManga;
final chapterId = history.chapter.value?.id;
final isManga = history.chapter.value?.manga.value?.isManga;
isar.historys.putSync(history
..chapterId = chapterId
..isManga = isManga);
}
// isManga in Track
for (var track in tracks) {
final isManga = isar.mangas.getSync(track.mangaId!)?.isManga;
isar.tracks.putSync(track..isManga = isManga);
}
});
}

View file

@ -6,7 +6,7 @@ part of 'migration.dart';
// RiverpodGenerator
// **************************************************************************
String _$migrationHash() => r'c37210dc71317d8be30be363f78793ff5c9c6fd1';
String _$migrationHash() => r'6e66a54603736b638f834ffa9e2dbe03ec3f494e';
/// See also [migration].
@ProviderFor(migration)

View file

@ -1366,7 +1366,9 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
return Column(
children: [
Icon(
isNotEmpty ? Icons.done_rounded : Icons.sync_outlined,
isNotEmpty
? Icons.done_rounded
: Icons.sync_outlined,
size: 20,
color: color,
),
@ -1688,7 +1690,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
return trackRes!.isNotEmpty
? TrackerWidget(
mangaId: widget.manga!.id!,
trackPreference: entries[index],
syncId: entries[index].syncId!,
trackRes: trackRes.first,
isManga: widget.manga!.isManga!)
: TrackListile(

View file

@ -52,7 +52,7 @@ class TrackState extends _$TrackState {
ref
.read(tracksProvider(syncId: track!.syncId!).notifier)
.updateTrackManga(updateTrack!);
.updateTrackManga(updateTrack!, isManga);
}
int getScoreMaxValue() {
@ -133,14 +133,12 @@ class TrackState extends _$TrackState {
.read(anilistProvider(syncId: syncId, isManga: isManga).notifier)
.findLibAnime(track);
findManga ??= isManga!
? await ref
.read(
anilistProvider(syncId: syncId, isManga: isManga).notifier)
.addLibManga(track)
: await ref
.read(
anilistProvider(syncId: syncId, isManga: isManga).notifier)
.addLibAnime(track);
? await ref
.read(anilistProvider(syncId: syncId, isManga: isManga).notifier)
.addLibManga(track)
: await ref
.read(anilistProvider(syncId: syncId, isManga: isManga).notifier)
.addLibAnime(track);
} else if (syncId == 3) {
findManga = isManga!
? await ref
@ -152,7 +150,7 @@ class TrackState extends _$TrackState {
}
ref
.read(tracksProvider(syncId: syncId).notifier)
.updateTrackManga(findManga!);
.updateTrackManga(findManga!, isManga);
}
List<TrackStatus> getStatusList() {

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/models/track_search.dart';
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
import 'package:mangayomi/modules/manga/detail/widgets/tracker_search_widget.dart';
@ -18,13 +17,15 @@ class TrackerWidget extends ConsumerStatefulWidget {
final bool isManga;
final Track trackRes;
final int mangaId;
final TrackPreference trackPreference;
final int syncId;
final bool hide;
const TrackerWidget(
{super.key,
required this.isManga,
required this.trackPreference,
required this.syncId,
required this.trackRes,
required this.mangaId});
required this.mangaId,
this.hide = false});
@override
ConsumerState<TrackerWidget> createState() => _TrackerWidgetState();
@ -46,8 +47,8 @@ class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
.findManga();
if (mounted) {
ref
.read(tracksProvider(syncId: widget.trackPreference.syncId!).notifier)
.updateTrackManga(findManga!);
.read(tracksProvider(syncId: widget.syncId).notifier)
.updateTrackManga(findManga!, widget.isManga);
}
}
@ -65,39 +66,43 @@ class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
if (!widget.hide)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12,vertical: 5),
child: Container(
color: const Color.fromRGBO(18, 25, 35, 1),
width: 70,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: trackInfos(widget.syncId).$3),
width: 50,
height: 45,
child: Image.asset(
trackInfos(widget.trackPreference.syncId!).$1,
trackInfos(widget.syncId).$1,
height: 30,
),
),
),
),
Expanded(
child: _elevatedButton(
context,
borderRadius:
const BorderRadius.only(topRight: Radius.circular(20)),
onPressed: () async {
final trackSearch = await trackersSearchraggableMenu(
context,
isManga: widget.isManga,
track: widget.trackRes) as TrackSearch?;
if (trackSearch != null) {
await ref
.read(trackStateProvider(
track: null, isManga: widget.isManga)
.notifier)
.setTrackSearch(trackSearch, widget.mangaId,
widget.trackPreference.syncId!);
}
},
borderRadius: const BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
onPressed: !widget.hide
? () async {
final trackSearch = await trackersSearchraggableMenu(
context,
isManga: widget.isManga,
track: widget.trackRes) as TrackSearch?;
if (trackSearch != null) {
await ref
.read(trackStateProvider(
track: null, isManga: widget.isManga)
.notifier)
.setTrackSearch(
trackSearch, widget.mangaId, widget.syncId);
}
}
: null,
child: Row(
children: [
Expanded(
@ -120,8 +125,7 @@ class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
IconButton(
onPressed: () {
ref
.read(tracksProvider(
syncId: widget.trackPreference.syncId!)
.read(tracksProvider(syncId: widget.syncId)
.notifier)
.deleteTrackManga(widget.trackRes);
},
@ -445,7 +449,7 @@ class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
}
Widget _elevatedButton(BuildContext context,
{required VoidCallback onPressed,
{required Function()? onPressed,
String text = "",
Widget? child,
BorderRadiusGeometry? borderRadius}) {

View file

@ -0,0 +1,80 @@
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/track_preference.dart';
import 'package:mangayomi/modules/widgets/gridview_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/media_query.dart';
class ManageTrackersScreen extends StatefulWidget {
const ManageTrackersScreen({super.key});
@override
State<ManageTrackersScreen> createState() => _ManageTrackersScreenState();
}
class _ManageTrackersScreenState extends State<ManageTrackersScreen> {
late List<TrackPreference> trackPreferences = [];
@override
void initState() {
trackPreferences =
isar.trackPreferences.filter().syncIdIsNotNull().findAllSync();
// trackPreferences.insert(0, TrackPreference(syncId: -1));
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(context.l10n.manage_trackers)),
body: GridViewWidget(
childAspectRatio: 0.69,
itemCount: trackPreferences.length,
itemBuilder: (context, index) {
final trackerPref = trackPreferences[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: MaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
onPressed: () {
context.push('/trackingDetail', extra: trackerPref);
},
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Container(
decoration: BoxDecoration(
color: trackerPref.syncId == -1
? Colors.grey
: trackInfos(trackerPref.syncId!).$3,
borderRadius: BorderRadius.circular(10)),
child: trackerPref.syncId == -1
? SizedBox(
width: mediaWidth(context, 1),
child: const Icon(Icons.local_library_rounded,
size: 60))
: Image.asset(
trackInfos(trackerPref.syncId!).$1)),
)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
trackerPref.syncId == -1
? 'Local'
: trackInfos(trackerPref.syncId!).$2,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 19)),
),
],
),
),
);
}),
);
}
}

View file

@ -0,0 +1,159 @@
import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/modules/manga/detail/widgets/tracker_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/constant.dart';
class TrackingDetail extends StatefulWidget {
final TrackPreference trackerPref;
const TrackingDetail({super.key, required this.trackerPref});
@override
State<TrackingDetail> createState() => _TrackingDetailState();
}
class _TrackingDetailState extends State<TrackingDetail>
with TickerProviderStateMixin {
late TabController _tabBarController;
@override
void initState() {
_tabBarController = TabController(length: 2, vsync: this);
_tabBarController.animateTo(0);
super.initState();
}
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context)!;
return DefaultTabController(
animationDuration: Duration.zero,
length: 2,
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
title: Text(widget.trackerPref.syncId == -1
? 'Local'
: trackInfos(widget.trackerPref.syncId!).$2),
bottom: TabBar(
indicatorSize: TabBarIndicatorSize.tab,
controller: _tabBarController,
tabs: [
Tab(text: l10n.manga),
Tab(text: l10n.anime),
],
),
),
body: TabBarView(controller: _tabBarController, children: [
TrackingTab(isManga: true, syncId: widget.trackerPref.syncId!),
TrackingTab(isManga: false, syncId: widget.trackerPref.syncId!)
]),
),
);
}
}
class TrackingTab extends StatelessWidget {
final bool isManga;
final int syncId;
const TrackingTab({super.key, required this.isManga, required this.syncId});
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: isar.tracks
.filter()
.idIsNotNull()
.isMangaEqualTo(isManga)
.syncIdEqualTo(syncId)
.watch(fireImmediately: true),
builder: (context, snapshot) {
List<Track>? trackRes = snapshot.hasData ? snapshot.data : [];
final mediaIds = trackRes!.map((e) => e.mediaId).toSet().toList();
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.separated(
padding: const EdgeInsets.all(0),
itemCount: mediaIds.length,
primary: false,
shrinkWrap: true,
itemBuilder: (context, index) {
final mediaId = mediaIds[index];
final track = trackRes
.firstWhere((element) => element.mediaId == mediaId);
return ExpansionTile(
title: Text(track.title!),
children: [
TrackingWidget(
isManga: isManga, syncId: syncId, mediaId: mediaId!)
],
);
},
separatorBuilder: (_, index) {
return const Divider();
},
),
);
});
}
}
class TrackingWidget extends StatelessWidget {
final int syncId;
final bool isManga;
final int mediaId;
const TrackingWidget(
{super.key,
required this.mediaId,
required this.isManga,
required this.syncId});
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: isar.tracks
.filter()
.idIsNotNull()
.mediaIdEqualTo(mediaId)
.isMangaEqualTo(isManga)
.watch(fireImmediately: true),
builder: (context, snapshot) {
List<Track>? trackRes = [];
List<Track> res = snapshot.data ?? [];
for (var track in res) {
if (!trackRes
.map((e) => e.mediaId)
.toList()
.contains(track.mediaId)) {
trackRes.add(track);
}
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.separated(
padding: const EdgeInsets.all(0),
itemCount: trackRes.length,
primary: false,
shrinkWrap: true,
itemBuilder: (context, index) {
final track = trackRes[index];
return TrackerWidget(
mangaId: track.mangaId!,
syncId: track.syncId!,
trackRes: track,
isManga: isManga,
hide: true,
);
},
separatorBuilder: (_, index) {
return const Divider();
},
),
);
});
}
}

View file

@ -24,7 +24,7 @@ class Tracks extends _$Tracks {
});
}
void updateTrackManga(Track track) {
void updateTrackManga(Track track, bool? isManga) {
final tra = isar.tracks
.filter()
.syncIdEqualTo(syncId)
@ -36,7 +36,9 @@ class Tracks extends _$Tracks {
}
}
isar.writeTxnSync(() => isar.tracks.putSync(track..syncId = syncId));
isar.writeTxnSync(() => isar.tracks.putSync(track
..syncId = syncId
..isManga = isManga));
}
void deleteTrackManga(Track track) {

View file

@ -1,9 +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/track_preference.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/trackers/anilist.dart';
import 'package:mangayomi/services/trackers/kitsu.dart';
@ -44,7 +46,6 @@ class TrackScreen extends ConsumerWidget {
),
),
TrackListile(
color: const Color.fromRGBO(18, 25, 35, 1),
onTap: () async {
await ref
.read(anilistProvider(syncId: 2).notifier)
@ -53,14 +54,12 @@ class TrackScreen extends ConsumerWidget {
id: 2,
entries: entries!),
TrackListile(
color: const Color.fromRGBO(51, 37, 50, 1),
onTap: () async {
_showDialogLogin(context, ref);
},
id: 3,
entries: entries),
TrackListile(
color: const Color.fromRGBO(46, 81, 162, 1),
onTap: () async {
await ref
.read(myAnimeListProvider(syncId: 1, isManga: null)
@ -84,7 +83,11 @@ class TrackScreen extends ConsumerWidget {
subtitle: Text(l10n.tracking_warning_info,
style: TextStyle(
fontSize: 11, color: secondaryColor(context))),
)
),
ListTileWidget(
title: l10n.manage_trackers,
icon: Icons.settings,
onTap: () => context.push('/manageTrackers')),
],
);
}),

View file

@ -11,13 +11,11 @@ class TrackListile extends ConsumerWidget {
final int id;
final List<TrackPreference> entries;
final String? text;
final Color? color;
const TrackListile(
{super.key,
required this.onTap,
required this.id,
required this.entries,
this.color,
this.text});
@override
@ -31,9 +29,8 @@ class TrackListile extends ConsumerWidget {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
leading: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: color,
),
borderRadius: BorderRadius.circular(10),
color: trackInfos(id).$3),
width: 60,
height: 70,
child: Image.asset(
@ -72,9 +69,11 @@ class TrackListile extends ConsumerWidget {
onPressed: () {
Navigator.pop(context);
},
child: Text(l10n.cancel,
style:
TextStyle(color: secondaryColor(context)),)),
child: Text(
l10n.cancel,
style: TextStyle(
color: secondaryColor(context)),
)),
const SizedBox(width: 15),
ElevatedButton(
style: ElevatedButton.styleFrom(
@ -82,14 +81,15 @@ class TrackListile extends ConsumerWidget {
Colors.red.withOpacity(0.7)),
onPressed: () {
ref
.read(tracksProvider(syncId: id).notifier)
.read(
tracksProvider(syncId: id).notifier)
.logout();
Navigator.pop(context);
},
child: Text(
l10n.log_out,
style:
TextStyle(color: secondaryColor(context)),
style: TextStyle(
color: secondaryColor(context)),
)),
],
)

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/modules/anime/anime_player_view.dart';
import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
@ -10,6 +11,8 @@ import 'package:mangayomi/modules/more/backup_and_restore/backup_and_restore.dar
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart';
import 'package:mangayomi/modules/more/settings/track/track.dart';
import 'package:mangayomi/modules/more/settings/track/manage_trackers/manage_trackers.dart';
import 'package:mangayomi/modules/more/settings/track/manage_trackers/tracking_detail.dart';
import 'package:mangayomi/modules/updates/updates_screen.dart';
import 'package:mangayomi/modules/webview/webview.dart';
import 'package:mangayomi/modules/browse/browse_screen.dart';
@ -39,7 +42,7 @@ GoRouter router(RouterRef ref) {
return GoRouter(
observers: [BotToastNavigatorObserver()],
initialLocation: '/MangaLibrary',
debugLogDiagnostics: false,
debugLogDiagnostics: true,
refreshListenable: router,
routes: router._routes,
);
@ -453,6 +456,34 @@ class RouterNotifier extends ChangeNotifier {
);
},
),
GoRoute(
path: "/manageTrackers",
name: "manageTrackers",
builder: (context, state) {
return const ManageTrackersScreen();
},
pageBuilder: (context, state) {
return CustomTransition(
key: state.pageKey,
child: const ManageTrackersScreen(),
);
},
),
GoRoute(
path: "/trackingDetail",
name: "trackingDetail",
builder: (context, state) {
final trackerPref = state.extra as TrackPreference;
return TrackingDetail(trackerPref: trackerPref);
},
pageBuilder: (context, state) {
final trackerPref = state.extra as TrackPreference;
return CustomTransition(
key: state.pageKey,
child: TrackingDetail(trackerPref: trackerPref),
);
},
),
];
}
@ -473,11 +504,3 @@ Route createRoute({required Widget page}) {
},
);
}
// I/vrf ( 6408): 4<EFBFBD>~
// I/URL_SAFE( 6408): NJV-
// I/DEFAULT ( 6408): TkpWLQ==
// I/shift ( 6408): QnlYJVAB
// I/shift_default( 6408): UW5sWUpWQUI=
// I/rot13 ( 6408): HJ5fJHcJDHV=
// I/vrfEncrypt( 6408): HJ5fJHcJDHV=
// I/flutter ( 6408): vrf=HJ5fJHcJDHV%3D%0A

View file

@ -6,7 +6,7 @@ part of 'anilist.dart';
// RiverpodGenerator
// **************************************************************************
String _$anilistHash() => r'0fd8945c8705e69d1609d9d5c6ccb5bacd84149e';
String _$anilistHash() => r'ee83d9fc6e85366bb7cbce876b0878207829a979';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -55,11 +55,23 @@ TrackStatus toTrackStatus(TrackStatus status, bool isManga, int syncId) {
: status;
}
(String, String) trackInfos(int id) {
(String, String, Color) trackInfos(int id) {
return switch (id) {
1 => ("assets/trackers_icons/tracker_mal.webp", "MyAnimeList"),
2 => ("assets/trackers_icons/tracker_anilist.webp", "Anilist"),
_ => ("assets/trackers_icons/tracker_kitsu.webp", "Kitsu"),
1 => (
"assets/trackers_icons/tracker_mal.webp",
"MyAnimeList",
const Color.fromRGBO(46, 81, 162, 1)
),
2 => (
"assets/trackers_icons/tracker_anilist.webp",
"Anilist",
const Color.fromRGBO(51, 37, 50, 1)
),
_ => (
"assets/trackers_icons/tracker_kitsu.webp",
"Kitsu",
const Color.fromRGBO(18, 25, 35, 1)
),
};
}