新增语音朗读

pull/5/head
fengyuecanzhu 4 years ago
parent 2937f4e698
commit 92526af0c7
  1. 4
      .idea/assetWizardSettings.xml
  2. BIN
      .idea/caches/build_file_checksums.ser
  3. 1
      .idea/gradle.xml
  4. 2
      .idea/modules.xml
  5. 210
      README.md
  6. 13
      app/build.gradle
  7. 29
      app/src/main/AndroidManifest.xml
  8. 8
      app/src/main/assets/updatelog.fy
  9. 45
      app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
  10. 2
      app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java
  11. 6
      app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
  12. 1
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
  13. 5
      app/src/main/java/xyz/fycz/myreader/ui/activity/FontsActivity.java
  14. 85
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
  15. 115
      app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java
  16. 4
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SuggestBookAdapter.java
  17. 3
      app/src/main/java/xyz/fycz/myreader/ui/dialog/SourceExchangeDialog.java
  18. 3
      app/src/main/java/xyz/fycz/myreader/ui/presenter/BookStorePresenter.java
  19. 3
      app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
  20. 30
      app/src/main/java/xyz/fycz/myreader/util/ScreenHelper.java
  21. 18
      app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java
  22. 49
      app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java
  23. 6
      app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java
  24. 296
      app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java
  25. 106
      app/src/main/java/xyz/fycz/myreader/widget/page/TxtChapter.java
  26. 15
      app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java
  27. 334
      app/src/main/res/layout/activity_read.xml
  28. 13
      app/src/main/res/layout/activity_splash.xml
  29. 22
      app/src/main/res/values/strings.xml
  30. 4
      app/version_code.properties
  31. 1
      gradle.properties
  32. BIN
      img/1.png
  33. BIN
      img/2.png
  34. BIN
      img/3.png
  35. BIN
      img/4.png
  36. BIN
      img/5.png
  37. BIN
      img/6.png
  38. BIN
      img/7.png
  39. BIN
      img/8.png
  40. BIN
      img/9.png

@ -19,8 +19,8 @@
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_bookmark" />
<entry key="sourceFile" value="F:\SVG图标\书签.svg" />
<entry key="outputName" value="ic_home" />
<entry key="sourceFile" value="F:\SVG图标\主页.svg" />
</map>
</option>
</PersistentState>

@ -14,6 +14,7 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/FYReader-master.iml" filepath="$PROJECT_DIR$/FYReader-master.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/FYReader-master.iml" filepath="$PROJECT_DIR$/.idea/modules/FYReader-master.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/FYReader-master.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/FYReader-master.app.iml" />
</modules>
</component>

