导出添加进度

pull/1352/head
gedoor 4 years ago
parent 35a1b51969
commit d03729f964
  1. 45
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  2. 29
      app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt
  3. 156
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  4. 46
      app/src/main/res/layout/item_download.xml

@ -6,7 +6,6 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
@ -208,6 +207,13 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
override fun observeLiveBus() { override fun observeLiveBus() {
viewModel.upAdapterLiveData.observe(this) {
adapter.getItems().forEachIndexed { index, book ->
if (book.bookUrl == it) {
adapter.notifyItemChanged(index, true)
}
}
}
observeEvent<String>(EventBus.UP_DOWNLOAD) { observeEvent<String>(EventBus.UP_DOWNLOAD) {
if (!CacheBook.isRun) { if (!CacheBook.isRun) {
menu?.findItem(R.id.menu_download)?.let { item -> menu?.findItem(R.id.menu_download)?.let { item ->
@ -263,25 +269,10 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
private fun startExport(path: String) { private fun startExport(path: String) {
if (exportPosition == -10) { if (exportPosition == -10) {
if (adapter.getItems().isNotEmpty()) { if (adapter.getItems().isNotEmpty()) {
Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE)
.show()
var exportSize = adapter.getItems().size
adapter.getItems().forEach { book -> adapter.getItems().forEach { book ->
when (AppConfig.exportType) { when (AppConfig.exportType) {
1 -> viewModel.exportEPUB(path, book) { 1 -> viewModel.exportEPUB(path, book)
exportSize-- else -> viewModel.export(path, book)
toastOnUi(it)
if (exportSize <= 0) {
binding.titleBar.snackbar(R.string.complete)
}
}
else -> viewModel.export(path, book) {
exportSize--
toastOnUi(it)
if (exportSize <= 0) {
binding.titleBar.snackbar(R.string.complete)
}
}
} }
} }
} else { } else {
@ -289,15 +280,9 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
} else if (exportPosition >= 0) { } else if (exportPosition >= 0) {
adapter.getItem(exportPosition)?.let { book -> adapter.getItem(exportPosition)?.let { book ->
Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE)
.show()
when (AppConfig.exportType) { when (AppConfig.exportType) {
1 -> viewModel.exportEPUB(path, book) { 1 -> viewModel.exportEPUB(path, book)
binding.titleBar.snackbar(it) else -> viewModel.export(path, book)
}
else -> viewModel.export(path, book) {
binding.titleBar.snackbar(it)
}
} }
} }
} }
@ -346,4 +331,12 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}.show() }.show()
} }
override fun exportProgress(bookUrl: String): Int? {
return viewModel.exportProgress[bookUrl]
}
override fun exportMsg(bookUrl: String): String? {
return viewModel.exportMsg[bookUrl]
}
} }

@ -3,12 +3,16 @@ package io.legado.app.ui.book.cache
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.databinding.ItemDownloadBinding import io.legado.app.databinding.ItemDownloadBinding
import io.legado.app.model.CacheBook import io.legado.app.model.CacheBook
import io.legado.app.utils.gone
import io.legado.app.utils.visible
class CacheAdapter(context: Context, private val callBack: CallBack) : class CacheAdapter(context: Context, private val callBack: CallBack) :
RecyclerAdapter<Book, ItemDownloadBinding>(context) { RecyclerAdapter<Book, ItemDownloadBinding>(context) {
@ -36,13 +40,13 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
tvDownload.text = tvDownload.text =
context.getString(R.string.download_count, cs.size, item.totalChapterNum) context.getString(R.string.download_count, cs.size, item.totalChapterNum)
} }
upDownloadIv(ivDownload, item)
} else { } else {
val cacheSize = cacheChapters[item.bookUrl]?.size ?: 0 val cacheSize = cacheChapters[item.bookUrl]?.size ?: 0
tvDownload.text = tvDownload.text =
context.getString(R.string.download_count, cacheSize, item.totalChapterNum) context.getString(R.string.download_count, cacheSize, item.totalChapterNum)
upDownloadIv(ivDownload, item)
} }
upDownloadIv(ivDownload, item)
upExportInfo(tvMsg, progressExport, item)
} }
} }
@ -79,7 +83,28 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
} }
} }
private fun upExportInfo(msgView: TextView, progressView: ProgressBar, book: Book) {
val msg = callBack.exportMsg(book.bookUrl)
if (msg != null) {
msgView.text = msg
msgView.visible()
progressView.gone()
return
}
msgView.gone()
val progress = callBack.exportProgress(book.bookUrl)
if (progress != null) {
progressView.max = book.totalChapterNum
progressView.progress = progress
progressView.visible()
return
}
progressView.gone()
}
interface CallBack { interface CallBack {
fun export(position: Int) fun export(position: Int)
fun exportProgress(bookUrl: String): Int?
fun exportMsg(bookUrl: String): String?
} }
} }

