Code refactor

This commit is contained in:
kodjomoustapha 2023-07-17 11:05:59 +01:00
parent 13b1fea0a3
commit dc26b51a70
10 changed files with 743 additions and 835 deletions

View file

@ -18,15 +18,13 @@ import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/models/track_search.dart';
import 'package:mangayomi/modules/library/providers/local_archive.dart';
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
import 'package:mangayomi/modules/manga/detail/widgets/tracker_search_widget.dart';
import 'package:mangayomi/modules/manga/detail/widgets/tracker_widget.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/myanimelist.dart';
import 'package:mangayomi/sources/utils/utils.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/date.dart';
import 'package:mangayomi/utils/headers.dart';
import 'package:mangayomi/utils/media_query.dart';
import 'package:mangayomi/utils/utils.dart';
@ -39,7 +37,6 @@ import 'package:mangayomi/modules/manga/detail/widgets/chapter_sort_list_tile_wi
import 'package:mangayomi/modules/manga/download/providers/download_provider.dart';
import 'package:mangayomi/modules/widgets/error_text.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:numberpicker/numberpicker.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:share_plus/share_plus.dart';
@ -1314,7 +1311,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
Theme.of(context).scaffoldBackgroundColor,
elevation: 0),
onPressed: () {
_trackingDialog(entries);
_trackingDraggableMenu(entries);
},
child: StreamBuilder(
stream: isar.tracks
@ -1536,8 +1533,6 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
TextEditingController(text: widget.manga!.name!);
TextEditingController? description =
TextEditingController(text: widget.manga!.description!);
// TextEditingController? tag;
showDialog(
context: context,
builder: (context) {
@ -1613,7 +1608,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
});
}
_trackingDialog(List<TrackPreference>? entries) {
_trackingDraggableMenu(List<TrackPreference>? entries) {
DraggableMenu.open(
context,
DraggableMenu(
@ -1624,758 +1619,60 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(20),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: ListView.separated(
padding: const EdgeInsets.all(0),
itemCount: entries!.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return StreamBuilder(
stream: isar.tracks
.filter()
.idIsNotNull()
.syncIdEqualTo(entries[index].syncId)
.mangaIdEqualTo(widget.manga!.id!)
.watch(fireImmediately: true),
builder: (context, snapshot) {
List<Track>? trackRes =
snapshot.hasData ? snapshot.data : [];
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.separated(
padding: const EdgeInsets.all(0),
itemCount: entries!.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return StreamBuilder(
stream: isar.tracks
.filter()
.idIsNotNull()
.syncIdEqualTo(entries[index].syncId)
.mangaIdEqualTo(widget.manga!.id!)
.watch(fireImmediately: true),
builder: (context, snapshot) {
List<Track>? trackRes =
snapshot.hasData ? snapshot.data : [];
return trackRes!.isNotEmpty
? Container(
decoration: BoxDecoration(border: Border.all()),
child: Column(
children: [
Row(
children: [
Image.asset(
"assets/tracker_mal.webp",
height: 30,
),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
shape: RoundedRectangleBorder(
side: BorderSide(
color:
primaryColor(context),
width: 0.2),
borderRadius:
BorderRadius.circular(
0))),
onPressed: () async {
final trackSearch =
await trackersSearchraggableMenu(
syncId: entries[index]
.syncId!,
query: trackRes
.first.title!)
as TrackSearch?;
if (trackSearch != null) {
await ref
.read(trackStateProvider(
track: null)
.notifier)
.setTrackSearch(
trackSearch,
widget.manga!.id!,
entries[index].syncId!);
}
},
child: Row(
children: [
Expanded(
child: Padding(
padding:
const EdgeInsets.all(8),
child: Text(
trackRes.first.title!,
style: TextStyle(
color: secondaryColor(
context),fontSize: 16,
fontWeight:
FontWeight.bold),
),
),
),
IconButton(
onPressed: () {
ref
.read(tracksProvider(
syncId: entries[
index]
.syncId!)
.notifier)
.deleteTrackManga(
trackRes.first);
},
icon: const Icon(
Icons.cancel_outlined))
],
),
),
)
],
),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide(
color: primaryColor(
context),
width: 0.2),
borderRadius:
BorderRadius.circular(
0))),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
"Status",
),
content: SizedBox(
width: mediaWidth(
context, 0.8),
child:
ListView.builder(
shrinkWrap: true,
itemCount: ref
.read(trackStateProvider(
track: trackRes
.first)
.notifier)
.getStatusList()
.length,
itemBuilder:
(context,
index) {
final status = ref
.read(trackStateProvider(
track:
trackRes.first)
.notifier)
.getStatusList()[index];
return RadioListTile(
dense: true,
contentPadding:
const EdgeInsets
.all(0),
value: status,
groupValue:
trackRes
.first
.status,
onChanged:
(value) {
ref
.read(trackStateProvider(
track: trackRes.first..status = status)
.notifier)
.updateItem();
Navigator.pop(
context);
},
title: Text(
getTrackStatus(
status)),
);
},
)),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment
.end,
children: [
TextButton(
onPressed:
() async {
Navigator.pop(
context);
},
child: Text(
"Cancel",
style: TextStyle(
color: primaryColor(
context)),
)),
],
)
],
);
});
},
child: Text(getTrackStatus(
trackRes.first.status))),
),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide(
color: primaryColor(
context),
width: 0.2),
borderRadius:
BorderRadius.circular(
0))),
onPressed: () {
int currentIntValue = trackRes
.first.lastChapterRead!;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
"Chapters",
),
content: StatefulBuilder(
builder: (context,
setState) =>
SizedBox(
height: 200,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
NumberPicker(
value:
currentIntValue,
minValue: 0,
maxValue: trackRes
.first
.totalChapter !=
0
? trackRes
.first
.totalChapter!
: 10000,
step: 1,
haptics: true,
onChanged: (value) =>
setState(() =>
currentIntValue =
value),
),
],
),
),
),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment
.end,
children: [
TextButton(
onPressed:
() async {
Navigator.pop(
context);
},
child: Text(
"Cancel",
style: TextStyle(
color: primaryColor(
context)),
)),
TextButton(
onPressed:
() async {
ref
.read(trackStateProvider(
track: trackRes.first..lastChapterRead = currentIntValue)
.notifier)
.updateItem();
Navigator.pop(
context);
},
child: Text(
"OK",
style: TextStyle(
color: primaryColor(
context)),
)),
],
)
],
);
});
},
child: Text(trackRes
.first.totalChapter !=
0
? "${trackRes.first.lastChapterRead}/${trackRes.first.totalChapter}"
: "${trackRes.first.lastChapterRead == 0 ? "Not Started" : trackRes.first.lastChapterRead}")),
),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide(
color: primaryColor(
context),
width: 0.2),
borderRadius:
BorderRadius.circular(
0))),
onPressed: () {
int currentIntValue =
trackRes.first.score!;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
"Score",
),
content: StatefulBuilder(
builder: (context,
setState) =>
SizedBox(
height: 200,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
NumberPicker(
value:
currentIntValue,
minValue: 0,
maxValue: 10,
step: 1,
haptics: true,
onChanged: (value) =>
setState(() =>
currentIntValue =
value),
),
],
),
),
),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment
.end,
children: [
TextButton(
onPressed:
() async {
Navigator.pop(
context);
},
child: Text(
"Cancel",
style: TextStyle(
color: primaryColor(
context)),
)),
TextButton(
onPressed:
() async {
ref
.read(trackStateProvider(
track: trackRes.first..score = currentIntValue)
.notifier)
.updateItem();
Navigator.pop(
context);
},
child: Text(
"OK",
style: TextStyle(
color: primaryColor(
context)),
)),
],
)
],
);
});
},
child: Text(
trackRes.first.score != 0
? trackRes.first.score
.toString()
: "Score")),
)
],
),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
side: BorderSide(
color:
primaryColor(context),
width: 0.2),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
0))),
onPressed: () async {
DateTime? newDate =
await showDatePicker(
helpText: 'Start date',
locale: const Locale(
"fr", "FR"),
context: context,
initialDate:
DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100));
if (newDate == null) return;
ref
.read(trackStateProvider(
track: trackRes.first
..startedReadingDate =
newDate
.millisecondsSinceEpoch)
.notifier)
.updateItem();
},
child: Text(trackRes.first
.startedReadingDate !=
null &&
trackRes.first
.startedReadingDate! >
DateTime(1970)
.millisecondsSinceEpoch
? dateFormat(
trackRes.first
.startedReadingDate
.toString(),
ref: ref,
useRelativeTimesTamps: false,
context: context)
: "Start date")),
),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide(
color: primaryColor(
context),
width: 0.2),
borderRadius:
BorderRadius.circular(
0))),
onPressed: () async {
DateTime? newDate =
await showDatePicker(
helpText: 'Finish date',
locale: const Locale(
"fr", "FR"),
context: context,
initialDate:
DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100));
if (newDate == null) return;
ref
.read(trackStateProvider(
track: trackRes.first
..startedReadingDate =
newDate
.millisecondsSinceEpoch)
.notifier)
.updateItem();
},
child: Text(trackRes.first
.finishedReadingDate !=
null &&
trackRes.first.finishedReadingDate! >
DateTime(1970)
.millisecondsSinceEpoch
? dateFormat(
trackRes.first
.finishedReadingDate
.toString(),
ref: ref,
useRelativeTimesTamps: false,
context: context)
: "Finish date")),
)
],
),
],
),
)
: TrackListile(
onTap: () async {
final trackSearch =
await trackersSearchraggableMenu(
syncId: entries[index].syncId!,
query: widget.manga!.name!)
as TrackSearch?;
if (trackSearch != null) {
await ref
.read(trackStateProvider(track: null)
.notifier)
.setTrackSearch(
trackSearch,
widget.manga!.id!,
entries[index].syncId!);
}
},
id: entries[index].syncId!,
entries: const []);
});
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
return trackRes!.isNotEmpty
? TrackerWidget(
mangaId: widget.manga!.id!,
trackPreference: entries[index],
trackRes: trackRes[index],
)
: TrackListile(
onTap: () async {
final trackSearch =
await trackersSearchraggableMenu(
context,
track: Track(
status: TrackStatus.planToRead,
syncId: entries[index].syncId!,
title: widget.manga!.name!),
) as TrackSearch?;
if (trackSearch != null) {
await ref
.read(trackStateProvider(track: null)
.notifier)
.setTrackSearch(
trackSearch,
widget.manga!.id!,
entries[index].syncId!);
}
},
id: entries[index].syncId!,
entries: const []);
});
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
),
),
));
}
trackersSearchraggableMenu(
{required int syncId, required String query}) async {
return await DraggableMenu.open(
context,
DraggableMenu(
blockMenuClosing: true,
ui: SoftModernDraggableMenu(
radius: 20,
barItem: Container(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.clear)),
)
],
),
)),
maxHeight: mediaHeight(context, 0.9),
child: TrackerWidgetSearch(
query: query,
syncId: syncId,
)));
}
}
class TrackerWidgetSearch extends ConsumerStatefulWidget {
final int syncId;
final String query;
const TrackerWidgetSearch(
{required this.syncId, required this.query, super.key});
@override
ConsumerState<TrackerWidgetSearch> createState() =>
_TrackerWidgetSearchState();
}
class _TrackerWidgetSearchState extends ConsumerState<TrackerWidgetSearch> {
@override
initState() {
_init();
super.initState();
}
late List<TrackSearch> tracks = [];
_init() async {
await Future.delayed(const Duration(microseconds: 100));
tracks = await ref
.read(myAnimeListProvider(syncId: widget.syncId).notifier)
.search(widget.query);
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
late String query = widget.query;
late final _controller = TextEditingController(text: query);
bool _isLoading = true;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: _isLoading
? const ProgressCenter()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: SizedBox(
height: mediaHeight(context, 0.8),
child: Column(
children: [
Flexible(
child: ListView.separated(
padding: const EdgeInsets.only(top: 20),
itemCount: tracks.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 5),
child: InkWell(
onTap: () {
Navigator.pop(context, tracks[index]);
},
child: Column(
children: [
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Material(
borderRadius: BorderRadius.circular(5),
color: Colors.transparent,
clipBehavior:
Clip.antiAliasWithSaveLayer,
child: Ink.image(
height: 120,
width: 80,
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
tracks[index].coverUrl!),
),
),
const SizedBox(
width: 10,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
width: mediaWidth(context, 0.6),
child: Text(
tracks[index].title!,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
),
Row(
children: [
const Text(
"Type : ",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12),
),
Text(
tracks[index].publishingType!,
style: const TextStyle(
fontSize: 12),
),
],
),
Row(
children: [
const Text(
"Status : ",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12),
),
Text(
tracks[index].publishingStatus!,
style: const TextStyle(
fontSize: 12),
),
],
),
],
)
],
),
Text(
tracks[index].summary!,
style: const TextStyle(
fontSize: 12,
overflow: TextOverflow.ellipsis,
),
maxLines: 3,
),
],
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: _controller,
keyboardType: TextInputType.text,
onChanged: (d) {
setState(() {
query = d;
});
},
onFieldSubmitted: (d) async {
setState(() {
_isLoading = true;
});
tracks = await ref
.read(myAnimeListProvider(syncId: widget.syncId)
.notifier)
.search(d);
if (mounted) {
setState(() {
_isLoading = false;
});
}
},
decoration: InputDecoration(
isDense: true,
filled: true,
fillColor: Colors.transparent,
suffixIcon: query.isEmpty
? null
: IconButton(
onPressed: () {
_controller.clear();
},
icon: const Icon(Icons.clear)),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: primaryColor(context)),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: primaryColor(context)),
),
border: OutlineInputBorder(
borderSide:
BorderSide(color: primaryColor(context)))),
),
),
],
),
),
),
);
}
}