@ -1,204 +1,26 @@
# FYReader-master
风月读书,一款开源、无广告的小说阅读软件。
demo下载:https://fycz.lanzoui.com/iaVu6i6ywlg
#### 一、关于书源
* 软件内置了15个书源如下:
* 11个网络小说书源:天籁小说、笔趣阁44、品书网、笔趣阁、
11个网络小说书源:天籁小说、笔趣阁44、品书网、笔趣阁、
全本小说、米趣小说、九桃小说、云中书库、
搜小说网、全小说网、奇奇小说
* 4个实体书书源:超星图书·实体、作品集·实体、99藏书·实体、100本·实体
* 如何自行制作并添加书源.
* 基于面向接口开发的思想,对于书源我设计了如下接口:
* ```java
// 这个接口位于xyz.fycz.myreader.webapi.crawler.base包下
public interface ReadCrawler {
String getSearchLink(); // 书源的搜索url
String getCharset(); // 书源的字符编码
String getSearchCharset(); // 书源搜索关键字的字符编码,和书源的字符编码就行
String getNameSpace(); // 书源主页地址
Boolean isPost(); // 是否以post请求搜索
String getContentFormHtml(String html); // 获取书籍内容规则
ArrayList<Chapter> getChaptersFromHtml(String html); // 获取书籍章节列表规则
ConcurrentMultiValueMap<SearchBookBean, Book> getBooksFromSearchHtml(String html); // 搜索书籍规则
}
```
* 了解上述接口的方法,我们就可以开始创建书源了
* 第一步:创建一个书源类实现上述接口,下面以笔趣阁44为例进行说明
* ```java
// 注意:如果搜索书籍页没有图片、最新章节、书籍简介等信息,可以通过实现BookInfoCrawler接口,从书籍详情页获取
public class BiQuGe44ReadCrawler implements ReadCrawler, BookInfoCrawler {
//网站主页地址
public static final String NAME_SPACE = "https://www.wqge.cc";
/*
搜索url,搜索关键词以{key}进行占位
如果是post请求,以“,”分隔url,“,”前是搜索地址,“,”后是请求体,搜索关键词同样以{key}占位
例如:"https://www.9txs.com/search.html,searchkey={key}"
*/
public static final String NOVEL_SEARCH = "https://www.wqge.cc/modules/article/search.php?searchkey={key}";
// 书源字符编码
public static final String CHARSET = "GBK";
// 书源搜索关键词编码
public static final String SEARCH_CHARSET = "utf-8";
@Override
public String getSearchLink() {
return NOVEL_SEARCH;
}
@Override
public String getCharset() {
return CHARSET;
}
@Override
public String getNameSpace() {
return NAME_SPACE;
}
@Override
public Boolean isPost() {
return false;
}
@Override
public String getSearchCharset() {
return SEARCH_CHARSET;
}
/**
* 从html中获取章节正文
* @param html
* @return
*/
public String getContentFormHtml(String html) {
Document doc = Jsoup.parse(html);
Element divContent = doc.getElementById("content");
if (divContent != null) {
String content = Html.fromHtml(divContent.html()).toString();
char c = 160;
String spaec = "" + c;
content = content.replace(spaec, " ");
return content;
} else {
return "";
}
}
/**
* 从html中获取章节列表
*
* @param html
* @return
*/
public ArrayList<Chapter> getChaptersFromHtml(String html) {
ArrayList<Chapter> chapters = new ArrayList<>();
Document doc = Jsoup.parse(html);
String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content");
Element divList = doc.getElementById("list");
String lastTile = null;
int i = 0;
Elements elementsByTag = divList.getElementsByTag("dd");
for (int j = 9; j < elementsByTag.size(); j++) {
Element dd = elementsByTag.get(j);
Elements as = dd.getElementsByTag("a");
if (as.size() > 0) {
Element a = as.get(0);
String title = a.text() ;
if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) {
continue;
}
Chapter chapter = new Chapter();
chapter.setNumber(i++);
chapter.setTitle(title);
String url = readUrl + a.attr("href");
chapter.setUrl(url);
chapters.add(chapter);
lastTile = title;
}
}
return chapters;
}
/**
* 从搜索html中得到书列表
* @param html
* @return
*/
public ConcurrentMultiValueMap<SearchBookBean, Book> getBooksFromSearchHtml(String html) {
ConcurrentMultiValueMap<SearchBookBean, Book> books = new ConcurrentMultiValueMap<>();
Document doc = Jsoup.parse(html);
Elements divs = doc.getElementsByTag("table");
Element div = divs.get(0);
Elements elementsByTag = div.getElementsByTag("tr");
for (int i = 1; i < elementsByTag.size(); i++) {
Element element = elementsByTag.get(i);
Book book = new Book();
Elements info = element.getElementsByTag("td");
book.setName(info.get(0).text());
book.setChapterUrl(NAME_SPACE + info.get(0).getElementsByTag("a").attr("href"));
book.setAuthor(info.get(2).text());
book.setNewestChapterTitle(info.get(1).text());
book.setSource(BookSource.biquge44.toString());
// SearchBookBean用于合并相同书籍
SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
books.add(sbb, book);
}
return books;
}
/**
* 获取书籍详细信息
* @param book
*/
public Book getBookInfo(String html, Book book){
Document doc = Jsoup.parse(html);
Element img = doc.getElementById("fmimg");
book.setImgUrl(img.getElementsByTag("img").get(0).attr("src"));
Element desc = doc.getElementById("intro");
book.setDesc(desc.getElementsByTag("p").get(0).text());
Element type = doc.getElementsByClass("con_top").get(0);
book.setType(type.getElementsByTag("a").get(2).text());
return book;
}
}
```
* 第二步:注册书源信息。有两个地方需要注册:
* 1)在xyz.fycz.myreader.enums.BookSource类(这是个枚举类型)中添加你的书源的命名以及书源名称,例如:
* ```java
biquge44("笔趣阁44") // biquge44是书源的命名,笔趣阁44是书源名称
```
* 2)在app/src/main/resources/crawler.properties配置文件中添加书源类信息,例如:
4个实体书书源:超星图书·实体、作品集·实体、99藏书·实体、100本·实体
* ```java
// biquge44书源的命名,与BookSource中的命名一致,xyz.fycz.myreader.webapi.crawler.read.BiQuGe44ReadCrawler是书源类的完整路径
biquge44=xyz.fycz.myreader.webapi.crawler.read.BiQuGe44ReadCrawler
```
发现界面:排行榜、分类、书城
* 第三步:启用书源(新增的书源默认禁用)。只需在软件内-我的-设置-禁用书源中取消该书源的禁用即可。
详细功能可查看图片或下载自行体验或自行编译
#### 二、关于发现界面
demo下载:https://fycz.lanzoui.com/iBofFh42pxg
* 软件内置的两个发现源:
* 某点的排行榜、分类,全本小说网
* 制作发现源方法与书源类似,在此不再赘述
如有问题请加QQ群:1085028304
![Image](./img/1.png)
![Image](./img/2.png)
![Image](./img/3.png)
![Image](./img/4.png)
![Image](./img/5.png)
![Image](./img/6.png)
![Image](./img/7.png)
![Image](./img/8.png)
![Image](./img/9.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/1.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/2.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/3.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/4.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/5.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/6.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/7.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/8.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/9.png)
![Image](https://github.com/fengyuecanzhu/FYReader/tree/master/img/10.png)

@ -78,8 +78,20 @@ android {
}
}
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
api 'androidx.core:core-ktx:1.3.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@ -141,6 +153,7 @@ dependencies {
//ImmersionBar
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
}
greendao {

@ -3,28 +3,9 @@
xmlns:tools="http://schemas.android.com/tools" package="xyz.fycz.myreader">
<!-- ********************************permission************************************ -->
<!-- 控制振动 -->
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- 读取手机状态和身份 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="com.example.broadcast.permission"/>
<!-- 拍摄照片和视频 -->
<!-- <uses-permission android:name="android.permission.CAMERA" /> -->
<!-- 访问闪光灯 -->
<!-- <uses-permission android:name="android.permission.FLASHLIGHT" /> -->
<!-- 读取短信 -->
<!-- <uses-permission android:name="android.permission.READ_SMS" /> -->
<!-- 修改短信 -->
<!-- <uses-permission android:name="android.permission.WRITE_SMS" /> -->
<!-- 读取联系人 -->
<!-- <uses-permission android:name="android.permission.READ_CONTACTS" /> -->
<!-- 修改您的通讯录 -->
<!-- <uses-permission android:name="android.permission.WRITE_CONTACTS" /> -->
<!-- <permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> -->
<!-- 读取日历活动和详情 -->
<!-- <uses-permission android:name="android.permission.READ_CALENDAR" /> -->
<!-- 添加或修改日历活动,并在所有者不知情的情况下向邀请对象发送电子邮件 -->
<!-- <uses-permission android:name="android.permission.WRITE_CALENDAR" /> -->
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 查看网络连接 -->
@ -32,14 +13,11 @@
<!-- 查看WLAN连接 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!-- 修改或删除您的USB存储设备中的内容 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEM"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 读取您的USB存储设备中的内容 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 拨打电话 -->
<!-- <uses-permission android:name="android.permission.CALL_PHONE" /> -->
<!-- 开机启动 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.Manifest.permission.INTERNAL_SYSTEM_WINDOW"/>
@ -54,7 +32,8 @@
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
<!-- 安装应用的权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".application.MyApplication"
android:allowBackup="true"
@ -122,8 +101,8 @@
<receiver android:name=".util.notification.NotificationClickReceiver"/>
<receiver android:name=".ui.presenter.BookcasePresenter$cancelDownloadReceiver"/>
<receiver android:name=".ui.presenter.ReadPresenter$cancelDownloadReceiver"/>
<service android:name=".model.audio.ReadService"/>
<service android:name=".model.audio.ReadAloudService"/>
</application>
</manifest>

@ -1,9 +1,13 @@
2020.11.11
风月读书v1.20.1111121
1、新增语音朗读(听书)
2020.11.08
风月读书v1.20.1110816
风月读书v1.20.1110811
1、修复安卓5闪退bug
2020.11.03
风月读书v1.20.1110312
风月读书v1.20.1110311
1、修复已知bug
2、新增WebDav备份教程

@ -22,6 +22,7 @@ import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import io.reactivex.internal.functions.Functions;
import io.reactivex.plugins.RxJavaPlugins;
import org.jsoup.Jsoup;
@ -160,11 +161,17 @@ public class MyApplication extends Application {
@TargetApi(26)
private void createNotificationChannel() {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(APPCONST.channelIdDownload, "下载通知", NotificationManager.IMPORTANCE_LOW);
channel.enableLights(true);//是否在桌面icon右上角展示小红点
channel.setLightColor(Color.RED);//小红点颜色
channel.setShowBadge(false); //是否在久按桌面图标时显示此渠道的通知
notificationManager.createNotificationChannel(channel);
NotificationChannel downloadChannel = new NotificationChannel(APPCONST.channelIdDownload, "下载通知", NotificationManager.IMPORTANCE_LOW);
downloadChannel.enableLights(true);//是否在桌面icon右上角展示小红点
downloadChannel.setLightColor(Color.RED);//小红点颜色
downloadChannel.setShowBadge(false); //是否在久按桌面图标时显示此渠道的通知
notificationManager.createNotificationChannel(downloadChannel);
NotificationChannel readChannel = new NotificationChannel(APPCONST.channelIdRead, "朗读通知", NotificationManager.IMPORTANCE_LOW);
readChannel.enableLights(true);//是否在桌面icon右上角展示小红点
readChannel.setLightColor(Color.RED);//小红点颜色
readChannel.setShowBadge(false); //是否在久按桌面图标时显示此渠道的通知
notificationManager.createNotificationChannel(readChannel);
}
@ -271,6 +278,13 @@ public class MyApplication extends Application {
updateContent = contents[3].substring(contents[3].indexOf(":") + 1);
SharedPreUtils spu = SharedPreUtils.getInstance();
spu.putString(getmContext().getString(R.string.lanzousKeyStart), contents[4].substring(contents[4].indexOf(":") + 1));
String newSplashTime = contents[5].substring(contents[5].indexOf(":") + 1);
String oldSplashTime = spu.getString("splashTime");
spu.putBoolean("needUdSI", !oldSplashTime.equals(newSplashTime));
spu.putString("splashTime", contents[5].substring(contents[5].indexOf(":") + 1));
spu.putString("splashImageUrl", contents[6].substring(contents[6].indexOf(":") + 1));
if (!StringHelper.isEmpty(downloadLink)) {
spu.putString(getmContext().getString(R.string.downloadLink), downloadLink);
} else {
@ -389,4 +403,25 @@ public class MyApplication extends Application {
return mActivity == null || mActivity.isFinishing() || mActivity.isDestroyed();
}
/****************
*
* 发起添加群流程群号风月读书交流群(1085028304) key 8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn
* 调用 joinQQGroup(8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn) 即可发起手Q客户端申请加群 风月读书交流群(1085028304)
*
* @param key 由官网生成的key
* @return 返回true表示呼起手Q成功返回false表示呼起失败
******************/
public static boolean joinQQGroup(Context context, String key) {
Intent intent = new Intent();
intent.setData(Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26jump_from%3Dwebapi%26k%3D" + key));
// 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
context.startActivity(intent);
return true;
} catch (Exception e) {
// 未安装手Q或安装的版本不支持
return false;
}
}
}

@ -205,7 +205,7 @@ public abstract class BaseActivity extends AppCompatActivity {
Drawable drawable = menu.getItem(i).getIcon();
if (drawable != null) {
drawable.mutate();
drawable.setColorFilter(getColor(R.color.textPrimary), PorterDuff.Mode.SRC_ATOP);
drawable.setColorFilter(getResources().getColor(R.color.textPrimary), PorterDuff.Mode.SRC_ATOP);
}
}
return super.onCreateOptionsMenu(menu);

@ -10,7 +10,7 @@ import java.io.File;
public class APPCONST {
public static String publicKey = "";//服务端公钥
public static String publicKey = "fyds1.0";//服务端公钥
public static String privateKey;//app私钥
public final static String s = "11940364935628058505";
public static final String KEY = "readerByFengyue";
@ -58,6 +58,7 @@ public class APPCONST {
public static final String FILE_NAME_SETTING = "setting";
public static final String FILE_NAME_UPDATE_INFO = "updateInfo";
public static final String FILE_NAME_SPLASH_IMAGE = "splashImage";
public static final int REQUEST_FONT = 1001;
public static final int REQUEST_CHAPTER_PAGE = 1002;
@ -85,6 +86,9 @@ public class APPCONST {
public final static String channelIdDownload = "channel_download";
public final static String channelIdRead = "channel_read_aloud";
public static final String DEFAULT_WEB_DAV_URL = "https://dav.jianguoyun.com/dav/";
}

@ -572,6 +572,7 @@ public class BookDetailedActivity extends BaseActivity {
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case APPCONST.REQUEST_READ:

@ -130,12 +130,13 @@ public class FontsActivity extends BaseActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
String path;
if (resultCode == Activity.RESULT_OK && requestCode == APPCONST.SELECT_FILE_CODE) {
Uri uri = data.getData();
if ("file".equalsIgnoreCase(uri.getScheme())){//使用第三方应用打开
if ("file".equalsIgnoreCase(uri.getScheme())) {//使用第三方应用打开
path = uri.getPath();
}else {
} else {
path = getPath(this, uri);
}
saveLocalFont(path);

@ -18,12 +18,14 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import butterknife.BindView;
import butterknife.OnClick;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.gyf.immersionbar.ImmersionBar;
import com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar;
import xyz.fycz.myreader.ActivityManage;
@ -42,7 +44,9 @@ import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.service.BookMarkService;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.greendao.service.ChapterService;
import xyz.fycz.myreader.model.audio.ReadAloudService;
import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.ui.dialog.AudioPlayerDialog;
import xyz.fycz.myreader.ui.dialog.CopyContentDialog;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
@ -111,6 +115,8 @@ public class ReadActivity extends BaseActivity {
TextView readTvSetting;
@BindView(R.id.read_ll_bottom_menu)
LinearLayout readLlBottomMenu;
@BindView(R.id.iv_listen_book)
FloatingActionButton readBtnListenBook;
/***************************variable*****************************/
private ImmersionBar immersionBar;
@ -164,6 +170,7 @@ public class ReadActivity extends BaseActivity {
private SourceExchangeDialog mSourceDialog;
private Dialog mSettingDialog;
private AudioPlayerDialog mAudioPlayerDialog;
private boolean hasChangeSource;
@ -360,7 +367,6 @@ public class ReadActivity extends BaseActivity {
preLoad(pos - 1 + i);
}
mBook.setHistoryChapterId(mChapters.get(pos).getTitle());
mHandler.sendMessage(mHandler.obtainMessage(4));
MyApplication.getApplication().newThread(() -> {
if (mPageLoader.getPageStatus() == PageLoader.STATUS_LOADING) {
if (!NetworkUtils.isNetWorkAvailable()) {
@ -370,7 +376,6 @@ public class ReadActivity extends BaseActivity {
}
}
});
}
@Override
@ -390,8 +395,18 @@ public class ReadActivity extends BaseActivity {
}
@Override
public void onPageChange(int pos) {
public void onPageChange(int pos, boolean resetRead) {
mHandler.sendMessage(mHandler.obtainMessage(4));
if ((ReadAloudService.running)) {
if (resetRead) {
mHandler.postDelayed(() ->mAudioPlayerDialog.readAloud(), 500);
return;
}
if (pos == 0) {
mHandler.postDelayed(() ->mAudioPlayerDialog.readAloud(), 500);
return;
}
}
}
}
);
@ -445,6 +460,23 @@ public class ReadActivity extends BaseActivity {
finish();
startActivity(intent);
});
readBtnListenBook.setOnClickListener(v -> {
if (mSetting.getPageMode() == PageMode.SCROLL) {
ToastUtils.showWarring("朗读暂不支持滚动翻页模式!");
return;
}
toggleMenu(true);
if (mAudioPlayerDialog == null) {
mAudioPlayerDialog = new AudioPlayerDialog(this, mPageLoader);
mAudioPlayerDialog.setOnDismissListener(dialog -> hideSystemBar());
}
if (!ReadAloudService.running) {
mAudioPlayerDialog.aloudStatus = ReadAloudService.Status.STOP;
SystemUtil.ignoreBatteryOptimization(this);
}
mAudioPlayerDialog.show();
});
}
@Override
@ -455,7 +487,6 @@ public class ReadActivity extends BaseActivity {
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
intentFilter.addAction(Intent.ACTION_TIME_TICK);
registerReceiver(mReceiver, intentFilter);
//当书籍Collected且书籍id不为空的时候保存上次阅读信息
if (isCollected && !StringHelper.isEmpty(mBook.getId())) {
//保存上次阅读信息
@ -500,6 +531,10 @@ public class ReadActivity extends BaseActivity {
} else if (mSettingDialog.isShowing()) {
mSettingDialog.dismiss();
return;
} else if (ReadAloudService.running && mAudioPlayerDialog.aloudStatus == ReadAloudService.Status.PLAY) {
ReadAloudService.pause(this);
ToastUtils.showInfo("朗读暂停!");
return;
}
finish();
}
@ -555,6 +590,7 @@ public class ReadActivity extends BaseActivity {
if (autoPage) {
autoPageStop();
}
ReadAloudService.stop(this);
for (int i = 0; i < 9; i++) {
mHandler.removeMessages(i + 1);
}
@ -655,6 +691,7 @@ public class ReadActivity extends BaseActivity {
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**************************method*********************************/
/**
@ -1047,6 +1084,9 @@ public class ReadActivity extends BaseActivity {
* 默认是隐藏的
*/
private void toggleMenu(boolean hideStatusBar) {
toggleMenu(hideStatusBar, false);
}
public void toggleMenu(boolean hideStatusBar, boolean home) {
initMenuAnim();
if (readAblTopMenu.getVisibility() == View.VISIBLE) {
//关闭
@ -1054,18 +1094,22 @@ public class ReadActivity extends BaseActivity {
readLlBottomMenu.startAnimation(mBottomOutAnim);
readAblTopMenu.setVisibility(GONE);
readLlBottomMenu.setVisibility(GONE);
readTvPageTip.setVisibility(GONE);
if (hideStatusBar) {
hideSystemBar();
}
} else {
readTvPageTip.setVisibility(VISIBLE);
readAblTopMenu.setVisibility(View.VISIBLE);
readLlBottomMenu.setVisibility(View.VISIBLE);
readAblTopMenu.startAnimation(mTopInAnim);
readLlBottomMenu.startAnimation(mBottomInAnim);
showSystemBar();
return;
}
if (ReadAloudService.running && !home){
if (mAudioPlayerDialog != null) {
mAudioPlayerDialog.show();
return;
}
}
readAblTopMenu.setVisibility(View.VISIBLE);
readLlBottomMenu.setVisibility(View.VISIBLE);
readAblTopMenu.startAnimation(mTopInAnim);
readLlBottomMenu.startAnimation(mBottomInAnim);
showSystemBar();
}
//初始化菜单动画
@ -1085,8 +1129,10 @@ public class ReadActivity extends BaseActivity {
private void hideSystemBar() {
//隐藏
SystemBarUtils.hideStableStatusBar(this);
SystemBarUtils.hideStableNavBar(this);
if (readAblTopMenu.getVisibility() != VISIBLE || (mAudioPlayerDialog != null && !mAudioPlayerDialog.isShowing())) {
SystemBarUtils.hideStableStatusBar(this);
SystemBarUtils.hideStableNavBar(this);
}
}
/******************设置相关*****************/
@ -1112,7 +1158,11 @@ public class ReadActivity extends BaseActivity {
mSettingDialog.dismiss();
}, this::showPageModeDialog, v -> {
if (mSetting.getPageMode() == PageMode.SCROLL) {
ToastUtils.showWarring("滚动暂时不支持自动翻页");
ToastUtils.showWarring("滚动暂时不支持自动翻页!");
return;
}
if (ReadAloudService.running){
ToastUtils.showWarring("请先关闭语音朗读!");
return;
}
mSettingDialog.dismiss();
@ -1309,7 +1359,7 @@ public class ReadActivity extends BaseActivity {
keepScreenOn(true);
return;
}
int screenOffTime = screenTimeOut * 1000 - ScreenHelper.getScreenOffTime(this);
int screenOffTime = screenTimeOut * 1000 - SystemUtil.getScreenOffTime(this);
if (screenOffTime > 0) {
mHandler.removeCallbacks(keepScreenRunnable);
keepScreenOn(true);
@ -1508,7 +1558,7 @@ public class ReadActivity extends BaseActivity {
/**
* 停止自动翻页
*/
private void autoPageStop() {
public void autoPageStop() {
autoPage = false;
autoPage();
}
@ -1525,4 +1575,5 @@ public class ReadActivity extends BaseActivity {
autoPage();
});
}
}

@ -7,16 +7,40 @@ import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.signature.ObjectKey;
import com.gyf.immersionbar.ImmersionBar;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import butterknife.BindView;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.util.DateHelper;
import xyz.fycz.myreader.util.IOUtils;
import xyz.fycz.myreader.util.PermissionsChecker;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.ImageLoader;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.util.utils.SystemBarUtils;
public class SplashActivity extends AppCompatActivity {
public class SplashActivity extends BaseActivity {
/*************Constant**********/
private static final int WAIT_INTERVAL = 1000;
private static int WAIT_INTERVAL = 1000;
private static final int PERMISSIONS_REQUEST_STORAGE = 1;
static final String[] PERMISSIONS = {
@ -24,6 +48,8 @@ public class SplashActivity extends AppCompatActivity {
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
@BindView(R.id.iv_splash)
AppCompatImageView ivSplash;
private PermissionsChecker mPermissionsChecker;
private Thread myThread = new Thread() {//创建子线程
@ -40,30 +66,89 @@ public class SplashActivity extends AppCompatActivity {
}
};
@Override
protected int getContentId() {
return R.layout.activity_splash;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setFullScreen();
setContentView(R.layout.activity_splash);
ImmersionBar.with(this)
.fullScreen(true)
.init();
SystemBarUtils.hideStableStatusBar(this);
loadImage();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M){
mPermissionsChecker = new PermissionsChecker(this);
requestPermission();
}else {
myThread.start();
}
}
private void loadImage() {
File imgFile = getFileStreamPath(APPCONST.FILE_NAME_SPLASH_IMAGE);
if (!imgFile.exists() || SharedPreUtils.getInstance().getBoolean("needUdSI")){
downLoadImage();
return;
}
String splashLoadDate = SharedPreUtils.getInstance().getString("splashTime", "");
if (splashLoadDate.equals("")) {
return;
}
long startTime = 0;
long endTime = 0;
long curTime = DateHelper.getLongDate();
if (splashLoadDate.contains("~")) {
String[] splashLoadDates = splashLoadDate.split("~");
startTime = DateHelper.strDateToLong(splashLoadDates[0] + " 00:00:00");
endTime = DateHelper.strDateToLong(splashLoadDates[1] + " 00:00:00");
}
if (startTime == 0){
startTime = DateHelper.strDateToLong(splashLoadDate + " 00:00:00");
}
if (endTime == 0){
endTime = startTime + 24 * 60 * 60 * 1000;
}
if (curTime >= startTime && curTime <= endTime){
WAIT_INTERVAL = 1500;
ImageLoader.INSTANCE
.load(this, imgFile)
.error(R.drawable.start)
.signature(new ObjectKey(splashLoadDate))
.into(ivSplash);
}
}
// 全屏显示
private void setFullScreen() {
// 如果该类是 extends Activity ,下面这句代码起作用
// 去除ActionBar(因使用的是NoActionBar的主题,故此句有无皆可)
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 如果该类是 extends AppCompatActivity, 下面这句代码起作用
if (getSupportActionBar() != null){ getSupportActionBar().hide(); }
// 去除状态栏,如 电量、Wifi信号等
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
private void downLoadImage() {
MyApplication.getApplication().newThread(() -> {
String url = SharedPreUtils.getInstance().getString("splashImageUrl", "");
if (!url.equals("")) {
InputStream is = null;
FileOutputStream fos = null;
try {
is = OkHttpUtils.getInputStream(url);
fos = openFileOutput(APPCONST.FILE_NAME_SPLASH_IMAGE, MODE_PRIVATE);
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
fos.flush();
Log.d("SplashActivity", "downLoadImage success!");
} catch (IOException e) {
File data = getFileStreamPath(APPCONST.FILE_NAME_SPLASH_IMAGE);
if (data != null && data.exists()){
data.delete();
}
e.printStackTrace();
}finally {
IOUtils.close(is, fos);
}
}
});
}
private void requestPermission(){

@ -13,9 +13,7 @@ import xyz.fycz.myreader.R;
import java.util.ArrayList;
/**
* Created by zhao on 2017/7/26.
*/
public class SuggestBookAdapter extends ArrayAdapter<String> {

@ -30,9 +30,6 @@ import java.util.ArrayList;
import java.util.List;
/**
* Created by Zhouas666 on 2019-04-11
* Github: https://github.com/zas023
* <p>
* 换源dialog
*/

@ -26,9 +26,6 @@ import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zhao on 2017/7/25.
*/
public class BookStorePresenter implements BasePresenter {

@ -39,6 +39,7 @@ import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.greendao.entity.BookGroup;
import xyz.fycz.myreader.greendao.service.BookGroupService;
import xyz.fycz.myreader.model.backup.BackupAndRestore;
import xyz.fycz.myreader.ui.activity.*;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
@ -79,6 +80,7 @@ public class BookcasePresenter implements BasePresenter {
private Setting mSetting;
private final List<Book> errorLoadingBooks = new ArrayList<>();
private int finishLoadBookCount = 0;
private final BackupAndRestore mBackupAndRestore;
// private int notifyId = 11;
private ExecutorService es = Executors.newFixedThreadPool(1);//更新/下载线程池
@ -174,6 +176,7 @@ public class BookcasePresenter implements BasePresenter {
mMainActivity = (MainActivity) (mBookcaseFragment.getActivity());
// mChapterService = new ChapterService();
mSetting = SysManager.getSetting();
mBackupAndRestore = new BackupAndRestore();
}
//启动

@ -1,30 +0,0 @@
package xyz.fycz.myreader.util;
import android.content.Context;
import android.provider.Settings;
import android.util.DisplayMetrics;
import androidx.appcompat.app.AppCompatActivity;
/**
*/
public class ScreenHelper {
public static double getScreenPhysicalSize(AppCompatActivity ctx) {
DisplayMetrics dm = new DisplayMetrics();
ctx.getWindowManager().getDefaultDisplay().getMetrics(dm);
double diagonalPixels = Math.sqrt(Math.pow(dm.widthPixels, 2) + Math.pow(dm.heightPixels, 2));
return diagonalPixels / (160 * dm.density);
}
public static int getScreenOffTime(Context context) {
int screenOffTime = 0;
try {
screenOffTime = Settings.System.getInt(context.getContentResolver(),
Settings.System.SCREEN_OFF_TIMEOUT);
} catch (Exception e) {
e.printStackTrace();
}
return screenOffTime;
}
}

@ -9,6 +9,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
public class OkHttpUtils {
@ -32,7 +33,7 @@ public class OkHttpUtils {
.addHeader("connection", "Keep-Alive")
//.addHeader("Charsert", "utf-8")
.addHeader("Cache-Control", "no-cache")
.addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36");
.addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36");
if (requestBody != null) {
builder.post(requestBody);
Log.d("HttpPost URl", url);
@ -66,4 +67,19 @@ public class OkHttpUtils {
return ssfFactory;
}
public static InputStream getInputStream(String url) throws IOException {
Request.Builder builder = new Request.Builder()
.addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36");
Request request = builder
.url(url)
.build();
Response response = okHttpClient
.newCall(request)
.execute();
if (response.body() == null){
return null;
}
return response.body().byteStream();
}
}

@ -2,15 +2,10 @@ package xyz.fycz.myreader.widget.page;
import android.util.Log;
import io.reactivex.*;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.entity.Void;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.service.ChapterService;
@ -68,10 +63,10 @@ public class LocalPageLoader extends PageLoader {
}
/*private List<TxtChapter> convertTxtChapter(List<BookChapterBean> bookChapters) {
List<TxtChapter> txtChapters = new ArrayList<>(bookChapters.size());
/*private List<BookChapterBean> convertTxtChapter(List<BookChapterBean> bookChapters) {
List<BookChapterBean> txtChapters = new ArrayList<>(bookChapters.size());
for (BookChapterBean bean : bookChapters) {
TxtChapter chapter = new TxtChapter();
BookChapterBean chapter = new BookChapterBean();
chapter.title = bean.getTitle();
chapter.start = bean.getStart();
chapter.end = bean.getEnd();
@ -85,11 +80,11 @@ public class LocalPageLoader extends PageLoader {
* 1. 序章的添加
* 2. 章节存在的书本的虚拟分章效果
*/
public List<TxtChapter> loadChapters() throws IOException {
public List<BookChapterBean> loadChapters() throws IOException {
mBookFile = new File(mCollBook.getChapterUrl());
//获取文件编码
mCharset = FileUtils.getFileEncode(mBookFile.getAbsolutePath());
List<TxtChapter> chapters = new ArrayList<>();
List<BookChapterBean> chapters = new ArrayList<>();
RandomAccessFile bookStream = null;
boolean hasChapter = false;
//获取文件流
@ -132,7 +127,7 @@ public class LocalPageLoader extends PageLoader {
//如果当前对整个文件的偏移位置为0的话,那么就是序章
if (curOffset == 0) {
//创建序章
TxtChapter preChapter = new TxtChapter();
BookChapterBean preChapter = new BookChapterBean();
preChapter.title = "序章";
preChapter.start = 0;
preChapter.end = chapterContent.getBytes(mCharset).length; //获取String的byte值,作为最终值
@ -143,7 +138,7 @@ public class LocalPageLoader extends PageLoader {
}
//创建当前章节
TxtChapter curChapter = new TxtChapter();
BookChapterBean curChapter = new BookChapterBean();
curChapter.title = matcher.group();
curChapter.start = preChapter.end;
chapters.add(curChapter);
@ -151,7 +146,7 @@ public class LocalPageLoader extends PageLoader {
//否则就block分割之后,上一个章节的剩余内容
else {
//获取上一章节
TxtChapter lastChapter = chapters.get(chapters.size() - 1);
BookChapterBean lastChapter = chapters.get(chapters.size() - 1);
//将当前段落添加上一章去
lastChapter.end += chapterContent.getBytes(mCharset).length;
@ -161,7 +156,7 @@ public class LocalPageLoader extends PageLoader {
}
//创建当前章节
TxtChapter curChapter = new TxtChapter();
BookChapterBean curChapter = new BookChapterBean();
curChapter.title = matcher.group();
curChapter.start = lastChapter.end;
chapters.add(curChapter);
@ -174,7 +169,7 @@ public class LocalPageLoader extends PageLoader {
seekPos += chapterContent.length();
//获取上一章节
TxtChapter lastChapter = chapters.get(chapters.size() - 1);
BookChapterBean lastChapter = chapters.get(chapters.size() - 1);
lastChapter.end = lastChapter.start + chapterContent.getBytes(mCharset).length;
//如果章节内容太小,则移除
@ -183,14 +178,14 @@ public class LocalPageLoader extends PageLoader {
}
//创建当前章节
TxtChapter curChapter = new TxtChapter();
BookChapterBean curChapter = new BookChapterBean();
curChapter.title = matcher.group();
curChapter.start = lastChapter.end;
chapters.add(curChapter);
}
//如果章节不存在则创建章节
else {
TxtChapter curChapter = new TxtChapter();
BookChapterBean curChapter = new BookChapterBean();
curChapter.title = matcher.group();
curChapter.start = 0;
chapters.add(curChapter);
@ -220,7 +215,7 @@ public class LocalPageLoader extends PageLoader {
break;
}
}
TxtChapter chapter = new TxtChapter();
BookChapterBean chapter = new BookChapterBean();
chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")";
chapter.start = curOffset + chapterOffset + 1;
chapter.end = curOffset + end;
@ -230,7 +225,7 @@ public class LocalPageLoader extends PageLoader {
//设置偏移的位置
chapterOffset = end;
} else {
TxtChapter chapter = new TxtChapter();
BookChapterBean chapter = new BookChapterBean();
chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")";
chapter.start = curOffset + chapterOffset + 1;
chapter.end = curOffset + length;
@ -245,7 +240,7 @@ public class LocalPageLoader extends PageLoader {
if (hasChapter) {
//设置上一章的结尾
TxtChapter lastChapter = chapters.get(chapters.size() - 1);
BookChapterBean lastChapter = chapters.get(chapters.size() - 1);
lastChapter.end = curOffset;
}
@ -264,26 +259,26 @@ public class LocalPageLoader extends PageLoader {
public void loadChapters(final ResultCallback resultCallback) {
// 通过RxJava异步处理分章事件
Single.create((SingleOnSubscribe<List<TxtChapter>>) e -> {
Single.create((SingleOnSubscribe<List<BookChapterBean>>) e -> {
e.onSuccess(loadChapters());
}).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver<List<TxtChapter>>() {
}).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver<List<BookChapterBean>>() {
@Override
public void onSubscribe(Disposable d) {
mChapterDisp = d;
}
@Override
public void onSuccess(List<TxtChapter> chapters) {
public void onSuccess(List<BookChapterBean> chapters) {
mChapterDisp = null;
isChapterListPrepare = true;
List<Chapter> mChapters = new ArrayList<>();
int i = 0;
for (TxtChapter txtChapter : chapters) {
for (BookChapterBean bookChapterBean : chapters) {
Chapter chapter = new Chapter();
chapter.setBookId(mCollBook.getId());
chapter.setTitle(txtChapter.getTitle().replace("\n", "").replaceAll("^\\s+|\\s+$", ""));
chapter.setTitle(bookChapterBean.getTitle().replace("\n", "").replaceAll("^\\s+|\\s+$", ""));
chapter.setId(StringHelper.getStringRandom(25));
String content = getChapterContent(txtChapter);
String content = getChapterContent(bookChapterBean);
if (StringHelper.isEmpty(content)) {
continue;
}
@ -312,7 +307,7 @@ public class LocalPageLoader extends PageLoader {
* @param chapter
* @return
*/
private String getChapterContent(TxtChapter chapter) {
private String getChapterContent(BookChapterBean chapter) {
RandomAccessFile bookStream = null;
try {
bookStream = new RandomAccessFile(mBookFile, "r");

@ -32,10 +32,10 @@ public class NetPageLoader extends PageLoader {
this.mReadCrawler = mReadCrawler;
}
/*private List<TxtChapter> convertTxtChapter(List<Chapter> bookChapters) {
List<TxtChapter> txtChapters = new ArrayList<>(bookChapters.size());
/*private List<BookChapterBean> convertTxtChapter(List<Chapter> bookChapters) {
List<BookChapterBean> txtChapters = new ArrayList<>(bookChapters.size());
for (Chapter bean : bookChapters) {
TxtChapter chapter = new TxtChapter();
BookChapterBean chapter = new BookChapterBean();
chapter.setBookId(bean.getBookId());
chapter.setTitle(bean.getTitle());
chapter.setLink(bean.getUrl());

@ -5,6 +5,8 @@ import android.graphics.*;
import android.text.TextUtils;
import androidx.core.content.ContextCompat;
import android.text.TextPaint;
import android.util.Log;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.SingleOnSubscribe;
@ -17,11 +19,11 @@ import xyz.fycz.myreader.enums.Language;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.model.audio.ReadAloudService;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.util.utils.ScreenUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
import xyz.fycz.myreader.widget.animation.PageAnimation;
import java.io.BufferedReader;
import java.io.File;
@ -67,11 +69,11 @@ public abstract class PageLoader {
// 当前显示的页
private TxtPage mCurPage;
// 上一章的页面列表缓存
private List<TxtPage> mPrePageList;
private TxtChapter mPreChapter;
// 当前章节的页面列表
private List<TxtPage> mCurPageList;
private TxtChapter mCurChapter;
// 下一章的页面列表缓存
private List<TxtPage> mNextPageList;
private TxtChapter mNextChapter;
// 绘制电池的画笔
private Paint mBatteryPaint;
@ -143,6 +145,9 @@ public abstract class PageLoader {
protected int mCurChapterPos = 0;
//上一章的记录
private int mLastChapterPos = 0;
private int readTextLength; //已读字符数
private boolean resetReadAloud; //是否重新朗读
private int readAloudParagraph; //正在朗读章节
/*****************************init params*******************************/
public PageLoader(PageView pageView, Book collBook, Setting setting) {
@ -306,13 +311,13 @@ public abstract class PageLoader {
mCurChapterPos = pos;
// 将上一章的缓存设置为null
mPrePageList = null;
mPreChapter = null;
// 如果当前下一章缓存正在执行,则取消
if (mPreLoadDisp != null) {
mPreLoadDisp.dispose();
}
// 将下一章缓存设置为null
mNextPageList = null;
mNextChapter = null;
// 打开指定章节
openChapter();
@ -349,6 +354,16 @@ public abstract class PageLoader {
public boolean skipToNextPage() {
return mPageView.autoNextPage();
}
/**
* 翻到下一页,无动画
*/
private void noAnimationToNextPage() {
if (getPagePos() < mCurChapter.getPageSize() - 1) {
skipToPage(getPagePos() + 1);
return;
}
skipNextChapter();
}
/**
* 更新时间
@ -399,8 +414,8 @@ public abstract class PageLoader {
/*// 存储文字大小
mSettingManager.setReadWordSize(mTextSize);*/
// 取消缓存
mPrePageList = null;
mNextPageList = null;
mPreChapter = null;
mNextChapter = null;
// 如果当前已经显示数据
if (isChapterListPrepare && mStatus == STATUS_FINISH) {
@ -408,12 +423,12 @@ public abstract class PageLoader {
dealLoadPageList(mCurChapterPos);
// 防止在最后一页,通过修改字体,以至于页面数减少导致崩溃的问题
if (mCurPage.position >= mCurPageList.size()) {
mCurPage.position = mCurPageList.size() - 1;
if (mCurPage.position >= mCurChapter.getPageSize()) {
mCurPage.position = mCurChapter.getPageSize() - 1;
}
// 重新获取指定页面
mCurPage = mCurPageList.get(mCurPage.position);
mCurPage = mCurChapter.getPage(mCurPage.position);
}
mPageView.drawCurPage(false);
@ -522,8 +537,8 @@ public abstract class PageLoader {
mTextPaint.setTypeface(mTypeFace);
mTitlePaint.setTypeface(mTypeFace);
// 取消缓存
mPrePageList = null;
mNextPageList = null;
mPreChapter = null;
mNextChapter = null;
// 如果当前已经显示数据
if (isChapterListPrepare && mStatus == STATUS_FINISH) {
@ -531,12 +546,12 @@ public abstract class PageLoader {
dealLoadPageList(mCurChapterPos);
// 防止在最后一页,通过修改字体,以至于页面数减少导致崩溃的问题
if (mCurPage.position >= mCurPageList.size()) {
mCurPage.position = mCurPageList.size() - 1;
if (mCurPage.position >= mCurChapter.getPageSize()) {
mCurPage.position = mCurChapter.getPageSize() - 1;
}
// 重新获取指定页面
mCurPage = mCurPageList.get(mCurPage.position);
mCurPage = mCurChapter.getPage(mCurPage.position);
}
mPageView.drawCurPage(false);
@ -658,10 +673,10 @@ public abstract class PageLoader {
* @return
*/
public int getAllPagePos() {
if (mCurPageList == null) {
if (mCurChapter == null) {
return 0;
}
return mCurPageList.size();
return mCurChapter.getPageSize();
}
/**
@ -743,8 +758,8 @@ public abstract class PageLoader {
int position = mCollBook.getLastReadPosition();
// 防止记录页的页号,大于当前最大页号
if (position >= mCurPageList.size()) {
position = mCurPageList.size() - 1;
if (position >= mCurChapter.getPageSize()) {
position = mCurChapter.getPageSize() - 1;
}
mCurPage = getCurPage(position);
mCancelPage = mCurPage;
@ -778,12 +793,11 @@ public abstract class PageLoader {
}
clearList(mChapterList);
clearList(mCurPageList);
clearList(mNextPageList);
mChapterList = null;
mCurPageList = null;
mNextPageList = null;
mPreChapter = null;
mCurChapter = null;
mNextChapter = null;
mPageView = null;
mCurPage = null;
}
@ -808,7 +822,7 @@ public abstract class PageLoader {
* @param chapterPos:章节序号
* @return
*/
private List<TxtPage> loadPageList(int chapterPos) throws Exception {
private TxtChapter loadPageList(int chapterPos) throws Exception {
// 获取章节
Chapter chapter = mChapterList.get(chapterPos);
// 判断章节是否存在
@ -817,9 +831,9 @@ public abstract class PageLoader {
}
// 获取章节的文本流
BufferedReader reader = getChapterReader(chapter);
List<TxtPage> chapters = loadPages(chapter, reader);
TxtChapter txtChapter = loadPages(chapter, reader);
return chapters;
return txtChapter;
}
@ -896,7 +910,7 @@ public abstract class PageLoader {
float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight;
// 只有finish的时候采用页码
if (mStatus == STATUS_FINISH) {
String percent = (mCurPage.position + 1) + "/" + mCurPageList.size();
String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize();
canvas.drawText(percent, mMarginWidth, y, mTipPaint);
}
@ -1012,8 +1026,11 @@ public abstract class PageLoader {
String str = null;
//对标题进行绘制
boolean isLight;
for (int i = 0; i < mCurPage.titleLines; ++i) {
str = mCurPage.lines.get(i);
isLight = ReadAloudService.running && readAloudParagraph == 0;
mTitlePaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : mTextColor);
//设置顶部间距
if (i == 0) {
@ -1035,8 +1052,13 @@ public abstract class PageLoader {
}
//对内容进行绘制
int strLength = 0;
for (int i = mCurPage.titleLines; i < mCurPage.lines.size(); ++i) {
str = mCurPage.lines.get(i);
strLength = strLength + str.length();
int paragraphLength = mCurPage.position == 0 ? strLength : mCurChapter.getPageLength(mCurPage.position - 1) + strLength;
isLight = ReadAloudService.running && readAloudParagraph == mCurChapter.getParagraphIndex(paragraphLength);
mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : mTextColor);
canvas.drawText(str, mMarginWidth, top, mTextPaint);
if (str.endsWith("\n")) {
top += para;
@ -1128,19 +1150,19 @@ public abstract class PageLoader {
mCurChapterPos = prevChapter;
// 当前章缓存为下一章
mNextPageList = mCurPageList;
mNextChapter = mCurChapter;
// 判断是否具有上一章缓存
if (mPrePageList != null) {
mCurPageList = mPrePageList;
mPrePageList = null;
if (mPreChapter != null) {
mCurChapter = mPreChapter;
mPreChapter = null;
// 回调
chapterChangeCallback();
} else {
dealLoadPageList(prevChapter);
}
return mCurPageList != null;
return mCurChapter != null;
}
private boolean hasPrevChapter() {
@ -1180,7 +1202,7 @@ public abstract class PageLoader {
mCancelPage = mCurPage;
// 解析下一章数据
if (parseNextChapter()) {
mCurPage = mCurPageList.get(0);
mCurPage = mCurChapter.getPage(0);
} else {
mCurPage = new TxtPage();
}
@ -1201,7 +1223,7 @@ public abstract class PageLoader {
dealLoadPageList(mCurChapterPos);
// 预加载下一页面
preLoadNextChapter();
return mCurPageList != null;
return mCurChapter != null;
}
/**
@ -1216,12 +1238,12 @@ public abstract class PageLoader {
mCurChapterPos = nextChapter;
// 将当前章的页面列表,作为上一章缓存
mPrePageList = mCurPageList;
mPreChapter = mCurChapter;
// 是否下一章数据已经预加载了
if (mNextPageList != null) {
mCurPageList = mNextPageList;
mNextPageList = null;
if (mNextChapter != null) {
mCurChapter = mNextChapter;
mNextChapter = null;
// 回调
chapterChangeCallback();
} else {
@ -1230,20 +1252,20 @@ public abstract class PageLoader {
}
// 预加载下一页面
preLoadNextChapter();
return mCurPageList != null;
return mCurChapter != null;
}
private void dealLoadPageList(int chapterPos) {
try {
mCurPageList = loadPageList(chapterPos);
if (mCurPageList != null) {
if (mCurPageList.isEmpty()) {
mCurChapter = loadPageList(chapterPos);
if (mCurChapter != null) {
if (mCurChapter.getTxtPageList().isEmpty()) {
mStatus = STATUS_EMPTY;
// 添加一个空数据
TxtPage page = new TxtPage();
page.lines = new ArrayList<>(1);
mCurPageList.add(page);
mCurChapter.addPage(page);
} else {
mStatus = STATUS_FINISH;
}
@ -1252,7 +1274,7 @@ public abstract class PageLoader {
}
} catch (Exception e) {
e.printStackTrace();
mCurPageList = null;
mCurChapter = null;
mStatus = STATUS_ERROR;
}
@ -1263,7 +1285,9 @@ public abstract class PageLoader {
private void chapterChangeCallback() {
if (mPageChangeListener != null) {
mPageChangeListener.onChapterChange(mCurChapterPos);
mPageChangeListener.onPageCountChange(mCurPageList != null ? mCurPageList.size() : 0);
mPageChangeListener.onPageChange(0, resetReadAloud);
resetReadAloud = true;
mPageChangeListener.onPageCountChange(mCurChapter != null ? mCurChapter.getPageSize() : 0);
}
}
@ -1282,17 +1306,17 @@ public abstract class PageLoader {
}
//调用异步进行预加载加载
Single.create((SingleOnSubscribe<List<TxtPage>>) e -> e.onSuccess(loadPageList(nextChapter)))
Single.create((SingleOnSubscribe<TxtChapter>) e -> e.onSuccess(loadPageList(nextChapter)))
.compose(RxUtils::toSimpleSingle)
.subscribe(new SingleObserver<List<TxtPage>>() {
.subscribe(new SingleObserver<TxtChapter>() {
@Override
public void onSubscribe(Disposable d) {
mPreLoadDisp = d;
}
@Override
public void onSuccess(List<TxtPage> pages) {
mNextPageList = pages;
public void onSuccess(TxtChapter txtChapter) {
mNextChapter = txtChapter;
}
@Override
@ -1305,7 +1329,7 @@ public abstract class PageLoader {
// 取消翻页
void pageCancel() {
if (mCurPage.position == 0 && mCurChapterPos > mLastChapterPos) { // 加载到下一章取消了
if (mPrePageList != null) {
if (mPreChapter != null) {
cancelNextChapter();
} else {
if (parsePrevChapter()) {
@ -1314,15 +1338,15 @@ public abstract class PageLoader {
mCurPage = new TxtPage();
}
}
} else if (mCurPageList == null
|| (mCurPage.position == mCurPageList.size() - 1
} else if (mCurChapter == null
|| (mCurPage.position == mCurChapter.getPageSize() - 1
&& mCurChapterPos < mLastChapterPos)) { // 加载上一章取消了
if (mNextPageList != null) {
if (mNextChapter != null) {
cancelPreChapter();
} else {
if (parseNextChapter()) {
mCurPage = mCurPageList.get(0);
mCurPage = mCurChapter.getPage(0);
} else {
mCurPage = new TxtPage();
}
@ -1338,9 +1362,9 @@ public abstract class PageLoader {
mLastChapterPos = mCurChapterPos;
mCurChapterPos = temp;
mNextPageList = mCurPageList;
mCurPageList = mPrePageList;
mPrePageList = null;
mNextChapter = mCurChapter;
mCurChapter = mPreChapter;
mPreChapter = null;
chapterChangeCallback();
@ -1354,9 +1378,9 @@ public abstract class PageLoader {
mLastChapterPos = mCurChapterPos;
mCurChapterPos = temp;
// 重置页面列表
mPrePageList = mCurPageList;
mCurPageList = mNextPageList;
mNextPageList = null;
mPreChapter = mCurChapter;
mCurChapter = mNextChapter;
mNextChapter = null;
chapterChangeCallback();
@ -1372,20 +1396,20 @@ public abstract class PageLoader {
* @param br章节的文本流
* @return
*/
private List<TxtPage> loadPages(Chapter chapter, BufferedReader br) {
//生成的页面
List<TxtPage> pages = new ArrayList<>();
private TxtChapter loadPages(Chapter chapter, BufferedReader br) {
TxtChapter txtChapter = new TxtChapter(chapter.getNumber());
//使用流的方式加载
List<String> lines = new ArrayList<>();
float rHeight = mVisibleHeight;
int titleLinesCount = 0;
boolean showTitle = true; // 是否展示标题
String paragraph = chapter.getTitle();//默认展示标题
paragraph = paragraph.trim() + "\n";
try {
while (showTitle || (paragraph = br.readLine()) != null) {
// 重置段落
if (!showTitle) {
paragraph = paragraph.replaceAll("\\s", "");
paragraph = paragraph.replaceAll("\\s", "").trim();
// 如果只有换行符,那么就不执行
if (paragraph.equals("")) continue;
paragraph = StringUtils.halfToFull(" " + paragraph + "\n");
@ -1393,6 +1417,7 @@ public abstract class PageLoader {
//设置 title 的顶部间距
rHeight -= mTitlePara;
}
addParagraphLength(txtChapter, paragraph.length());
int wordCount = 0;
String subStr = null;
while (paragraph.length() > 0) {
@ -1406,11 +1431,12 @@ public abstract class PageLoader {
if (rHeight <= 0) {
// 创建Page
TxtPage page = new TxtPage();
page.position = pages.size();
page.position = txtChapter.getTxtPageList().size();
page.title = chapter.getTitle();
page.lines = new ArrayList<>(lines);
page.titleLines = titleLinesCount;
pages.add(page);
txtChapter.addPage(page);
addTxtPageLength(txtChapter, page.getContent().length());
// 重置Lines
lines.clear();
rHeight = mVisibleHeight;
@ -1458,11 +1484,12 @@ public abstract class PageLoader {
if (lines.size() != 0) {
//创建Page
TxtPage page = new TxtPage();
page.position = pages.size();
page.position = txtChapter.getTxtPageList().size();
page.title = chapter.getTitle();
page.lines = new ArrayList<>(lines);
page.titleLines = titleLinesCount;
pages.add(page);
txtChapter.addPage(page);
addTxtPageLength(txtChapter, page.getContent().length());
//重置Lines
lines.clear();
}
@ -1477,18 +1504,44 @@ public abstract class PageLoader {
}
}
}
return pages;
return txtChapter;
}
/**
* 添加TxtChapter的长度
* @param txtChapter
* @param length
*/
private void addTxtPageLength(TxtChapter txtChapter, int length) {
if (txtChapter.getTxtPageLengthList().isEmpty()) {
txtChapter.addTxtPageLength(length);
} else {
txtChapter.addTxtPageLength(txtChapter.getTxtPageLengthList().get(txtChapter.getTxtPageLengthList().size() - 1) + length);
}
}
/**
* 添加TxtChapter段落长度
* @param txtChapter
* @param length
*/
private void addParagraphLength(TxtChapter txtChapter, int length) {
if (txtChapter.getParagraphLengthList().isEmpty()) {
txtChapter.addParagraphLength(length);
} else {
txtChapter.addParagraphLength(txtChapter.getParagraphLengthList().get(txtChapter.getParagraphLengthList().size() - 1) + length);
}
}
/**
* @return:获取初始显示的页面
*/
private TxtPage getCurPage(int pos) {
if (mPageChangeListener != null) {
mPageChangeListener.onPageChange(pos);
mPageChangeListener.onPageChange(pos, resetReadAloud);
resetReadAloud = true;
}
return mCurPageList.get(pos);
return mCurChapter.getPage(pos);
}
/**
@ -1500,9 +1553,10 @@ public abstract class PageLoader {
return null;
}
if (mPageChangeListener != null) {
mPageChangeListener.onPageChange(pos);
mPageChangeListener.onPageChange(pos,resetReadAloud);
resetReadAloud = true;
}
return mCurPageList.get(pos);
return mCurChapter.getPage(pos);
}
/**
@ -1510,26 +1564,28 @@ public abstract class PageLoader {
*/
private TxtPage getNextPage() {
int pos = mCurPage.position + 1;
if (pos >= mCurPageList.size()) {
if (pos >= mCurChapter.getPageSize()) {
return null;
}
if (mPageChangeListener != null) {
mPageChangeListener.onPageChange(pos);
mPageChangeListener.onPageChange(pos, resetReadAloud);
resetReadAloud = true;
}
return mCurPageList.get(pos);
return mCurChapter.getPage(pos);
}
/**
* @return:获取上一个章节的最后一页
*/
private TxtPage getPrevLastPage() {
int pos = mCurPageList.size() - 1;
int pos = mCurChapter.getPageSize() - 1;
if (mPageChangeListener != null) {
mPageChangeListener.onPageChange(pos);
mPageChangeListener.onPageChange(pos, resetReadAloud);
resetReadAloud = true;
}
return mCurPageList.get(pos);
return mCurChapter.getPage(pos);
}
/**
@ -1596,8 +1652,8 @@ public abstract class PageLoader {
* @return 本页内容
*/
public String getContent() {
if (mCurPageList == null) return null;
if (mCurPageList.size() == 0) return null;
if (mCurChapter == null) return null;
if (mCurChapter.getPageSize() == 0) return null;
TxtPage txtPage = mCurPage;
StringBuilder s = new StringBuilder();
int size = txtPage.lines.size();
@ -1608,6 +1664,80 @@ public abstract class PageLoader {
}
return s.toString();
}
/**
* @return 本章未读内容
*/
public String getUnReadContent() {
if (mCurPage == null) return null;
if (mCurChapter == null || mCurChapter.getPageSize() == 0) return null;
StringBuilder s = new StringBuilder();
String content = getContent();
if (content != null) {
s.append(content);
}
int mCurPagePos = getPagePos();
content = getContentStartPage(mCurPagePos + 1);
if (content != null) {
s.append(content);
}
readTextLength = mCurPagePos > 0 ? mCurChapter.getPageLength(mCurPagePos - 1) : 0;
/*if (mPageMode == PageAnimation.Mode.SCROLL) {
for (int i = 0; i < Math.min(Math.max(0, linePos), curChapter().txtChapter.getPage(mCurPagePos).size() - 1); i++) {
readTextLength += curChapter().txtChapter.getPage(mCurPagePos).getLine(i).length();
}
}*/
return s.toString();
}
/**
* @param page 开始页数
* @return 从page页开始的的当前章节所有内容
*/
private String getContentStartPage(int page) {
if (mCurChapter == null) return null;
if (mCurChapter.getTxtPageList().isEmpty()) return null;
StringBuilder s = new StringBuilder();
if (mCurChapter.getPageSize() > page) {
for (int i = page; i < mCurChapter.getPageSize(); i++) {
s.append(mCurChapter.getPage(i).getContent());
}
}
return s.toString();
}
/**
* @param start 开始朗读字数
*/
public void readAloudStart(int start) {
start = readTextLength + start;
int x = mCurChapter.getParagraphIndex(start);
if (readAloudParagraph != x) {
readAloudParagraph = x;
mPageView.drawCurPage(false);
//mPageView.invalidate();
/*mPageView.drawPage(-1);
mPageView.drawPage(1);
mPageView.invalidate();*/
}
}
/**
* @param readAloudLength 已朗读字数
*/
public void readAloudLength(int readAloudLength) {
if (mCurChapter == null) return;
if (getPageStatus() != STATUS_FINISH) return;
if (mCurChapter.getPageLength(getPagePos()) < 0) return;
if (mPageView.isRunning()) return;
readAloudLength = readTextLength + readAloudLength;
if (readAloudLength >= mCurChapter.getPageLength(getPagePos())) {
resetReadAloud = false;
noAnimationToNextPage();
mPageView.invalidate();
}
}
/*****************************************interface*****************************************/
public interface OnPageChangeListener {
@ -1644,7 +1774,7 @@ public abstract class PageLoader {
*
* @param pos:当前的页面的序号
*/
void onPageChange(int pos);
void onPageChange(int pos, boolean resetRead);
}
}

@ -1,59 +1,99 @@
package xyz.fycz.myreader.widget.page;
import java.util.ArrayList;
import java.util.List;
/**
* Created by newbiechen on 17-7-1.
* Created by zhouas666 on 18-1-23.
* 书籍chapter
*/
public class TxtChapter {
public class TxtChapter{
//章节所属的小说(网络)
String bookId;
//章节名
String title;
private int position;
private List<TxtPage> txtPageList = new ArrayList<>();
private List<Integer> txtPageLengthList = new ArrayList<>();
private List<Integer> paragraphLengthList = new ArrayList<>();
private Status status = Status.LOADING;
private String msg;
//章节内容在文章中的起始位置(本地)
long start;
//章节内容在文章中的终止位置(本地)
long end;
TxtChapter(int position) {
this.position = position;
}
public String getBookId() {
return bookId;
public int getPosition() {
return position;
}
public void setBookId(String id) {
this.bookId = id;
List<TxtPage> getTxtPageList() {
return txtPageList;
}
public String getTitle() {
return title;
void addPage(TxtPage txtPage) {
txtPageList.add(txtPage);
}
public void setTitle(String title) {
this.title = title;
int getPageSize() {
return txtPageList.size();
}
public long getStart() {
return start;
TxtPage getPage(int page) {
if (!txtPageList.isEmpty()) {
return txtPageList.get(Math.max(0, Math.min(page, txtPageList.size() - 1)));
}
return null;
}
public void setStart(long start) {
this.start = start;
Status getStatus() {
return status;
}
public long getEnd() {
return end;
void setStatus(Status mStatus) {
this.status = mStatus;
}
public void setEnd(long end) {
this.end = end;
public String getMsg() {
return msg;
}
@Override
public String toString() {
return "TxtChapter{" +
"title='" + title + '\'' +
", start=" + start +
", end=" + end +
'}';
public void setMsg(String msg) {
this.msg = msg;
}
int getPageLength(int position) {
if (txtPageLengthList != null && position >= 0 && position < txtPageLengthList.size()) {
return txtPageLengthList.get(position);
}
return -1;
}
void addTxtPageLength(int length) {
txtPageLengthList.add(length);
}
List<Integer> getTxtPageLengthList() {
return txtPageLengthList;
}
List<Integer> getParagraphLengthList() {
return paragraphLengthList;
}
void addParagraphLength(int length) {
paragraphLengthList.add(length);
}
int getParagraphIndex(int length) {
for (int i = 0; i < paragraphLengthList.size(); i++) {
if ((i == 0 || paragraphLengthList.get(i - 1) < length) && length <= paragraphLengthList.get(i)) {
return i;
}
}
return -1;
}
public enum Status {
LOADING, FINISH, ERROR, EMPTY, CATEGORY_EMPTY, CHANGE_SOURCE,
}
}

@ -2,16 +2,13 @@ package xyz.fycz.myreader.widget.page;
import java.util.List;
/**
* Created by newbiechen on 17-7-1.
*/
public class TxtPage {
int position;
String title;
int titleLines; //当前 lines 中为 title 的行数。
List<String> lines;
public int getTitleLines() {
return titleLines;
}
@ -23,4 +20,14 @@ public class TxtPage {
public String getLine(int i) {
return lines.get(i);
}
public String getContent() {
StringBuilder s = new StringBuilder();
if (lines != null) {
for (int i = 0; i < lines.size(); i++) {
s.append(lines.get(i));
}
}
return s.toString();
}
}

@ -1,199 +1,239 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/read_abl_top_menu"
android:id="@+id/read_abl_top_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/read_menu_bg"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/read_menu_bg"
android:visibility="gone"
tools:visibility="visible">
android:orientation="vertical">
<LinearLayout
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/NoPaddingToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="45dp"
android:gravity="center_vertical"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarStyle"
android:transitionName="sharedView"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="大主宰" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/NoPaddingToolbar"
android:layout_width="match_parent"
android:layout_height="45dp"
android:minHeight="?attr/actionBarSize"
android:gravity="center_vertical"
android:theme="?attr/actionBarStyle"
android:transitionName="sharedView"
app:title="大主宰"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay">
</androidx.appcompat.widget.Toolbar>
<View
android:layout_width="fill_parent"
android:layout_height="0.5dp"
android:background="@color/sys_dialog_setting_line" />
android:layout_width="fill_parent"
android:layout_height="0.5dp"
android:background="@color/sys_dialog_setting_line" />
<LinearLayout
android:id="@+id/ll_chapter_view"
android:id="@+id/ll_chapter_view"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_chapter_title_top"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="vertical">
<TextView android:id="@+id/tv_chapter_title_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:textSize="15sp"
android:text="章节名称"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/read_menu_text">
</TextView>
<TextView android:id="@+id/tv_chapter_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:ellipsize="end"
android:textSize="13sp"
android:text="章节链接"
android:maxLines="1"
android:textColor="@color/read_menu_text">
</TextView>
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:ellipsize="end"
android:maxLines="1"
android:text="章节名称"
android:textColor="@color/read_menu_text"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_chapter_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:ellipsize="end"
android:maxLines="1"
android:text="章节链接"
android:textColor="@color/read_menu_text"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<!--阅读页面-->
<xyz.fycz.myreader.widget.page.PageView
android:id="@+id/read_pv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
tools:visibility="gone"/>
android:id="@+id/read_pv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
tools:visibility="gone" />
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp">
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
android:id="@+id/pb_nextPage"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp">
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
android:id="@+id/pb_nextPage"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:splitTrack="false"
app:seekBarRotation="CW270"
android:visibility="invisible"
tools:visibility="visible"/>
android:splitTrack="false"
android:visibility="invisible"
app:seekBarRotation="CW270"
tools:visibility="visible" />
</com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
tools:visibility="visible"/>
android:id="@+id/pb_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
tools:visibility="visible" />
<!--切换页面提示-->
<TextView
android:id="@+id/read_tv_page_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/read_ll_bottom_menu"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:background="@color/read_menu_bg"
android:gravity="center"
android:paddingLeft="12dp"
android:paddingTop="8dp"
android:paddingRight="12dp"
android:paddingBottom="8dp"
android:text="1/12"
android:textColor="@color/md_white_1000"
android:visibility="gone"/>
<!--底部页面-->
<LinearLayout
android:id="@+id/read_ll_bottom_menu"
android:id="@+id/read_ll_bottom_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:clickable="true"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/read_menu_bg"
android:clickable="true"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
android:layout_height="wrap_content">
<!--切换页面提示-->
<TextView
android:id="@+id/read_tv_page_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:background="@color/read_menu_bg"
android:gravity="center"
android:layout_marginTop="4dp"
android:paddingLeft="12dp"
android:paddingTop="8dp"
android:paddingRight="12dp"
android:paddingBottom="8dp"
android:text="1/12"
android:textColor="@color/md_white_1000" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/iv_listen_book"
app:backgroundTint="@color/read_menu_bg"
android:layout_marginEnd="15dp"
android:layout_marginBottom="12dp"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:tint="@color/read_menu_text"
android:src="@drawable/ic_listen_book"
app:elevation="2dp"
app:fabCustomSize="40dp"
app:pressedTranslationZ="2dp"
/>
</RelativeLayout>
<!--页面进度栏-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:orientation="horizontal">
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/read_menu_bg"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/read_tv_pre_chapter"
style="@style/MAppTheme.TextAppearance.Read.BottomMenu"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="上一章"/>
android:id="@+id/read_tv_pre_chapter"
style="@style/MAppTheme.TextAppearance.Read.BottomMenu"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:text="上一章" />
<SeekBar
android:id="@+id/read_sb_chapter_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:maxHeight="3dp"
android:minHeight="3dp"
android:progressDrawable="@drawable/seekbar_bg"
android:thumb="@mipmap/thumb"
android:thumbOffset="10dp"/>
android:id="@+id/read_sb_chapter_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:maxHeight="3dp"
android:minHeight="3dp"
android:progressDrawable="@drawable/seekbar_bg"
android:thumb="@mipmap/thumb"
android:thumbOffset="10dp" />
<TextView
android:id="@+id/read_tv_next_chapter"
style="@style/MAppTheme.TextAppearance.Read.BottomMenu"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="下一章"/>
android:id="@+id/read_tv_next_chapter"
style="@style/MAppTheme.TextAppearance.Read.BottomMenu"
android:layout_width="80dp"
android:layout_height="match_parent"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:text="下一章" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="0.5dp"
android:background="@color/sys_dialog_setting_line" />
android:layout_width="fill_parent"
android:layout_height="0.5dp"
android:background="@color/sys_dialog_setting_line" />
<!--菜单栏-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:orientation="horizontal"
android:paddingTop="12dp"
android:paddingBottom="12dp">
android:layout_width="match_parent"
android:layout_height="72dp"
android:background="@color/read_menu_bg"
android:orientation="horizontal"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/read_tv_category"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:drawableTop="@mipmap/t3"
android:text="目录"/>
android:id="@+id/read_tv_category"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:text="目录"
android:clickable="true"
android:focusable="true"
app:drawableTopCompat="@mipmap/t3" />
<TextView
android:id="@+id/read_tv_download"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:drawableTop="@mipmap/download"
android:text="下载"/>
android:id="@+id/read_tv_download"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:text="下载"
android:clickable="true"
android:focusable="true"
app:drawableTopCompat="@mipmap/download" />
<TextView
android:id="@+id/read_tv_night_mode"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:drawableTop="@mipmap/ao"
android:text="夜间"/>
android:id="@+id/read_tv_night_mode"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:text="夜间"
android:clickable="true"
android:focusable="true"
app:drawableTopCompat="@mipmap/ao" />
<TextView
android:id="@+id/read_tv_setting"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:drawableTop="@mipmap/t6"
android:text="设置"/>
android:id="@+id/read_tv_setting"
style="@style/AppTheme.TextAppearance.Read.BottomMenu.Button"
android:text="设置"
android:clickable="true"
android:focusable="true"
app:drawableTopCompat="@mipmap/t6" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/start"/>
<androidx.appcompat.widget.AppCompatImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/iv_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
app:srcCompat="@drawable/start"/>

@ -152,7 +152,7 @@
<string name="menu_group_man">分组管理</string>
<string name="menu_group_setting">设置分组</string>
<string name="thread_num">搜索线程数</string>
<string name="cur_thread_num">当前线程数:%1$s</string>
<string name="cur_thread_num">当前线程数:%1$d</string>
<string name="newest_chapter">最新章节:%1$s</string>
<string name="source_title_num">书源:%1$s 共%2$d个源</string>
@ -179,6 +179,26 @@
<string name="menu_copy_content">拷贝内容</string>
<!-- 朗读 -->
<string name="read_aloud">朗读</string>
<string name="read_aloud_t">正在朗读</string>
<string name="read_aloud_s">点击返回阅读界面</string>
<string name="text_return">返回</string>
<string name="refresh">重新加载</string>
<string name="start">开始</string>
<string name="stop">停止</string>
<string name="pause">暂停</string>
<string name="resume">继续</string>
<string name="last">上一段</string>
<string name="next">下一段</string>
<string name="set_timer">定时</string>
<string name="read_aloud_pause">朗读暂停</string>
<string name="read_aloud_timer">正在朗读(还剩%d分钟)</string>
<string name="read_aloud_timerlong">"正在朗读(还剩%d小时%d分)"</string>
<string name="read_aloud_timerremaining">%d分钟</string>
<string name="read_aloud_timerremaininglong">"%d小时%d分"</string>
<string name="read_aloud_timerstop">"计时已取消"</string>
<string-array name="reset_screen_time">
<item>常亮</item>
<item>1分钟</item>

@ -1,2 +1,2 @@
#Mon Nov 09 21:09:26 CST 2020
VERSION_CODE=155
#Wed Nov 11 20:37:05 CST 2020
VERSION_CODE=154

@ -17,3 +17,4 @@ org.gradle.jvmargs=-Xmx1536m
# org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true
android.overridePathCheck=true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Loading…
Cancel
Save