Compare commits

...

8 Commits

Author SHA1 Message Date
kunfei
3205f4a90f
Merge 988a1b5385 into 94fb7564f1 2024-04-29 09:28:49 +08:00
Xwite
988a1b5385 Bump cronet from 123.0.6312.80 to 124.0.6367.82
- Changes in the [Git log](https://chromium.googlesource.com/chromium/src/+log/123.0.6312.80..124.0.6367.82)
2024-04-29 01:28:47 +00:00
Horis
94fb7564f1 优化 2024-04-28 16:41:45 +08:00
Horis
11eb304fae 优化 2024-04-28 16:39:48 +08:00
Horis
33e9286a8e 优化 2024-04-28 12:56:35 +08:00
Horis
e57ad3a09e 优化 2024-04-28 12:14:13 +08:00
Horis
f83a2bc357 优化 2024-04-27 22:28:44 +08:00
Antecer
cb06d149f7 修复ttf字体轮廓查询不到的bug 2024-04-27 12:49:11 +08:00
18 changed files with 184 additions and 142 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
{"arm64-v8a":"bbf2c50d1ebf0763d451b08e290a3244","armeabi-v7a":"ecb872f7b1b5342f4d7c36262bec0600","x86":"4da4832b89e2412d808c5b1ecdc24e3d","x86_64":"62080f051db02ed0e939affb39ce67fb","version":"123.0.6312.80"} {"x86":"cb929a66b3c21a5b27b49f6ff79b5441","armeabi-v7a":"4b5b7c7be1d4cab7f848d982081062f6","x86_64":"698858d140e577226364d7310ea015fb","arm64-v8a":"d03030209bb386fcf6842b98e93a9942","version":"124.0.6367.82"}

View File

@ -13,6 +13,7 @@
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2024/02/27** **2024/02/27**
* 更新cronet: 124.0.6367.82
* 更新cronet: 123.0.6312.80 * 更新cronet: 123.0.6312.80
* 更新cronet: 123.0.6312.40 * 更新cronet: 123.0.6312.40

View File

@ -178,7 +178,8 @@ let options = {
if(b64){ if(b64){
var f1 = java.queryBase64TTF(b64[1]); var f1 = java.queryBase64TTF(b64[1]);
var f2 = java.queryTTF("https://alanskycn.gitee.io/teachme/assets/font/Source Han Sans CN Regular.ttf"); var f2 = java.queryTTF("https://alanskycn.gitee.io/teachme/assets/font/Source Han Sans CN Regular.ttf");
return java.replaceFont(result, f1, f2); // return java.replaceFont(result, f1, f2);
return java.replaceFont(result, f1, f2, true); // 过滤掉f1中不存在的字形
} }
return result; return result;
})() })()

View File

@ -799,23 +799,34 @@ interface JsExtensions : JsEncodeUtils {
fun replaceFont( fun replaceFont(
text: String, text: String,
errorQueryTTF: QueryTTF?, errorQueryTTF: QueryTTF?,
correctQueryTTF: QueryTTF? correctQueryTTF: QueryTTF?,
filter: Boolean
): String { ): String {
if (errorQueryTTF == null || correctQueryTTF == null) return text if (errorQueryTTF == null || correctQueryTTF == null) return text
val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节 val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节
contentArray.forEachIndexed { index, s -> contentArray.forEachIndexed { index, s ->
val oldCode = s.codePointAt(0) val oldCode = s.codePointAt(0)
if (errorQueryTTF.inLimit(oldCode)) { val glyf = errorQueryTTF.getGlyfByCode(oldCode)
val glyf = errorQueryTTF.getGlyfByCode(oldCode) val code = correctQueryTTF.getCodeByGlyf(glyf)
val code = correctQueryTTF.getCodeByGlyf(glyf) if (code != 0) {
if (code != 0) { contentArray[index] = code.toChar().toString()
contentArray[index] = code.toChar().toString() }
} if (glyf == "" && filter) {
// 删除轮廓数据为空的字符
contentArray[index] = ""
} }
} }
return contentArray.joinToString("") return contentArray.joinToString("")
} }
fun replaceFont(
text: String,
errorQueryTTF: QueryTTF?,
correctQueryTTF: QueryTTF?
): String {
return replaceFont(text, errorQueryTTF, correctQueryTTF, false)
}
/** /**
* 章节数转数字 * 章节数转数字

View File

@ -11,7 +11,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@SuppressWarnings({"FieldCanBeLocal", "StatementWithEmptyBody", "unused"}) @SuppressWarnings({"FieldCanBeLocal", "unused"})
public class QueryTTF { public class QueryTTF {
private static class Header { private static class Header {
public int majorVersion; public int majorVersion;
@ -222,7 +222,7 @@ public class QueryTTF {
private final MaxpLayout maxp = new MaxpLayout(); private final MaxpLayout maxp = new MaxpLayout();
private final List<Integer> loca = new LinkedList<>(); private final List<Integer> loca = new LinkedList<>();
private final CmapLayout Cmap = new CmapLayout(); private final CmapLayout Cmap = new CmapLayout();
private final List<GlyfLayout> glyf = new LinkedList<>(); private final List<String> glyf = new LinkedList<>();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final Pair<Integer, Integer>[] pps = new Pair[]{ private final Pair<Integer, Integer>[] pps = new Pair[]{
Pair.of(3, 10), Pair.of(3, 10),
@ -233,10 +233,9 @@ public class QueryTTF {
Pair.of(0, 1) Pair.of(0, 1)
}; };
public final Map<Integer, String> codeToGlyph = new HashMap<>(); public final Map<Integer, String> unicodeToGlyph = new HashMap<>();
public final Map<String, Integer> glyphToCode = new HashMap<>(); public final Map<String, Integer> glyphToUnicode = new HashMap<>();
private int limitMix = Integer.MAX_VALUE; public final Map<Integer, Integer> unicodeToGlyphIndex = new HashMap<>();
private int limitMax = 0;
/** /**
* 构造函数 * 构造函数
@ -406,83 +405,42 @@ public class QueryTTF {
} }
} }
} }
// 解析 glyf (字体轮廓数据表) // 读取 glyf (字体轮廓数据表)
for (Directory Temp : directorys) { for (Directory Temp : directorys) {
if (Temp.tag.equals("glyf")) { if (Temp.tag.equals("glyf")) {
fontReader.index = Temp.offset; int glyfCount = maxp.numGlyphs;
for (int i = 0; i < maxp.numGlyphs; ++i) { for (int i = 0; i < glyfCount; ) {
fontReader.index = Temp.offset + loca.get(i); fontReader.index = Temp.offset + loca.get(i);
++i;
int glyfNextIndex = i < glyfCount ? loca.get(i) : Temp.length;
byte[] glyph;
short numberOfContours = fontReader.ReadInt16(); short numberOfContours = fontReader.ReadInt16();
if (numberOfContours > 0) { if (numberOfContours > 0) {
GlyfLayout g = new GlyfLayout(); short g_xMin = fontReader.ReadInt16();
g.numberOfContours = numberOfContours; short g_yMin = fontReader.ReadInt16();
g.xMin = fontReader.ReadInt16(); short g_xMax = fontReader.ReadInt16();
g.yMin = fontReader.ReadInt16(); short g_yMax = fontReader.ReadInt16();
g.xMax = fontReader.ReadInt16(); int[] endPtsOfContours = fontReader.GetUInt16Array(numberOfContours);
g.yMax = fontReader.ReadInt16(); glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - Temp.offset));
g.endPtsOfContours = fontReader.GetUInt16Array(numberOfContours);
g.instructionLength = fontReader.ReadUInt16();
g.instructions = fontReader.GetBytes(g.instructionLength);
int flagLength = g.endPtsOfContours[g.endPtsOfContours.length - 1] + 1;
// 获取轮廓点描述标志
g.flags = new byte[flagLength];
for (int n = 0; n < flagLength; ++n) {
g.flags[n] = fontReader.GetByte();
if ((g.flags[n] & 0x08) != 0x00) {
for (int m = fontReader.ReadUInt8(); m > 0; --m) {
g.flags[++n] = g.flags[n - 1];
}
}
}
// 获取轮廓点描述x轴相对值
g.xCoordinates = new short[flagLength];
for (int n = 0; n < flagLength; ++n) {
short same = (short) ((g.flags[n] & 0x10) != 0 ? 1 : -1);
if ((g.flags[n] & 0x02) != 0) {
g.xCoordinates[n] = (short) (same * fontReader.ReadUInt8());
} else {
g.xCoordinates[n] = same == 1 ? (short) 0 : fontReader.ReadInt16();
}
}
// 获取轮廓点描述y轴相对值
g.yCoordinates = new short[flagLength];
for (int n = 0; n < flagLength; ++n) {
short same = (short) ((g.flags[n] & 0x20) != 0 ? 1 : -1);
if ((g.flags[n] & 0x04) != 0) {
g.yCoordinates[n] = (short) (same * fontReader.ReadUInt8());
} else {
g.yCoordinates[n] = same == 1 ? (short) 0 : fontReader.ReadInt16();
}
}
/* 相对坐标转绝对坐标
for (int n = 1; n < flagLength; ++n) {
xCoordinates[n] += xCoordinates[n - 1];
yCoordinates[n] += yCoordinates[n - 1];
}*/
glyf.add(g);
} else { } else {
// 复合字体暂未使用 glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - 2));
} }
glyf.add(getHexFromBytes(glyph));
} }
} }
} }
// 建立Unicode&Glyph双向表 // 建立Unicode&Glyph双向表
for (int key = 0; key < 130000; ++key) { for (int key = 0; key < 130000; ++key) {
if (key == 0xFF) key = 0x3400; // if (key == 0xFF) key = 0x3400;
int gid = getGlyfIndex(key); int gid = queryGlyfIndex(key);
if (gid == 0 || gid >= glyf.size()) continue; if (gid >= glyf.size()) continue;
StringBuilder sb = new StringBuilder(); unicodeToGlyphIndex.put(key, gid);
// 字型数据转String方便存HashMap var val = glyf.get(gid);
for (short b : glyf.get(gid).xCoordinates) sb.append(b); unicodeToGlyph.put(key, val);
for (short b : glyf.get(gid).yCoordinates) sb.append(b); if (glyphToUnicode.containsKey(val)) continue;
String val = sb.toString(); glyphToUnicode.put(val, key);
if (limitMix > key) limitMix = key;
if (limitMax < key) limitMax = key;
codeToGlyph.put(key, val);
if (glyphToCode.containsKey(val)) continue;
glyphToCode.put(val, key);
} }
} }
@ -509,11 +467,11 @@ public class QueryTTF {
/** /**
* 使用Unicode值查找轮廓索引 * 使用Unicode值查找轮廓索引
* *
* @param code 传入Unicode十进制 * @param unicode 传入Unicode
* @return 返回十进制轮廓索引 * @return 返回十进制轮廓索引
*/ */
private int getGlyfIndex(int code) { public int queryGlyfIndex(int unicode) {
if (code == 0) return 0; if (unicode == 0) return 0;
int fmtKey = 0; int fmtKey = 0;
for (Pair<Integer, Integer> item : pps) { for (Pair<Integer, Integer> item : pps) {
for (CmapRecord record : Cmap.records) { for (CmapRecord record : Cmap.records) {
@ -531,34 +489,33 @@ public class QueryTTF {
assert table != null; assert table != null;
int fmt = table.format; int fmt = table.format;
if (fmt == 0) { if (fmt == 0) {
if (code < table.glyphIdArray.length) glyfID = table.glyphIdArray[code] & 0xFF; if (unicode < table.glyphIdArray.length) glyfID = table.glyphIdArray[unicode] & 0xFF;
} else if (fmt == 4) { } else if (fmt == 4) {
CmapFormat4 tab = (CmapFormat4) table; CmapFormat4 tab = (CmapFormat4) table;
if (code > tab.endCode[tab.endCode.length - 1]) return 0; if (unicode > tab.endCode[tab.endCode.length - 1]) return 0;
// 二分法查找数值索引 // 二分法查找数值索引
int start = 0, middle, end = tab.endCode.length - 1; int start = 0, middle, end = tab.endCode.length - 1;
while (start + 1 < end) { while (start + 1 < end) {
middle = (start + end) / 2; middle = (start + end) / 2;
if (tab.endCode[middle] <= code) start = middle; if (tab.endCode[middle] <= unicode) start = middle;
else end = middle; else end = middle;
} }
if (tab.endCode[start] < code) ++start; if (tab.endCode[start] < unicode) ++start;
if (code < tab.startCode[start]) return 0; if (unicode < tab.startCode[start]) return 0;
if (tab.idRangeOffset[start] != 0) { if (tab.idRangeOffset[start] != 0) {
glyfID = tab.glyphIdArray[code - tab.startCode[start] + (tab.idRangeOffset[start] >> 1) - (tab.idRangeOffset.length - start)]; glyfID = tab.glyphIdArray[unicode - tab.startCode[start] + (tab.idRangeOffset[start] >> 1) - (tab.idRangeOffset.length - start)];
} else glyfID = code + tab.idDelta[start]; } else glyfID = unicode + tab.idDelta[start];
glyfID &= 0xFFFF; glyfID &= 0xFFFF;
} else if (fmt == 6) { } else if (fmt == 6) {
CmapFormat6 tab = (CmapFormat6) table; CmapFormat6 tab = (CmapFormat6) table;
int index = code - tab.firstCode; int index = unicode - tab.firstCode;
if (index < 0 || index >= tab.glyphIdArray.length) glyfID = 0; if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index];
else glyfID = tab.glyphIdArray[index];
} else if (fmt == 12) { } else if (fmt == 12) {
CmapFormat12 tab = (CmapFormat12) table; CmapFormat12 tab = (CmapFormat12) table;
if (code > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0; if (unicode > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0;
for (int i = 0; i < tab.numGroups; i++) { for (int i = 0; i < tab.numGroups; i++) {
if (tab.groups.get(i).getLeft() <= code && code <= tab.groups.get(i).getMiddle()) { if (tab.groups.get(i).getLeft() <= unicode && unicode <= tab.groups.get(i).getMiddle()) {
glyfID = tab.groups.get(i).getRight() + code - tab.groups.get(i).getLeft(); glyfID = tab.groups.get(i).getRight() + unicode - tab.groups.get(i).getLeft();
break; break;
} }
} }
@ -567,33 +524,54 @@ public class QueryTTF {
} }
/** /**
* 判断Unicode值是否在字体范围内 * 使用Unicode值获取轮廓索引
* *
* @param code 传入Unicode十进制 * @param unicode 传入Unicode
* @return 返回bool查询结果 * @return 返回十进制轮廓索引
*/ */
public boolean inLimit(int code) { public int getGlyfIndex(int unicode) {
return (limitMix <= code) && (code <= limitMax); var glyfIndex = unicodeToGlyphIndex.get(unicode);
if (glyfIndex == null) return 0;
return glyfIndex;
} }
/** /**
* 使用Unicode值获取轮廓数据 * 使用Unicode值获取轮廓数据
* *
* @param key 传入Unicode十进制 * @param unicode 传入Unicode
* @return 返回轮廓数组的String值 * @return 返回轮廓数组的String值
*/ */
public String getGlyfByCode(int key) { public String getGlyfByCode(int unicode) {
return codeToGlyph.getOrDefault(key, ""); return unicodeToGlyph.getOrDefault(unicode, "");
} }
/** /**
* 使用轮廓数据获取Unicode值 * 使用轮廓数据获取Unicode值
* *
* @param val 传入轮廓数组的String值 * @param glyph 传入轮廓数组的String值
* @return 返回Unicode十进制值 * @return 返回Unicode十进制值
*/ */
public int getCodeByGlyf(String val) { public int getCodeByGlyf(String glyph) {
//noinspection ConstantConditions //noinspection ConstantConditions
return glyphToCode.getOrDefault(val, 0); return glyphToUnicode.getOrDefault(glyph, 0);
}
/**
* 字体轮廓数据转Hex字符串
*
* @param glyph 字体轮廓数据
* @return 返回轮廓数组的String值
*/
public String getHexFromBytes(byte[] glyph) {
if (glyph == null) return "";
StringBuilder sb = new StringBuilder();
for (byte b : glyph) {
String hex = Integer.toHexString(b);
if (hex.length() == 1) {
sb.append("0");//当16进制为个位数时在前面补0
}
sb.append(hex);//将16进制加入字符串
}
return sb.toString().toUpperCase();
} }
} }

View File

@ -7,6 +7,7 @@ import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseService import io.legado.app.base.BaseService
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
@ -15,7 +16,16 @@ import io.legado.app.constant.IntentAction
import io.legado.app.constant.NotificationId import io.legado.app.constant.NotificationId
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.receiver.NetworkChangedListener import io.legado.app.receiver.NetworkChangedListener
import io.legado.app.utils.* import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.postEvent
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.sendToClip
import io.legado.app.utils.servicePendingIntent
import io.legado.app.utils.startService
import io.legado.app.utils.stopService
import io.legado.app.utils.toastOnUi
import io.legado.app.web.HttpServer import io.legado.app.web.HttpServer
import io.legado.app.web.WebSocketServer import io.legado.app.web.WebSocketServer
import splitties.init.appCtx import splitties.init.appCtx
@ -33,6 +43,11 @@ class WebService : BaseService() {
context.startService<WebService>() context.startService<WebService>()
} }
fun startForeground(context: Context) {
val intent = Intent(context, WebService::class.java)
ContextCompat.startForegroundService(context, intent)
}
fun stop(context: Context) { fun stop(context: Context) {
context.stopService<WebService>() context.stopService<WebService>()
} }

View File

@ -1,10 +1,14 @@
package io.legado.app.service package io.legado.app.service
import android.app.Dialog
import android.app.ForegroundServiceStartNotAllowedException
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.service.quicksettings.Tile import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import android.view.WindowManager.BadTokenException
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import io.legado.app.R
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
@ -22,6 +26,7 @@ class WebTileService : TileService() {
state = Tile.STATE_ACTIVE state = Tile.STATE_ACTIVE
updateTile() updateTile()
} }
IntentAction.stop -> qsTile?.run { IntentAction.stop -> qsTile?.run {
state = Tile.STATE_INACTIVE state = Tile.STATE_INACTIVE
updateTile() updateTile()
@ -49,7 +54,24 @@ class WebTileService : TileService() {
if (WebService.isRun) { if (WebService.isRun) {
WebService.stop(this) WebService.stop(this)
} else { } else {
WebService.start(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val dialog = Dialog(this, R.style.AppTheme_Transparent)
dialog.setOnShowListener {
try {
WebService.startForeground(this)
} catch (e: ForegroundServiceStartNotAllowedException) {
e.printStackTrace()
}
dialog.dismiss()
}
try {
showDialog(dialog)
} catch (e: BadTokenException) {
e.printStackTrace()
}
} else {
WebService.start(this)
}
} }
} }

View File

@ -1,18 +1,15 @@
package io.legado.app.ui.book.read.page package io.legado.app.ui.book.read.page
import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Picture
import android.graphics.Rect
import android.os.Build
import android.os.SystemClock import android.os.SystemClock
import androidx.core.graphics.withClip import androidx.core.graphics.withClip
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.screenshot import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeeded
/** /**
* 自动翻页 * 自动翻页
@ -24,13 +21,10 @@ class AutoPager(private val readView: ReadView) {
private var scrollOffsetRemain = 0.0 private var scrollOffsetRemain = 0.0
private var scrollOffset = 0 private var scrollOffset = 0
private var lastTimeMillis = 0L private var lastTimeMillis = 0L
private var bitmap: Bitmap? = null private var canvasRecorder = CanvasRecorderFactory.create()
private var picture: Picture? = null
private var pictureIsDirty = true
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
private val rect = Rect()
private val paint by lazy { Paint() } private val paint by lazy { Paint() }
fun start() { fun start() {
isRunning = true isRunning = true
paint.color = ThemeStore.accentColor paint.color = ThemeStore.accentColor
@ -48,7 +42,7 @@ class AutoPager(private val readView: ReadView) {
readView.curPage.upSelectAble(AppConfig.textSelectAble) readView.curPage.upSelectAble(AppConfig.textSelectAble)
readView.invalidate() readView.invalidate()
reset() reset()
picture = null canvasRecorder.recycle()
} }
fun pause() { fun pause() {
@ -71,9 +65,12 @@ class AutoPager(private val readView: ReadView) {
progress = 0 progress = 0
scrollOffsetRemain = 0.0 scrollOffsetRemain = 0.0
scrollOffset = 0 scrollOffset = 0
bitmap?.recycle() canvasRecorder.invalidate()
bitmap = null }
pictureIsDirty = true
fun upRecorder() {
canvasRecorder.recycle()
canvasRecorder = CanvasRecorderFactory.create()
} }
fun onDraw(canvas: Canvas) { fun onDraw(canvas: Canvas) {
@ -86,24 +83,12 @@ class AutoPager(private val readView: ReadView) {
} else { } else {
val bottom = progress val bottom = progress
val width = readView.width val width = readView.width
if (atLeastApi23) {
if (picture == null) { canvasRecorder.recordIfNeeded(readView.nextPage)
picture = Picture() canvas.withClip(0, 0, width, bottom) {
} canvasRecorder.draw(this)
if (pictureIsDirty) {
pictureIsDirty = false
readView.nextPage.screenshot(picture!!)
}
canvas.withClip(0, 0, width, bottom) {
drawPicture(picture!!)
}
} else {
if (bitmap == null) {
bitmap = readView.nextPage.screenshot()
}
rect.set(0, 0, width, bottom)
canvas.drawBitmap(bitmap!!, rect, rect, null)
} }
canvas.drawRect( canvas.drawRect(
0f, 0f,
bottom.toFloat() - 1, bottom.toFloat() - 1,

View File

@ -514,6 +514,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
(pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage (pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage
if (upRecorder) { if (upRecorder) {
(pageDelegate as? HorizontalPageDelegate)?.upRecorder() (pageDelegate as? HorizontalPageDelegate)?.upRecorder()
autoPager.upRecorder()
} }
pageDelegate?.setViewSize(width, height) pageDelegate?.setViewSize(width, height)
if (isScroll) { if (isScroll) {

View File

@ -6,7 +6,6 @@ import android.view.View
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -29,6 +28,7 @@ import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.MainViewModel
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.flowWithLifecycleFirst
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
@ -175,7 +175,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime } else -> list.sortedByDescending { it.durChapterTime }
} }
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { }.flowWithLifecycleFirst(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch {
AppLog.put("书架更新出错", it) AppLog.put("书架更新出错", it)
}.conflate().flowOn(Dispatchers.Default).collect { list -> }.conflate().flowOn(Dispatchers.Default).collect { list ->
binding.tvEmptyMsg.isGone = list.isNotEmpty() binding.tvEmptyMsg.isGone = list.isNotEmpty()

View File

@ -1,13 +1,17 @@
package io.legado.app.utils package io.legado.app.utils
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -185,3 +189,18 @@ inline fun <T> Flow<T>.onEachAsyncIndexed(
}.onEach { semaphore.release() } }.onEach { semaphore.release() }
}.buffer(0) }.buffer(0)
} }
fun <T> Flow<T>.flowWithLifecycleFirst(
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T> = callbackFlow {
if (!lifecycle.currentState.isAtLeast(minActiveState)) {
send(first())
}
lifecycle.repeatOnLifecycle(minActiveState) {
this@flowWithLifecycleFirst.collect {
send(it)
}
}
close()
}

View File

@ -1,6 +1,7 @@
package io.legado.app.utils.canvasrecorder package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas import android.graphics.Canvas
import android.view.View
import androidx.core.graphics.withSave import androidx.core.graphics.withSave
inline fun CanvasRecorder.recordIfNeeded( inline fun CanvasRecorder.recordIfNeeded(
@ -13,6 +14,14 @@ inline fun CanvasRecorder.recordIfNeeded(
return true return true
} }
fun CanvasRecorder.recordIfNeeded(view: View): Boolean {
if (!needRecord()) return false
record(view.width, view.height) {
view.draw(this)
}
return true
}
inline fun CanvasRecorder.record(width: Int, height: Int, block: Canvas.() -> Unit) { inline fun CanvasRecorder.record(width: Int, height: Int, block: Canvas.() -> Unit) {
try { try {
val canvas = beginRecording(width, height) val canvas = beginRecording(width, height)

View File

@ -42,7 +42,7 @@ android.defaults.buildfeatures.shaders=false
# and none from the library's dependencies, thereby reducing the size of the R class for that library. # and none from the library's dependencies, thereby reducing the size of the R class for that library.
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
# https://chromiumdash.appspot.com/releases?platform=Android # https://chromiumdash.appspot.com/releases?platform=Android
CronetVersion=123.0.6312.80 CronetVersion=124.0.6367.82
CronetMainVersion=123.0.0.0 CronetMainVersion=124.0.0.0
android.injected.testOnly=false android.injected.testOnly=false
android.nonFinalResIds=true android.nonFinalResIds=true