View file

@ -12,10 +12,10 @@ class TrackState extends _$TrackState {
return track!;
}
Future updateItem() async {
Future updateManga() async {
final updateTrack = await ref
.read(myAnimeListProvider(syncId: 1).notifier)
.updateItem(track!);
.updateManga(track!);
ref
.read(tracksProvider(syncId: track!.syncId!).notifier)
@ -24,7 +24,6 @@ class TrackState extends _$TrackState {
Future setTrackSearch(
TrackSearch trackSearch, int mangaId, int syncId) async {
final track = Track(
mangaId: mangaId,
score: 0,
@ -36,13 +35,13 @@ class TrackState extends _$TrackState {
status: TrackStatus.planToRead,
startedReadingDate: 0,
finishedReadingDate: 0);
final findTrack = await ref
final findManga = await ref
.read(myAnimeListProvider(syncId: 1).notifier)
.findListItem(track);
.findManga(track);
ref
.read(tracksProvider(syncId: syncId).notifier)
.updateTrackManga(findTrack);
.updateTrackManga(findManga);
}
List<TrackStatus> getStatusList() {
@ -60,4 +59,24 @@ class TrackState extends _$TrackState {
}
return list;
}
Future<Track?> findManga() async {
Track? findManga;
if (track!.syncId == 1) {
findManga = await ref
.read(myAnimeListProvider(syncId: 1).notifier)
.findManga(track!);
}
return findManga;
}
Future<List<TrackSearch>?> search(String query) async {
List<TrackSearch>? tracks;
if (track!.syncId == 1) {
tracks = await ref
.read(myAnimeListProvider(syncId: track!.syncId!).notifier)
.search(query);
}
return tracks;
}
}

View file

@ -6,7 +6,7 @@ part of 'track_state_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$trackStateHash() => r'3e7b916624f8035766d9a6408812bf1cc1247915';
String _$trackStateHash() => r'493c91f74d44d2fcffd75bdbf3e434580fd5ac03';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -0,0 +1,244 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:draggable_menu/draggable_menu.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_search.dart';
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/media_query.dart';
class TrackerWidgetSearch extends ConsumerStatefulWidget {
final Track track;
const TrackerWidgetSearch({required this.track, super.key});
@override
ConsumerState<TrackerWidgetSearch> createState() =>
_TrackerWidgetSearchState();
}
class _TrackerWidgetSearchState extends ConsumerState<TrackerWidgetSearch> {
@override
initState() {
_init();
super.initState();
}
late String query = widget.track.title!.trim();
late List<TrackSearch>? tracks = [];
_init() async {
await Future.delayed(const Duration(microseconds: 100));
tracks = await ref
.read(trackStateProvider(track: widget.track).notifier)
.search(query);
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
late final _controller = TextEditingController(text: query);
bool _isLoading = true;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: _isLoading
? const ProgressCenter()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: SizedBox(
height: mediaHeight(context, 0.8),
child: Column(
children: [
Flexible(
child: ListView.separated(
padding: const EdgeInsets.only(top: 20),
itemCount: tracks!.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 5),
child: InkWell(
onTap: () {
Navigator.pop(context, tracks![index]);
},
child: Column(
children: [
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Material(
borderRadius: BorderRadius.circular(5),
color: Colors.transparent,
clipBehavior:
Clip.antiAliasWithSaveLayer,
child: Ink.image(
height: 120,
width: 80,
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
tracks![index].coverUrl!),
),
),
const SizedBox(
width: 10,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
width: mediaWidth(context, 0.6),
child: Text(
tracks![index].title!,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
),
Row(
children: [
const Text(
"Type : ",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12),
),
Text(
tracks![index].publishingType!,
style: const TextStyle(
fontSize: 12),
),
],
),
Row(
children: [
const Text(
"Status : ",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12),
),
Text(
tracks![index]
.publishingStatus!,
style: const TextStyle(
fontSize: 12),
),
],
),
],
)
],
),
Text(
tracks![index].summary!,
style: const TextStyle(
fontSize: 12,
overflow: TextOverflow.ellipsis,
),
maxLines: 3,
),
],
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: _controller,
keyboardType: TextInputType.text,
onChanged: (d) {
setState(() {
query = d;
});
},
onFieldSubmitted: (d) async {
setState(() {
_isLoading = true;
});
tracks = await ref
.read(trackStateProvider(track: widget.track)
.notifier)
.search(d.trim());
if (mounted) {
setState(() {
_isLoading = false;
});
}
},
decoration: InputDecoration(
isDense: true,
filled: true,
fillColor: Colors.transparent,
suffixIcon: query.isEmpty
? null
: IconButton(
onPressed: () {
_controller.clear();
},
icon: const Icon(Icons.clear)),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: primaryColor(context)),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: primaryColor(context)),
),
border: OutlineInputBorder(
borderSide:
BorderSide(color: primaryColor(context)))),
),
),
],
),
),
),
);
}
}
trackersSearchraggableMenu(BuildContext context, {required Track track}) async {
return await DraggableMenu.open(
context,
DraggableMenu(
blockMenuClosing: true,
ui: ClassicDraggableMenu(
radius: 20,
barItem: Container(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.clear)),
)
],
),
)),
maxHeight: mediaHeight(context, 0.9),
child: TrackerWidgetSearch(
track: track,
)));
}

