mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-01-11 22:40:36 +00:00
fix #637
This commit is contained in:
parent
701a696820
commit
9458ae120b
4 changed files with 196 additions and 87 deletions
|
|
@ -306,20 +306,16 @@ Future<void> _scanDirectory(Ref ref, Directory? dir) async {
|
|||
: Uint8List.fromList(coverImage).getCoverImage;
|
||||
saveManga++;
|
||||
}
|
||||
for (var chapter in book.Chapters ?? []) {
|
||||
chaptersToSave.add(
|
||||
Chapter(
|
||||
mangaId: manga.id,
|
||||
name: chapter.Title is String && chapter.Title.isEmpty
|
||||
? "Book"
|
||||
: chapter.Title,
|
||||
archivePath: chapterPath,
|
||||
downloadSize: chapterFile.existsSync()
|
||||
? chapterFile.lengthSync().formattedFileSize()
|
||||
: null,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
}
|
||||
chaptersToSave.add(
|
||||
Chapter(
|
||||
mangaId: manga.id,
|
||||
name: book.Title,
|
||||
archivePath: chapterPath,
|
||||
downloadSize: chapterFile.existsSync()
|
||||
? chapterFile.lengthSync().formattedFileSize()
|
||||
: null,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
} else {
|
||||
final chap = Chapter(
|
||||
mangaId: manga.id,
|
||||
|
|
|
|||
|
|
@ -85,18 +85,14 @@ Future importArchivesFromFile(
|
|||
: Uint8List.fromList(coverImage).getCoverImage,
|
||||
);
|
||||
}
|
||||
for (var chapter in book.Chapters ?? []) {
|
||||
chapters.add(
|
||||
Chapter(
|
||||
mangaId: mangaId,
|
||||
name: chapter.Title is String && chapter.Title.isEmpty
|
||||
? "Book"
|
||||
: chapter.Title,
|
||||
archivePath: file.path,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
}
|
||||
chapters.add(
|
||||
Chapter(
|
||||
mangaId: mangaId,
|
||||
name: book.Title,
|
||||
archivePath: file.path,
|
||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||
)..manga.value = manga,
|
||||
);
|
||||
} else {
|
||||
chapters.add(
|
||||
Chapter(
|
||||
|
|
|
|||
|
|
@ -268,11 +268,27 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
novelReaderTextColorStateProvider,
|
||||
);
|
||||
|
||||
Color parseColor(String hex) {
|
||||
final hexColor = hex.replaceAll('#', '');
|
||||
return Color(
|
||||
int.parse('FF$hexColor', radix: 16),
|
||||
);
|
||||
Color parseColor(String hex, {Color? fallback}) {
|
||||
try {
|
||||
String hexColor = hex.trim().replaceAll(
|
||||
'#',
|
||||
'',
|
||||
);
|
||||
// Ensure we have a valid 6-character hex color
|
||||
if (hexColor.length == 6) {
|
||||
return Color(
|
||||
int.parse('FF$hexColor', radix: 16),
|
||||
);
|
||||
} else if (hexColor.length == 8) {
|
||||
// Already has alpha channel
|
||||
return Color(
|
||||
int.parse(hexColor, radix: 16),
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
// If parsing fails, use fallback
|
||||
}
|
||||
return fallback ?? Colors.grey;
|
||||
}
|
||||
|
||||
TextAlign getTextAlign() {
|
||||
|
|
@ -289,7 +305,7 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
}
|
||||
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
const Duration(milliseconds: 100),
|
||||
() {
|
||||
if (!scrolled &&
|
||||
_scrollController.hasClients) {
|
||||
|
|
@ -339,9 +355,13 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
),
|
||||
color: parseColor(
|
||||
customTextColor,
|
||||
fallback: Colors.white,
|
||||
),
|
||||
backgroundColor: parseColor(
|
||||
customBackgroundColor,
|
||||
fallback: const Color(
|
||||
0xFF292832,
|
||||
),
|
||||
),
|
||||
margin: Margins.zero,
|
||||
padding: HtmlPaddings.all(
|
||||
|
|
@ -384,6 +404,7 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
"h1, h2, h3, h4, h5, h6": Style(
|
||||
color: parseColor(
|
||||
customTextColor,
|
||||
fallback: Colors.white,
|
||||
),
|
||||
lineHeight: LineHeight(
|
||||
lineHeight,
|
||||
|
|
@ -402,10 +423,60 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
),
|
||||
height: Height.auto(),
|
||||
),
|
||||
"table": Style(
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 1,
|
||||
),
|
||||
margin: Margins.symmetric(
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
"td, th": Style(
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 0.5,
|
||||
),
|
||||
padding: HtmlPaddings.all(8),
|
||||
),
|
||||
"th": Style(
|
||||
fontWeight: FontWeight.bold,
|
||||
backgroundColor: Colors.grey
|
||||
.withValues(alpha: 0.2),
|
||||
),
|
||||
"blockquote": Style(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: Colors.grey,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
padding: HtmlPaddings.only(
|
||||
left: 15,
|
||||
),
|
||||
margin: Margins.symmetric(
|
||||
vertical: 10,
|
||||
),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
"pre, code": Style(
|
||||
backgroundColor: Colors.grey
|
||||
.withValues(alpha: 0.2),
|
||||
padding: HtmlPaddings.all(8),
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
"hr": Style(
|
||||
margin: Margins.symmetric(
|
||||
vertical: 20,
|
||||
),
|
||||
),
|
||||
},
|
||||
extensions: [
|
||||
TagExtension(
|
||||
tagsToExtend: {"img"},
|
||||
tagsToExtend: {
|
||||
"img",
|
||||
"source",
|
||||
},
|
||||
builder: (extensionContext) {
|
||||
final element =
|
||||
extensionContext.node
|
||||
|
|
@ -1125,22 +1196,48 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
|||
}
|
||||
|
||||
Widget? _buildCustomWidgets(dom.Element element) {
|
||||
if (element.localName == "img" &&
|
||||
element.getSrc != null &&
|
||||
epubBook != null) {
|
||||
final fileName = element.getSrc!.split("/").last;
|
||||
final image = epubBook!.Content!.Images!.entries
|
||||
.firstWhereOrNull((img) => img.key.endsWith(fileName))
|
||||
if (epubBook == null) return null;
|
||||
|
||||
if (element.localName == "img" && element.getSrc != null) {
|
||||
final src = element.getSrc!;
|
||||
final fileName = src.split("/").last;
|
||||
final image = epubBook!.Content?.Images?.entries
|
||||
.firstWhereOrNull(
|
||||
(img) =>
|
||||
img.key.endsWith(fileName) ||
|
||||
img.key.contains(fileName.replaceAll('%20', ' ')),
|
||||
)
|
||||
?.value
|
||||
.Content;
|
||||
return image != null
|
||||
? widgets.Image(
|
||||
errorBuilder: (context, error, stackTrace) => Text("❌"),
|
||||
fit: BoxFit.scaleDown,
|
||||
image: MemoryImage(image as Uint8List) as ImageProvider,
|
||||
)
|
||||
: null;
|
||||
|
||||
if (image != null) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: widgets.Image(
|
||||
errorBuilder: (context, error, stackTrace) => Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: Colors.red.withValues(alpha: 0.1),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.broken_image, color: Colors.red),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Image not loaded: $fileName',
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
fit: BoxFit.contain,
|
||||
image: MemoryImage(image as Uint8List) as ImageProvider,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Future<(String, EpubBook?)> getHtmlContent(
|
|||
required Chapter chapter,
|
||||
}) async {
|
||||
final keepAlive = ref.keepAlive();
|
||||
(String, EpubBook?) result;
|
||||
(String, EpubBook?)? result;
|
||||
try {
|
||||
if (!chapter.manga.isLoaded) {
|
||||
chapter.manga.loadSync();
|
||||
|
|
@ -26,50 +26,53 @@ Future<(String, EpubBook?)> getHtmlContent(
|
|||
if (await htmlFile.exists()) {
|
||||
final bytes = await htmlFile.readAsBytes();
|
||||
final book = await EpubReader.readBook(bytes);
|
||||
final tempChapter = book.Chapters?.where(
|
||||
(element) => element.Title!.isNotEmpty
|
||||
? element.Title == chapter.name
|
||||
: "Book" == chapter.name,
|
||||
).firstOrNull;
|
||||
result = (_buildHtml(tempChapter?.HtmlContent ?? "No content"), book);
|
||||
String htmlContent = "";
|
||||
for (var subChapter in book.Content!.Html!.values) {
|
||||
htmlContent += "\n<hr/>\n${subChapter.Content}";
|
||||
}
|
||||
|
||||
result = (_buildHtml(htmlContent), book);
|
||||
}
|
||||
result = (_buildHtml("Local epub file not found!"), null);
|
||||
result ??= (_buildHtml("Local epub file not found!"), null);
|
||||
}
|
||||
final storageProvider = StorageProvider();
|
||||
final mangaMainDirectory = await storageProvider.getMangaMainDirectory(
|
||||
chapter,
|
||||
);
|
||||
final chapterDirectory = (await storageProvider.getMangaChapterDirectory(
|
||||
chapter,
|
||||
mangaMainDirectory: mangaMainDirectory,
|
||||
))!;
|
||||
if (result == null) {
|
||||
final storageProvider = StorageProvider();
|
||||
final mangaMainDirectory = await storageProvider.getMangaMainDirectory(
|
||||
chapter,
|
||||
);
|
||||
final chapterDirectory = (await storageProvider.getMangaChapterDirectory(
|
||||
chapter,
|
||||
mangaMainDirectory: mangaMainDirectory,
|
||||
))!;
|
||||
|
||||
final htmlPath = p.join(chapterDirectory.path, "${chapter.name}.html");
|
||||
final htmlPath = p.join(chapterDirectory.path, "${chapter.name}.html");
|
||||
|
||||
final htmlFile = File(htmlPath);
|
||||
String? htmlContent;
|
||||
if (await htmlFile.exists()) {
|
||||
htmlContent = await htmlFile.readAsString();
|
||||
final htmlFile = File(htmlPath);
|
||||
String? htmlContent;
|
||||
if (await htmlFile.exists()) {
|
||||
htmlContent = await htmlFile.readAsString();
|
||||
}
|
||||
final source = getSource(
|
||||
chapter.manga.value!.lang!,
|
||||
chapter.manga.value!.source!,
|
||||
chapter.manga.value!.sourceId,
|
||||
);
|
||||
String? html;
|
||||
final proxyServer = ref.read(androidProxyServerStateProvider);
|
||||
if (htmlContent != null) {
|
||||
html = await getExtensionService(
|
||||
source!,
|
||||
proxyServer,
|
||||
).cleanHtmlContent(htmlContent);
|
||||
} else {
|
||||
html = await getExtensionService(
|
||||
source!,
|
||||
proxyServer,
|
||||
).getHtmlContent(chapter.manga.value!.name!, chapter.url!);
|
||||
}
|
||||
result = (_buildHtml(html.substring(1, html.length - 1)), null);
|
||||
}
|
||||
final source = getSource(
|
||||
chapter.manga.value!.lang!,
|
||||
chapter.manga.value!.source!,
|
||||
chapter.manga.value!.sourceId,
|
||||
);
|
||||
String? html;
|
||||
final proxyServer = ref.read(androidProxyServerStateProvider);
|
||||
if (htmlContent != null) {
|
||||
html = await getExtensionService(
|
||||
source!,
|
||||
proxyServer,
|
||||
).cleanHtmlContent(htmlContent);
|
||||
} else {
|
||||
html = await getExtensionService(
|
||||
source!,
|
||||
proxyServer,
|
||||
).getHtmlContent(chapter.manga.value!.name!, chapter.url!);
|
||||
}
|
||||
result = (_buildHtml(html.substring(1, html.length - 1)), null);
|
||||
|
||||
keepAlive.close();
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
|
@ -91,11 +94,28 @@ String _buildHtml(String input) {
|
|||
// Parse HTML to clean it
|
||||
final document = parse(cleaned);
|
||||
|
||||
// Remove unwanted elements
|
||||
// Remove unwanted elements (ads, tracking, etc.)
|
||||
document.querySelectorAll('iframe').forEach((el) => el.remove());
|
||||
document.querySelectorAll('script').forEach((el) => el.remove());
|
||||
document.querySelectorAll('[data-aa]').forEach((el) => el.remove());
|
||||
|
||||
// Improve styles for EPUB tables
|
||||
document.querySelectorAll('table').forEach((table) {
|
||||
table.attributes['style'] =
|
||||
'${table.attributes['style'] ?? ''} border-collapse: collapse; width: 100%; margin: 10px 0;';
|
||||
});
|
||||
|
||||
document.querySelectorAll('td, th').forEach((cell) {
|
||||
cell.attributes['style'] =
|
||||
'${cell.attributes['style'] ?? ''} border: 1px solid #ddd; padding: 8px;';
|
||||
});
|
||||
|
||||
// Improve citations/blockquotes
|
||||
document.querySelectorAll('blockquote').forEach((quote) {
|
||||
quote.attributes['style'] =
|
||||
'${quote.attributes['style'] ?? ''} border-left: 4px solid #ccc; padding-left: 15px; margin: 10px 0; font-style: italic;';
|
||||
});
|
||||
|
||||
// Get cleaned HTML
|
||||
String htmlContent = document.body?.innerHtml ?? cleaned;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue