mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
feat: add more options to manage covers
This commit is contained in:
parent
94ebf47e4e
commit
f71c40f0f7
26 changed files with 680 additions and 281 deletions
|
|
@ -3,7 +3,7 @@ import 'package:bot_toast/bot_toast.dart';
|
|||
import 'package:dart_eval/dart_eval_bridge.dart';
|
||||
import 'package:dart_eval/stdlib/core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/dom.dart' hide Text;
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:js_packer/js_packer.dart';
|
||||
|
|
@ -631,13 +631,23 @@ void botToast(String title,
|
|||
double? fontSize,
|
||||
double alignX = 0,
|
||||
double alignY = 0.99}) {
|
||||
BotToast.showSimpleNotification(
|
||||
titleStyle: TextStyle(fontSize: fontSize),
|
||||
onlyOne: true,
|
||||
dismissDirections: [DismissDirection.horizontal, DismissDirection.down],
|
||||
align: Alignment(alignX, alignY),
|
||||
duration: Duration(seconds: second),
|
||||
title: title);
|
||||
final assets = [
|
||||
'assets/app_icons/icon-black.png',
|
||||
'assets/app_icons/icon-red.png'
|
||||
];
|
||||
BotToast.showNotification(
|
||||
onlyOne: true,
|
||||
dismissDirections: [DismissDirection.horizontal, DismissDirection.down],
|
||||
align: Alignment(alignX, alignY),
|
||||
duration: Duration(seconds: second),
|
||||
animationDuration: const Duration(milliseconds: 200),
|
||||
animationReverseDuration: const Duration(milliseconds: 200),
|
||||
leading: (_) => Image.asset((assets..shuffle()).first, height: 25),
|
||||
title: (_) => Text(
|
||||
title,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
(encrypt.Encrypter, encrypt.IV) _encrypt(String keyy, String ivv) {
|
||||
|
|
|
|||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "الفصل التالي",
|
||||
"next_5_chapters": "الفصول الخمسة التالية",
|
||||
"next_10_chapters": "الفصول العشرة التالية",
|
||||
"next_25_chapters": "الفصول الخمسة والعشرون التالية"
|
||||
"next_25_chapters": "الفصول الخمسة والعشرون التالية",
|
||||
"cover_saved": "الغلاف المحفوظ",
|
||||
"set_as_cover": "تعيين كغطاء",
|
||||
"use_this_as_cover_art": "هل تريد استخدام هذا كفن الغلاف؟",
|
||||
"save": "حفظ",
|
||||
"picture_saved": "الصورة المحفوظة",
|
||||
"cover_updated": "تم تحديث الغلاف"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Nächstes Kapitel",
|
||||
"next_5_chapters": "Nächsten 5 Kapitel",
|
||||
"next_10_chapters": "Nächsten 10 Kapitel",
|
||||
"next_25_chapters": "Nächsten 25 Kapitel"
|
||||
"next_25_chapters": "Nächsten 25 Kapitel",
|
||||
"cover_saved": "Titelbild gespeichert",
|
||||
"set_as_cover": "Als Titelbild festlegen",
|
||||
"use_this_as_cover_art": "Dies als Titelbild verwenden?",
|
||||
"save": "Speichern",
|
||||
"picture_saved": "Bild gespeichert",
|
||||
"cover_updated": "Cover aktualisiert"
|
||||
}
|
||||
|
|
@ -289,5 +289,11 @@
|
|||
"next_chapter": "Next chapter",
|
||||
"next_5_chapters": "Next 5 chapters",
|
||||
"next_10_chapters": "Next 10 chapters",
|
||||
"next_25_chapters": "Next 25 chapters"
|
||||
"next_25_chapters": "Next 25 chapters",
|
||||
"cover_saved": "Cover saved",
|
||||
"set_as_cover": "Set as cover",
|
||||
"use_this_as_cover_art": "Use this as cover art?",
|
||||
"save": "Save",
|
||||
"picture_saved": "Picture saved",
|
||||
"cover_updated": "Cover updated"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Próximo capítulo",
|
||||
"next_5_chapters": "Próximos 5 capítulos",
|
||||
"next_10_chapters": "Próximos 10 capítulos",
|
||||
"next_25_chapters": "Próximos 25 capítulos"
|
||||
"next_25_chapters": "Próximos 25 capítulos",
|
||||
"cover_saved": "Portada guardada",
|
||||
"set_as_cover": "Establecer como portada",
|
||||
"use_this_as_cover_art": "¿Usar esto como portada?",
|
||||
"save": "Guardar",
|
||||
"picture_saved": "Imagen guardada",
|
||||
"cover_updated": "Portada actualizada"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Siguiente capítulo",
|
||||
"next_5_chapters": "Siguientes 5 capítulos",
|
||||
"next_10_chapters": "Siguientes 10 capítulos",
|
||||
"next_25_chapters": "Siguientes 25 capítulos"
|
||||
"next_25_chapters": "Siguientes 25 capítulos",
|
||||
"cover_saved": "Portada guardada",
|
||||
"set_as_cover": "Establecer como portada",
|
||||
"use_this_as_cover_art": "¿Usar esto como portada?",
|
||||
"save": "Guardar",
|
||||
"picture_saved": "Imagen guardada",
|
||||
"cover_updated": "Portada actualizada"
|
||||
}
|
||||
|
|
@ -288,5 +288,11 @@
|
|||
"next_chapter": "Chapitre suivant",
|
||||
"next_5_chapters": "5 chapitres suivants",
|
||||
"next_10_chapters": "10 chapitres suivants",
|
||||
"next_25_chapters": "25 chapitres suivants"
|
||||
"next_25_chapters": "25 chapitres suivants",
|
||||
"cover_saved": "Couverture enregistrée",
|
||||
"set_as_cover": "Définir comme couverture",
|
||||
"use_this_as_cover_art": "Utiliser ceci comme illustration de couverture ?",
|
||||
"save": "Enregistrer",
|
||||
"picture_saved": "Image enregistrée",
|
||||
"cover_updated": "Couverture mise à jour"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Berikutnya bab",
|
||||
"next_5_chapters": "5 bab berikutnya",
|
||||
"next_10_chapters": "10 bab berikutnya",
|
||||
"next_25_chapters": "25 bab berikutnya"
|
||||
"next_25_chapters": "25 bab berikutnya",
|
||||
"cover_saved": "Sampul disimpan",
|
||||
"set_as_cover": "Atur sebagai sampul",
|
||||
"use_this_as_cover_art": "Gunakan ini sebagai seni sampul?",
|
||||
"save": "Simpan",
|
||||
"picture_saved": "Gambar disimpan",
|
||||
"cover_updated": "Penutup diperbarui"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Capitolo successivo",
|
||||
"next_5_chapters": "Prossimi 5 capitoli",
|
||||
"next_10_chapters": "Prossimi 10 capitoli",
|
||||
"next_25_chapters": "Prossimi 25 capitoli"
|
||||
"next_25_chapters": "Prossimi 25 capitoli",
|
||||
"cover_saved": "Copertina salvata",
|
||||
"set_as_cover": "Imposta come copertina",
|
||||
"use_this_as_cover_art": "Usare questo come copertina?",
|
||||
"save": "Salva",
|
||||
"picture_saved": "Immagine salvata",
|
||||
"cover_updated": "Copertina aggiornata"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Próximo capítulo",
|
||||
"next_5_chapters": "Próximos 5 capítulos",
|
||||
"next_10_chapters": "Próximos 10 capítulos",
|
||||
"next_25_chapters": "Próximos 25 capítulos"
|
||||
"next_25_chapters": "Próximos 25 capítulos",
|
||||
"cover_saved": "Capa salva",
|
||||
"set_as_cover": "Definir como capa",
|
||||
"use_this_as_cover_art": "Usar isso como arte de capa?",
|
||||
"save": "Salvar",
|
||||
"picture_saved": "Imagem salva",
|
||||
"cover_updated": "Capa atualizada"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Próximo capítulo",
|
||||
"next_5_chapters": "Próximos 5 capítulos",
|
||||
"next_10_chapters": "Próximos 10 capítulos",
|
||||
"next_25_chapters": "Próximos 25 capítulos"
|
||||
"next_25_chapters": "Próximos 25 capítulos",
|
||||
"cover_saved": "Capa salva",
|
||||
"set_as_cover": "Definir como capa",
|
||||
"use_this_as_cover_art": "Usar isso como arte de capa?",
|
||||
"save": "Salvar",
|
||||
"picture_saved": "Foto salva",
|
||||
"cover_updated": "Capa atualizada"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Следующая глава",
|
||||
"next_5_chapters": "Следующие 5 глав",
|
||||
"next_10_chapters": "Следующие 10 глав",
|
||||
"next_25_chapters": "Следующие 25 глав"
|
||||
"next_25_chapters": "Следующие 25 глав",
|
||||
"cover_saved": "Обложка сохранена",
|
||||
"set_as_cover": "Установить как обложку",
|
||||
"use_this_as_cover_art": "Использовать это как обложку?",
|
||||
"save": "Сохранить",
|
||||
"picture_saved": "Изображение сохранено",
|
||||
"cover_updated": "Обложка обновлена"
|
||||
}
|
||||
|
|
@ -287,5 +287,11 @@
|
|||
"next_chapter": "Sonraki bölüm",
|
||||
"next_5_chapters": "Sonraki 5 bölüm",
|
||||
"next_10_chapters": "Sonraki 10 bölüm",
|
||||
"next_25_chapters": "Sonraki 25 bölüm"
|
||||
"next_25_chapters": "Sonraki 25 bölüm",
|
||||
"cover_saved": "Kapak kaydedildi",
|
||||
"set_as_cover": "Kapak olarak ayarla",
|
||||
"use_this_as_cover_art": "Bu resmi kapak sanatı olarak kullan?",
|
||||
"save": "Kaydet",
|
||||
"picture_saved": "Resim kaydedildi",
|
||||
"cover_updated": "Kapak güncellendi"
|
||||
}
|
||||
|
|
@ -289,5 +289,11 @@
|
|||
"next_chapter": "下一章",
|
||||
"next_5_chapters": "下5章",
|
||||
"next_10_chapters": "下10章",
|
||||
"next_25_chapters": "下25章"
|
||||
"next_25_chapters": "下25章",
|
||||
"cover_saved": "封面已保存",
|
||||
"set_as_cover": "设置为封面",
|
||||
"use_this_as_cover_art": "使用此作为封面?",
|
||||
"save": "保存",
|
||||
"picture_saved": "图片已保存",
|
||||
"cover_updated": "封面已更新"
|
||||
}
|
||||
|
|
@ -29,7 +29,8 @@ class DesktopControllerWidget extends StatefulWidget {
|
|||
required this.tempDuration});
|
||||
|
||||
@override
|
||||
State<DesktopControllerWidget> createState() => _DesktopControllerWidgetState();
|
||||
State<DesktopControllerWidget> createState() =>
|
||||
_DesktopControllerWidgetState();
|
||||
}
|
||||
|
||||
class _DesktopControllerWidgetState extends State<DesktopControllerWidget> {
|
||||
|
|
@ -797,13 +798,16 @@ class _CustomMaterialDesktopFullscreenButtonState
|
|||
|
||||
Future<bool> setFullScreen({bool? value}) async {
|
||||
if (value != null) {
|
||||
await windowManager.setTitleBarStyle(
|
||||
value == false ? TitleBarStyle.normal : TitleBarStyle.hidden);
|
||||
await windowManager.setFullScreen(value);
|
||||
if (value == false) {
|
||||
await windowManager.center();
|
||||
final isFullScreen = await windowManager.isFullScreen();
|
||||
if (value != isFullScreen) {
|
||||
await windowManager.setTitleBarStyle(
|
||||
value == false ? TitleBarStyle.normal : TitleBarStyle.hidden);
|
||||
await windowManager.setFullScreen(value);
|
||||
if (value == false) {
|
||||
await windowManager.center();
|
||||
}
|
||||
await windowManager.show();
|
||||
}
|
||||
await windowManager.show();
|
||||
return value;
|
||||
}
|
||||
final isFullScreen = await windowManager.isFullScreen();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/download.dart';
|
||||
|
|
@ -24,10 +25,12 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
|
|||
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/services/get_source_baseurl.dart';
|
||||
import 'package:mangayomi/sources/utils/utils.dart';
|
||||
import 'package:mangayomi/utils/cached_network.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
import 'package:mangayomi/utils/headers.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/isar_providers.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
|
||||
|
|
@ -1581,7 +1584,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
);
|
||||
}
|
||||
|
||||
_openImage(ImageProvider imageProvider) {
|
||||
void _openImage(ImageProvider imageProvider) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
|
|
@ -1610,141 +1613,221 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
StreamBuilder(
|
||||
stream: isar.trackPreferences
|
||||
.filter()
|
||||
.syncIdIsNotNull()
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
List<TrackPreference>? entries =
|
||||
snapshot.hasData ? snapshot.data! : [];
|
||||
if (entries.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
return Column(
|
||||
children: entries
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: MaterialButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(10)),
|
||||
onPressed: () async {
|
||||
final trackSearch =
|
||||
await trackersSearchraggableMenu(
|
||||
context,
|
||||
isManga: widget.manga!.isManga!,
|
||||
track: Track(
|
||||
status:
|
||||
TrackStatus.planToRead,
|
||||
syncId: e.syncId!,
|
||||
title: widget.manga!.name!),
|
||||
) as TrackSearch?;
|
||||
if (trackSearch != null) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(widget
|
||||
.manga!
|
||||
..customCoverFromTracker =
|
||||
trackSearch.coverUrl);
|
||||
});
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: StreamBuilder(
|
||||
stream: isar.trackPreferences
|
||||
.filter()
|
||||
.syncIdIsNotNull()
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
List<TrackPreference>? entries =
|
||||
snapshot.hasData ? snapshot.data! : [];
|
||||
if (entries.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
return Column(
|
||||
children: entries
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: MaterialButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(10)),
|
||||
onPressed: () async {
|
||||
final trackSearch =
|
||||
await trackersSearchraggableMenu(
|
||||
context,
|
||||
isManga: widget.manga!.isManga!,
|
||||
track: Track(
|
||||
status:
|
||||
TrackStatus.planToRead,
|
||||
syncId: e.syncId!,
|
||||
title: widget.manga!.name!),
|
||||
) as TrackSearch?;
|
||||
if (trackSearch != null) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(
|
||||
widget.manga!
|
||||
..customCoverImage = null
|
||||
..customCoverFromTracker =
|
||||
trackSearch.coverUrl);
|
||||
});
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
botToast(
|
||||
context.l10n.cover_updated,
|
||||
second: 3);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
color:
|
||||
trackInfos(e.syncId!).$3),
|
||||
width: 45,
|
||||
height: 50,
|
||||
child: Image.asset(
|
||||
trackInfos(e.syncId!).$1,
|
||||
height: 30,
|
||||
),
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
color:
|
||||
trackInfos(e.syncId!).$3),
|
||||
width: 45,
|
||||
height: 50,
|
||||
child: Image.asset(
|
||||
trackInfos(e.syncId!).$1,
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
if (widget.manga!.customCoverImage != null ||
|
||||
widget.manga!.customCoverFromTracker !=
|
||||
null)
|
||||
PopupMenuItem<int>(
|
||||
value: 0,
|
||||
child: Text(context.l10n.delete)),
|
||||
PopupMenuItem<int>(
|
||||
value: 1, child: Text(context.l10n.edit)),
|
||||
];
|
||||
},
|
||||
onSelected: (value) async {
|
||||
final manga = widget.manga!;
|
||||
if (value == 0) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(manga
|
||||
..customCoverImage = null
|
||||
..customCoverFromTracker = null);
|
||||
});
|
||||
Navigator.pop(context);
|
||||
} else if (value == 1) {
|
||||
FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: [
|
||||
'png',
|
||||
'jpg',
|
||||
'jpeg'
|
||||
]);
|
||||
if (result != null) {
|
||||
if (result.files.first.size < 5000000) {
|
||||
final customCoverImage =
|
||||
File(result.files.first.path!)
|
||||
.readAsBytesSync();
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(manga
|
||||
..customCoverImage = customCoverImage);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: CircleAvatar(
|
||||
child: Icon(Icons.edit_outlined)),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: context.mediaWidth(1),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: context.isLight
|
||||
? Colors.white
|
||||
: Colors.black),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.close),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
// IconButton(
|
||||
// onPressed: () async {
|
||||
// Uint8List? bytes;
|
||||
// if (isLocalArchive) {
|
||||
// bytes =
|
||||
// widget.manga!.customCoverImage as Uint8List?;
|
||||
// }
|
||||
// await Share.shareXFiles([
|
||||
// XFile.fromData(bytes!,
|
||||
// name: widget.manga!.name,
|
||||
// mimeType: 'image/jpeg')
|
||||
// ]);
|
||||
// },
|
||||
// icon: const CircleAvatar(child: Icon(Icons.share))),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: context.isLight
|
||||
? Colors.white
|
||||
: Colors.black),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final bytes = await imageProvider
|
||||
.getBytes(context);
|
||||
if (bytes != null) {
|
||||
await Share.shareXFiles([
|
||||
XFile.fromData(bytes,
|
||||
name: widget.manga!.name,
|
||||
mimeType: 'image/png')
|
||||
]);
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.share),
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final dir = await StorageProvider()
|
||||
.getGalleryDirectory();
|
||||
if (context.mounted) {
|
||||
final bytes = await imageProvider
|
||||
.getBytes(context);
|
||||
if (bytes != null &&
|
||||
context.mounted) {
|
||||
final file = File(
|
||||
'${dir!.path}/${widget.manga!.name}.png');
|
||||
file.writeAsBytesSync(bytes);
|
||||
botToast(context.l10n.cover_saved,
|
||||
second: 3);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.save_outlined),
|
||||
)),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
if (widget.manga!.customCoverImage !=
|
||||
null ||
|
||||
widget.manga!
|
||||
.customCoverFromTracker !=
|
||||
null)
|
||||
PopupMenuItem<int>(
|
||||
value: 0,
|
||||
child:
|
||||
Text(context.l10n.delete)),
|
||||
PopupMenuItem<int>(
|
||||
value: 1,
|
||||
child: Text(context.l10n.edit)),
|
||||
];
|
||||
},
|
||||
onSelected: (value) async {
|
||||
final manga = widget.manga!;
|
||||
if (value == 0) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(manga
|
||||
..customCoverImage = null
|
||||
..customCoverFromTracker = null);
|
||||
});
|
||||
Navigator.pop(context);
|
||||
} else if (value == 1) {
|
||||
FilePickerResult? result =
|
||||
await FilePicker.platform
|
||||
.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: [
|
||||
'png',
|
||||
'jpg',
|
||||
'jpeg'
|
||||
]);
|
||||
if (result != null &&
|
||||
context.mounted) {
|
||||
if (result.files.first.size <
|
||||
5000000) {
|
||||
final customCoverImage =
|
||||
File(result.files.first.path!)
|
||||
.readAsBytesSync();
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(manga
|
||||
..customCoverImage =
|
||||
customCoverImage);
|
||||
});
|
||||
botToast(
|
||||
context.l10n.cover_updated,
|
||||
second: 3);
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
Icons.edit_outlined,
|
||||
color: !context.isLight
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:photo_view/photo_view_gallery.dart';
|
|||
class DoubleColummView extends StatefulWidget {
|
||||
final bool cropBorders;
|
||||
final List<UChapDataPreload?> datas;
|
||||
final Function(UChapDataPreload datas) onLongPressData;
|
||||
final Function(double) scale;
|
||||
final BackgroundColor backgroundColor;
|
||||
final Function(bool) isFailedToLoadImage;
|
||||
|
|
@ -21,6 +22,7 @@ class DoubleColummView extends StatefulWidget {
|
|||
{super.key,
|
||||
required this.datas,
|
||||
required this.scale,
|
||||
required this.onLongPressData,
|
||||
required this.backgroundColor,
|
||||
required this.isFailedToLoadImage,
|
||||
required this.cropBorders});
|
||||
|
|
@ -199,6 +201,8 @@ class _DoubleColummViewState extends State<DoubleColummView>
|
|||
return null;
|
||||
},
|
||||
cropBorders: widget.cropBorders,
|
||||
onLongPressData: (datas) =>
|
||||
widget.onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
// if (widget.datas[1] != null) const SizedBox(width: 10),
|
||||
|
|
@ -276,6 +280,8 @@ class _DoubleColummViewState extends State<DoubleColummView>
|
|||
return null;
|
||||
},
|
||||
cropBorders: widget.cropBorders,
|
||||
onLongPressData: (datas) =>
|
||||
widget.onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
|||
class DoubleColummVerticalView extends StatelessWidget {
|
||||
final bool cropBorders;
|
||||
final List<UChapDataPreload?> datas;
|
||||
final Function(UChapDataPreload datas) onLongPressData;
|
||||
final Function(double) scale;
|
||||
final BackgroundColor backgroundColor;
|
||||
final Function(bool) isFailedToLoadImage;
|
||||
|
|
@ -18,6 +19,7 @@ class DoubleColummVerticalView extends StatelessWidget {
|
|||
{super.key,
|
||||
required this.datas,
|
||||
required this.scale,
|
||||
required this.onLongPressData,
|
||||
required this.backgroundColor,
|
||||
required this.isFailedToLoadImage,
|
||||
required this.cropBorders});
|
||||
|
|
@ -103,6 +105,7 @@ class DoubleColummVerticalView extends StatelessWidget {
|
|||
return null;
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
onLongPressData: (datas) => onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
// if (datas[1] != null) const SizedBox(width: 10),
|
||||
|
|
@ -174,6 +177,7 @@ class DoubleColummVerticalView extends StatelessWidget {
|
|||
return null;
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
onLongPressData: (datas) => onLongPressData.call(datas),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
|||
class ImageViewCenter extends ConsumerWidget {
|
||||
final UChapDataPreload datas;
|
||||
final bool cropBorders;
|
||||
final Function(UChapDataPreload datas) onLongPressData;
|
||||
final Widget? Function(ExtendedImageState state) loadStateChanged;
|
||||
final Function(ExtendedImageGestureState state)? onDoubleTap;
|
||||
final GestureConfig Function(ExtendedImageState state)?
|
||||
|
|
@ -20,6 +21,7 @@ class ImageViewCenter extends ConsumerWidget {
|
|||
super.key,
|
||||
required this.datas,
|
||||
required this.cropBorders,
|
||||
required this.onLongPressData,
|
||||
required this.loadStateChanged,
|
||||
this.onDoubleTap,
|
||||
this.initGestureConfigHandler,
|
||||
|
|
@ -47,15 +49,18 @@ class ImageViewCenter extends ConsumerWidget {
|
|||
source: datas.chapter!.manga.value!.source!,
|
||||
lang: datas.chapter!.manga.value!.lang!)));
|
||||
|
||||
return ExtendedImage(
|
||||
image: image as ImageProvider<Object>,
|
||||
fit: getBoxFit(scaleType),
|
||||
filterQuality: FilterQuality.medium,
|
||||
enableMemoryCache: true,
|
||||
mode: ExtendedImageMode.gesture,
|
||||
handleLoadingProgress: true,
|
||||
loadStateChanged: loadStateChanged,
|
||||
initGestureConfigHandler: initGestureConfigHandler,
|
||||
onDoubleTap: onDoubleTap);
|
||||
return GestureDetector(
|
||||
onLongPress: () => onLongPressData.call(datas),
|
||||
child: ExtendedImage(
|
||||
image: image as ImageProvider<Object>,
|
||||
fit: getBoxFit(scaleType),
|
||||
filterQuality: FilterQuality.medium,
|
||||
enableMemoryCache: true,
|
||||
mode: ExtendedImageMode.gesture,
|
||||
handleLoadingProgress: true,
|
||||
loadStateChanged: loadStateChanged,
|
||||
initGestureConfigHandler: initGestureConfigHandler,
|
||||
onDoubleTap: onDoubleTap),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicat
|
|||
|
||||
class ImageViewVertical extends ConsumerWidget {
|
||||
final UChapDataPreload datas;
|
||||
final Function(UChapDataPreload datas) onLongPressData;
|
||||
final bool cropBorders;
|
||||
|
||||
final Function(bool) failedToLoadImage;
|
||||
|
|
@ -21,6 +22,7 @@ class ImageViewVertical extends ConsumerWidget {
|
|||
const ImageViewVertical(
|
||||
{super.key,
|
||||
required this.datas,
|
||||
required this.onLongPressData,
|
||||
required this.cropBorders,
|
||||
required this.failedToLoadImage});
|
||||
|
||||
|
|
@ -47,79 +49,83 @@ class ImageViewVertical extends ConsumerWidget {
|
|||
lang: datas.chapter!.manga.value!.lang!)));
|
||||
final scaleType = ref.watch(scaleTypeStateProvider);
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (datas.index == 0)
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.top,
|
||||
),
|
||||
ExtendedImage(
|
||||
image: image as ImageProvider<Object>,
|
||||
filterQuality: FilterQuality.medium,
|
||||
handleLoadingProgress: true,
|
||||
fit: getBoxFit(scaleType),
|
||||
enableMemoryCache: true,
|
||||
enableLoadState: true,
|
||||
loadStateChanged: (state) {
|
||||
if (state.extendedImageLoadState == LoadState.completed) {
|
||||
failedToLoadImage(false);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress = state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
height: context.mediaHeight(0.8),
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.failed) {
|
||||
failedToLoadImage(true);
|
||||
return Container(
|
||||
return GestureDetector(
|
||||
onLongPress: () => onLongPressData.call(datas),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (datas.index == 0)
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.top,
|
||||
),
|
||||
ExtendedImage(
|
||||
image: image as ImageProvider<Object>,
|
||||
filterQuality: FilterQuality.medium,
|
||||
handleLoadingProgress: true,
|
||||
fit: getBoxFit(scaleType),
|
||||
enableMemoryCache: true,
|
||||
enableLoadState: true,
|
||||
loadStateChanged: (state) {
|
||||
if (state.extendedImageLoadState == LoadState.completed) {
|
||||
failedToLoadImage(false);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: 0;
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
height: context.mediaHeight(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(l10n.image_loading_error,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
failedToLoadImage(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
failedToLoadImage(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
child: Text(
|
||||
l10n.retry,
|
||||
child: CircularProgressIndicatorAnimateRotate(
|
||||
progress: progress),
|
||||
);
|
||||
}
|
||||
if (state.extendedImageLoadState == LoadState.failed) {
|
||||
failedToLoadImage(true);
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
height: context.mediaHeight(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(l10n.image_loading_error,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
state.reLoadImage();
|
||||
failedToLoadImage(false);
|
||||
},
|
||||
onTap: () {
|
||||
state.reLoadImage();
|
||||
failedToLoadImage(false);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius: BorderRadius.circular(30)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
child: Text(
|
||||
l10n.retry,
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mangayomi/messages/crop_borders.pb.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'crop_borders_provider.g.dart';
|
||||
|
||||
|
|
@ -16,18 +14,8 @@ Future<Uint8List?> cropBorders(CropBordersRef ref,
|
|||
Uint8List? imageBytes;
|
||||
|
||||
if (cropBorder) {
|
||||
if (datas.archiveImage != null) {
|
||||
imageBytes = datas.archiveImage;
|
||||
} else if (datas.isLocale!) {
|
||||
imageBytes = File('${datas.path!.path}${padIndex(datas.index! + 1)}.jpg')
|
||||
.readAsBytesSync();
|
||||
} else {
|
||||
File? cachedImage;
|
||||
if (datas.url != null) {
|
||||
cachedImage = await getCachedImageFile(datas.url!);
|
||||
}
|
||||
imageBytes = cachedImage?.readAsBytesSync();
|
||||
}
|
||||
imageBytes = await datas.getImageBytes;
|
||||
|
||||
if (imageBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/messages/generated.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/modules/anime/widgets/desktop.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/double_columm_view_vertical.dart';
|
||||
|
|
@ -19,10 +21,12 @@ import 'package:mangayomi/modules/manga/reader/double_columm_view_center.dart';
|
|||
import 'package:mangayomi/modules/manga/reader/providers/crop_borders_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/sources/utils/utils.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||
import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:mangayomi/utils/extensions/others.dart';
|
||||
import 'package:mangayomi/utils/headers.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/image_view_center.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/image_view_vertical.dart';
|
||||
|
|
@ -34,6 +38,7 @@ import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
|||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
typedef DoubleClickAnimationListener = void Function();
|
||||
|
|
@ -241,6 +246,134 @@ class _MangaChapterPageGalleryState
|
|||
ref.read(fullScreenReaderStateProvider.notifier).set(!value!);
|
||||
}
|
||||
|
||||
void _onLongPressImageDialog(
|
||||
UChapDataPreload datas, BuildContext context) async {
|
||||
Widget button(String label, IconData icon, Function() onPressed) =>
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
shadowColor: Colors.transparent),
|
||||
onPressed: onPressed,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Icon(icon),
|
||||
),
|
||||
Text(label)
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
final imageBytes = await datas.getImageBytes;
|
||||
if (imageBytes != null && context.mounted) {
|
||||
final name =
|
||||
"${widget.chapter.manga.value!.name} ${widget.chapter.name} - ${datas.pageIndex}"
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9 .()\-\s]'), '_');
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: context.mediaWidth(1),
|
||||
),
|
||||
builder: (context) {
|
||||
return SizedBox(
|
||||
height: 120,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20)),
|
||||
color: context.themeData.scaffoldBackgroundColor),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
height: 7,
|
||||
width: 35,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: context.secondaryColor.withOpacity(0.4)),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
button(context.l10n.set_as_cover, Icons.image_outlined,
|
||||
() async {
|
||||
final res = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content:
|
||||
Text(context.l10n.use_this_as_cover_art),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.cancel)),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final manga =
|
||||
widget.chapter.manga.value!;
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.putSync(manga
|
||||
..customCoverImage =
|
||||
imageBytes);
|
||||
});
|
||||
if (mounted) {
|
||||
Navigator.pop(context, "ok");
|
||||
}
|
||||
},
|
||||
child: Text(context.l10n.ok)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
if (res != null && res == "ok" && context.mounted) {
|
||||
Navigator.pop(context);
|
||||
botToast(context.l10n.cover_updated, second: 3);
|
||||
}
|
||||
}),
|
||||
button(context.l10n.share, Icons.share_outlined,
|
||||
() async {
|
||||
await Share.shareXFiles([
|
||||
XFile.fromData(imageBytes,
|
||||
name: name, mimeType: 'image/png')
|
||||
]);
|
||||
}),
|
||||
button(context.l10n.save, Icons.save_outlined, () async {
|
||||
final dir =
|
||||
await StorageProvider().getGalleryDirectory();
|
||||
final file = File("${dir!.path}/$name.png");
|
||||
file.writeAsBytesSync(imageBytes);
|
||||
if (context.mounted) {
|
||||
botToast(context.l10n.picture_saved, second: 3);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = ref.watch(backgroundColorStateProvider);
|
||||
|
|
@ -404,6 +537,10 @@ class _MangaChapterPageGalleryState
|
|||
backgroundColor,
|
||||
isFailedToLoadImage: (val) {},
|
||||
cropBorders: cropBorders,
|
||||
onLongPressData: (datas) {
|
||||
_onLongPressImageDialog(
|
||||
datas, context);
|
||||
},
|
||||
)
|
||||
: ImageViewVertical(
|
||||
datas:
|
||||
|
|
@ -412,6 +549,10 @@ class _MangaChapterPageGalleryState
|
|||
// _failedToLoadImage.value = value;
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
onLongPressData: (datas) {
|
||||
_onLongPressImageDialog(
|
||||
datas, context);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -462,6 +603,10 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
onLongPressData: (datas) {
|
||||
_onLongPressImageDialog(
|
||||
datas, context);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount:
|
||||
|
|
@ -658,6 +803,10 @@ class _MangaChapterPageGalleryState
|
|||
.forward();
|
||||
},
|
||||
cropBorders: cropBorders,
|
||||
onLongPressData: (datas) {
|
||||
_onLongPressImageDialog(
|
||||
datas, context);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: _uChapDataPreload.length,
|
||||
|
|
@ -1666,15 +1815,17 @@ class _MangaChapterPageGalleryState
|
|||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: usePageTapZones
|
||||
? () {
|
||||
if (_isReverseHorizontal) {
|
||||
_onBtnTapped(_currentIndex! + 1, false);
|
||||
} else {
|
||||
_onBtnTapped(_currentIndex! - 1, true);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
onTap: () {
|
||||
if (usePageTapZones) {
|
||||
if (_isReverseHorizontal) {
|
||||
_onBtnTapped(_currentIndex! + 1, false);
|
||||
} else {
|
||||
_onBtnTapped(_currentIndex! - 1, true);
|
||||
}
|
||||
} else {
|
||||
_isViewFunction();
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
|
|
@ -1711,15 +1862,17 @@ class _MangaChapterPageGalleryState
|
|||
flex: 2,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: usePageTapZones
|
||||
? () {
|
||||
if (_isReverseHorizontal) {
|
||||
_onBtnTapped(_currentIndex! - 1, true);
|
||||
} else {
|
||||
_onBtnTapped(_currentIndex! + 1, false);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
onTap: () {
|
||||
if (usePageTapZones) {
|
||||
if (_isReverseHorizontal) {
|
||||
_onBtnTapped(_currentIndex! - 1, true);
|
||||
} else {
|
||||
_onBtnTapped(_currentIndex! + 1, false);
|
||||
}
|
||||
} else {
|
||||
_isViewFunction();
|
||||
}
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
_toggleScale(details.globalPosition);
|
||||
|
|
@ -1749,7 +1902,7 @@ class _MangaChapterPageGalleryState
|
|||
? _isViewFunction()
|
||||
: usePageTapZones
|
||||
? _onBtnTapped(_currentIndex! - 1, true)
|
||||
: null;
|
||||
: _isViewFunction();
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
|
|
@ -1773,7 +1926,7 @@ class _MangaChapterPageGalleryState
|
|||
? _isViewFunction()
|
||||
: usePageTapZones
|
||||
? _onBtnTapped(_currentIndex! + 1, false)
|
||||
: null;
|
||||
: _isViewFunction();
|
||||
},
|
||||
onDoubleTapDown: _isVerticalContinous()
|
||||
? (TapDownDetails details) {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ void doBackUp(DoBackUpRef ref,
|
|||
encoder.addFile(File(backupFilePath));
|
||||
encoder.close();
|
||||
Directory(backupFilePath).deleteSync(recursive: true);
|
||||
final assets = [
|
||||
'assets/app_icons/icon-black.png',
|
||||
'assets/app_icons/icon-red.png'
|
||||
];
|
||||
if (context != null) {
|
||||
Navigator.pop(context);
|
||||
BotToast.showNotification(
|
||||
|
|
@ -133,8 +137,7 @@ void doBackUp(DoBackUpRef ref,
|
|||
animationReverseDuration: const Duration(milliseconds: 200),
|
||||
duration: const Duration(seconds: 5),
|
||||
backButtonBehavior: BackButtonBehavior.none,
|
||||
leading: (cancel) =>
|
||||
Image.asset('assets/app_icons/icon-red.png', height: 40),
|
||||
leading: (_) => Image.asset((assets..shuffle()).first, height: 25),
|
||||
title: (_) => const Text(
|
||||
"Backup created!",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class StorageProvider {
|
|||
final RegExp _regExpChar = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
|
||||
Future<bool> requestPermission() async {
|
||||
Permission permission = Permission.manageExternalStorage;
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
if (Platform.isAndroid) {
|
||||
if (await permission.isGranted) {
|
||||
return true;
|
||||
} else {
|
||||
|
|
@ -105,6 +105,17 @@ class StorageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Directory?> getGalleryDirectory() async {
|
||||
String gPath = (await getDirectory())!.path;
|
||||
if (Platform.isAndroid) {
|
||||
gPath = "/storage/emulated/0/Pictures/Mangayomi/";
|
||||
} else {
|
||||
gPath = path.join(gPath, 'Pictures');
|
||||
}
|
||||
await Directory(gPath).create(recursive: true);
|
||||
return Directory(gPath);
|
||||
}
|
||||
|
||||
Future<Isar> initDB(String? path, {bool? inspector = false}) async {
|
||||
Directory? dir;
|
||||
if (path == null) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,53 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mangayomi/modules/manga/reader/reader_view.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
|
||||
extension LetExtension<T> on T {
|
||||
R let<R>(R Function(T) block) {
|
||||
return block(this);
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageProviderExtension on ImageProvider {
|
||||
Future<Uint8List?> getBytes(BuildContext context,
|
||||
{ImageByteFormat format = ImageByteFormat.png}) async {
|
||||
final imageStream = resolve(createLocalImageConfiguration(context));
|
||||
final Completer<Uint8List?> completer = Completer<Uint8List?>();
|
||||
final ImageStreamListener listener = ImageStreamListener(
|
||||
(imageInfo, synchronousCall) async {
|
||||
final bytes = await imageInfo.image.toByteData(format: format);
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(bytes?.buffer.asUint8List());
|
||||
}
|
||||
},
|
||||
);
|
||||
imageStream.addListener(listener);
|
||||
final imageBytes = await completer.future;
|
||||
imageStream.removeListener(listener);
|
||||
return imageBytes;
|
||||
}
|
||||
}
|
||||
|
||||
extension UChapDataPreloadExtensions on UChapDataPreload {
|
||||
Future<Uint8List?> get getImageBytes async {
|
||||
Uint8List? imageBytes;
|
||||
if (archiveImage != null) {
|
||||
imageBytes = archiveImage;
|
||||
} else if (isLocale!) {
|
||||
imageBytes =
|
||||
File('${path!.path}${padIndex(index! + 1)}.jpg').readAsBytesSync();
|
||||
} else {
|
||||
File? cachedImage;
|
||||
if (url != null) {
|
||||
cachedImage = await getCachedImageFile(url!);
|
||||
}
|
||||
imageBytes = cachedImage?.readAsBytesSync();
|
||||
}
|
||||
return imageBytes;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ part 'headers.g.dart';
|
|||
Map<String, String> headers(HeadersRef ref,
|
||||
{required String source, required String lang}) {
|
||||
final mSource = getSource(lang, source);
|
||||
|
||||
if (mSource == null) return {};
|
||||
Map<String, String> headers = {};
|
||||
if (mSource?.headers?.isNotEmpty ?? false) {
|
||||
headers = (jsonDecode(mSource!.headers!) as Map)
|
||||
if (mSource.headers?.isNotEmpty ?? false) {
|
||||
headers = (jsonDecode(mSource.headers!) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
|
||||
final cookies = MInterceptor.getCookiesPref(mSource!.baseUrl!);
|
||||
final cookies = MInterceptor.getCookiesPref(mSource.baseUrl!);
|
||||
headers.addAll(cookies);
|
||||
|
||||
return headers;
|
||||
|
|
|
|||
Loading…
Reference in a new issue