parent
b983ca07c6
commit
0fe7b59989
@ -0,0 +1,31 @@ |
||||
package xyz.fycz.myreader.entity; |
||||
|
||||
/** |
||||
* @author fengyue |
||||
* @date 2021/6/12 20:55 |
||||
*/ |
||||
public class Quotation { |
||||
private String hitokoto; |
||||
private String from; |
||||
|
||||
public String getHitokoto() { |
||||
return hitokoto; |
||||
} |
||||
|
||||
public void setHitokoto(String hitokoto) { |
||||
this.hitokoto = hitokoto; |
||||
} |
||||
|
||||
public String getFrom() { |
||||
return from; |
||||
} |
||||
|
||||
public void setFrom(String from) { |
||||
this.from = from; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return hitokoto + " --- " + from; |
||||
} |
||||
} |
@ -1,56 +0,0 @@ |
||||
package xyz.fycz.myreader.widget.page; |
||||
|
||||
|
||||
public class BookChapterBean { |
||||
|
||||
//章节所属的小说(网络)
|
||||
public String bookId; |
||||
//章节名
|
||||
public String title; |
||||
|
||||
//章节内容在文章中的起始位置(本地)
|
||||
public long start; |
||||
//章节内容在文章中的终止位置(本地)
|
||||
public long end; |
||||
|
||||
public String getBookId() { |
||||
return bookId; |
||||
} |
||||
|
||||
public void setBookId(String id) { |
||||
this.bookId = id; |
||||
} |
||||
|
||||
public String getTitle() { |
||||
return title; |
||||
} |
||||
|
||||
public void setTitle(String title) { |
||||
this.title = title; |
||||
} |
||||
|
||||
public long getStart() { |
||||
return start; |
||||
} |
||||
|
||||
public void setStart(long start) { |
||||
this.start = start; |
||||
} |
||||
|
||||
public long getEnd() { |
||||
return end; |
||||
} |
||||
|
||||
public void setEnd(long end) { |
||||
this.end = end; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "BookChapterBean{" + |
||||
"title='" + title + '\'' + |
||||
", start=" + start + |
||||
", end=" + end + |
||||
'}'; |
||||
} |
||||
} |
@ -0,0 +1,328 @@ |
||||
package xyz.fycz.myreader.widget.page; |
||||
|
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.graphics.Canvas; |
||||
import android.text.TextUtils; |
||||
import android.util.Log; |
||||
|
||||
|
||||
import net.sf.jazzlib.ZipFile; |
||||
|
||||
import org.jsoup.Jsoup; |
||||
import org.jsoup.nodes.Document; |
||||
import org.jsoup.nodes.Element; |
||||
import org.jsoup.nodes.TextNode; |
||||
import org.jsoup.select.Elements; |
||||
|
||||
import java.io.BufferedReader; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.io.InputStreamReader; |
||||
import java.nio.charset.Charset; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import io.reactivex.Observable; |
||||
import io.reactivex.ObservableOnSubscribe; |
||||
import io.reactivex.Observer; |
||||
import io.reactivex.android.schedulers.AndroidSchedulers; |
||||
import io.reactivex.disposables.Disposable; |
||||
import io.reactivex.schedulers.Schedulers; |
||||
import nl.siegmann.epublib.domain.Book; |
||||
import nl.siegmann.epublib.domain.MediaType; |
||||
import nl.siegmann.epublib.domain.Metadata; |
||||
import nl.siegmann.epublib.domain.Resource; |
||||
import nl.siegmann.epublib.domain.SpineReference; |
||||
import nl.siegmann.epublib.domain.TOCReference; |
||||
import nl.siegmann.epublib.epub.EpubReader; |
||||
import nl.siegmann.epublib.service.MediatypeService; |
||||
import xyz.fycz.myreader.base.observer.MyObserver; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.entity.Setting; |
||||
import xyz.fycz.myreader.greendao.entity.Chapter; |
||||
import xyz.fycz.myreader.greendao.service.ChapterService; |
||||
import xyz.fycz.myreader.util.help.StringHelper; |
||||
import xyz.fycz.myreader.util.utils.FileUtils; |
||||
import xyz.fycz.myreader.util.utils.StringUtils; |
||||
|
||||
public class EpubPageLoader extends PageLoader { |
||||
|
||||
//编码类型
|
||||
private Charset mCharset; |
||||
|
||||
private Book epubBook; |
||||
|
||||
EpubPageLoader(PageView pageView, xyz.fycz.myreader.greendao.entity.Book bookShelfBean, Setting setting) { |
||||
super(pageView, bookShelfBean, setting); |
||||
} |
||||
|
||||
@Override |
||||
public void refreshChapterList() { |
||||
if (mCollBook == null) return; |
||||
|
||||
Observable.create((ObservableOnSubscribe<xyz.fycz.myreader.greendao.entity.Book>) e -> { |
||||
File bookFile = new File(mCollBook.getChapterUrl()); |
||||
epubBook = readBook(bookFile); |
||||
|
||||
if (epubBook == null) { |
||||
e.onError(new Exception("文件解析失败")); |
||||
return; |
||||
} |
||||
if (TextUtils.isEmpty(mCollBook.getInfoUrl())) { |
||||
mCollBook.setInfoUrl("UTF-8"); |
||||
} |
||||
mCharset = Charset.forName(mCollBook.getInfoUrl()); |
||||
|
||||
if (TextUtils.isEmpty(mCollBook.getImgUrl())){ |
||||
saveCover(); |
||||
} |
||||
e.onNext(mCollBook); |
||||
e.onComplete(); |
||||
}).subscribeOn(Schedulers.single()) |
||||
.flatMap(this::checkChapterList) |
||||
.observeOn(AndroidSchedulers.mainThread()) |
||||
.subscribe(new MyObserver<xyz.fycz.myreader.greendao.entity.Book>() { |
||||
@Override |
||||
public void onSubscribe(Disposable d) { |
||||
mChapterDis = d; |
||||
} |
||||
|
||||
@Override |
||||
public void onNext(xyz.fycz.myreader.greendao.entity.Book bookShelfBean) { |
||||
isChapterListPrepare = true; |
||||
// 加载并显示当前章节
|
||||
openChapter(); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
chapterError(e.getMessage()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
|
||||
public static Book readBook(File file) { |
||||
try { |
||||
EpubReader epubReader = new EpubReader(); |
||||
MediaType[] lazyTypes = { |
||||
MediatypeService.CSS, |
||||
MediatypeService.GIF, |
||||
MediatypeService.JPG, |
||||
MediatypeService.PNG, |
||||
MediatypeService.MP3, |
||||
MediatypeService.MP4}; |
||||
ZipFile zipFile = new ZipFile(file); |
||||
return epubReader.readEpubLazy(zipFile, "utf-8", Arrays.asList(lazyTypes)); |
||||
} catch (Exception e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private void saveCover() throws IOException { |
||||
byte[] data = epubBook.getCoverImage().getData(); |
||||
FileUtils.writeFile(data, APPCONST.COVER_DIR, mCollBook.getId() + ".jpg"); |
||||
mCollBook.setImgUrl(APPCONST.COVER_DIR + mCollBook.getId() + ".jpg"); |
||||
} |
||||
|
||||
/*private void extractScaledCoverImage() { |
||||
try { |
||||
byte[] data = epubBook.getCoverImage().getData(); |
||||
Bitmap rawCover = BitmapFactory.decodeByteArray(data, 0, data.length); |
||||
int width = rawCover.getWidth(); |
||||
int height = rawCover.getHeight(); |
||||
if (width <= mVisibleWidth && width >= 0.8 * mVisibleWidth) { |
||||
cover = rawCover; |
||||
return; |
||||
} |
||||
height = Math.round(mVisibleWidth * 1.0f * height / width); |
||||
cover = Bitmap.createScaledBitmap(rawCover, mVisibleWidth, height, true); |
||||
} catch (Exception ignored) { |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void drawCover(Canvas canvas, float top) { |
||||
if (cover == null) { |
||||
extractScaledCoverImage(); |
||||
} |
||||
if (cover == null) |
||||
return; |
||||
int left = (mDisplayWidth - cover.getWidth()) / 2; |
||||
canvas.drawBitmap(cover, left, top, mTextPaint); |
||||
}*/ |
||||
|
||||
private List<Chapter> loadChapters() { |
||||
Metadata metadata = epubBook.getMetadata(); |
||||
mCollBook.setName(metadata.getFirstTitle()); |
||||
if (metadata.getAuthors().size() > 0) { |
||||
String author = metadata.getAuthors().get(0).toString().replaceAll("^, |, $", ""); |
||||
mCollBook.setAuthor(author); |
||||
} |
||||
if (metadata.getDescriptions().size() > 0) { |
||||
mCollBook.setDesc(Jsoup.parse(metadata.getDescriptions().get(0)).text()); |
||||
} |
||||
mChapterList = new ArrayList<>(); |
||||
List<TOCReference> refs = epubBook.getTableOfContents().getTocReferences(); |
||||
if (refs == null || refs.isEmpty()) { |
||||
List<SpineReference> spineReferences = epubBook.getSpine().getSpineReferences(); |
||||
for (int i = 0, size = spineReferences.size(); i < size; i++) { |
||||
Resource resource = spineReferences.get(i).getResource(); |
||||
String title = resource.getTitle(); |
||||
if (TextUtils.isEmpty(title)) { |
||||
try { |
||||
Document doc = Jsoup.parse(new String(resource.getData(), mCharset)); |
||||
Elements elements = doc.getElementsByTag("title"); |
||||
if (elements.size() > 0) { |
||||
title = elements.get(0).text(); |
||||
} |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
Chapter bean = new Chapter(); |
||||
bean.setNumber(i); |
||||
bean.setBookId(mCollBook.getId()); |
||||
bean.setUrl(resource.getHref()); |
||||
if (i == 0 && title.isEmpty()) { |
||||
bean.setTitle("封面"); |
||||
} else { |
||||
bean.setTitle(title); |
||||
} |
||||
mChapterList.add(bean); |
||||
} |
||||
} else { |
||||
parseMenu(refs, 0); |
||||
for (int i = 0; i < mChapterList.size(); i++) { |
||||
mChapterList.get(i).setNumber(i); |
||||
} |
||||
} |
||||
|
||||
return mChapterList; |
||||
} |
||||
|
||||
private void parseMenu(List<TOCReference> refs, int level) { |
||||
if (refs == null) return; |
||||
for (TOCReference ref : refs) { |
||||
if (ref.getResource() != null) { |
||||
Chapter chapter = new Chapter(); |
||||
chapter.setBookId(mCollBook.getId()); |
||||
chapter.setTitle(ref.getTitle()); |
||||
chapter.setUrl(ref.getCompleteHref()); |
||||
chapter.setId(StringHelper.getStringRandom(15)); |
||||
mChapterList.add(chapter); |
||||
} |
||||
if (ref.getChildren() != null && !ref.getChildren().isEmpty()) { |
||||
parseMenu(ref.getChildren(), level + 1); |
||||
} |
||||
} |
||||
} |
||||
|
||||
protected String getChapterContent(Chapter chapter) throws Exception { |
||||
Resource resource = epubBook.getResources().getByHref(chapter.getUrl()); |
||||
StringBuilder content = new StringBuilder(); |
||||
Document doc = Jsoup.parse(new String(resource.getData(), mCharset)); |
||||
Elements elements = doc.getAllElements(); |
||||
for (Element element : elements) { |
||||
List<TextNode> contentEs = element.textNodes(); |
||||
for (int i = 0; i < contentEs.size(); i++) { |
||||
String text = contentEs.get(i).text().trim(); |
||||
text = StringUtils.formatHtml(text); |
||||
if (elements.size() > 1) { |
||||
if (text.length() > 0) { |
||||
if (content.length() > 0) { |
||||
content.append("\r\n"); |
||||
} |
||||
content.append("\u3000\u3000").append(text); |
||||
} |
||||
} else { |
||||
content.append(text); |
||||
} |
||||
} |
||||
} |
||||
return content.toString(); |
||||
} |
||||
|
||||
private Observable<xyz.fycz.myreader.greendao.entity.Book> checkChapterList(xyz.fycz.myreader.greendao.entity.Book collBook) { |
||||
if (mCollBook.getChapterTotalNum() != 0) { |
||||
mChapterList = ChapterService.getInstance().findBookAllChapterByBookId(mCollBook.getId()); |
||||
mPageChangeListener.onCategoryFinish(mChapterList); |
||||
return Observable.just(collBook); |
||||
} else { |
||||
return Observable.create((ObservableOnSubscribe<List<Chapter>>) e -> { |
||||
List<Chapter> chapterList = loadChapters(); |
||||
if (!chapterList.isEmpty()) { |
||||
e.onNext(chapterList); |
||||
} else { |
||||
e.onError(new IllegalAccessException("epubBook sub-chapter failed!")); |
||||
} |
||||
e.onComplete(); |
||||
}) |
||||
.flatMap(chapterList -> { |
||||
mPageChangeListener.onCategoryFinish(chapterList); |
||||
return Observable.just(collBook); |
||||
}) |
||||
.doOnNext(bookShelfBean -> { |
||||
// 存储章节到数据库
|
||||
ChapterService.getInstance().addChapters(mChapterList); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected BufferedReader getChapterReader(Chapter chapter) throws Exception { |
||||
Log.d("getChapterReader", chapter.getTitle()); |
||||
byte[] content = getChapterContent(chapter).getBytes(); |
||||
ByteArrayInputStream bais = new ByteArrayInputStream(content); |
||||
BufferedReader br = new BufferedReader(new InputStreamReader(bais)); |
||||
return br; |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasChapterData(Chapter chapter) { |
||||
return true; |
||||
} |
||||
|
||||
/*public void updateChapter() { |
||||
mPageView.getActivity().toast("目录更新中"); |
||||
Observable.create((ObservableOnSubscribe<BookShelfBean>) e -> { |
||||
if (TextUtils.isEmpty(book.getBookInfoBean().getCharset())) { |
||||
book.getBookInfoBean().setCharset("UTF-8"); |
||||
} |
||||
mCharset = Charset.forName(book.getBookInfoBean().getCharset()); |
||||
//清除原目录
|
||||
BookshelfHelp.delChapterList(book.getNoteUrl()); |
||||
callback.getChapterList().clear(); |
||||
e.onNext(book); |
||||
e.onComplete(); |
||||
}).flatMap(this::checkChapterList) |
||||
.compose(RxUtils::toSimpleSingle) |
||||
.subscribe(new Observer<BookShelfBean>() { |
||||
@Override |
||||
public void onSubscribe(Disposable d) { |
||||
compositeDisposable.add(d); |
||||
} |
||||
|
||||
@Override |
||||
public void onNext(BookShelfBean bookShelfBean) { |
||||
mPageView.getActivity().toast("更新完成"); |
||||
isChapterListPrepare = true; |
||||
// 加载并显示当前章节
|
||||
skipToChapter(bookShelfBean.getDurChapter(), bookShelfBean.getDurChapterPage()); |
||||
} |
||||
|
||||
@Override |
||||
public void onError(Throwable e) { |
||||
durDhapterError(e.getMessage()); |
||||
} |
||||
|
||||
@Override |
||||
public void onComplete() { |
||||
|
||||
} |
||||
}); |
||||
}*/ |
||||
} |
@ -0,0 +1,27 @@ |
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:width="24dp" |
||||
android:height="24dp" |
||||
android:viewportWidth="1024" |
||||
android:viewportHeight="1024"> |
||||
<path |
||||
android:pathData="M512,512m-512,0a512,512 0,1 0,1024 0,512 512,0 1,0 -1024,0Z" |
||||
android:fillColor="#FFFFFF"/> |
||||
<path |
||||
android:pathData="M685.6,306.4L350.4,306.4L499.2,176c11.2,-9.6 27.2,-9.6 38.4,0l148,130.4zM417.6,280.8h200L520,195.2c-1.6,-0.8 -3.2,-0.8 -4,0L417.6,280.8z" |
||||
android:fillColor="#333333"/> |
||||
<path |
||||
android:pathData="M208,288h624c17.6,0 32,14.4 32,32v464c0,17.6 -14.4,32 -32,32H208c-17.6,0 -32,-14.4 -32,-32V320c0,-17.6 14.4,-32 32,-32z" |
||||
android:fillColor="#81B7EF"/> |
||||
<path |
||||
android:pathData="M832,827.2L208,827.2c-24,0 -43.2,-19.2 -43.2,-43.2L164.8,320c0,-24 19.2,-43.2 43.2,-43.2h624c24,0 43.2,19.2 43.2,43.2v464c0,24 -19.2,43.2 -43.2,43.2zM208,299.2c-11.2,0 -20.8,8.8 -20.8,20.8v464c0,11.2 8.8,20.8 20.8,20.8h624c11.2,0 20.8,-8.8 20.8,-20.8L852.8,320c0,-11.2 -8.8,-20.8 -20.8,-20.8L208,299.2z" |
||||
android:fillColor="#333333"/> |
||||
<path |
||||
android:pathData="M744,452.8H536c-7.2,0 -12.8,-5.6 -12.8,-12.8s5.6,-12.8 12.8,-12.8h208c7.2,0 12.8,5.6 12.8,12.8s-5.6,12.8 -12.8,12.8zM744,512.8H536c-7.2,0 -12.8,-5.6 -12.8,-12.8s5.6,-12.8 12.8,-12.8h208c7.2,0 12.8,5.6 12.8,12.8s-5.6,12.8 -12.8,12.8zM664,576.8H536c-7.2,0 -12.8,-5.6 -12.8,-12.8s5.6,-12.8 12.8,-12.8h128c7.2,0 12.8,5.6 12.8,12.8s-5.6,12.8 -12.8,12.8zM288,752h96c17.6,0 32,14.4 32,32s-14.4,32 -32,32H288c-17.6,0 -32,-14.4 -32,-32s14.4,-32 32,-32z" |
||||
android:fillColor="#FFFFFF"/> |
||||
<path |
||||
android:pathData="M384,827.2L288,827.2c-24,0 -43.2,-19.2 -43.2,-43.2s19.2,-43.2 43.2,-43.2h96c24,0 43.2,19.2 43.2,43.2s-19.2,43.2 -43.2,43.2zM288,763.2c-11.2,0 -20.8,8.8 -20.8,20.8s8.8,20.8 20.8,20.8h96c11.2,0 20.8,-8.8 20.8,-20.8s-8.8,-20.8 -20.8,-20.8L288,763.2z" |
||||
android:fillColor="#333333"/> |
||||
<path |
||||
android:pathData="M520,216m-24,0a24,24 0,1 0,48 0,24 24,0 1,0 -48,0Z" |
||||
android:fillColor="#333333"/> |
||||
</vector> |
Loading…
Reference in new issue