commit
6f3e1c9fc8
@ -0,0 +1,99 @@ |
||||
name: Build and Release |
||||
|
||||
on: |
||||
push: |
||||
branches: |
||||
- master |
||||
paths: |
||||
- 'CHANGELOG.md' |
||||
# schedule: |
||||
# - cron: '0 4 * * *' |
||||
|
||||
jobs: |
||||
build: |
||||
runs-on: ubuntu-latest |
||||
env: |
||||
# 登录蓝奏云后在控制台运行document.cookie |
||||
ylogin: ${{ secrets.LANZOU_ID }} |
||||
phpdisk_info: ${{ secrets.LANZOU_PSD }} |
||||
# 蓝奏云里的文件夹ID(阅读3测试版:2670621) |
||||
LANZOU_FOLDER_ID: 'b0f7pt4ja' |
||||
# 是否上传到artifact |
||||
UPLOAD_ARTIFACT: 'true' |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- uses: actions/cache@v2 |
||||
with: |
||||
path: | |
||||
~/.gradle/caches |
||||
~/.gradle/wrapper |
||||
key: ${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} |
||||
restore-keys: | |
||||
${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}- |
||||
- name: Release Apk Sign |
||||
run: | |
||||
echo "给apk增加签名" |
||||
cp $GITHUB_WORKSPACE/.github/workflows/legado.jks $GITHUB_WORKSPACE/app/legado.jks |
||||
sed '$a\RELEASE_STORE_FILE=./legado.jks' $GITHUB_WORKSPACE/gradle.properties -i |
||||
sed '$a\RELEASE_KEY_ALIAS=legado' $GITHUB_WORKSPACE/gradle.properties -i |
||||
sed '$a\RELEASE_STORE_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i |
||||
sed '$a\RELEASE_KEY_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i |
||||
- name: Unify Version Name |
||||
run: | |
||||
echo "统一版本号" |
||||
VERSION=$(date -d "8 hour" -u +3.%y.%m%d%H) |
||||
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV |
||||
sed "/def version/c def version = \"$VERSION\"" $GITHUB_WORKSPACE/app/build.gradle -i |
||||
- name: Build With Gradle |
||||
run: | |
||||
echo "开始进行release构建" |
||||
chmod +x gradlew |
||||
./gradlew assembleAppRelease --build-cache --parallel |
||||
echo "开始进行cronet构建" |
||||
./gradlew assembleCronetRelease --build-cache --parallel |
||||
- name: Organize the Files |
||||
run: | |
||||
mkdir -p ${{ github.workspace }}/apk/ |
||||
cp -rf ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk ${{ github.workspace }}/apk/ |
||||
- name: Upload App To Artifact |
||||
if: ${{ env.UPLOAD_ARTIFACT != 'false' }} |
||||
uses: actions/upload-artifact@v2 |
||||
with: |
||||
name: legado apk |
||||
path: ${{ github.workspace }}/apk/*.apk |
||||
- name: Upload App To Lanzou |
||||
if: ${{ env.ylogin }} |
||||
run: | |
||||
path="$GITHUB_WORKSPACE/apk/" |
||||
python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py "$path" "$LANZOU_FOLDER_ID" |
||||
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 分享链接: https://kunfei.lanzoux.com/b0f7pt4ja" |
||||
- name: Release |
||||
uses: softprops/action-gh-release@59c3b4891632ff9a897f99a91d7bc557467a3a22 |
||||
with: |
||||
name: legado_app_${{ env.RELEASE_VERSION }} |
||||
tag_name: ${{ env.RELEASE_VERSION }} |
||||
body_path: ${{ github.workspace }}/CHANGELOG.md |
||||
draft: false |
||||
prerelease: false |
||||
files: ${{ github.workspace }}/apk/*.apk |
||||
env: |
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
- name: Push Assets To "release" Branch |
||||
run: | |
||||
cd $GITHUB_WORKSPACE/apk || exit 1 |
||||
git init |
||||
git config --local user.name "github-actions[bot]" |
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" |
||||
git checkout -b release |
||||
git add *.apk |
||||
git commit -m "${{ env.RELEASE_VERSION }}" |
||||
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}" |
||||
git push -f -u origin release |
||||
- name: Purge Jsdelivr Cache |
||||
run: | |
||||
result=$(curl -s https://purge.jsdelivr.net/gh/${{ github.repository }}@release/) |
||||
if echo $result |grep -q 'success.*true'; then |
||||
echo "jsdelivr缓存更新成功" |
||||
else |
||||
echo $result |
||||
fi |
@ -1,4 +1,4 @@ |
||||
# 1.0.0 (2020-02-09) |
||||
|
||||
|
||||
**2021/08/09** |
||||
|
||||
1. 修复选择文字不能选择单个文字的bug |
||||
2. |
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,11 @@ |
||||
package io.legado.app.data.entities.rule |
||||
|
||||
import android.os.Parcelable |
||||
import kotlinx.parcelize.Parcelize |
||||
|
||||
@Parcelize |
||||
data class LoginRule( |
||||
val ui: HashMap<String, String>? = null, |
||||
val url: String? = null, |
||||
val checkJs: String? = null |
||||
) : Parcelable |
@ -1,23 +1,67 @@ |
||||
package io.legado.app.help.http.cronet |
||||
|
||||
import okhttp3.Call |
||||
import okhttp3.Interceptor |
||||
import okhttp3.Request |
||||
import okhttp3.Response |
||||
import io.legado.app.help.http.CookieStore |
||||
import okhttp3.* |
||||
import java.io.IOException |
||||
|
||||
class CronetInterceptor : Interceptor { |
||||
class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor { |
||||
@Throws(IOException::class) |
||||
override fun intercept(chain: Interceptor.Chain): Response { |
||||
return proceedWithCronet(chain.request(), chain.call()) |
||||
val original: Request = chain.request() |
||||
val builder: Request.Builder = original.newBuilder() |
||||
//Cronet未初始化 |
||||
return if (!CronetLoader.install()) { |
||||
chain.proceed(original) |
||||
} else try { |
||||
//移除Keep-Alive,手动设置会导致400 BadRequest |
||||
builder.removeHeader("Keep-Alive") |
||||
val cookieStr = getCookie(original.url) |
||||
//设置Cookie |
||||
if (cookieStr.length > 3) { |
||||
builder.header("Cookie", cookieStr) |
||||
} |
||||
val new = builder.build() |
||||
val response: Response = proceedWithCronet(new, chain.call()) |
||||
//从Response 中保存Cookie到CookieJar |
||||
cookieJar?.saveFromResponse(new.url, Cookie.parseAll(new.url, response.headers)) |
||||
response |
||||
} catch (e: Exception) { |
||||
//遇到Cronet处理有问题时的情况,如证书过期等等,回退到okhttp处理 |
||||
if (e.message.toString().contains("ERR_CERT_DATE_INVALID", true)) { |
||||
chain.proceed(original) |
||||
} else { |
||||
throw e |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
@Throws(IOException::class) |
||||
private fun proceedWithCronet(request: Request, call: Call): Response { |
||||
|
||||
val callback = CronetUrlRequestCallback(request, call) |
||||
val urlRequest = buildRequest(request, callback) |
||||
urlRequest.start() |
||||
return callback.waitForDone() |
||||
} |
||||
|
||||
private fun getCookie(url: HttpUrl): String { |
||||
val sb = StringBuilder() |
||||
//处理从 Cookjar 获取到的Cookies |
||||
if (cookieJar != null) { |
||||
val cookies = cookieJar.loadForRequest(url) |
||||
for (cookie in cookies) { |
||||
sb.append(cookie.name).append("=").append(cookie.value).append("; ") |
||||
} |
||||
} |
||||
//处理自定义的Cookie |
||||
val cookie = CookieStore.getCookie(url.toString()) |
||||
if (cookie.length > 3) { |
||||
sb.append(cookie) |
||||
} |
||||
return sb.toString() |
||||
} |
||||
|
||||
} |
@ -0,0 +1,357 @@ |
||||
package io.legado.app.help.http.cronet |
||||
|
||||
import android.annotation.SuppressLint |
||||
import android.content.Context |
||||
import android.content.pm.ApplicationInfo |
||||
import android.os.Build |
||||
import android.text.TextUtils |
||||
import android.util.Log |
||||
import io.legado.app.help.coroutine.Coroutine |
||||
import io.legado.app.utils.getPrefString |
||||
import io.legado.app.utils.putPrefString |
||||
import org.chromium.net.CronetEngine |
||||
import org.chromium.net.impl.ImplVersion |
||||
import splitties.init.appCtx |
||||
import java.io.* |
||||
import java.math.BigInteger |
||||
import java.net.HttpURLConnection |
||||
import java.net.URL |
||||
import java.security.MessageDigest |
||||
import java.util.* |
||||
|
||||
object CronetLoader : CronetEngine.Builder.LibraryLoader() { |
||||
//https://storage.googleapis.com/chromium-cronet/android/92.0.4515.127/Release/cronet/libs/arm64-v8a/libcronet.92.0.4515.127.so |
||||
//https://cdn.jsdelivr.net/gh/ag2s20150909/cronet-repo@92.0.4515.127/cronet/92.0.4515.127/arm64-v8a/libcronet.92.0.4515.127.so.js |
||||
private const val TAG = "CronetLoader" |
||||
private val soName = "libcronet." + ImplVersion.getCronetVersion() + ".so" |
||||
private val soUrl: String |
||||
private val md5Url: String |
||||
private val soFile: File |
||||
private val downloadFile: File |
||||
private var cpuAbi: String? = null |
||||
private var md5: String? = appCtx.getPrefString("soMd5") |
||||
private val version: String? = appCtx.getPrefString("soVersion", ImplVersion.getCronetVersion()) |
||||
var download = false |
||||
|
||||
init { |
||||
soUrl = ("https://storage.googleapis.com/chromium-cronet/android/" |
||||
+ ImplVersion.getCronetVersion() + "/Release/cronet/libs/" |
||||
+ getCpuAbi(appCtx) + "/" + soName) |
||||
md5Url = ("https://cdn.jsdelivr.net/gh/ag2s20150909/cronet-repo@" + |
||||
ImplVersion.getCronetVersion() + "/cronet/" + ImplVersion.getCronetVersion() + "/" |
||||
+ getCpuAbi(appCtx) + "/" + soName + ".js") |
||||
val dir = appCtx.getDir("lib", Context.MODE_PRIVATE) |
||||
soFile = File(dir.toString() + "/" + getCpuAbi(appCtx), soName) |
||||
downloadFile = File(appCtx.cacheDir.toString() + "/so_download", soName) |
||||
Log.e(TAG, "soName+:$soName") |
||||
Log.e(TAG, "destSuccessFile:$soFile") |
||||
Log.e(TAG, "tempFile:$downloadFile") |
||||
Log.e(TAG, "soUrl:$soUrl") |
||||
} |
||||
|
||||
fun install(): Boolean { |
||||
return soFile.exists() |
||||
} |
||||
|
||||
fun preDownload() { |
||||
Coroutine.async { |
||||
md5 = getUrlMd5(md5Url) |
||||
if (soFile.exists() && md5 == getFileMD5(soFile)) { |
||||
Log.e(TAG, "So 库已存在") |
||||
} else { |
||||
download(soUrl, md5, downloadFile, soFile) |
||||
} |
||||
Log.e(TAG, soName) |
||||
} |
||||
} |
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode") |
||||
override fun loadLibrary(libName: String) { |
||||
Log.e(TAG, "libName:$libName") |
||||
val start = System.currentTimeMillis() |
||||
@Suppress("SameParameterValue") |
||||
try { |
||||
//非cronet的so调用系统方法加载 |
||||
if (!libName.contains("cronet")) { |
||||
System.loadLibrary(libName) |
||||
return |
||||
} |
||||
//以下逻辑为cronet加载,优先加载本地,否则从远程加载 |
||||
//首先调用系统行为进行加载 |
||||
System.loadLibrary(libName) |
||||
Log.i(TAG, "load from system") |
||||
} catch (e: Throwable) { |
||||
//如果找不到,则从远程下载 |
||||
//删除历史文件 |
||||
deleteHistoryFile(Objects.requireNonNull(soFile.parentFile), soFile) |
||||
md5 = getUrlMd5(md5Url) |
||||
Log.i(TAG, "soMD5:$md5") |
||||
if (md5 == null || md5!!.length != 32 || soUrl.isEmpty()) { |
||||
//如果md5或下载的url为空,则调用系统行为进行加载 |
||||
System.loadLibrary(libName) |
||||
return |
||||
} |
||||
if (!soFile.exists() || !soFile.isFile) { |
||||
soFile.delete() |
||||
download(soUrl, md5, downloadFile, soFile) |
||||
//如果文件不存在或不是文件,则调用系统行为进行加载 |
||||
System.loadLibrary(libName) |
||||
return |
||||
} |
||||
if (soFile.exists()) { |
||||
//如果文件存在,则校验md5值 |
||||
val fileMD5 = getFileMD5(soFile) |
||||
if (fileMD5 != null && fileMD5.equals(md5, ignoreCase = true)) { |
||||
//md5值一样,则加载 |
||||
System.load(soFile.absolutePath) |
||||
Log.e(TAG, "load from:$soFile") |
||||
return |
||||
} |
||||
//md5不一样则删除 |
||||
soFile.delete() |
||||
} |
||||
//不存在则下载 |
||||
download(soUrl, md5, downloadFile, soFile) |
||||
//使用系统加载方法 |
||||
System.loadLibrary(libName) |
||||
} finally { |
||||
Log.e(TAG, "time:" + (System.currentTimeMillis() - start)) |
||||
} |
||||
} |
||||
|
||||
@SuppressLint("DiscouragedPrivateApi") |
||||
private fun getCpuAbi(context: Context): String? { |
||||
if (cpuAbi != null) { |
||||
return cpuAbi |
||||
} |
||||
// 5.0以上Application才有primaryCpuAbi字段 |
||||
try { |
||||
val appInfo = context.applicationInfo |
||||
val abiField = ApplicationInfo::class.java.getDeclaredField("primaryCpuAbi") |
||||
abiField.isAccessible = true |
||||
cpuAbi = abiField[appInfo] as String |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
if (TextUtils.isEmpty(cpuAbi)) { |
||||
cpuAbi = Build.SUPPORTED_ABIS[0] |
||||
} |
||||
return cpuAbi |
||||
} |
||||
|
||||
@Suppress("SameParameterValue") |
||||
private fun getUrlMd5(url: String): String? { |
||||
//这样在下载成功后,遇到无网条件下,只要版本未发生变化也能获取md5 |
||||
if (md5 != null && md5!!.length == 32 && version == ImplVersion.getCronetVersion()) { |
||||
appCtx.putPrefString("soMd5", md5) |
||||
appCtx.putPrefString("soVersion", ImplVersion.getCronetVersion()) |
||||
return md5 |
||||
} |
||||
val inputStream: InputStream |
||||
val outputStream: OutputStream |
||||
return try { |
||||
outputStream = ByteArrayOutputStream() |
||||
val connection = URL(url).openConnection() as HttpURLConnection |
||||
inputStream = connection.inputStream |
||||
val buffer = ByteArray(1024) |
||||
var read: Int |
||||
while (inputStream.read(buffer).also { read = it } != -1) { |
||||
outputStream.write(buffer, 0, read) |
||||
outputStream.flush() |
||||
} |
||||
val tmd5 = outputStream.toString() |
||||
//成功获取到md5后保存md5和版本 |
||||
if (tmd5.length == 32) { |
||||
appCtx.putPrefString("soMd5", tmd5) |
||||
appCtx.putPrefString("soVersion", ImplVersion.getCronetVersion()) |
||||
} |
||||
|
||||
return tmd5 |
||||
|
||||
} catch (e: IOException) { |
||||
null |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 删除历史文件 |
||||
*/ |
||||
private fun deleteHistoryFile(dir: File, currentFile: File?) { |
||||
val files = dir.listFiles() |
||||
@Suppress("SameParameterValue") |
||||
if (files != null && files.isNotEmpty()) { |
||||
for (f in files) { |
||||
if (f.exists() && (currentFile == null || f.absolutePath != currentFile.absolutePath)) { |
||||
val delete = f.delete() |
||||
Log.e(TAG, "delete file: $f result: $delete") |
||||
if (!delete) { |
||||
f.deleteOnExit() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 下载文件 |
||||
*/ |
||||
private fun downloadFileIfNotExist(url: String, destFile: File): Boolean { |
||||
var inputStream: InputStream? = null |
||||
var outputStream: OutputStream? = null |
||||
try { |
||||
val connection = URL(url).openConnection() as HttpURLConnection |
||||
inputStream = connection.inputStream |
||||
if (destFile.exists()) { |
||||
return true |
||||
} |
||||
destFile.parentFile!!.mkdirs() |
||||
destFile.createNewFile() |
||||
outputStream = FileOutputStream(destFile) |
||||
val buffer = ByteArray(32768) |
||||
var read: Int |
||||
while (inputStream.read(buffer).also { read = it } != -1) { |
||||
outputStream.write(buffer, 0, read) |
||||
outputStream.flush() |
||||
} |
||||
return true |
||||
} catch (e: Throwable) { |
||||
e.printStackTrace() |
||||
if (destFile.exists() && !destFile.delete()) { |
||||
destFile.deleteOnExit() |
||||
} |
||||
} finally { |
||||
if (inputStream != null) { |
||||
try { |
||||
inputStream.close() |
||||
} catch (e: IOException) { |
||||
e.printStackTrace() |
||||
} |
||||
} |
||||
if (outputStream != null) { |
||||
try { |
||||
outputStream.close() |
||||
} catch (e: IOException) { |
||||
e.printStackTrace() |
||||
} |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
/** |
||||
* 下载并拷贝文件 |
||||
*/ |
||||
@Suppress("SameParameterValue") |
||||
@Synchronized |
||||
private fun download( |
||||
url: String, |
||||
md5: String?, |
||||
downloadTempFile: File, |
||||
destSuccessFile: File |
||||
) { |
||||
if (download) { |
||||
return |
||||
} |
||||
download = true |
||||
executor.execute { |
||||
val result = downloadFileIfNotExist(url, downloadTempFile) |
||||
Log.e(TAG, "download result:$result") |
||||
//文件md5再次校验 |
||||
val fileMD5 = getFileMD5(downloadTempFile) |
||||
if (md5 != null && !md5.equals(fileMD5, ignoreCase = true)) { |
||||
val delete = downloadTempFile.delete() |
||||
if (!delete) { |
||||
downloadTempFile.deleteOnExit() |
||||
} |
||||
download = false |
||||
return@execute |
||||
} |
||||
Log.e(TAG, "download success, copy to $destSuccessFile") |
||||
//下载成功拷贝文件 |
||||
copyFile(downloadTempFile, destSuccessFile) |
||||
val parentFile = downloadTempFile.parentFile |
||||
@Suppress("SameParameterValue") |
||||
deleteHistoryFile(parentFile!!, null) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 拷贝文件 |
||||
*/ |
||||
private fun copyFile(source: File?, dest: File?): Boolean { |
||||
if (source == null || !source.exists() || !source.isFile || dest == null) { |
||||
return false |
||||
} |
||||
if (source.absolutePath == dest.absolutePath) { |
||||
return true |
||||
} |
||||
var fileInputStream: FileInputStream? = null |
||||
var os: FileOutputStream? = null |
||||
val parent = dest.parentFile |
||||
if (parent != null && !parent.exists()) { |
||||
val mkdirs = parent.mkdirs() |
||||
if (!mkdirs) { |
||||
parent.mkdirs() |
||||
} |
||||
} |
||||
try { |
||||
fileInputStream = FileInputStream(source) |
||||
os = FileOutputStream(dest, false) |
||||
val buffer = ByteArray(1024 * 512) |
||||
var length: Int |
||||
while (fileInputStream.read(buffer).also { length = it } > 0) { |
||||
os.write(buffer, 0, length) |
||||
} |
||||
return true |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} finally { |
||||
if (fileInputStream != null) { |
||||
try { |
||||
fileInputStream.close() |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
} |
||||
if (os != null) { |
||||
try { |
||||
os.close() |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
/** |
||||
* 获得文件md5 |
||||
*/ |
||||
private fun getFileMD5(file: File): String? { |
||||
var fileInputStream: FileInputStream? = null |
||||
try { |
||||
fileInputStream = FileInputStream(file) |
||||
val md5 = MessageDigest.getInstance("MD5") |
||||
val buffer = ByteArray(1024) |
||||
var numRead: Int |
||||
while (fileInputStream.read(buffer).also { numRead = it } > 0) { |
||||
md5.update(buffer, 0, numRead) |
||||
} |
||||
return String.format("%032x", BigInteger(1, md5.digest())).lowercase() |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} catch (e: OutOfMemoryError) { |
||||
e.printStackTrace() |
||||
} finally { |
||||
if (fileInputStream != null) { |
||||
try { |
||||
fileInputStream.close() |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
} |
@ -0,0 +1,72 @@ |
||||
package io.legado.app.service |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Build |
||||
import android.service.quicksettings.Tile |
||||
import android.service.quicksettings.TileService |
||||
import androidx.annotation.RequiresApi |
||||
import io.legado.app.constant.IntentAction |
||||
import io.legado.app.utils.startService |
||||
|
||||
/** |
||||
* web服务快捷开关 |
||||
*/ |
||||
@RequiresApi(Build.VERSION_CODES.N) |
||||
class WebTileService : TileService() { |
||||
|
||||
companion object { |
||||
|
||||
fun setState(context: Context, active: Boolean) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
||||
context.startService<WebTileService> { |
||||
action = if (active) { |
||||
IntentAction.start |
||||
} else { |
||||
IntentAction.stop |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
||||
try { |
||||
when (intent?.action) { |
||||
IntentAction.start -> { |
||||
qsTile.state = Tile.STATE_ACTIVE |
||||
qsTile.updateTile() |
||||
} |
||||
IntentAction.stop -> { |
||||
qsTile.state = Tile.STATE_INACTIVE |
||||
qsTile.updateTile() |
||||
} |
||||
} |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
return super.onStartCommand(intent, flags, startId) |
||||
} |
||||
|
||||
override fun onStartListening() { |
||||
super.onStartListening() |
||||
if (WebService.isRun) { |
||||
qsTile.state = Tile.STATE_ACTIVE |
||||
qsTile.updateTile() |
||||
} else { |
||||
qsTile.state = Tile.STATE_INACTIVE |
||||
qsTile.updateTile() |
||||
} |
||||
} |
||||
|
||||
override fun onClick() { |
||||
super.onClick() |
||||
if (WebService.isRun) { |
||||
WebService.stop(this) |
||||
} else { |
||||
WebService.start(this) |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,25 +1,23 @@ |
||||
package io.legado.app.service.help |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import io.legado.app.constant.IntentAction |
||||
import io.legado.app.service.DownloadService |
||||
import io.legado.app.utils.startService |
||||
|
||||
object Download { |
||||
|
||||
fun start(context: Context, downloadId: Long, fileName: String) { |
||||
Intent(context, DownloadService::class.java).let { |
||||
it.action = IntentAction.start |
||||
it.putExtra("downloadId", downloadId) |
||||
it.putExtra("fileName", fileName) |
||||
context.startService(it) |
||||
context.startService<DownloadService> { |
||||
action = IntentAction.start |
||||
putExtra("downloadId", downloadId) |
||||
putExtra("fileName", fileName) |
||||
} |
||||
} |
||||
|
||||
fun stop(context: Context) { |
||||
Intent(context, DownloadService::class.java).let { |
||||
it.action = IntentAction.stop |
||||
context.startService(it) |
||||
context.startService<DownloadService> { |
||||
action = IntentAction.stop |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,120 @@ |
||||
package io.legado.app.ui.book.group |
||||
|
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import androidx.fragment.app.FragmentManager |
||||
import androidx.fragment.app.viewModels |
||||
import io.legado.app.R |
||||
import io.legado.app.base.BaseDialogFragment |
||||
import io.legado.app.data.entities.BookGroup |
||||
import io.legado.app.databinding.DialogBookGroupEditBinding |
||||
import io.legado.app.lib.dialogs.alert |
||||
import io.legado.app.lib.theme.primaryColor |
||||
import io.legado.app.utils.* |
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding |
||||
import splitties.views.onClick |
||||
|
||||
class GroupEditDialog : BaseDialogFragment() { |
||||
|
||||
companion object { |
||||
|
||||
fun start(fragmentManager: FragmentManager, bookGroup: BookGroup? = null) { |
||||
GroupEditDialog().apply { |
||||
arguments = Bundle().apply { |
||||
putParcelable("group", bookGroup) |
||||
} |
||||
}.show(fragmentManager, "bookGroupEdit") |
||||
} |
||||
|
||||
} |
||||
|
||||
private val binding by viewBinding(DialogBookGroupEditBinding::bind) |
||||
private val viewModel by viewModels<GroupViewModel>() |
||||
private var bookGroup: BookGroup? = null |
||||
val selectImage = registerForActivityResult(SelectImageContract()) { |
||||
it?.second?.read(this) { name, bytes -> |
||||
var file = requireContext().externalFiles |
||||
file = FileUtils.createFileIfNotExist(file, "covers", name) |
||||
file.writeBytes(bytes) |
||||
binding.ivCover.load(file.absolutePath) |
||||
} |
||||
} |
||||
|
||||
override fun onStart() { |
||||
super.onStart() |
||||
val dm = requireActivity().windowSize |
||||
dialog?.window?.setLayout( |
||||
(dm.widthPixels * 0.9).toInt(), |
||||
ViewGroup.LayoutParams.WRAP_CONTENT |
||||
) |
||||
} |
||||
|
||||
override fun onCreateView( |
||||
inflater: LayoutInflater, |
||||
container: ViewGroup?, |
||||
savedInstanceState: Bundle? |
||||
): View? { |
||||
return inflater.inflate(R.layout.dialog_book_group_edit, container) |
||||
} |
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { |
||||
binding.toolBar.setBackgroundColor(primaryColor) |
||||
bookGroup = arguments?.getParcelable("group") |
||||
bookGroup?.let { |
||||
binding.tieGroupName.setText(it.groupName) |
||||
binding.ivCover.load(it.cover) |
||||
} ?: let { |
||||
binding.toolBar.title = getString(R.string.add_group) |
||||
binding.btnDelete.gone() |
||||
binding.ivCover.load() |
||||
} |
||||
binding.run { |
||||
ivCover.onClick { |
||||
selectImage.launch(null) |
||||
} |
||||
btnCancel.onClick { |
||||
dismiss() |
||||
} |
||||
btnOk.onClick { |
||||
val groupName = tieGroupName.text?.toString() |
||||
if (groupName.isNullOrEmpty()) { |
||||
toastOnUi("分组名称不能为空") |
||||
} else { |
||||
bookGroup?.let { |
||||
it.groupName = groupName |
||||
it.cover = binding.ivCover.path |
||||
viewModel.upGroup(it) { |
||||
dismiss() |
||||
} |
||||
} ?: let { |
||||
viewModel.addGroup(groupName, binding.ivCover.path) { |
||||
dismiss() |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
btnDelete.onClick { |
||||
deleteGroup { |
||||
bookGroup?.let { |
||||
viewModel.delGroup(it) { |
||||
dismiss() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun deleteGroup(ok: () -> Unit) { |
||||
alert(R.string.delete, R.string.sure_del) { |
||||
okButton { |
||||
ok.invoke() |
||||
} |
||||
noButton() |
||||
}.show() |
||||
} |
||||
|
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue