master
fengyuecanzhu 3 years ago
parent 4bd520cd2f
commit c480adbf8f
  1. 4
      .idea/assetWizardSettings.xml
  2. 36
      .idea/inspectionProfiles/Project_Default.xml
  3. 1
      .idea/misc.xml
  4. 5
      app/release.md
  5. 6
      app/src/main/assets/updatelog.fy
  6. 90
      app/src/main/java/xyz/fycz/myreader/experiment/BookWCEstimate.kt
  7. 91
      app/src/main/java/xyz/fycz/myreader/experiment/DoubleUtils.java
  8. 128
      app/src/main/java/xyz/fycz/myreader/experiment/LinearRegression.java
  9. 2
      app/src/main/java/xyz/fycz/myreader/experiment/README.md
  10. 61
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
  11. 9
      app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java
  12. 2
      app/src/main/java/xyz/fycz/myreader/util/help/JsExtensions.java
  13. 2
      app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
  14. 12
      app/src/main/res/drawable/ic_wc.xml
  15. 6
      app/src/main/res/menu/menu_book_detail.xml
  16. 5
      app/src/main/res/menu/menu_book_detail_local.xml
  17. 1
      app/src/main/res/values/strings.xml
  18. 4
      app/version_code.properties

@ -14,8 +14,8 @@
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_right_arrow_small" />
<entry key="sourceFile" value="F:\SVG图标\向右.svg" />
<entry key="outputName" value="ic_wc" />
<entry key="sourceFile" value="F:\SVG图标\数字.svg" />
</map>
</option>
</PersistentState>

@ -0,0 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
</profile>
</component>

@ -25,6 +25,7 @@
<entry key="..\:/android/FYReader/app/src/main/res/layout/view_file_picker.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book.xml" value="0.13703703703703704" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book_detail.xml" value="0.24947916666666667" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book_detail_local.xml" value="0.24947916666666667" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_read.xml" value="0.24947916666666667" />
</map>
</option>

@ -1,3 +1,2 @@
* 1、修复切换书源对话框内存泄漏的问题
* 2、优化导入书源选择
* 3、修复已知bug
* 1、修复从[文档或最近]导入书源时文件读取失败的问题
* 2、新增实验性功能:书籍字数计算(书籍详情页菜单)

@ -1,3 +1,9 @@
2021.12.04
风月读书v2.2.3
更新内容:
1、修复从[文档或最近]导入书源时文件读取失败的问题
2、新增实验性功能:书籍字数计算(书籍详情页菜单)
2021.11.12
风月读书v2.2.2
更新内容:

@ -0,0 +1,90 @@
package xyz.fycz.myreader.experiment
import xyz.fycz.myreader.common.APPCONST
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.service.ChapterService
import xyz.fycz.myreader.util.IOUtils
import xyz.fycz.myreader.util.utils.FileUtils
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.io.IOException
/**
* @author fengyue
* @date 2021/12/4 10:33
*/
class BookWCEstimate {
/**
* -1本地文件不存在
* -2书籍章节缓存数量少于20
*/
fun getWordCount(book: Book): Int {
if (book.type == "本地书籍") {
return getLocalWordCount(book)
} else if (book.type.isNotEmpty()) {
return getNetWordCount(book)
}
return 0
}
private fun getLocalWordCount(book: Book): Int {
val file = File(book.chapterUrl)
if (!file.exists()) return -1
return -countChar(file)
}
/**
* 计算方式使用最小二乘法进行线性回归预测
*/
private fun getNetWordCount(book: Book): Int {
val chapterService = ChapterService.getInstance()
val chapters = chapterService.findBookAllChapterByBookId(book.id)
val map = mutableMapOf<Double, Double>()
var sum = 0.0
var i = 0
var cachedChapterSize = 0
for (chapter in chapters) {
if (cachedChapterSize >= 20) break
if (ChapterService.isChapterCached(chapter.bookId, chapter.title)) {
cachedChapterSize++
}
}
if (cachedChapterSize < 20 && chapters.size > 50) return -2
chapters.forEach { chapter ->
if (ChapterService.isChapterCached(chapter.bookId, chapter.title)) {
sum += countChar(
File(
APPCONST.BOOK_CACHE_PATH + chapter.bookId
+ File.separator + chapter.title + FileUtils.SUFFIX_FY
)
)
map[i++.toDouble()] = sum
}
}
if (map.size == chapters.size) return sum.toInt()
val lr = LinearRegression(map)
return lr.getY(chapters.size.toDouble()).toInt()
}
private fun countChar(file: File): Int { //统计字符数
var charnum = 0 //字符数
var x: Int
var fReader: FileReader? = null
try {
fReader = FileReader(file)
while ((fReader.read().also { x = it }) != -1) { //按字符读文件,判断,符合则字符加一
val a = x.toChar()
if (a != '\n' && a != '\r') {
charnum++
}
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
IOUtils.close(fReader)
}
return charnum //返回结果
}
}

@ -0,0 +1,91 @@
package xyz.fycz.myreader.experiment;
import java.math.BigDecimal;
/**
* @author fengyue
* @date 2021/12/4 12:16
*/
public class DoubleUtils {
private static final int DEF_DIV_SCALE = 10;
/**
* * 两个Double数相加 *
*
* @param v1 *
* @param v2 *
* @return Double
*/
public static Double add(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.add(b2).doubleValue();
}
/**
* * 两个Double数相减 *
*
* @param v1 *
* @param v2 *
* @return Double
*/
public static Double sub(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.subtract(b2).doubleValue();
}
/**
* * 两个Double数相乘 *
*
* @param v1 *
* @param v2 *
* @return Double
*/
public static Double mul(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.multiply(b2).doubleValue();
}
/**
* * 两个Double数相除 *
*
* @param v1 *
* @param v2 *
* @return Double
*/
public static Double div(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.divide(b2, DEF_DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* * 两个Double数相除并保留scale位小数 *
*
* @param v1 *
* @param v2 *
* @param scale *
* @return Double
*/
public static Double div(Double v1, Double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
public static int max(int a, int b) {
return Math.max(a, b);
}
public static int min(int a, int b) {
return Math.min(a, b);
}
}

@ -0,0 +1,128 @@
package xyz.fycz.myreader.experiment;
import static xyz.fycz.myreader.experiment.DoubleUtils.add;
import static xyz.fycz.myreader.experiment.DoubleUtils.div;
import static xyz.fycz.myreader.experiment.DoubleUtils.mul;
import static xyz.fycz.myreader.experiment.DoubleUtils.sub;
import java.util.HashMap;
import java.util.Map;
/**
* 使用最小二乘法实现线性回归预测
*
* @author fengyue
* @date 2021/12/4 12:15
*/
public class LinearRegression {
/**
* 训练集数据
*/
private Map<Double, Double> initData = new HashMap<>();
/**
* 截距
*/
private double intercept = 0.0;
//斜率
private double slope = 0.0;
/**
* xy平均值
*/
private double averageX, averageY;
/**
* 求斜率的上下两个分式的值
*/
private double slopeUp, slopeDown;
public LinearRegression(Map<Double, Double> initData) {
this.initData = initData;
initData();
}
public LinearRegression() {
}
/**
* 根据训练集数据进行训练预测
* 并计算斜率和截距
*/
public void initData() {
if (initData.size() > 0) {
//数据个数
int number = 0;
//x值、y值总和
double sumX = 0;
double sumY = 0;
averageX = 0;
averageY = 0;
slopeUp = 0;
slopeDown = 0;
for (Double x : initData.keySet()) {
if (x == null || initData.get(x) == null) {
continue;
}
number++;
sumX = add(sumX, x);
sumY = add(sumY, initData.get(x));
}
//求x,y平均值
averageX = div(sumX, (double) number);
averageY = div(sumY, (double) number);
for (Double x : initData.keySet()) {
if (x == null || initData.get(x) == null) {
continue;
}
slopeUp = add(slopeUp, mul(sub(x, averageX), sub(initData.get(x), averageY)));
slopeDown = add(slopeDown, mul(sub(x, averageX), sub(x, averageX)));
}
initSlopeIntercept();
}
}
/**
* 计算斜率和截距
*/
private void initSlopeIntercept() {
if (slopeUp != 0 && slopeDown != 0) {
slope = slopeUp / slopeDown;
}
intercept = averageY - averageX * slope;
}
/**
* 根据x值预测y值
*
* @param x x值
* @return y值
*/
public Double getY(Double x) {
return add(intercept, mul(slope, x));
}
/**
* 根据y值预测x值
*
* @param y y值
* @return x值
*/
public Double getX(Double y) {
return div(sub(y, intercept), slope);
}
public Map<Double, Double> getInitData() {
return initData;
}
public void setInitData(Map<Double, Double> initData) {
this.initData = initData;
}
}

@ -0,0 +1,2 @@
* 实验性功能
* 回归模型预测小说字数

@ -13,7 +13,6 @@ import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -28,6 +27,8 @@ import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import xyz.fycz.myreader.R;
@ -36,8 +37,10 @@ import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.BitIntentDataManager;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivityBookDetailBinding;
import xyz.fycz.myreader.experiment.BookWCEstimate;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
@ -48,6 +51,7 @@ import xyz.fycz.myreader.ui.adapter.BookTagAdapter;
import xyz.fycz.myreader.ui.adapter.DetailCatalogAdapter;
import xyz.fycz.myreader.ui.dialog.BookGroupDialog;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.LoadingDialog;
import xyz.fycz.myreader.ui.dialog.SourceExchangeDialog;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.help.StringHelper;
@ -83,6 +87,7 @@ public class BookDetailedActivity extends BaseActivity {
private BookGroupDialog mBookGroupDia;
private List<String> tagList = new ArrayList<>();
private Disposable chaptersDis;
private Disposable wcDis;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@ -588,6 +593,7 @@ public class BookDetailedActivity extends BaseActivity {
* @param item
* @return
*/
@SuppressLint("NonConstantResourceId")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@ -631,12 +637,65 @@ public class BookDetailedActivity extends BaseActivity {
startActivity(sourceIntent);
}
break;
case R.id.action_word_count:
getWordCount();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
private void getWordCount() {
LoadingDialog dialog = new LoadingDialog(this, "正在计算", () -> {
if (wcDis != null) {
wcDis.dispose();
}
});
dialog.show();
Single.create((SingleOnSubscribe<Integer>) emitter -> {
BookWCEstimate bookWCEstimate = new BookWCEstimate();
emitter.onSuccess(bookWCEstimate.getWordCount(mBook));
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<Integer>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
wcDis = d;
}
@Override
public void onSuccess(@NonNull Integer count) {
dialog.dismiss();
if (count == -1) {
ToastUtils.showWarring("书籍源文件不存在,无法计算字数");
} else if (count == -2) {
ToastUtils.showWarring("已缓存章节数量不足20,无法计算字数");
} else {
String tip = "\n";
if (count > 0) {
tip += "注:当前为网络书籍,无法获取准确字数,软件采用简单线性回归模型进行字数预测,仅供参考";
} else {
tip += "注:当前为本地书籍,字数统计自书籍源文件";
}
count = count < 0 ? -count : count;
String countTag = count > 100000 ? count / 10000 + "万" : count + "";
mBook.setWordCount(countTag);
initTagList();
mBookService.updateEntity(mBook);
DialogCreator.createTipDialog(BookDetailedActivity.this, "书籍字数(实验性功能)",
"书籍字数计算成功,本书字数:" + count + tip);
}
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("书籍字数计算失败!\n" + e.getLocalizedMessage());
dialog.dismiss();
}
});
}
/**
* 阅读/章节界面反馈结果处理

@ -25,6 +25,8 @@ import com.hjq.permissions.XXPermissions;
import com.kongzue.dialogx.dialogs.BottomMenu;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -57,6 +59,7 @@ import xyz.fycz.myreader.util.ShareUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.UriFileUtil;
import xyz.fycz.myreader.util.utils.ClipBoardUtil;
import xyz.fycz.myreader.util.utils.DocumentUtil;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.util.utils.RxUtils;
@ -320,9 +323,9 @@ public class DIYSourceFragment extends BaseFragment {
});
dialog.show();
Single.create((SingleOnSubscribe<String>) emitter -> {
String path = UriFileUtil.getChooseFileResultPath(getContext(), data.getData());
Log.d(TAG, "filePath:" + path);
emitter.onSuccess(path);
// String json = FileUtils.readInStream(DocumentUtil.getFileInputSteam(getContext(), data.getData()));
String json = new String(DocumentUtil.readBytes(getContext(), data.getData()), StandardCharsets.UTF_8);
emitter.onSuccess(json);
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<String>() {
@Override
public void onSubscribe(Disposable d) {

@ -35,6 +35,8 @@ import xyz.fycz.myreader.util.utils.StringUtils;
@Keep
@SuppressWarnings({"unused"})
public interface JsExtensions {
String TAG = JsExtensions.class.getSimpleName();

@ -364,7 +364,7 @@ public class FileUtils {
}
}
private static String readInStream(FileInputStream inStream) {
public static String readInStream(InputStream inStream) {
ByteArrayOutputStream outStream = null;
try {
outStream = new ByteArrayOutputStream();

@ -0,0 +1,12 @@
<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="M832.04,64.66H192.03C121.26,64.66 63.94,121.99 63.94,192.69v446.79C63.94,710.21 121.26,767.64 192.03,767.64h133.35a63.94,63.94 0,0 1,55.22 31.58l76.1,129.84c12.36,21.09 33.79,31.63 55.22,31.63s42.83,-10.54 55.2,-31.62l76.22,-129.87a63.94,63.94 0,0 1,55.21 -31.55h133.54c70.58,0 127.73,-57.29 127.73,-127.8V192.39C959.83,121.85 902.64,64.66 832.04,64.66zM895.88,639.84A63.85,63.85 0,0 1,832.09 703.7h-133.54a127.75,127.75 0,0 0,-110.35 63.1l-76.22,129.86a0.27,0.27 0,0 1,0 -0.05h-0.03s-0.02,0.06 -0.03,0.06l-76.15,-129.85A127.8,127.8 0,0 0,325.38 703.7H192.03A64.21,64.21 0,0 1,127.88 639.49V192.69A64.1,64.1 0,0 1,192.03 128.6h640.01A63.8,63.8 0,0 1,895.88 192.39v447.45z"
android:fillColor="#ffffff"/>
<path
android:pathData="M608.15,288.09A31.97,31.97 0,0 0,576.18 320.06v160.08l-134.65,-179.28A31.97,31.97 0,0 0,384 320.06v255.76a31.97,31.97 0,0 0,63.94 0v-159.96l134.65,179.27a31.97,31.97 0,0 0,57.53 -19.2V320.06a31.97,31.97 0,0 0,-31.97 -31.97z"
android:fillColor="#ffffff"/>
</vector>

@ -49,4 +49,10 @@
android:icon="@drawable/ic_source"
android:title="@string/book_source"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_word_count"
android:icon="@drawable/ic_wc"
android:title="@string/book_word_count"
app:showAsAction="ifRoom"/>
</menu>

@ -19,4 +19,9 @@
android:icon="@drawable/ic_group"
android:title="@string/menu_group_setting"
app:showAsAction="never" />
<item
android:id="@+id/action_word_count"
android:icon="@drawable/ic_wc"
android:title="@string/book_word_count"
app:showAsAction="ifRoom"/>
</menu>

@ -510,6 +510,7 @@
<string name="manage_book_group">管理书籍分组</string>
<string name="book_group">书籍分组</string>
<string name="book_group_tip">在书架内显示书籍分组</string>
<string name="book_word_count">计算字数(实验)</string>
<string-array name="reset_screen_time">

@ -1,3 +1,3 @@
#Fri Jun 18 21:45:31 CST 2021
VERSION_CODE=222
NEED_CREATE_RELEASE=true
VERSION_CODE=223
NEED_CREATE_RELEASE=false

Loading…
Cancel
Save