View file

@ -0,0 +1,386 @@
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';
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
import 'package:mangayomi/utils/colors.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/date.dart';
import 'package:mangayomi/utils/media_query.dart';
import 'package:numberpicker/numberpicker.dart';
class TrackerWidget extends ConsumerStatefulWidget {
final Track trackRes;
final int mangaId;
final TrackPreference trackPreference;
const TrackerWidget(
{super.key,
required this.trackPreference,
required this.trackRes,
required this.mangaId});
@override
ConsumerState<TrackerWidget> createState() => _TrackerWidgetState();
}
class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
@override
initState() {
_init();
super.initState();
}
_init() async {
await Future.delayed(const Duration(microseconds: 100));
final findManga = await ref
.read(trackStateProvider(track: widget.trackRes).notifier)
.findManga();
ref
.read(tracksProvider(syncId: widget.trackPreference.syncId!).notifier)
.updateTrackManga(findManga!);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Image.asset(
trackInfos(widget.trackPreference.syncId!).$1,
height: 30,
),
Expanded(
child: _elevatedButton(
context,
onPressed: () async {
final trackSearch = await trackersSearchraggableMenu(context,
track: widget.trackRes) as TrackSearch?;
if (trackSearch != null) {
await ref
.read(trackStateProvider(track: null).notifier)
.setTrackSearch(trackSearch, widget.mangaId,
widget.trackPreference.syncId!);
}
},
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
widget.trackRes.title!,
style: TextStyle(
color: secondaryColor(context),
fontSize: 16,
fontWeight: FontWeight.bold),
),
),
),
IconButton(
onPressed: () {
ref
.read(tracksProvider(
syncId: widget.trackPreference.syncId!)
.notifier)
.deleteTrackManga(widget.trackRes);
},
icon: const Icon(Icons.cancel_outlined))
],
),
),
)
],
),
Row(
children: [
Expanded(
child: _elevatedButton(context, onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
"Status",
),
content: SizedBox(
width: mediaWidth(context, 0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: ref
.read(
trackStateProvider(track: widget.trackRes)
.notifier)
.getStatusList()
.length,
itemBuilder: (context, index) {
final status = ref
.read(trackStateProvider(
track: widget.trackRes)
.notifier)
.getStatusList()[index];
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: status,
groupValue: widget.trackRes.status,
onChanged: (value) {
ref
.read(trackStateProvider(
track: widget.trackRes
..status = status)
.notifier)
.updateManga();
Navigator.pop(context);
},
title: Text(getTrackStatus(status)),
);
},
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
"Cancel",
style:
TextStyle(color: primaryColor(context)),
)),
],
)
],
);
});
}, text: getTrackStatus(widget.trackRes.status)),
),
Expanded(
child: _elevatedButton(context, onPressed: () {
int currentIntValue = widget.trackRes.lastChapterRead!;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
"Chapters",
),
content: StatefulBuilder(
builder: (context, setState) => SizedBox(
height: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NumberPicker(
value: currentIntValue,
minValue: 0,
maxValue: widget.trackRes.totalChapter != 0
? widget.trackRes.totalChapter!
: 10000,
step: 1,
haptics: true,
onChanged: (value) =>
setState(() => currentIntValue = value),
),
],
),
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
"Cancel",
style:
TextStyle(color: primaryColor(context)),
)),
TextButton(
onPressed: () async {
ref
.read(trackStateProvider(
track: widget.trackRes
..lastChapterRead =
currentIntValue)
.notifier)
.updateManga();
Navigator.pop(context);
},
child: Text(
"OK",
style:
TextStyle(color: primaryColor(context)),
)),
],
)
],
);
});
},
text: widget.trackRes.totalChapter != 0
? "${widget.trackRes.lastChapterRead}/${widget.trackRes.totalChapter}"
: "${widget.trackRes.lastChapterRead == 0 ? "Not Started" : widget.trackRes.lastChapterRead}"),
),
Expanded(
child: _elevatedButton(context, onPressed: () {
int currentIntValue = widget.trackRes.score!;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
"Score",
),
content: StatefulBuilder(
builder: (context, setState) => SizedBox(
height: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NumberPicker(
value: currentIntValue,
minValue: 0,
maxValue: 10,
step: 1,
haptics: true,
onChanged: (value) =>
setState(() => currentIntValue = value),
),
],
),
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
"Cancel",
style:
TextStyle(color: primaryColor(context)),
)),
TextButton(
onPressed: () async {
ref
.read(trackStateProvider(
track: widget.trackRes
..score = currentIntValue)
.notifier)
.updateManga();
Navigator.pop(context);
},
child: Text(
"OK",
style:
TextStyle(color: primaryColor(context)),
)),
],
)
],
);
});
},
text: widget.trackRes.score != 0
? widget.trackRes.score.toString()
: "Score"),
)
],
),
Row(
children: [
Expanded(
child: _elevatedButton(context, onPressed: () async {
DateTime? newDate = await showDatePicker(
helpText: 'Start date',
locale: const Locale("fr", "FR"),
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100));
if (newDate == null) return;
ref
.read(trackStateProvider(
track: widget.trackRes
..startedReadingDate =
newDate.millisecondsSinceEpoch)
.notifier)
.updateManga();
},
text: widget.trackRes.startedReadingDate != null &&
widget.trackRes.startedReadingDate! >
DateTime(1970).millisecondsSinceEpoch
? dateFormat(
widget.trackRes.startedReadingDate.toString(),
ref: ref,
useRelativeTimesTamps: false,
context: context)
: "Start date"),
),
Expanded(
child: _elevatedButton(context, onPressed: () async {
DateTime? newDate = await showDatePicker(
helpText: 'Finish date',
locale: const Locale("fr", "FR"),
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100));
if (newDate == null) return;
ref
.read(trackStateProvider(
track: widget.trackRes
..finishedReadingDate =
newDate.millisecondsSinceEpoch)
.notifier)
.updateManga();
},
text: widget.trackRes.finishedReadingDate != null &&
widget.trackRes.finishedReadingDate! >
DateTime(1970).millisecondsSinceEpoch
? dateFormat(
widget.trackRes.finishedReadingDate.toString(),
ref: ref,
useRelativeTimesTamps: false,
context: context)
: "Finish date"),
)
],
),
],
);
}
}
Widget _elevatedButton(BuildContext context,
{required VoidCallback onPressed, String text = "", Widget? child}) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
elevation: 0,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(width: 0.05, color: secondaryColor(context)),
borderRadius: BorderRadius.circular(0))),
onPressed: onPressed,
child: child ??
Text(
text,
style:
TextStyle(color: Theme.of(context).textTheme.bodyMedium!.color),
));
}

