Merge pull request #8 from gedoor/master

up
pull/69/head
口口吕 5 years ago committed by GitHub
commit f41d6373d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      app/src/main/assets/updateLog.md
  2. 25
      app/src/main/java/io/legado/app/help/storage/OldRule.kt
  3. 17
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  4. 55
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  5. 36
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  6. 84
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  7. 14
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt
  8. 20
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  9. 1
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  10. 32
      app/src/main/java/io/legado/app/utils/DocumentUtils.kt
  11. 61
      app/src/main/java/io/legado/app/utils/UriExtensions.kt

@ -5,6 +5,7 @@
**2020/01/07**
* 修复备份问题
* 设置背景不再需要存储权限
**2020/01/06**
* 适配Android 10 权限

@ -3,6 +3,7 @@ package io.legado.app.help.storage
import io.legado.app.constant.AppConst
import io.legado.app.constant.BookType
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.data.entities.rule.*
import io.legado.app.help.storage.Restore.jsonPath
import io.legado.app.utils.*
@ -205,5 +206,27 @@ object OldRule {
return GSON.toJson(map)
}
fun jsonToReplaceRule(json: String): ReplaceRule? {
var replaceRule: ReplaceRule? = null
runCatching {
replaceRule = GSON.fromJsonObject<ReplaceRule>(json.trim())
}
runCatching {
if (replaceRule == null || replaceRule?.pattern.isNullOrBlank()) {
val jsonItem = jsonPath.parse(json.trim())
val rule = ReplaceRule()
rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis()
rule.pattern = jsonItem.readString("$.regex") ?: ""
if (rule.pattern.isEmpty()) return null
rule.name = jsonItem.readString("$.replaceSummary") ?: ""
rule.replacement = jsonItem.readString("$.replacement") ?: ""
rule.isRegex = jsonItem.readBool("$.isRegex") == true
rule.scope = jsonItem.readString("$.useTo")
rule.isEnabled = jsonItem.readBool("$.enable") == true
rule.order = jsonItem.readInt("$.serialNumber") ?: 0
return rule
}
}
return replaceRule
}
}

@ -226,20 +226,11 @@ object Restore {
fun importOldReplaceRule(json: String): Int {
val replaceRules = mutableListOf<ReplaceRule>()
val items: List<Map<String, Any>> = jsonPath.parse(json).read("$")
val existingRules = App.db.replaceRuleDao().all.map { it.pattern }.toSet()
for ((index: Int, item: Map<String, Any>) in items.withIndex()) {
for (item in items) {
val jsonItem = jsonPath.parse(item)
val rule = ReplaceRule()
rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis().plus(index)
rule.pattern = jsonItem.readString("$.regex") ?: ""
if (rule.pattern.isEmpty() || rule.pattern in existingRules) continue
rule.name = jsonItem.readString("$.replaceSummary") ?: ""
rule.replacement = jsonItem.readString("$.replacement") ?: ""
rule.isRegex = jsonItem.readBool("$.isRegex") == true
rule.scope = jsonItem.readString("$.useTo")
rule.isEnabled = jsonItem.readBool("$.enable") == true
rule.order = jsonItem.readInt("$.serialNumber") ?: index
replaceRules.add(rule)
OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let {
replaceRules.add(it)
}
}
App.db.replaceRuleDao().insert(*replaceRules.toTypedArray())
return replaceRules.size

@ -12,6 +12,7 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -20,11 +21,13 @@ import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Bus
import io.legado.app.help.FileHelp
import io.legado.app.help.ImageLoader
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.ui.book.read.Help
import io.legado.app.utils.DocumentUtils
import io.legado.app.utils.FileUtils
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.postEvent
@ -32,6 +35,7 @@ import kotlinx.android.synthetic.main.dialog_read_bg_text.*
import kotlinx.android.synthetic.main.item_bg_image.view.*
import org.jetbrains.anko.sdk27.listeners.onCheckedChange
import org.jetbrains.anko.sdk27.listeners.onClick
import java.io.File
class BgTextConfigDialog : DialogFragment() {
@ -133,17 +137,10 @@ class BgTextConfigDialog : DialogFragment() {
}
private fun selectImage() {
PermissionsCompat.Builder(this)
.addPermissions(Permissions.READ_EXTERNAL_STORAGE, Permissions.WRITE_EXTERNAL_STORAGE)
.rationale(R.string.bg_image_per)
.onGranted {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
startActivityForResult(intent, resultSelectBg)
Unit
}
.request()
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
startActivityForResult(intent, resultSelectBg)
}
class BgAdapter(context: Context) :
@ -170,11 +167,37 @@ class BgTextConfigDialog : DialogFragment() {
when (requestCode) {
resultSelectBg -> {
if (resultCode == RESULT_OK) {
data?.data?.let {
FileUtils.getPath(requireContext(), it)?.let { path ->
ReadBookConfig.getConfig().setBg(2, path)
ReadBookConfig.upBg()
postEvent(Bus.UP_CONFIG, false)
data?.data?.let { uri ->
if (DocumentFile.isDocumentUri(requireContext(), uri)) {
val doc = DocumentFile.fromSingleUri(requireContext(), uri)
doc?.let {
var file = requireContext().getExternalFilesDir(null)
?: requireContext().filesDir
file =
FileHelp.getFile(file.absolutePath + File.separator + "bg" + File.separator + doc.name)
DocumentUtils.readBytes(requireContext(), uri)?.let {
file.writeBytes(it)
ReadBookConfig.getConfig().setBg(2, file.absolutePath)
ReadBookConfig.upBg()
postEvent(Bus.UP_CONFIG, false)
}
}
} else {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
FileUtils.getPath(requireContext(), uri)?.let { path ->
ReadBookConfig.getConfig().setBg(2, path)
ReadBookConfig.upBg()
postEvent(Bus.UP_CONFIG, false)
}
Unit
}
.request()
}
}
}

@ -49,7 +49,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
get() = getViewModel(BookSourceViewModel::class.java)
private val qrRequestCode = 101
private val importSource = 13141
private val importSource = 132
private lateinit var adapter: BookSourceAdapter
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
private var groups = hashSetOf<String>()
@ -82,7 +82,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
supportFragmentManager,
"groupManage"
)
R.id.menu_import_source_local -> selectFile()
R.id.menu_import_source_local -> selectFileSys()
R.id.menu_select_all -> adapter.selectAll()
R.id.menu_revert_selection -> adapter.revertSelection()
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds())
@ -219,10 +219,15 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
}
private fun selectFileSys() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
selectFile()
}
}
override fun onMenuClick(menu: String) {
@ -284,16 +289,17 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
}
}
importSource -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
val path = FileUtils.getPath(this, it)
if (path != null) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE)
.show()
viewModel.importSourceFromFilePath(path) { msg ->
title_bar.snackbar(msg)
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE)
.show()
viewModel.importSource(it) { msg ->
toast(msg)
}
}
} else {
toast(R.string.uri_to_path_fail)
} catch (e: Exception) {
e.localizedMessage?.let { toast(it) }
}
}
}

@ -1,6 +1,8 @@
package io.legado.app.ui.replacerule
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -13,18 +15,25 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.ItemTouchCallback
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.cancelButton
import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.lib.theme.view.ATEAutoCompleteTextView
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.utils.getViewModel
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_replace_rule.*
import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.toast
class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule),
@ -32,7 +41,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
ReplaceRuleAdapter.CallBack {
override val viewModel: ReplaceRuleViewModel
get() = getViewModel(ReplaceRuleViewModel::class.java)
private val importSource = 132
private lateinit var adapter: ReplaceRuleAdapter
private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null
@ -68,6 +77,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds())
R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_local -> selectFileSys()
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds())
}
return super.onCompatOptionsItemSelected(item)
@ -134,6 +145,52 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
}
}
@SuppressLint("InflateParams")
private fun showImportDialog() {
val aCache = ACache.get(this, cacheDir = false)
val cacheUrls: MutableList<String> = aCache
.getAsString("replaceRuleUrl")
?.splitNotBlank(",")
?.toMutableList() ?: mutableListOf()
alert(titleResource = R.string.import_replace_rule_on_line) {
var editText: ATEAutoCompleteTextView? = null
customView {
layoutInflater.inflate(R.layout.dialog_edit_text, null).apply {
editText = edit_view
edit_view.setFilterValues(cacheUrls) {
cacheUrls.remove(it)
aCache.put("replaceRuleUrl", cacheUrls.joinToString(","))
}
}
}
okButton {
val text = editText?.text?.toString()
text?.let {
if (!cacheUrls.contains(it)) {
cacheUrls.add(0, it)
aCache.put("replaceRuleUrl", cacheUrls.joinToString(","))
}
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSource(it) { msg ->
title_bar.snackbar(msg)
}
}
}
cancelButton()
}.show().applyTint()
}
private fun selectFileSys() {
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
}
}
override fun onQueryTextChange(newText: String?): Boolean {
observeReplaceRuleData("%$newText%")
@ -144,6 +201,27 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
return false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE)
.show()
viewModel.importSource(it) { msg ->
toast(msg)
}
}
} catch (e: Exception) {
e.localizedMessage?.let { toast(it) }
}
}
}
}
}
override fun update(vararg rule: ReplaceRule) {
viewModel.update(*rule)
}

@ -3,10 +3,12 @@ package io.legado.app.ui.replacerule
import android.app.Application
import android.text.TextUtils
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.FileHelp
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore
import io.legado.app.utils.GSON
import io.legado.app.utils.splitNotBlank
import org.jetbrains.anko.toast
@ -14,6 +16,15 @@ import java.io.File
class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) {
fun importSource(text: String, toast: (msg: String) -> Unit) {
execute {
Restore.importOldReplaceRule(text)
}.onError {
toast(it.localizedMessage ?: "ERROR")
}.onSuccess {
toast(context.getString(R.string.success))
}
}
fun update(vararg rule: ReplaceRule) {
execute {
@ -68,7 +79,8 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
App.db.replaceRuleDao().findById(it)
}.let {
val json = GSON.toJson(it)
val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json")
val file =
FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json")
file.writeText(json)
}
}.onSuccess {

@ -80,7 +80,7 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds())
R.id.menu_import_source_local -> selectFile()
R.id.menu_import_source_local -> selectFileSys()
R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_group_manage -> GroupManageDialog()
@ -203,10 +203,14 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
}
private fun selectFileSys() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
selectFile()
}
}
override fun onMenuClick(menu: String) {
@ -229,11 +233,11 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
FileUtils.getPath(this, it)?.let { path ->
data?.data?.let { uri ->
uri.readText(this)?.let {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE)
.show()
viewModel.importSourceFromFilePath(path) { msg ->
viewModel.importSource(it) { msg ->
title_bar.snackbar(msg)
}
}

@ -115,7 +115,6 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
}
}
fun importSourceFromFilePath(path: String, finally: (msg: String) -> Unit) {
execute {
val file = File(path)

@ -6,25 +6,24 @@ import android.net.Uri
object DocumentUtils {
@JvmStatic
@Throws(Exception::class)
fun writeText(context: Context, data: String, fileUri: Uri): Boolean {
return writeBytes(context, data.toByteArray(), fileUri)
}
@JvmStatic
@Throws(Exception::class)
fun writeBytes(context: Context, data: ByteArray, fileUri: Uri): Boolean {
try {
context.contentResolver.openOutputStream(fileUri)?.let {
it.write(data)
it.close()
return true
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
context.contentResolver.openOutputStream(fileUri)?.let {
it.write(data)
it.close()
return true
}
return false
}
@JvmStatic
@Throws(Exception::class)
fun readText(context: Context, uri: Uri): String? {
readBytes(context, uri)?.let {
return String(it)
@ -33,17 +32,14 @@ object DocumentUtils {
}
@JvmStatic
@Throws(Exception::class)
fun readBytes(context: Context, uri: Uri): ByteArray? {
try {
context.contentResolver.openInputStream(uri)?.let {
val len: Int = it.available()
val buffer = ByteArray(len)
it.read(buffer)
it.close()
return buffer
}
} catch (e: Exception) {
e.printStackTrace()
context.contentResolver.openInputStream(uri)?.let {
val len: Int = it.available()
val buffer = ByteArray(len)
it.read(buffer)
it.close()
return buffer
}
return null
}

@ -0,0 +1,61 @@
package io.legado.app.utils
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import java.io.File
@Throws(Exception::class)
fun Uri.readBytes(context: Context): ByteArray? {
if (DocumentFile.isDocumentUri(context, this)) {
return DocumentUtils.readBytes(context, this)
} else {
val path = FileUtils.getPath(context, this)
if (path?.isNotEmpty() == true) {
return File(path).readBytes()
}
}
return null
}
@Throws(Exception::class)
fun Uri.readText(context: Context): String? {
if (DocumentFile.isDocumentUri(context, this)) {
return DocumentUtils.readText(context, this)
} else {
val path = FileUtils.getPath(context, this)
if (path?.isNotEmpty() == true) {
return File(path).readText()
}
}
return null
}
@Throws(Exception::class)
fun Uri.writeBytes(context: Context, byteArray: ByteArray): Boolean {
if (DocumentFile.isDocumentUri(context, this)) {
return DocumentUtils.writeBytes(context, byteArray, this)
} else {
val path = FileUtils.getPath(context, this)
if (path?.isNotEmpty() == true) {
File(path).writeBytes(byteArray)
return true
}
}
return false
}
@Throws(Exception::class)
fun Uri.writeText(context: Context, text: String): Boolean {
if (DocumentFile.isDocumentUri(context, this)) {
return DocumentUtils.writeText(context, text, this)
} else {
val path = FileUtils.getPath(context, this)
if (path?.isNotEmpty() == true) {
File(path).writeText(text)
return true
}
}
return false
}
Loading…
Cancel
Save