commit
e6010b2b03
@ -0,0 +1,26 @@ |
|||||||
|
package io.legado.app.ui.book.toc |
||||||
|
|
||||||
|
import android.app.Activity.RESULT_OK |
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import androidx.activity.result.contract.ActivityResultContract |
||||||
|
|
||||||
|
class TocActivityResult : ActivityResultContract<String, Pair<Int, Int>?>() { |
||||||
|
|
||||||
|
override fun createIntent(context: Context, input: String?): Intent { |
||||||
|
return Intent(context, ChapterListActivity::class.java) |
||||||
|
.putExtra("bookUrl", input) |
||||||
|
} |
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): Pair<Int, Int>? { |
||||||
|
if (resultCode == RESULT_OK) { |
||||||
|
intent?.let { |
||||||
|
return Pair( |
||||||
|
it.getIntExtra("index", 0), |
||||||
|
it.getIntExtra("chapterPos", 0) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
} |
@ -1,195 +0,0 @@ |
|||||||
package io.legado.app.ui.config |
|
||||||
|
|
||||||
import android.app.Activity.RESULT_OK |
|
||||||
import android.content.Intent |
|
||||||
import android.net.Uri |
|
||||||
import androidx.documentfile.provider.DocumentFile |
|
||||||
import androidx.fragment.app.Fragment |
|
||||||
import io.legado.app.R |
|
||||||
import io.legado.app.constant.PreferKey |
|
||||||
import io.legado.app.help.AppConfig |
|
||||||
import io.legado.app.help.coroutine.Coroutine |
|
||||||
import io.legado.app.help.storage.Backup |
|
||||||
import io.legado.app.help.storage.BookWebDav |
|
||||||
import io.legado.app.help.storage.ImportOldData |
|
||||||
import io.legado.app.help.storage.Restore |
|
||||||
import io.legado.app.lib.permission.Permissions |
|
||||||
import io.legado.app.lib.permission.PermissionsCompat |
|
||||||
import io.legado.app.ui.filepicker.FilePicker |
|
||||||
import io.legado.app.utils.getPrefString |
|
||||||
import io.legado.app.utils.isContentScheme |
|
||||||
import io.legado.app.utils.longToast |
|
||||||
import io.legado.app.utils.toastOnUi |
|
||||||
import kotlinx.coroutines.Dispatchers.Main |
|
||||||
import splitties.init.appCtx |
|
||||||
|
|
||||||
object BackupRestoreUi { |
|
||||||
private const val selectFolderRequestCode = 21 |
|
||||||
private const val backupSelectRequestCode = 22 |
|
||||||
private const val restoreSelectRequestCode = 33 |
|
||||||
private const val oldDataRequestCode = 11 |
|
||||||
|
|
||||||
fun backup(fragment: Fragment) { |
|
||||||
val backupPath = AppConfig.backupPath |
|
||||||
if (backupPath.isNullOrEmpty()) { |
|
||||||
selectBackupFolder(fragment, backupSelectRequestCode) |
|
||||||
} else { |
|
||||||
if (backupPath.isContentScheme()) { |
|
||||||
val uri = Uri.parse(backupPath) |
|
||||||
val doc = DocumentFile.fromTreeUri(fragment.requireContext(), uri) |
|
||||||
if (doc?.canWrite() == true) { |
|
||||||
Coroutine.async { |
|
||||||
Backup.backup(fragment.requireContext(), backupPath) |
|
||||||
}.onSuccess { |
|
||||||
fragment.toastOnUi(R.string.backup_success) |
|
||||||
} |
|
||||||
} else { |
|
||||||
selectBackupFolder(fragment, backupSelectRequestCode) |
|
||||||
} |
|
||||||
} else { |
|
||||||
backupUsePermission(fragment, backupPath) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun backupUsePermission( |
|
||||||
fragment: Fragment, |
|
||||||
path: String |
|
||||||
) { |
|
||||||
PermissionsCompat.Builder(fragment) |
|
||||||
.addPermissions(*Permissions.Group.STORAGE) |
|
||||||
.rationale(R.string.tip_perm_request_storage) |
|
||||||
.onGranted { |
|
||||||
Coroutine.async { |
|
||||||
AppConfig.backupPath = path |
|
||||||
Backup.backup(fragment.requireContext(), path) |
|
||||||
}.onSuccess { |
|
||||||
fragment.toastOnUi(R.string.backup_success) |
|
||||||
} |
|
||||||
} |
|
||||||
.request() |
|
||||||
} |
|
||||||
|
|
||||||
fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) { |
|
||||||
FilePicker.selectFolder(fragment, requestCode) |
|
||||||
} |
|
||||||
|
|
||||||
fun restore(fragment: Fragment) { |
|
||||||
Coroutine.async(context = Main) { |
|
||||||
BookWebDav.showRestoreDialog(fragment.requireContext()) |
|
||||||
}.onError { |
|
||||||
fragment.longToast("WebDavError:${it.localizedMessage}\n将从本地备份恢复。") |
|
||||||
val backupPath = fragment.getPrefString(PreferKey.backupPath) |
|
||||||
if (backupPath?.isNotEmpty() == true) { |
|
||||||
if (backupPath.isContentScheme()) { |
|
||||||
val uri = Uri.parse(backupPath) |
|
||||||
val doc = DocumentFile.fromTreeUri(fragment.requireContext(), uri) |
|
||||||
if (doc?.canWrite() == true) { |
|
||||||
Restore.restore(fragment.requireContext(), backupPath) |
|
||||||
} else { |
|
||||||
selectBackupFolder(fragment, restoreSelectRequestCode) |
|
||||||
} |
|
||||||
} else { |
|
||||||
restoreUsePermission(fragment, backupPath) |
|
||||||
} |
|
||||||
} else { |
|
||||||
selectBackupFolder(fragment, restoreSelectRequestCode) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun restoreByFolder(fragment: Fragment) { |
|
||||||
selectBackupFolder(fragment, restoreSelectRequestCode) |
|
||||||
} |
|
||||||
|
|
||||||
private fun restoreUsePermission(fragment: Fragment, path: String) { |
|
||||||
PermissionsCompat.Builder(fragment) |
|
||||||
.addPermissions(*Permissions.Group.STORAGE) |
|
||||||
.rationale(R.string.tip_perm_request_storage) |
|
||||||
.onGranted { |
|
||||||
Coroutine.async { |
|
||||||
AppConfig.backupPath = path |
|
||||||
Restore.restoreDatabase(path) |
|
||||||
Restore.restoreConfig(path) |
|
||||||
} |
|
||||||
} |
|
||||||
.request() |
|
||||||
} |
|
||||||
|
|
||||||
fun importOldData(fragment: Fragment) { |
|
||||||
FilePicker.selectFolder(fragment, oldDataRequestCode) |
|
||||||
} |
|
||||||
|
|
||||||
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
|
||||||
when (requestCode) { |
|
||||||
backupSelectRequestCode -> if (resultCode == RESULT_OK) { |
|
||||||
data?.data?.let { uri -> |
|
||||||
if (uri.isContentScheme()) { |
|
||||||
appCtx.contentResolver.takePersistableUriPermission( |
|
||||||
uri, |
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||||
) |
|
||||||
AppConfig.backupPath = uri.toString() |
|
||||||
Coroutine.async { |
|
||||||
Backup.backup(appCtx, uri.toString()) |
|
||||||
}.onSuccess { |
|
||||||
appCtx.toastOnUi(R.string.backup_success) |
|
||||||
} |
|
||||||
} else { |
|
||||||
uri.path?.let { path -> |
|
||||||
AppConfig.backupPath = path |
|
||||||
Coroutine.async { |
|
||||||
Backup.backup(appCtx, path) |
|
||||||
}.onSuccess { |
|
||||||
appCtx.toastOnUi(R.string.backup_success) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
restoreSelectRequestCode -> if (resultCode == RESULT_OK) { |
|
||||||
data?.data?.let { uri -> |
|
||||||
if (uri.isContentScheme()) { |
|
||||||
appCtx.contentResolver.takePersistableUriPermission( |
|
||||||
uri, |
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||||
) |
|
||||||
AppConfig.backupPath = uri.toString() |
|
||||||
Coroutine.async { |
|
||||||
Restore.restore(appCtx, uri.toString()) |
|
||||||
} |
|
||||||
} else { |
|
||||||
uri.path?.let { path -> |
|
||||||
AppConfig.backupPath = path |
|
||||||
Coroutine.async { |
|
||||||
Restore.restore(appCtx, path) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
selectFolderRequestCode -> if (resultCode == RESULT_OK) { |
|
||||||
data?.data?.let { uri -> |
|
||||||
if (uri.isContentScheme()) { |
|
||||||
appCtx.contentResolver.takePersistableUriPermission( |
|
||||||
uri, |
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||||
) |
|
||||||
AppConfig.backupPath = uri.toString() |
|
||||||
} else { |
|
||||||
AppConfig.backupPath = uri.path |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
oldDataRequestCode -> if (resultCode == RESULT_OK) { |
|
||||||
data?.data?.let { uri -> |
|
||||||
ImportOldData.importUri(appCtx, uri) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,43 @@ |
|||||||
|
package io.legado.app.ui.document |
||||||
|
|
||||||
|
import android.app.Activity.RESULT_OK |
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.net.Uri |
||||||
|
import androidx.activity.result.contract.ActivityResultContract |
||||||
|
|
||||||
|
@Suppress("unused") |
||||||
|
class FilePicker : ActivityResultContract<FilePickerParam, Uri?>() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
const val DIRECTORY = 0 |
||||||
|
const val FILE = 1 |
||||||
|
} |
||||||
|
|
||||||
|
override fun createIntent(context: Context, input: FilePickerParam?): Intent { |
||||||
|
val intent = Intent(context, FilePickerActivity::class.java) |
||||||
|
input?.let { |
||||||
|
intent.putExtra("mode", it.mode) |
||||||
|
intent.putExtra("title", it.title) |
||||||
|
intent.putExtra("allowExtensions", it.allowExtensions) |
||||||
|
intent.putExtra("otherActions", it.otherActions) |
||||||
|
} |
||||||
|
return intent |
||||||
|
} |
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): Uri? { |
||||||
|
if (resultCode == RESULT_OK) { |
||||||
|
return intent?.data |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("ArrayInDataClass") |
||||||
|
data class FilePickerParam( |
||||||
|
var mode: Int = 0, |
||||||
|
var title: String? = null, |
||||||
|
var allowExtensions: Array<String> = arrayOf(), |
||||||
|
var otherActions: Array<String>? = null, |
||||||
|
) |
@ -0,0 +1,137 @@ |
|||||||
|
package io.legado.app.ui.document |
||||||
|
|
||||||
|
import android.content.Intent |
||||||
|
import android.net.Uri |
||||||
|
import android.os.Build |
||||||
|
import android.os.Bundle |
||||||
|
import android.webkit.MimeTypeMap |
||||||
|
import androidx.activity.result.contract.ActivityResultContracts |
||||||
|
import io.legado.app.R |
||||||
|
import io.legado.app.base.BaseActivity |
||||||
|
import io.legado.app.constant.Theme |
||||||
|
import io.legado.app.databinding.ActivityTranslucenceBinding |
||||||
|
import io.legado.app.lib.dialogs.alert |
||||||
|
import io.legado.app.lib.permission.Permissions |
||||||
|
import io.legado.app.lib.permission.PermissionsCompat |
||||||
|
import io.legado.app.utils.isContentScheme |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
class FilePickerActivity : |
||||||
|
BaseActivity<ActivityTranslucenceBinding>( |
||||||
|
theme = Theme.Transparent |
||||||
|
), FilePickerDialog.CallBack { |
||||||
|
|
||||||
|
private val selectDocTree = |
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { |
||||||
|
onResult(Intent().setData(it)) |
||||||
|
} |
||||||
|
|
||||||
|
private val selectDoc = registerForActivityResult(ActivityResultContracts.OpenDocument()) { |
||||||
|
onResult(Intent().setData(it)) |
||||||
|
} |
||||||
|
|
||||||
|
override fun getViewBinding(): ActivityTranslucenceBinding { |
||||||
|
return ActivityTranslucenceBinding.inflate(layoutInflater) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||||
|
val mode = intent.getIntExtra("mode", 0) |
||||||
|
val allowExtensions = intent.getStringArrayExtra("allowExtensions") |
||||||
|
val selectList = if (mode == FilePicker.DIRECTORY) { |
||||||
|
arrayListOf(getString(R.string.sys_folder_picker)) |
||||||
|
} else { |
||||||
|
arrayListOf(getString(R.string.sys_file_picker)) |
||||||
|
} |
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { |
||||||
|
selectList.add(getString(R.string.app_folder_picker)) |
||||||
|
} |
||||||
|
intent.getStringArrayListExtra("otherActions")?.let { |
||||||
|
selectList.addAll(it) |
||||||
|
} |
||||||
|
val title = intent.getStringExtra("title") ?: let { |
||||||
|
if (mode == FilePicker.DIRECTORY) { |
||||||
|
return@let getString(R.string.select_folder) |
||||||
|
} else { |
||||||
|
return@let getString(R.string.select_file) |
||||||
|
} |
||||||
|
} |
||||||
|
alert(title) { |
||||||
|
items(selectList) { _, index -> |
||||||
|
when (index) { |
||||||
|
0 -> if (mode == FilePicker.DIRECTORY) { |
||||||
|
selectDocTree.launch(null) |
||||||
|
} else { |
||||||
|
selectDoc.launch(typesOfExtensions(allowExtensions)) |
||||||
|
} |
||||||
|
1 -> if (mode == FilePicker.DIRECTORY) { |
||||||
|
checkPermissions { |
||||||
|
FilePickerDialog.show( |
||||||
|
supportFragmentManager, |
||||||
|
mode = FilePicker.DIRECTORY |
||||||
|
) |
||||||
|
} |
||||||
|
} else { |
||||||
|
checkPermissions { |
||||||
|
FilePickerDialog.show( |
||||||
|
supportFragmentManager, |
||||||
|
mode = FilePicker.FILE, |
||||||
|
allowExtensions = allowExtensions |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
else -> { |
||||||
|
val path = selectList[index] |
||||||
|
val uri = if (path.isContentScheme()) { |
||||||
|
Uri.fromFile(File(path)) |
||||||
|
} else { |
||||||
|
Uri.parse(path) |
||||||
|
} |
||||||
|
onResult(Intent().setData(uri)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
onCancelled { |
||||||
|
finish() |
||||||
|
} |
||||||
|
}.show() |
||||||
|
} |
||||||
|
|
||||||
|
private fun checkPermissions(success: (() -> Unit)? = null) { |
||||||
|
PermissionsCompat.Builder(this) |
||||||
|
.addPermissions(*Permissions.Group.STORAGE) |
||||||
|
.rationale(R.string.tip_perm_request_storage) |
||||||
|
.onGranted { |
||||||
|
success?.invoke() |
||||||
|
} |
||||||
|
.request() |
||||||
|
} |
||||||
|
|
||||||
|
private fun typesOfExtensions(allowExtensions: Array<String>?): Array<String> { |
||||||
|
val types = hashSetOf<String>() |
||||||
|
if (allowExtensions.isNullOrEmpty()) { |
||||||
|
types.add("*/*") |
||||||
|
} else { |
||||||
|
allowExtensions.forEach { |
||||||
|
when (it) { |
||||||
|
"*" -> types.add("*/*") |
||||||
|
"txt", "xml" -> types.add("text/*") |
||||||
|
else -> { |
||||||
|
val mime = MimeTypeMap.getSingleton() |
||||||
|
.getMimeTypeFromExtension("json") |
||||||
|
?: "application/octet-stream" |
||||||
|
types.add(mime) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return types.toTypedArray() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onResult(data: Intent) { |
||||||
|
if (data.data != null) { |
||||||
|
setResult(RESULT_OK, data) |
||||||
|
} |
||||||
|
finish() |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
package io.legado.app.ui.filepicker.entity |
package io.legado.app.ui.document.entity |
||||||
|
|
||||||
import android.graphics.drawable.Drawable |
import android.graphics.drawable.Drawable |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package io.legado.app.ui.filepicker.entity |
package io.legado.app.ui.document.entity |
||||||
|
|
||||||
import java.io.Serializable |
import java.io.Serializable |
||||||
import java.lang.reflect.Field |
import java.lang.reflect.Field |
@ -1,4 +1,4 @@ |
|||||||
package io.legado.app.ui.filepicker.utils |
package io.legado.app.ui.document.utils |
||||||
|
|
||||||
import android.content.res.Resources |
import android.content.res.Resources |
||||||
import android.graphics.Bitmap |
import android.graphics.Bitmap |
@ -1,4 +1,4 @@ |
|||||||
package io.legado.app.ui.filepicker.utils; |
package io.legado.app.ui.document.utils; |
||||||
|
|
||||||
/** |
/** |
||||||
* Generated by https://github.com/gzu-liyujiang/Image2ByteVar
|
* Generated by https://github.com/gzu-liyujiang/Image2ByteVar
|
@ -1,284 +0,0 @@ |
|||||||
package io.legado.app.ui.filepicker |
|
||||||
|
|
||||||
import android.content.Intent |
|
||||||
import android.os.Build |
|
||||||
import androidx.appcompat.app.AppCompatActivity |
|
||||||
import androidx.fragment.app.Fragment |
|
||||||
import io.legado.app.R |
|
||||||
import io.legado.app.lib.dialogs.alert |
|
||||||
import io.legado.app.lib.permission.Permissions |
|
||||||
import io.legado.app.lib.permission.PermissionsCompat |
|
||||||
|
|
||||||
@Suppress("unused") |
|
||||||
object FilePicker { |
|
||||||
|
|
||||||
fun selectFolder( |
|
||||||
activity: AppCompatActivity, |
|
||||||
requestCode: Int, |
|
||||||
title: String = activity.getString(R.string.select_folder), |
|
||||||
otherActions: List<String>? = null, |
|
||||||
otherFun: ((action: String) -> Unit)? = null |
|
||||||
) { |
|
||||||
val selectList = arrayListOf(activity.getString(R.string.sys_folder_picker)) |
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { |
|
||||||
selectList.add(activity.getString(R.string.app_folder_picker)) |
|
||||||
} |
|
||||||
otherActions?.let { |
|
||||||
selectList.addAll(otherActions) |
|
||||||
} |
|
||||||
activity.alert(title = title) { |
|
||||||
items(selectList) { _, index -> |
|
||||||
when (index) { |
|
||||||
0 -> { |
|
||||||
kotlin.runCatching { |
|
||||||
val intent = createSelectDirIntent() |
|
||||||
activity.startActivityForResult(intent, requestCode) |
|
||||||
}.onFailure { |
|
||||||
checkPermissions(activity) { |
|
||||||
FilePickerDialog.show( |
|
||||||
activity.supportFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.DIRECTORY |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val selectText = selectList[index] |
|
||||||
if (selectText == activity.getString(R.string.app_folder_picker)) { |
|
||||||
checkPermissions(activity) { |
|
||||||
FilePickerDialog.show( |
|
||||||
activity.supportFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.DIRECTORY |
|
||||||
) |
|
||||||
} |
|
||||||
} else { |
|
||||||
otherFun?.invoke(selectText) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}.show() |
|
||||||
} |
|
||||||
|
|
||||||
fun selectFolder( |
|
||||||
fragment: Fragment, |
|
||||||
requestCode: Int, |
|
||||||
title: String = fragment.getString(R.string.select_folder), |
|
||||||
otherActions: List<String>? = null, |
|
||||||
otherFun: ((action: String) -> Unit)? = null |
|
||||||
) { |
|
||||||
val selectList = arrayListOf(fragment.getString(R.string.sys_folder_picker)) |
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { |
|
||||||
selectList.add(fragment.getString(R.string.app_folder_picker)) |
|
||||||
} |
|
||||||
otherActions?.let { |
|
||||||
selectList.addAll(otherActions) |
|
||||||
} |
|
||||||
fragment.alert(title = title) { |
|
||||||
items(selectList) { _, index -> |
|
||||||
when (index) { |
|
||||||
0 -> { |
|
||||||
kotlin.runCatching { |
|
||||||
val intent = createSelectDirIntent() |
|
||||||
fragment.startActivityForResult(intent, requestCode) |
|
||||||
}.onFailure { |
|
||||||
checkPermissions(fragment) { |
|
||||||
FilePickerDialog.show( |
|
||||||
fragment.childFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.DIRECTORY |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val selectText = selectList[index] |
|
||||||
if (selectText == fragment.getString(R.string.app_folder_picker)) { |
|
||||||
checkPermissions(fragment) { |
|
||||||
FilePickerDialog.show( |
|
||||||
fragment.childFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.DIRECTORY |
|
||||||
) |
|
||||||
} |
|
||||||
} else { |
|
||||||
otherFun?.invoke(selectText) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}.show() |
|
||||||
} |
|
||||||
|
|
||||||
fun selectFile( |
|
||||||
activity: AppCompatActivity, |
|
||||||
requestCode: Int, |
|
||||||
title: String = activity.getString(R.string.select_file), |
|
||||||
allowExtensions: Array<String> = arrayOf(), |
|
||||||
otherActions: List<String>? = null, |
|
||||||
otherFun: ((action: String) -> Unit)? = null |
|
||||||
) { |
|
||||||
val selectList = arrayListOf(activity.getString(R.string.sys_file_picker)) |
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { |
|
||||||
selectList.add(activity.getString(R.string.app_file_picker)) |
|
||||||
} |
|
||||||
otherActions?.let { |
|
||||||
selectList.addAll(otherActions) |
|
||||||
} |
|
||||||
activity.alert(title = title) { |
|
||||||
items(selectList) { _, index -> |
|
||||||
when (index) { |
|
||||||
0 -> { |
|
||||||
kotlin.runCatching { |
|
||||||
val intent = createSelectFileIntent() |
|
||||||
intent.putExtra( |
|
||||||
Intent.EXTRA_MIME_TYPES, |
|
||||||
typesOfExtensions(allowExtensions) |
|
||||||
) |
|
||||||
activity.startActivityForResult(intent, requestCode) |
|
||||||
}.onFailure { |
|
||||||
checkPermissions(activity) { |
|
||||||
FilePickerDialog.show( |
|
||||||
activity.supportFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.FILE, |
|
||||||
allowExtensions = allowExtensions |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val selectText = selectList[index] |
|
||||||
if (selectText == activity.getString(R.string.app_file_picker)) { |
|
||||||
checkPermissions(activity) { |
|
||||||
FilePickerDialog.show( |
|
||||||
activity.supportFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.FILE, |
|
||||||
allowExtensions = allowExtensions |
|
||||||
) |
|
||||||
} |
|
||||||
} else { |
|
||||||
otherFun?.invoke(selectText) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}.show() |
|
||||||
} |
|
||||||
|
|
||||||
fun selectFile( |
|
||||||
fragment: Fragment, |
|
||||||
requestCode: Int, |
|
||||||
title: String = fragment.getString(R.string.select_file), |
|
||||||
allowExtensions: Array<String> = arrayOf(), |
|
||||||
otherActions: List<String>? = null, |
|
||||||
otherFun: ((action: String) -> Unit)? = null |
|
||||||
) { |
|
||||||
val selectList = arrayListOf(fragment.getString(R.string.sys_file_picker)) |
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { |
|
||||||
selectList.add(fragment.getString(R.string.app_file_picker)) |
|
||||||
} |
|
||||||
otherActions?.let { |
|
||||||
selectList.addAll(otherActions) |
|
||||||
} |
|
||||||
fragment.alert(title = title) { |
|
||||||
items(selectList) { _, index -> |
|
||||||
when (index) { |
|
||||||
0 -> { |
|
||||||
kotlin.runCatching { |
|
||||||
val intent = createSelectFileIntent() |
|
||||||
intent.putExtra( |
|
||||||
Intent.EXTRA_MIME_TYPES, |
|
||||||
typesOfExtensions(allowExtensions) |
|
||||||
) |
|
||||||
fragment.startActivityForResult(intent, requestCode) |
|
||||||
}.onFailure { |
|
||||||
checkPermissions(fragment) { |
|
||||||
FilePickerDialog.show( |
|
||||||
fragment.childFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.FILE, |
|
||||||
allowExtensions = allowExtensions |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val selectText = selectList[index] |
|
||||||
if (selectText == fragment.getString(R.string.app_file_picker)) { |
|
||||||
checkPermissions(fragment) { |
|
||||||
FilePickerDialog.show( |
|
||||||
fragment.childFragmentManager, |
|
||||||
requestCode, |
|
||||||
mode = FilePickerDialog.FILE, |
|
||||||
allowExtensions = allowExtensions |
|
||||||
) |
|
||||||
} |
|
||||||
} else { |
|
||||||
otherFun?.invoke(selectText) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}.show() |
|
||||||
} |
|
||||||
|
|
||||||
private fun createSelectFileIntent(): Intent { |
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT) |
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) |
|
||||||
intent.type = "*/*" |
|
||||||
return intent |
|
||||||
} |
|
||||||
|
|
||||||
private fun createSelectDirIntent(): Intent { |
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) |
|
||||||
return intent |
|
||||||
} |
|
||||||
|
|
||||||
private fun checkPermissions(fragment: Fragment, success: (() -> Unit)? = null) { |
|
||||||
PermissionsCompat.Builder(fragment) |
|
||||||
.addPermissions(*Permissions.Group.STORAGE) |
|
||||||
.rationale(R.string.tip_perm_request_storage) |
|
||||||
.onGranted { |
|
||||||
success?.invoke() |
|
||||||
} |
|
||||||
.request() |
|
||||||
} |
|
||||||
|
|
||||||
private fun checkPermissions(activity: AppCompatActivity, success: (() -> Unit)? = null) { |
|
||||||
PermissionsCompat.Builder(activity) |
|
||||||
.addPermissions(*Permissions.Group.STORAGE) |
|
||||||
.rationale(R.string.tip_perm_request_storage) |
|
||||||
.onGranted { |
|
||||||
success?.invoke() |
|
||||||
} |
|
||||||
.request() |
|
||||||
} |
|
||||||
|
|
||||||
private fun typesOfExtensions(allowExtensions: Array<String>): Array<String> { |
|
||||||
val types = hashSetOf<String>() |
|
||||||
if (allowExtensions.isNullOrEmpty()) { |
|
||||||
types.add("*/*") |
|
||||||
} else { |
|
||||||
allowExtensions.forEach { |
|
||||||
when (it) { |
|
||||||
"*" -> types.add("*/*") |
|
||||||
"txt", "xml" -> types.add("text/*") |
|
||||||
else -> types.add("application/$it") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return types.toTypedArray() |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,23 @@ |
|||||||
|
package io.legado.app.ui.qrcode |
||||||
|
|
||||||
|
import android.app.Activity.RESULT_OK |
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import androidx.activity.result.contract.ActivityResultContract |
||||||
|
|
||||||
|
class QrCodeResult : ActivityResultContract<Unit, String?>() { |
||||||
|
|
||||||
|
override fun createIntent(context: Context, input: Unit?): Intent { |
||||||
|
return Intent(context, QrCodeActivity::class.java) |
||||||
|
} |
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): String? { |
||||||
|
if (resultCode == RESULT_OK) { |
||||||
|
intent?.getStringExtra("result")?.let { |
||||||
|
return it |
||||||
|
} |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,323 +0,0 @@ |
|||||||
package me.ag2s.epublib.domain; |
|
||||||
|
|
||||||
|
|
||||||
import java.io.Serializable; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
/** |
|
||||||
* Representation of a Book. |
|
||||||
* <p> |
|
||||||
* All resources of a Book (html, css, xml, fonts, images) are represented |
|
||||||
* as Resources. See getResources() for access to these.<br/> |
|
||||||
* A Book as 3 indexes into these Resources, as per the epub specification.<br/> |
|
||||||
* <dl> |
|
||||||
* <dt>Spine</dt> |
|
||||||
* <dd>these are the Resources to be shown when a user reads the book from |
|
||||||
* start to finish.</dd> |
|
||||||
* <dt>Table of Contents<dt> |
|
||||||
* <dd>The table of contents. Table of Contents references may be in a |
|
||||||
* different order and contain different Resources than the spine, and often do. |
|
||||||
* <dt>Guide</dt> |
|
||||||
* <dd>The Guide has references to a set of special Resources like the |
|
||||||
* cover page, the Glossary, the copyright page, etc. |
|
||||||
* </dl> |
|
||||||
* <p/> |
|
||||||
* The complication is that these 3 indexes may and usually do point to |
|
||||||
* different pages. |
|
||||||
* A chapter may be split up in 2 pieces to fit it in to memory. Then the |
|
||||||
* spine will contain both pieces, but the Table of Contents only the first. |
|
||||||
* <p> |
|
||||||
* The Content page may be in the Table of Contents, the Guide, but not |
|
||||||
* in the Spine. |
|
||||||
* Etc. |
|
||||||
* <p/> |
|
||||||
* <p> |
|
||||||
* Please see the illustration at: doc/schema.svg |
|
||||||
* |
|
||||||
* @author paul |
|
||||||
* @author jake |
|
||||||
*/ |
|
||||||
public class Book implements Serializable { |
|
||||||
|
|
||||||
private static final long serialVersionUID = 2068355170895770100L; |
|
||||||
|
|
||||||
private Resources resources = new Resources(); |
|
||||||
private Metadata metadata = new Metadata(); |
|
||||||
private Spine spine = new Spine(); |
|
||||||
private TableOfContents tableOfContents = new TableOfContents(); |
|
||||||
private final Guide guide = new Guide(); |
|
||||||
private Resource opfResource; |
|
||||||
private Resource ncxResource; |
|
||||||
private Resource coverImage; |
|
||||||
|
|
||||||
|
|
||||||
private String version="2.0"; |
|
||||||
|
|
||||||
public String getVersion() { |
|
||||||
return version; |
|
||||||
} |
|
||||||
|
|
||||||
public void setVersion(String version) { |
|
||||||
this.version = version; |
|
||||||
} |
|
||||||
|
|
||||||
public boolean isEpub3() { |
|
||||||
return this.version.startsWith("3."); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("UnusedReturnValue") |
|
||||||
public TOCReference addSection( |
|
||||||
TOCReference parentSection, String sectionTitle, Resource resource) { |
|
||||||
return addSection(parentSection, sectionTitle, resource, null); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds the resource to the table of contents of the book as a child |
|
||||||
* section of the given parentSection |
|
||||||
* |
|
||||||
* @param parentSection parentSection |
|
||||||
* @param sectionTitle sectionTitle |
|
||||||
* @param resource resource |
|
||||||
* @param fragmentId fragmentId |
|
||||||
* @return The table of contents |
|
||||||
*/ |
|
||||||
public TOCReference addSection( |
|
||||||
TOCReference parentSection, String sectionTitle, Resource resource, |
|
||||||
String fragmentId) { |
|
||||||
getResources().add(resource); |
|
||||||
if (spine.findFirstResourceById(resource.getId()) < 0) { |
|
||||||
spine.addSpineReference(new SpineReference(resource)); |
|
||||||
} |
|
||||||
return parentSection.addChildSection( |
|
||||||
new TOCReference(sectionTitle, resource, fragmentId)); |
|
||||||
} |
|
||||||
|
|
||||||
public TOCReference addSection(String title, Resource resource) { |
|
||||||
return addSection(title, resource, null); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a resource to the book's set of resources, table of contents and |
|
||||||
* if there is no resource with the id in the spine also adds it to the spine. |
|
||||||
* |
|
||||||
* @param title title |
|
||||||
* @param resource resource |
|
||||||
* @param fragmentId fragmentId |
|
||||||
* @return The table of contents |
|
||||||
*/ |
|
||||||
public TOCReference addSection( |
|
||||||
String title, Resource resource, String fragmentId) { |
|
||||||
getResources().add(resource); |
|
||||||
TOCReference tocReference = tableOfContents |
|
||||||
.addTOCReference(new TOCReference(title, resource, fragmentId)); |
|
||||||
if (spine.findFirstResourceById(resource.getId()) < 0) { |
|
||||||
spine.addSpineReference(new SpineReference(resource)); |
|
||||||
} |
|
||||||
return tocReference; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
public void generateSpineFromTableOfContents() { |
|
||||||
Spine spine = new Spine(tableOfContents); |
|
||||||
|
|
||||||
// in case the tocResource was already found and assigned
|
|
||||||
spine.setTocResource(this.spine.getTocResource()); |
|
||||||
|
|
||||||
this.spine = spine; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The Book's metadata (titles, authors, etc) |
|
||||||
* |
|
||||||
* @return The Book's metadata (titles, authors, etc) |
|
||||||
*/ |
|
||||||
public Metadata getMetadata() { |
|
||||||
return metadata; |
|
||||||
} |
|
||||||
|
|
||||||
public void setMetadata(Metadata metadata) { |
|
||||||
this.metadata = metadata; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public void setResources(Resources resources) { |
|
||||||
this.resources = resources; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
public Resource addResource(Resource resource) { |
|
||||||
return resources.add(resource); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The collection of all images, chapters, sections, xhtml files, |
|
||||||
* stylesheets, etc that make up the book. |
|
||||||
* |
|
||||||
* @return The collection of all images, chapters, sections, xhtml files, |
|
||||||
* stylesheets, etc that make up the book. |
|
||||||
*/ |
|
||||||
public Resources getResources() { |
|
||||||
return resources; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* The sections of the book that should be shown if a user reads the book |
|
||||||
* from start to finish. |
|
||||||
* |
|
||||||
* @return The Spine |
|
||||||
*/ |
|
||||||
public Spine getSpine() { |
|
||||||
return spine; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public void setSpine(Spine spine) { |
|
||||||
this.spine = spine; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* The Table of Contents of the book. |
|
||||||
* |
|
||||||
* @return The Table of Contents of the book. |
|
||||||
*/ |
|
||||||
public TableOfContents getTableOfContents() { |
|
||||||
return tableOfContents; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public void setTableOfContents(TableOfContents tableOfContents) { |
|
||||||
this.tableOfContents = tableOfContents; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The book's cover page as a Resource. |
|
||||||
* An XHTML document containing a link to the cover image. |
|
||||||
* |
|
||||||
* @return The book's cover page as a Resource |
|
||||||
*/ |
|
||||||
public Resource getCoverPage() { |
|
||||||
Resource coverPage = guide.getCoverPage(); |
|
||||||
if (coverPage == null) { |
|
||||||
coverPage = spine.getResource(0); |
|
||||||
} |
|
||||||
return coverPage; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public void setCoverPage(Resource coverPage) { |
|
||||||
if (coverPage == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (resources.notContainsByHref(coverPage.getHref())) { |
|
||||||
resources.add(coverPage); |
|
||||||
} |
|
||||||
guide.setCoverPage(coverPage); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Gets the first non-blank title from the book's metadata. |
|
||||||
* |
|
||||||
* @return the first non-blank title from the book's metadata. |
|
||||||
*/ |
|
||||||
public String getTitle() { |
|
||||||
return getMetadata().getFirstTitle(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* The book's cover image. |
|
||||||
* |
|
||||||
* @return The book's cover image. |
|
||||||
*/ |
|
||||||
public Resource getCoverImage() { |
|
||||||
return coverImage; |
|
||||||
} |
|
||||||
|
|
||||||
public void setCoverImage(Resource coverImage) { |
|
||||||
if (coverImage == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (resources.notContainsByHref(coverImage.getHref())) { |
|
||||||
resources.add(coverImage); |
|
||||||
} |
|
||||||
this.coverImage = coverImage; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The guide; contains references to special sections of the book like |
|
||||||
* colophon, glossary, etc. |
|
||||||
* |
|
||||||
* @return The guide; contains references to special sections of the book |
|
||||||
* like colophon, glossary, etc. |
|
||||||
*/ |
|
||||||
public Guide getGuide() { |
|
||||||
return guide; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* All Resources of the Book that can be reached via the Spine, the |
|
||||||
* TableOfContents or the Guide. |
|
||||||
* <p/> |
|
||||||
* Consists of a list of "reachable" resources: |
|
||||||
* <ul> |
|
||||||
* <li>The coverpage</li> |
|
||||||
* <li>The resources of the Spine that are not already in the result</li> |
|
||||||
* <li>The resources of the Table of Contents that are not already in the |
|
||||||
* result</li> |
|
||||||
* <li>The resources of the Guide that are not already in the result</li> |
|
||||||
* </ul> |
|
||||||
* To get all html files that make up the epub file use |
|
||||||
* {@link #getResources()} |
|
||||||
* |
|
||||||
* @return All Resources of the Book that can be reached via the Spine, |
|
||||||
* the TableOfContents or the Guide. |
|
||||||
*/ |
|
||||||
public List<Resource> getContents() { |
|
||||||
Map<String, Resource> result = new LinkedHashMap<>(); |
|
||||||
addToContentsResult(getCoverPage(), result); |
|
||||||
|
|
||||||
for (SpineReference spineReference : getSpine().getSpineReferences()) { |
|
||||||
addToContentsResult(spineReference.getResource(), result); |
|
||||||
} |
|
||||||
|
|
||||||
for (Resource resource : getTableOfContents().getAllUniqueResources()) { |
|
||||||
addToContentsResult(resource, result); |
|
||||||
} |
|
||||||
|
|
||||||
for (GuideReference guideReference : getGuide().getReferences()) { |
|
||||||
addToContentsResult(guideReference.getResource(), result); |
|
||||||
} |
|
||||||
|
|
||||||
return new ArrayList<>(result.values()); |
|
||||||
} |
|
||||||
|
|
||||||
private static void addToContentsResult(Resource resource, |
|
||||||
Map<String, Resource> allReachableResources) { |
|
||||||
if (resource != null && (!allReachableResources |
|
||||||
.containsKey(resource.getHref()))) { |
|
||||||
allReachableResources.put(resource.getHref(), resource); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public Resource getOpfResource() { |
|
||||||
return opfResource; |
|
||||||
} |
|
||||||
|
|
||||||
public void setOpfResource(Resource opfResource) { |
|
||||||
this.opfResource = opfResource; |
|
||||||
} |
|
||||||
|
|
||||||
public void setNcxResource(Resource ncxResource) { |
|
||||||
this.ncxResource = ncxResource; |
|
||||||
} |
|
||||||
|
|
||||||
public Resource getNcxResource() { |
|
||||||
return ncxResource; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -1,9 +1,323 @@ |
|||||||
package me.ag2s.epublib.domain; |
package me.ag2s.epublib.domain; |
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* Representation of a Book. |
||||||
|
* <p> |
||||||
|
* All resources of a Book (html, css, xml, fonts, images) are represented |
||||||
|
* as Resources. See getResources() for access to these.<br/> |
||||||
|
* A Book as 3 indexes into these Resources, as per the epub specification.<br/> |
||||||
|
* <dl> |
||||||
|
* <dt>Spine</dt> |
||||||
|
* <dd>these are the Resources to be shown when a user reads the book from |
||||||
|
* start to finish.</dd> |
||||||
|
* <dt>Table of Contents<dt> |
||||||
|
* <dd>The table of contents. Table of Contents references may be in a |
||||||
|
* different order and contain different Resources than the spine, and often do. |
||||||
|
* <dt>Guide</dt> |
||||||
|
* <dd>The Guide has references to a set of special Resources like the |
||||||
|
* cover page, the Glossary, the copyright page, etc. |
||||||
|
* </dl> |
||||||
|
* <p/> |
||||||
|
* The complication is that these 3 indexes may and usually do point to |
||||||
|
* different pages. |
||||||
|
* A chapter may be split up in 2 pieces to fit it in to memory. Then the |
||||||
|
* spine will contain both pieces, but the Table of Contents only the first. |
||||||
|
* <p> |
||||||
|
* The Content page may be in the Table of Contents, the Guide, but not |
||||||
|
* in the Spine. |
||||||
|
* Etc. |
||||||
|
* <p/> |
||||||
|
* <p> |
||||||
|
* Please see the illustration at: doc/schema.svg |
||||||
|
* |
||||||
|
* @author paul |
||||||
|
* @author jake |
||||||
|
*/ |
||||||
|
public class EpubBook implements Serializable { |
||||||
|
|
||||||
|
private static final long serialVersionUID = 2068355170895770100L; |
||||||
|
|
||||||
|
private Resources resources = new Resources(); |
||||||
|
private Metadata metadata = new Metadata(); |
||||||
|
private Spine spine = new Spine(); |
||||||
|
private TableOfContents tableOfContents = new TableOfContents(); |
||||||
|
private final Guide guide = new Guide(); |
||||||
|
private Resource opfResource; |
||||||
|
private Resource ncxResource; |
||||||
|
private Resource coverImage; |
||||||
|
|
||||||
|
|
||||||
|
private String version = "2.0"; |
||||||
|
|
||||||
|
public String getVersion() { |
||||||
|
return version; |
||||||
|
} |
||||||
|
|
||||||
|
public void setVersion(String version) { |
||||||
|
this.version = version; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isEpub3() { |
||||||
|
return this.version.startsWith("3."); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("UnusedReturnValue") |
||||||
|
public TOCReference addSection( |
||||||
|
TOCReference parentSection, String sectionTitle, Resource resource) { |
||||||
|
return addSection(parentSection, sectionTitle, resource, null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds the resource to the table of contents of the book as a child |
||||||
|
* section of the given parentSection |
||||||
|
* |
||||||
|
* @param parentSection parentSection |
||||||
|
* @param sectionTitle sectionTitle |
||||||
|
* @param resource resource |
||||||
|
* @param fragmentId fragmentId |
||||||
|
* @return The table of contents |
||||||
|
*/ |
||||||
|
public TOCReference addSection( |
||||||
|
TOCReference parentSection, String sectionTitle, Resource resource, |
||||||
|
String fragmentId) { |
||||||
|
getResources().add(resource); |
||||||
|
if (spine.findFirstResourceById(resource.getId()) < 0) { |
||||||
|
spine.addSpineReference(new SpineReference(resource)); |
||||||
|
} |
||||||
|
return parentSection.addChildSection( |
||||||
|
new TOCReference(sectionTitle, resource, fragmentId)); |
||||||
|
} |
||||||
|
|
||||||
|
public TOCReference addSection(String title, Resource resource) { |
||||||
|
return addSection(title, resource, null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a resource to the book's set of resources, table of contents and |
||||||
|
* if there is no resource with the id in the spine also adds it to the spine. |
||||||
|
* |
||||||
|
* @param title title |
||||||
|
* @param resource resource |
||||||
|
* @param fragmentId fragmentId |
||||||
|
* @return The table of contents |
||||||
|
*/ |
||||||
|
public TOCReference addSection( |
||||||
|
String title, Resource resource, String fragmentId) { |
||||||
|
getResources().add(resource); |
||||||
|
TOCReference tocReference = tableOfContents |
||||||
|
.addTOCReference(new TOCReference(title, resource, fragmentId)); |
||||||
|
if (spine.findFirstResourceById(resource.getId()) < 0) { |
||||||
|
spine.addSpineReference(new SpineReference(resource)); |
||||||
|
} |
||||||
|
return tocReference; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
public void generateSpineFromTableOfContents() { |
||||||
|
Spine spine = new Spine(tableOfContents); |
||||||
|
|
||||||
|
// in case the tocResource was already found and assigned
|
||||||
|
spine.setTocResource(this.spine.getTocResource()); |
||||||
|
|
||||||
|
this.spine = spine; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The Book's metadata (titles, authors, etc) |
||||||
|
* |
||||||
|
* @return The Book's metadata (titles, authors, etc) |
||||||
|
*/ |
||||||
|
public Metadata getMetadata() { |
||||||
|
return metadata; |
||||||
|
} |
||||||
|
|
||||||
|
public void setMetadata(Metadata metadata) { |
||||||
|
this.metadata = metadata; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void setResources(Resources resources) { |
||||||
|
this.resources = resources; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
public Resource addResource(Resource resource) { |
||||||
|
return resources.add(resource); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The collection of all images, chapters, sections, xhtml files, |
||||||
|
* stylesheets, etc that make up the book. |
||||||
|
* |
||||||
|
* @return The collection of all images, chapters, sections, xhtml files, |
||||||
|
* stylesheets, etc that make up the book. |
||||||
|
*/ |
||||||
|
public Resources getResources() { |
||||||
|
return resources; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* The sections of the book that should be shown if a user reads the book |
||||||
|
* from start to finish. |
||||||
|
* |
||||||
|
* @return The Spine |
||||||
|
*/ |
||||||
|
public Spine getSpine() { |
||||||
|
return spine; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void setSpine(Spine spine) { |
||||||
|
this.spine = spine; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* The Table of Contents of the book. |
||||||
|
* |
||||||
|
* @return The Table of Contents of the book. |
||||||
|
*/ |
||||||
|
public TableOfContents getTableOfContents() { |
||||||
|
return tableOfContents; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void setTableOfContents(TableOfContents tableOfContents) { |
||||||
|
this.tableOfContents = tableOfContents; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The book's cover page as a Resource. |
||||||
|
* An XHTML document containing a link to the cover image. |
||||||
|
* |
||||||
|
* @return The book's cover page as a Resource |
||||||
|
*/ |
||||||
|
public Resource getCoverPage() { |
||||||
|
Resource coverPage = guide.getCoverPage(); |
||||||
|
if (coverPage == null) { |
||||||
|
coverPage = spine.getResource(0); |
||||||
|
} |
||||||
|
return coverPage; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void setCoverPage(Resource coverPage) { |
||||||
|
if (coverPage == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (resources.notContainsByHref(coverPage.getHref())) { |
||||||
|
resources.add(coverPage); |
||||||
|
} |
||||||
|
guide.setCoverPage(coverPage); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the first non-blank title from the book's metadata. |
||||||
|
* |
||||||
|
* @return the first non-blank title from the book's metadata. |
||||||
|
*/ |
||||||
|
public String getTitle() { |
||||||
|
return getMetadata().getFirstTitle(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
/** |
/** |
||||||
* 这个是用于其它与Book类同名时替换的 |
* The book's cover image. |
||||||
|
* |
||||||
|
* @return The book's cover image. |
||||||
*/ |
*/ |
||||||
@SuppressWarnings("unused declaration") |
public Resource getCoverImage() { |
||||||
public class EpubBook extends Book { |
return coverImage; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCoverImage(Resource coverImage) { |
||||||
|
if (coverImage == null) { |
||||||
|
return; |
||||||
} |
} |
||||||
|
if (resources.notContainsByHref(coverImage.getHref())) { |
||||||
|
resources.add(coverImage); |
||||||
|
} |
||||||
|
this.coverImage = coverImage; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The guide; contains references to special sections of the book like |
||||||
|
* colophon, glossary, etc. |
||||||
|
* |
||||||
|
* @return The guide; contains references to special sections of the book |
||||||
|
* like colophon, glossary, etc. |
||||||
|
*/ |
||||||
|
public Guide getGuide() { |
||||||
|
return guide; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* All Resources of the Book that can be reached via the Spine, the |
||||||
|
* TableOfContents or the Guide. |
||||||
|
* <p/> |
||||||
|
* Consists of a list of "reachable" resources: |
||||||
|
* <ul> |
||||||
|
* <li>The coverpage</li> |
||||||
|
* <li>The resources of the Spine that are not already in the result</li> |
||||||
|
* <li>The resources of the Table of Contents that are not already in the |
||||||
|
* result</li> |
||||||
|
* <li>The resources of the Guide that are not already in the result</li> |
||||||
|
* </ul> |
||||||
|
* To get all html files that make up the epub file use |
||||||
|
* {@link #getResources()} |
||||||
|
* |
||||||
|
* @return All Resources of the Book that can be reached via the Spine, |
||||||
|
* the TableOfContents or the Guide. |
||||||
|
*/ |
||||||
|
public List<Resource> getContents() { |
||||||
|
Map<String, Resource> result = new LinkedHashMap<>(); |
||||||
|
addToContentsResult(getCoverPage(), result); |
||||||
|
|
||||||
|
for (SpineReference spineReference : getSpine().getSpineReferences()) { |
||||||
|
addToContentsResult(spineReference.getResource(), result); |
||||||
|
} |
||||||
|
|
||||||
|
for (Resource resource : getTableOfContents().getAllUniqueResources()) { |
||||||
|
addToContentsResult(resource, result); |
||||||
|
} |
||||||
|
|
||||||
|
for (GuideReference guideReference : getGuide().getReferences()) { |
||||||
|
addToContentsResult(guideReference.getResource(), result); |
||||||
|
} |
||||||
|
|
||||||
|
return new ArrayList<>(result.values()); |
||||||
|
} |
||||||
|
|
||||||
|
private static void addToContentsResult(Resource resource, |
||||||
|
Map<String, Resource> allReachableResources) { |
||||||
|
if (resource != null && (!allReachableResources |
||||||
|
.containsKey(resource.getHref()))) { |
||||||
|
allReachableResources.put(resource.getHref(), resource); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Resource getOpfResource() { |
||||||
|
return opfResource; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOpfResource(Resource opfResource) { |
||||||
|
this.opfResource = opfResource; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNcxResource(Resource ncxResource) { |
||||||
|
this.ncxResource = ncxResource; |
||||||
|
} |
||||||
|
|
||||||
|
public Resource getNcxResource() { |
||||||
|
return ncxResource; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Loading…
Reference in new issue