View file

@ -6,7 +6,7 @@ part of 'track_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$tracksHash() => r'11a1c74c458db7f5b790de1451d239662cec1ed3';
String _$tracksHash() => r'65e6092128a8d24edcecb215287d4a774df1b180';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -2,16 +2,19 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
import 'package:mangayomi/utils/constant.dart';
class TrackListile extends ConsumerWidget {
final VoidCallback onTap;
final int id;
final List<TrackPreference> entries;
final Widget? trailing;
const TrackListile(
{super.key,
required this.onTap,
required this.id,
required this.entries});
required this.entries,
this.trailing});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -21,17 +24,17 @@ class TrackListile extends ConsumerWidget {
leading: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
_track(id).$1,
trackInfos(id).$1,
height: 30,
),
),
trailing: isLogged
? const Icon(
Icons.check,
size: 30,
color: Colors.green,
)
: null,
trailing: trailing ?? (isLogged
? const Icon(
Icons.check,
size: 30,
color: Colors.green,
)
: null),
onTap: isLogged
? () {
showDialog(
@ -39,7 +42,7 @@ class TrackListile extends ConsumerWidget {
builder: (context) {
return AlertDialog(
title: Text(
"Log out from ${_track(id).$2}",
"Log out from ${trackInfos(id).$2}",
),
actions: [
Row(
@ -68,14 +71,8 @@ class TrackListile extends ConsumerWidget {
});
}
: onTap,
title: Text(_track(id).$2),
title: Text(trackInfos(id).$2),
);
}
}
(String, String) _track(int id) {
return switch (id) {
1 => ("assets/tracker_mal.webp", "MyAnimeList"),
_ => ("", ""),
};
}

View file

@ -193,7 +193,7 @@ class MyAnimeList extends _$MyAnimeList {
return jsonDecode(response.body)['name'];
}
Future<Track> findListItem(Track track) async {
Future<Track> findManga(Track track) async {
final accessToken = await _getAccesToken();
final uri = Uri.parse('$baseApiUrl/manga/${track.mediaId}')
.replace(queryParameters: {
@ -204,74 +204,33 @@ class MyAnimeList extends _$MyAnimeList {
final mJson = jsonDecode(response.body);
track.totalChapter = mJson['num_chapters'] ?? 0;
if (mJson['my_list_status'] != null) {
track = parseMangaItem(mJson["my_list_status"], track);
track = _parseMangaItem(mJson["my_list_status"], track);
} else {
track = await updateItem(track);
track = await updateManga(track);
}
return track;
}
Future<List<TrackSearch>> findListItems(String query,
{int offset = 0}) async {
final accessToken = await _getAccesToken();
final json = await getListPage(offset);
final obj = json['data'] as List<dynamic>;
List<int> mangaIds = obj
.where((data) => data['node']['title']
.toString()
.toLowerCase()
.contains(query.toLowerCase()))
.map((data) => data['node']['id'] as int)
.toList();
List<TrackSearch> trackSearchResult = [];
for (var mangaId in mangaIds) {
final trackSearch = await getMangaDetails(mangaId, accessToken);
trackSearchResult.add(trackSearch);
}
if (json['paging']['next'] != null) {
final newV =
await findListItems(query, offset: offset + listPaginationAmount);
trackSearchResult.addAll(newV);
}
return trackSearchResult;
}
Future<Map<String, dynamic>> getListPage(int offset) async {
final urlBuilder =
Uri.parse('$baseApiUrl/users/@me/mangalist').replace(queryParameters: {
'fields': 'list_status{start_date,finish_date}',
'limit': listPaginationAmount.toString(),
});
if (offset > 0) {
urlBuilder.queryParameters['offset'] = offset.toString();
}
final url = urlBuilder.toString();
final response =
await http.get(Uri.parse(url), headers: {'X-MAL-CLIENT-ID': clientId});
return jsonDecode(response.body);
}
Track parseMangaItem(Map<String, dynamic> mJson, Track track) {
Track _parseMangaItem(Map<String, dynamic> mJson, Track track) {
bool isRereading = mJson["is_rereading"] ?? false;
track.status = isRereading
? TrackStatus.rereading
: _getMALTrackStatus(mJson["status"]);
track.lastChapterRead = int.parse(mJson["num_chapters_read"].toString());
track.score = int.parse(mJson["score"].toString());
track.startedReadingDate = parseDate(mJson["start_date"]);
track.finishedReadingDate = parseDate(mJson["finish_date"]);
track.startedReadingDate = _parseDate(mJson["start_date"]);
track.finishedReadingDate = _parseDate(mJson["finish_date"]);
return track;
}
int? parseDate(String? isoDate) {
int? _parseDate(String? isoDate) {
if (isoDate == null) return null;
final date = DateFormat('yyyy-MM-dd', 'en_US').parse(isoDate);
return date.millisecondsSinceEpoch;
}
Future<Track> updateItem(Track track) async {
Future<Track> updateManga(Track track) async {
final accessToken = await _getAccesToken();
final formBody = {
'status': (toMyAnimeListStatus(track.status) ?? 'reading').toString(),
@ -289,6 +248,6 @@ class MyAnimeList extends _$MyAnimeList {
request.headers.addAll({'Authorization': 'Bearer $accessToken'});
final response = await http.Client().send(request);
final mJson = jsonDecode(await response.stream.bytesToString());
return parseMangaItem(mJson, track);
return _parseMangaItem(mJson, track);
}
}
}

View file

@ -6,7 +6,7 @@ part of 'myanimelist.dart';
// RiverpodGenerator
// **************************************************************************
String _$myAnimeListHash() => r'd3ec65023fe7f920fad11afa4866072fb75ee257';
String _$myAnimeListHash() => r'4fca14c944acc71eb514057708b8c60e28aa1744';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -40,3 +40,9 @@ String getTrackStatus(TrackStatus status) {
};
}
(String, String) trackInfos(int id) {
return switch (id) {
1 => ("assets/tracker_mal.webp", "MyAnimeList"),
_ => ("", ""),
};
}