@ -5,6 +5,7 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
@ -19,7 +20,10 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor import io.legado.app.help.ContentProcessor
import io.legado.app.help.storage.AppWebDav import io.legado.app.help.storage.AppWebDav
import io.legado.app.model.NoStackTraceException
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import me.ag2s.epublib.domain.* import me.ag2s.epublib.domain.*
import me.ag2s.epublib.epub.EpubWriter import me.ag2s.epublib.epub.EpubWriter
import me.ag2s.epublib.util.ResourceUtil import me.ag2s.epublib.util.ResourceUtil
@ -28,10 +32,14 @@ import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.concurrent.ConcurrentHashMap
import javax.script.SimpleBindings import javax.script.SimpleBindings
class CacheViewModel(application: Application) : BaseViewModel(application) { class CacheViewModel(application: Application) : BaseViewModel(application) {
val upAdapterLiveData = MutableLiveData<String>()
val exportProgress = ConcurrentHashMap<String, Int>()
val exportMsg = ConcurrentHashMap<String, String>()
private fun getExportFileName(book: Book): String { private fun getExportFileName(book: Book): String {
val jsStr = AppConfig.bookExportFileName val jsStr = AppConfig.bookExportFileName
@ -44,44 +52,44 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
return AppConst.SCRIPT_ENGINE.eval(jsStr, bindings).toString() return AppConst.SCRIPT_ENGINE.eval(jsStr, bindings).toString()
} }
fun export(path: String, book: Book, finally: (msg: String) -> Unit) { fun export(path: String, book: Book) {
if (exportProgress.contains(book.bookUrl)) return
exportProgress[book.bookUrl] = 0
exportMsg.remove(book.bookUrl)
upAdapterLiveData.postValue(book.bookUrl)
execute { execute {
if (path.isContentScheme()) { if (path.isContentScheme()) {
val uri = Uri.parse(path) val uri = Uri.parse(path)
DocumentFile.fromTreeUri(context, uri)?.let { val doc = DocumentFile.fromTreeUri(context, uri)
export(it, book) ?: throw NoStackTraceException("获取导出文档失败")
} export(this, doc, book)
} else { } else {
export(FileUtils.createFolderIfNotExist(path), book) export(this, FileUtils.createFolderIfNotExist(path), book)
} }
}.onError { }.onError {
finally(it.localizedMessage ?: "ERROR") exportProgress.remove(book.bookUrl)
exportMsg[book.bookUrl] = it.localizedMessage ?: "ERROR"
upAdapterLiveData.postValue(book.bookUrl)
it.printOnDebug() it.printOnDebug()
}.onSuccess { }.onSuccess {
finally(context.getString(R.string.success)) exportProgress.remove(book.bookUrl)
exportMsg[book.bookUrl] = context.getString(R.string.export_success)
upAdapterLiveData.postValue(book.bookUrl)
} }
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
private suspend fun export(doc: DocumentFile, book: Book) { private suspend fun export(scope: CoroutineScope, doc: DocumentFile, book: Book) {
val filename = "${getExportFileName(book)}.txt" val filename = "${getExportFileName(book)}.txt"
DocumentUtils.delete(doc, filename) DocumentUtils.delete(doc, filename)
DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
?: throw NoStackTraceException("创建文档失败")
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
getAllContents(book) { getAllContents(scope, book) { text, srcList ->
bookOs.write(it.toByteArray(Charset.forName(AppConfig.exportCharset))) bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset)))
stringBuilder.append(it) stringBuilder.append(text)
} srcList?.forEach {
}
if (AppConfig.exportToWebDav) {
// 导出到webdav
val byteArray =
stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset))
AppWebDav.exportWebDav(byteArray, filename)
}
}
getSrcList(book).forEach {
val vFile = BookHelp.getImage(book, it.third) val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) { if (vFile.exists()) {
DocumentUtils.createFileIfNotExist( DocumentUtils.createFileIfNotExist(
@ -92,22 +100,24 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
} }
} }
private suspend fun export(file: File, book: Book) {
val filename = "${getExportFileName(book)}.txt"
val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath)
val stringBuilder = StringBuilder()
getAllContents(book) {
bookFile.appendText(it, Charset.forName(AppConfig.exportCharset))
stringBuilder.append(it)
} }
if (AppConfig.exportToWebDav) { if (AppConfig.exportToWebDav) {
// 导出到webdav
val byteArray = val byteArray =
stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset)) stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset))
AppWebDav.exportWebDav(byteArray, filename) // 导出到webdav AppWebDav.exportWebDav(byteArray, filename)
}
} }
getSrcList(book).forEach {
private suspend fun export(scope: CoroutineScope, file: File, book: Book) {
val filename = "${getExportFileName(book)}.txt"
val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath)
val stringBuilder = StringBuilder()
getAllContents(scope, book) { text, srcList ->
bookFile.appendText(text, Charset.forName(AppConfig.exportCharset))
stringBuilder.append(text)
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.third) val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) { if (vFile.exists()) {
FileUtils.createFileIfNotExist( FileUtils.createFileIfNotExist(
@ -120,24 +130,33 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
} }
} }
if (AppConfig.exportToWebDav) {
val byteArray =
stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset))
AppWebDav.exportWebDav(byteArray, filename) // 导出到webdav
}
}
private fun getAllContents(book: Book, append: (text: String) -> Unit) { private fun getAllContents(
scope: CoroutineScope,
book: Book,
append: (text: String, srcList: ArrayList<Triple<String, Int, String>>?) -> Unit
) {
val useReplace = AppConfig.exportUseReplace val useReplace = AppConfig.exportUseReplace
val contentProcessor = ContentProcessor.get(book.name, book.origin) val contentProcessor = ContentProcessor.get(book.name, book.origin)
append( val qy = "${book.name}\n${
"${book.name}\n${ context.getString(R.string.author_show, book.getRealAuthor())
context.getString(
R.string.author_show,
book.getRealAuthor()
)
}\n${ }\n${
context.getString( context.getString(
R.string.intro_show, R.string.intro_show,
"\n" + HtmlFormatter.format(book.getDisplayIntro()) "\n" + HtmlFormatter.format(book.getDisplayIntro())
) )
}" }"
) append(qy, null)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter -> appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
scope.ensureActive()
upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index
BookHelp.getContent(book, chapter).let { content -> BookHelp.getContent(book, chapter).let { content ->
val content1 = contentProcessor val content1 = contentProcessor
.getContent( .getContent(
@ -149,16 +168,8 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
chineseConvert = false, chineseConvert = false,
reSegment = false reSegment = false
).joinToString("\n") ).joinToString("\n")
append.invoke("\n\n$content1")
}
}
}
private fun getSrcList(book: Book): ArrayList<Triple<String, Int, String>> {
val srcList = arrayListOf<Triple<String, Int, String>>() val srcList = arrayListOf<Triple<String, Int, String>>()
appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter -> content?.split("\n")?.forEachIndexed { index, text ->
BookHelp.getContent(book, chapter)?.let { content ->
content.split("\n").forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text) val matcher = AppPattern.imgPattern.matcher(text)
while (matcher.find()) { while (matcher.find()) {
matcher.group(1)?.let { matcher.group(1)?.let {
@ -167,34 +178,43 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
} }
} }
append.invoke("\n\n$content1", srcList)
} }
} }
return srcList
} }
//////////////////Start EPUB //////////////////Start EPUB
/** /**
* 导出Epub * 导出Epub
*/ */
fun exportEPUB(path: String, book: Book, finally: (msg: String) -> Unit) { fun exportEPUB(path: String, book: Book) {
if (exportProgress.contains(book.bookUrl)) return
exportProgress[book.bookUrl] = 0
exportMsg.remove(book.bookUrl)
upAdapterLiveData.postValue(book.bookUrl)
execute { execute {
if (path.isContentScheme()) { if (path.isContentScheme()) {
val uri = Uri.parse(path) val uri = Uri.parse(path)
DocumentFile.fromTreeUri(context, uri)?.let { val doc = DocumentFile.fromTreeUri(context, uri)
exportEpub(it, book) ?: throw NoStackTraceException("获取导出文档失败")
} exportEpub(this, doc, book)
} else { } else {
exportEpub(FileUtils.createFolderIfNotExist(path), book) exportEpub(this, FileUtils.createFolderIfNotExist(path), book)
} }
}.onError { }.onError {
finally(it.localizedMessage ?: "ERROR") exportProgress.remove(book.bookUrl)
exportMsg[book.bookUrl] = it.localizedMessage ?: "ERROR"
upAdapterLiveData.postValue(book.bookUrl)
it.printOnDebug() it.printOnDebug()
}.onSuccess { }.onSuccess {
finally(context.getString(R.string.success)) exportProgress.remove(book.bookUrl)
exportMsg[book.bookUrl] = context.getString(R.string.export_success)
upAdapterLiveData.postValue(book.bookUrl)
} }
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
private fun exportEpub(doc: DocumentFile, book: Book) { private fun exportEpub(scope: CoroutineScope, doc: DocumentFile, book: Book) {
val filename = "${getExportFileName(book)}.epub" val filename = "${getExportFileName(book)}.epub"
DocumentUtils.delete(doc, filename) DocumentUtils.delete(doc, filename)
val epubBook = EpubBook() val epubBook = EpubBook()
@ -207,7 +227,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val contentModel = setAssets(doc, book, epubBook) val contentModel = setAssets(doc, book, epubBook)
//设置正文 //设置正文
setEpubContent(contentModel, book, epubBook) setEpubContent(scope, contentModel, book, epubBook)
DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc ->
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
EpubWriter().write(epubBook, bookOs) EpubWriter().write(epubBook, bookOs)
@ -217,7 +237,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private fun exportEpub(file: File, book: Book) { private fun exportEpub(scope: CoroutineScope, file: File, book: Book) {
val filename = "${getExportFileName(book)}.epub" val filename = "${getExportFileName(book)}.epub"
val epubBook = EpubBook() val epubBook = EpubBook()
epubBook.version = "2.0" epubBook.version = "2.0"
@ -231,7 +251,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val bookPath = FileUtils.getPath(file, filename) val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath) val bookFile = FileUtils.createFileWithReplace(bookPath)
//设置正文 //设置正文
setEpubContent(contentModel, book, epubBook) setEpubContent(scope, contentModel, book, epubBook)
EpubWriter().write(epubBook, FileOutputStream(bookFile)) EpubWriter().write(epubBook, FileOutputStream(bookFile))
} }
@ -386,11 +406,19 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
}) })
} }
private fun setEpubContent(contentModel: String, book: Book, epubBook: EpubBook) { private fun setEpubContent(
scope: CoroutineScope,
contentModel: String,
book: Book,
epubBook: EpubBook
) {
//正文 //正文
val useReplace = AppConfig.exportUseReplace val useReplace = AppConfig.exportUseReplace
val contentProcessor = ContentProcessor.get(book.name, book.origin) val contentProcessor = ContentProcessor.get(book.name, book.origin)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
scope.ensureActive()
upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index
BookHelp.getContent(book, chapter).let { content -> BookHelp.getContent(book, chapter).let { content ->
var content1 = fixPic(epubBook, book, content ?: "null", chapter) var content1 = fixPic(epubBook, book, content ?: "null", chapter)
content1 = contentProcessor content1 = contentProcessor

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"> android:padding="16dp">
<TextView <TextView
@ -12,10 +12,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:textColor="@color/primaryText" android:textColor="@color/primaryText"
tools:text="@string/name" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/iv_download" app:layout_constraintRight_toLeftOf="@+id/iv_download"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent" /> tools:text="@string/name" />
<TextView <TextView
android:id="@+id/tv_author" android:id="@+id/tv_author"
@ -23,10 +23,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:textColor="@color/primaryText" android:textColor="@color/primaryText"
tools:text="@string/author" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/iv_download" app:layout_constraintRight_toLeftOf="@+id/iv_download"
app:layout_constraintTop_toBottomOf="@+id/tv_name" app:layout_constraintTop_toBottomOf="@+id/tv_name"
app:layout_constraintLeft_toLeftOf="parent" /> tools:text="@string/author" />
<TextView <TextView
android:id="@+id/tv_download" android:id="@+id/tv_download"
@ -34,22 +34,22 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:textColor="@color/secondaryText" android:textColor="@color/secondaryText"
tools:text="@string/action_download" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/iv_download" app:layout_constraintRight_toLeftOf="@+id/iv_download"
app:layout_constraintTop_toBottomOf="@id/tv_author" app:layout_constraintTop_toBottomOf="@id/tv_author"
app:layout_constraintLeft_toLeftOf="parent" /> tools:text="@string/action_download" />
<ImageView <ImageView
android:id="@+id/iv_download" android:id="@+id/iv_download"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"
android:padding="6dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_play_24dp"
android:contentDescription="@string/start" android:contentDescription="@string/start"
app:layout_constraintTop_toTopOf="parent" android:padding="6dp"
app:layout_constraintBottom_toBottomOf="parent" android:src="@drawable/ic_play_24dp"
app:layout_constraintBottom_toBottomOf="@+id/tv_download"
app:layout_constraintRight_toLeftOf="@+id/tv_export" app:layout_constraintRight_toLeftOf="@+id/tv_export"
app:layout_constraintTop_toTopOf="@id/tv_name"
app:tint="@color/primaryText" /> app:tint="@color/primaryText" />
<TextView <TextView
@ -60,8 +60,24 @@
android:padding="10dp" android:padding="10dp"
android:text="@string/export" android:text="@string/export"
android:textColor="@color/primaryText" android:textColor="@color/primaryText"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="@+id/tv_download"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintRight_toRightOf="parent" /> app:layout_constraintTop_toTopOf="@id/tv_name" />
<TextView
android:id="@+id/tv_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/secondaryText"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/tv_download" />
<io.legado.app.lib.theme.view.ATEProgressBar
android:id="@+id/progress_export"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/tv_download" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save