Merge pull request #9 from gedoor/master

merge
pull/441/head
口口吕 4 years ago committed by GitHub
commit 897d3fd8ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/src/main/assets/updateLog.md
  2. 9
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  3. 12
      app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
  4. 23
      app/src/main/java/io/legado/app/data/entities/BookChapter.kt
  5. 14
      app/src/main/java/io/legado/app/data/entities/BookGroup.kt
  6. 39
      app/src/main/java/io/legado/app/data/entities/RssSource.kt
  7. 19
      app/src/main/java/io/legado/app/help/BookHelp.kt
  8. 16
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt
  9. 14
      app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
  10. 45
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  11. 42
      app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt
  12. 38
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  13. 8
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  14. 21
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  15. 25
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt
  16. 22
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
  17. 17
      app/src/main/java/io/legado/app/ui/widget/SearchView.kt
  18. 5
      app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt
  19. 20
      app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt
  20. 42
      app/src/main/java/io/legado/app/ui/widget/image/FilletImageView.kt
  21. 408
      app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt
  22. 2
      app/src/main/java/io/legado/app/ui/widget/text/AccentBgTextView.kt
  23. 13
      app/src/main/java/io/legado/app/ui/widget/text/AutoCompleteTextView.kt
  24. 10
      app/src/main/java/io/legado/app/utils/ContextExtensions.kt
  25. 43
      app/src/main/java/io/legado/app/utils/EncodingDetect.java
  26. 2
      app/src/main/java/io/legado/app/utils/Toasts.kt

@ -3,6 +3,9 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/10/20**
* 默认分组无书籍时自动隐藏
**2020/10/19** **2020/10/19**
* 优化分组管理 * 优化分组管理
* 修复预下载没有保存的bug * 修复预下载没有保存的bug

@ -18,16 +18,9 @@ interface BookDao {
@Query("SELECT * FROM books WHERE origin = '${BookType.local}'") @Query("SELECT * FROM books WHERE origin = '${BookType.local}'")
fun observeLocal(): LiveData<List<Book>> fun observeLocal(): LiveData<List<Book>>
@Query( @Query("select * from books where type != ${BookType.audio} and origin != '${BookType.local}' and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0")
"""
select * from books where ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0 and type != ${BookType.audio} and origin != '${BookType.local}'
"""
)
fun observeNoGroup(): LiveData<List<Book>> fun observeNoGroup(): LiveData<List<Book>>
@Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups where groupId > 0) & `group` = 0")
fun observeNoGroupSize(): LiveData<Int>
@Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}'") @Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}'")
fun observeLocalUri(): LiveData<List<String>> fun observeLocalUri(): LiveData<List<String>>

@ -2,6 +2,7 @@ package io.legado.app.data.dao
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import io.legado.app.constant.BookType
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
@Dao @Dao
@ -16,7 +17,16 @@ interface BookGroupDao {
@Query("SELECT * FROM book_groups ORDER BY `order`") @Query("SELECT * FROM book_groups ORDER BY `order`")
fun liveDataAll(): LiveData<List<BookGroup>> fun liveDataAll(): LiveData<List<BookGroup>>
@Query("SELECT * FROM book_groups where show > 0 or groupId >= 0 ORDER BY `order`") @Query(
"""
SELECT * FROM book_groups where groupId >= 0
or (groupId = -4 and show > 0 and (select count(bookUrl) from books where type != ${BookType.audio} and origin != '${BookType.local}' and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0) > 0)
or (groupId = -3 and show > 0 and (select count(bookUrl) from books where type = ${BookType.audio}) > 0)
or (groupId = -2 and show > 0 and (select count(bookUrl) from books where origin = '${BookType.local}') > 0)
or (groupId = -1 and show > 0)
ORDER BY `order`
"""
)
fun liveDataShow(): LiveData<List<BookGroup>> fun liveDataShow(): LiveData<List<BookGroup>>
@Query("SELECT * FROM book_groups where groupId >= 0 ORDER BY `order`") @Query("SELECT * FROM book_groups where groupId >= 0 ORDER BY `order`")

@ -36,25 +36,26 @@ data class BookChapter(
var variable: String? = null //变量 var variable: String? = null //变量
) : Parcelable { ) : Parcelable {
@Ignore @delegate:Transient
@delegate:Ignore
@IgnoredOnParcel @IgnoredOnParcel
var variableMap: HashMap<String, String>? = null val variableMap by lazy {
private set GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
get() { }
if (field == null) {
field = GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
}
return field
}
fun putVariable(key: String, value: String) { fun putVariable(key: String, value: String) {
variableMap?.put(key, value) variableMap[key] = value
variable = GSON.toJson(variableMap) variable = GSON.toJson(variableMap)
} }
override fun hashCode() = url.hashCode() override fun hashCode() = url.hashCode()
override fun equals(other: Any?) = if (other is BookChapter) other.url == url else false override fun equals(other: Any?): Boolean {
if (other is BookChapter) {
return other.url == url
}
return false
}
} }

@ -28,4 +28,18 @@ data class BookGroup(
} }
} }
override fun hashCode(): Int {
return groupId.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other is BookGroup) {
return other.groupId == groupId
&& other.groupName == groupName
&& other.order == order
&& other.show == show
}
return false
}
} }

@ -41,9 +41,14 @@ data class RssSource(
var loadWithBaseUrl: Boolean = false, var loadWithBaseUrl: Boolean = false,
var customOrder: Int = 0 var customOrder: Int = 0
): Parcelable, JsExtensions { ) : Parcelable, JsExtensions {
override fun equals(other: Any?) = if (other is RssSource) other.sourceUrl == sourceUrl else false override fun equals(other: Any?): Boolean {
if (other is RssSource) {
return other.sourceUrl == sourceUrl
}
return false
}
override fun hashCode() = sourceUrl.hashCode() override fun hashCode() = sourceUrl.hashCode()
@ -69,22 +74,26 @@ data class RssSource(
* 执行JS * 执行JS
*/ */
@Throws(Exception::class) @Throws(Exception::class)
private fun evalJS(jsStr: String): Any = AppConst.SCRIPT_ENGINE.eval(jsStr, SimpleBindings().apply { this["java"] = this@RssSource }) private fun evalJS(jsStr: String): Any? {
val bindings = SimpleBindings()
bindings["java"] = this
return AppConst.SCRIPT_ENGINE.eval(jsStr, bindings)
}
fun equal(source: RssSource): Boolean { fun equal(source: RssSource): Boolean {
return equal(sourceUrl, source.sourceUrl) return equal(sourceUrl, source.sourceUrl)
&& equal(sourceIcon, source.sourceIcon) && equal(sourceIcon, source.sourceIcon)
&& enabled == source.enabled && enabled == source.enabled
&& equal(sourceGroup, source.sourceGroup) && equal(sourceGroup, source.sourceGroup)
&& equal(ruleArticles, source.ruleArticles) && equal(ruleArticles, source.ruleArticles)
&& equal(ruleNextPage, source.ruleNextPage) && equal(ruleNextPage, source.ruleNextPage)
&& equal(ruleTitle, source.ruleTitle) && equal(ruleTitle, source.ruleTitle)
&& equal(rulePubDate, source.rulePubDate) && equal(rulePubDate, source.rulePubDate)
&& equal(ruleDescription, source.ruleDescription) && equal(ruleDescription, source.ruleDescription)
&& equal(ruleLink, source.ruleLink) && equal(ruleLink, source.ruleLink)
&& equal(ruleContent, source.ruleContent) && equal(ruleContent, source.ruleContent)
&& enableJs == source.enableJs && enableJs == source.enableJs
&& loadWithBaseUrl == source.loadWithBaseUrl && loadWithBaseUrl == source.loadWithBaseUrl
} }
private fun equal(a: String?, b: String?): Boolean { private fun equal(a: String?, b: String?): Boolean {

@ -11,7 +11,6 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -307,17 +306,13 @@ object BookHelp {
private var replaceRules: List<ReplaceRule> = arrayListOf() private var replaceRules: List<ReplaceRule> = arrayListOf()
@Synchronized @Synchronized
suspend fun upReplaceRules() { fun upReplaceRules() {
withContext(IO) { val o = bookOrigin
synchronized(this) { bookName?.let {
val o = bookOrigin replaceRules = if (o.isNullOrEmpty()) {
bookName?.let { App.db.replaceRuleDao().findEnabledByScope(it)
replaceRules = if (o.isNullOrEmpty()) { } else {
App.db.replaceRuleDao().findEnabledByScope(it) App.db.replaceRuleDao().findEnabledByScope(it, o)
} else {
App.db.replaceRuleDao().findEnabledByScope(it, o)
}
}
} }
} }
} }

@ -196,20 +196,20 @@ class AnalyzeByJSoup {
private fun filterElements(elements: Elements, rules: Array<String>?): Elements { private fun filterElements(elements: Elements, rules: Array<String>?): Elements {
if (rules == null || rules.size < 2) return elements if (rules == null || rules.size < 2) return elements
val selectedEls = Elements() val result = Elements()
for (ele in elements) { for (element in elements) {
var isOk = false var isOk = false
when (rules[0]) { when (rules[0]) {
"class" -> isOk = ele.getElementsByClass(rules[1]).size > 0 "class" -> isOk = element.getElementsByClass(rules[1]).size > 0
"id" -> isOk = ele.getElementById(rules[1]) != null "id" -> isOk = element.getElementById(rules[1]) != null
"tag" -> isOk = ele.getElementsByTag(rules[1]).size > 0 "tag" -> isOk = element.getElementsByTag(rules[1]).size > 0
"text" -> isOk = ele.getElementsContainingOwnText(rules[1]).size > 0 "text" -> isOk = element.getElementsContainingOwnText(rules[1]).size > 0
} }
if (isOk) { if (isOk) {
selectedEls.add(ele) result.add(element)
} }
} }
return selectedEls return result
} }
/** /**

@ -13,8 +13,8 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.sendMail
import io.legado.app.utils.sendToClip import io.legado.app.utils.sendToClip
import io.legado.app.utils.toast
class AboutFragment : PreferenceFragmentCompat() { class AboutFragment : PreferenceFragmentCompat() {
@ -48,7 +48,7 @@ class AboutFragment : PreferenceFragmentCompat() {
"contributors" -> openUrl(R.string.contributors_url) "contributors" -> openUrl(R.string.contributors_url)
"update_log" -> showUpdateLog() "update_log" -> showUpdateLog()
"check_update" -> openUrl(R.string.latest_release_url) "check_update" -> openUrl(R.string.latest_release_url)
"mail" -> sendMail() "mail" -> requireContext().sendMail("kunfei.ge@gmail.com")
"sourceRuleSummary" -> openUrl(R.string.source_rule_url) "sourceRuleSummary" -> openUrl(R.string.source_rule_url)
"git" -> openUrl(R.string.this_github_url) "git" -> openUrl(R.string.this_github_url)
"home_page" -> openUrl(R.string.home_page_url) "home_page" -> openUrl(R.string.home_page_url)
@ -66,16 +66,6 @@ class AboutFragment : PreferenceFragmentCompat() {
requireContext().openUrl(getString(addressID)) requireContext().openUrl(getString(addressID))
} }
private fun sendMail() {
try {
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:kunfei.ge@gmail.com")
startActivity(intent)
} catch (e: Exception) {
toast(e.localizedMessage ?: "Error")
}
}
private fun showUpdateLog() { private fun showUpdateLog() {
val log = String(requireContext().assets.open("updateLog.md").readBytes()) val log = String(requireContext().assets.open("updateLog.md").readBytes())
TextDialog.show(childFragmentManager, log, TextDialog.MD) TextDialog.show(childFragmentManager, log, TextDialog.MD)

@ -21,37 +21,30 @@ import kotlinx.android.synthetic.main.view_read_menu.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.sdk27.listeners.onLongClick import org.jetbrains.anko.sdk27.listeners.onLongClick
class ReadMenu : FrameLayout { /**
* 阅读界面菜单
*/
class ReadMenu @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
var cnaShowMenu: Boolean = false var cnaShowMenu: Boolean = false
private var callBack: CallBack? = null private val callBack: CallBack? get() = activity as? CallBack
private lateinit var menuTopIn: Animation private lateinit var menuTopIn: Animation
private lateinit var menuTopOut: Animation private lateinit var menuTopOut: Animation
private lateinit var menuBottomIn: Animation private lateinit var menuBottomIn: Animation
private lateinit var menuBottomOut: Animation private lateinit var menuBottomOut: Animation
private val bgColor: Int private val bgColor: Int = context.bottomBackground
private val textColor: Int private val textColor: Int = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))
private var bottomBackgroundList: ColorStateList private val bottomBackgroundList: ColorStateList = Selector.colorBuild()
.setDefaultColor(bgColor)
.setPressedColor(ColorUtils.darkenColor(bgColor))
.create()
private var onMenuOutEnd: (() -> Unit)? = null private var onMenuOutEnd: (() -> Unit)? = null
val showBrightnessView get() = context.getPrefBoolean(PreferKey.showBrightnessView, true) val showBrightnessView get() = context.getPrefBoolean(PreferKey.showBrightnessView, true)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
init { init {
callBack = activity as? CallBack
bgColor = context.bottomBackground
textColor = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))
bottomBackgroundList = Selector.colorBuild()
.setDefaultColor(bgColor)
.setPressedColor(ColorUtils.darkenColor(bgColor))
.create()
inflate(context, R.layout.view_read_menu, this) inflate(context, R.layout.view_read_menu, this)
if (AppConfig.isNightTheme) { if (AppConfig.isNightTheme) {
fabNightTheme.setImageResource(R.drawable.ic_daytime) fabNightTheme.setImageResource(R.drawable.ic_daytime)
@ -254,9 +247,7 @@ class ReadMenu : FrameLayout {
} }
} }
override fun onAnimationRepeat(animation: Animation) { override fun onAnimationRepeat(animation: Animation) = Unit
}
}) })
//隐藏菜单 //隐藏菜单
@ -277,9 +268,7 @@ class ReadMenu : FrameLayout {
callBack?.upSystemUiVisibility() callBack?.upSystemUiVisibility()
} }
override fun onAnimationRepeat(animation: Animation) { override fun onAnimationRepeat(animation: Animation) = Unit
}
}) })
} }

@ -0,0 +1,42 @@
package io.legado.app.ui.book.read.config
import android.content.Context
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.EventBus
import io.legado.app.help.ImageLoader
import io.legado.app.help.ReadBookConfig
import io.legado.app.utils.postEvent
import kotlinx.android.synthetic.main.item_bg_image.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import java.io.File
class BgAdapter(context: Context, val textColor: Int) :
SimpleRecyclerAdapter<String>(context, R.layout.item_bg_image) {
override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList<Any>) {
with(holder.itemView) {
ImageLoader.load(
context,
context.assets.open("bg${File.separator}$item").readBytes()
)
.centerCrop()
.into(iv_bg)
tv_name.setTextColor(textColor)
tv_name.text = item.substringBeforeLast(".")
}
}
override fun registerListener(holder: ItemViewHolder) {
holder.itemView.apply {
this.onClick {
getItemByLayoutPosition(holder.layoutPosition)?.let {
ReadBookConfig.durConfig.setCurBg(1, it)
ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false)
}
}
}
}
}

@ -2,7 +2,6 @@ package io.legado.app.ui.book.read.config
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
@ -14,10 +13,7 @@ import androidx.documentfile.provider.DocumentFile
import com.jaredrummler.android.colorpicker.ColorPickerDialog import com.jaredrummler.android.colorpicker.ColorPickerDialog
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.help.ImageLoader
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.Permissions
@ -53,8 +49,8 @@ class BgTextConfigDialog : BaseDialogFragment(), FileChooserDialog.CallBack {
private val requestCodeImport = 132 private val requestCodeImport = 132
private val configFileName = "readConfig.zip" private val configFileName = "readConfig.zip"
private lateinit var adapter: BgAdapter private lateinit var adapter: BgAdapter
var primaryTextColor = 0 private var primaryTextColor = 0
var secondaryTextColor = 0 private var secondaryTextColor = 0
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -109,7 +105,7 @@ class BgTextConfigDialog : BaseDialogFragment(), FileChooserDialog.CallBack {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private fun initData() = with(ReadBookConfig.durConfig) { private fun initData() = with(ReadBookConfig.durConfig) {
sw_dark_status_icon.isChecked = curStatusIconDark() sw_dark_status_icon.isChecked = curStatusIconDark()
adapter = BgAdapter(requireContext()) adapter = BgAdapter(requireContext(), secondaryTextColor)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val headerView = LayoutInflater.from(requireContext()) val headerView = LayoutInflater.from(requireContext())
.inflate(R.layout.item_bg_image, recycler_view, false) .inflate(R.layout.item_bg_image, recycler_view, false)
@ -119,7 +115,7 @@ class BgTextConfigDialog : BaseDialogFragment(), FileChooserDialog.CallBack {
headerView.iv_bg.setImageResource(R.drawable.ic_image) headerView.iv_bg.setImageResource(R.drawable.ic_image)
headerView.iv_bg.setColorFilter(primaryTextColor) headerView.iv_bg.setColorFilter(primaryTextColor)
headerView.onClick { selectImage() } headerView.onClick { selectImage() }
requireContext().assets.list("bg/")?.let { requireContext().assets.list("bg${File.separator}")?.let {
adapter.setItems(it.toList()) adapter.setItems(it.toList())
} }
} }
@ -189,32 +185,6 @@ class BgTextConfigDialog : BaseDialogFragment(), FileChooserDialog.CallBack {
startActivityForResult(intent, requestCodeBg) startActivityForResult(intent, requestCodeBg)
} }
inner class BgAdapter(context: Context) :
SimpleRecyclerAdapter<String>(context, R.layout.item_bg_image) {
override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList<Any>) {
with(holder.itemView) {
ImageLoader.load(context, context.assets.open("bg/$item").readBytes())
.centerCrop()
.into(iv_bg)
tv_name.setTextColor(secondaryTextColor)
tv_name.text = item.substringBeforeLast(".")
}
}
override fun registerListener(holder: ItemViewHolder) {
holder.itemView.apply {
this.onClick {
getItemByLayoutPosition(holder.layoutPosition)?.let {
ReadBookConfig.durConfig.setCurBg(1, it)
ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false)
}
}
}
}
}
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
private fun exportConfig(uri: Uri) { private fun exportConfig(uri: Uri) {
execute { execute {

@ -191,10 +191,10 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
return when (getId(position)) { return when (getId(position)) {
0 -> BookshelfFragment() 0 -> fragmentMap[0] ?: BookshelfFragment()
1 -> ExploreFragment() 1 -> fragmentMap[1] ?: ExploreFragment()
2 -> RssFragment() 2 -> fragmentMap[2] ?: RssFragment()
else -> MyFragment() else -> fragmentMap[3] ?: MyFragment()
} }
} }

@ -105,11 +105,12 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
private fun initBookGroupData() { private fun initBookGroupData() {
bookGroupLiveData?.removeObservers(viewLifecycleOwner) bookGroupLiveData?.removeObservers(viewLifecycleOwner)
bookGroupLiveData = App.db.bookGroupDao().liveDataShow() bookGroupLiveData = App.db.bookGroupDao().liveDataShow().apply {
bookGroupLiveData?.observe(viewLifecycleOwner, { observe(viewLifecycleOwner) {
viewModel.checkGroup(it) viewModel.checkGroup(it)
upGroup(it) upGroup(it)
}) }
}
} }
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
@ -126,10 +127,12 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
if (data.isEmpty()) { if (data.isEmpty()) {
App.db.bookGroupDao().enableGroup(AppConst.bookGroupAllId) App.db.bookGroupDao().enableGroup(AppConst.bookGroupAllId)
} else { } else {
bookGroups.clear() if (data != bookGroups) {
bookGroups.addAll(data) bookGroups.clear()
adapter.notifyDataSetChanged() bookGroups.addAll(data)
selectLastTab() adapter.notifyDataSetChanged()
selectLastTab()
}
} }
} }

@ -107,19 +107,20 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
AppConst.bookGroupAudioId -> App.db.bookDao().observeAudio() AppConst.bookGroupAudioId -> App.db.bookDao().observeAudio()
AppConst.bookGroupNoneId -> App.db.bookDao().observeNoGroup() AppConst.bookGroupNoneId -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }.apply {
bookshelfLiveData?.observe(this, { list -> observe(viewLifecycleOwner) { list ->
tv_empty_msg.isGone = list.isNotEmpty() tv_empty_msg.isGone = list.isNotEmpty()
val books = when (getPrefInt(PreferKey.bookshelfSort)) { val books = when (getPrefInt(PreferKey.bookshelfSort)) {
1 -> list.sortedByDescending { it.latestChapterTime } 1 -> list.sortedByDescending { it.latestChapterTime }
2 -> list.sortedBy { it.name } 2 -> list.sortedBy { it.name }
3 -> list.sortedBy { it.order } 3 -> list.sortedBy { it.order }
else -> list.sortedByDescending { it.durChapterTime } else -> list.sortedByDescending { it.durChapterTime }
}
val diffResult = DiffUtil
.calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books))
booksAdapter.setItems(books, diffResult)
} }
val diffResult = DiffUtil }
.calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books))
booksAdapter.setItems(books, diffResult)
})
} }
fun getBooks(): List<Book> { fun getBooks(): List<Book> {

@ -29,12 +29,8 @@ import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.prefs.NameListPreference import io.legado.app.ui.widget.prefs.NameListPreference
import io.legado.app.ui.widget.prefs.PreferenceCategory import io.legado.app.ui.widget.prefs.PreferenceCategory
import io.legado.app.ui.widget.prefs.SwitchPreference import io.legado.app.ui.widget.prefs.SwitchPreference
import io.legado.app.utils.LogUtils import io.legado.app.utils.*
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.observeEventSticky
import io.legado.app.utils.putPrefBoolean
import kotlinx.android.synthetic.main.view_title_bar.* import kotlinx.android.synthetic.main.view_title_bar.*
import org.jetbrains.anko.startActivity
class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog.CallBack { class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog.CallBack {
@ -138,20 +134,20 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog.
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) { when (preference?.key) {
"bookSourceManage" -> context?.startActivity<BookSourceActivity>() "bookSourceManage" -> startActivity<BookSourceActivity>()
"replaceManage" -> context?.startActivity<ReplaceRuleActivity>() "replaceManage" -> startActivity<ReplaceRuleActivity>()
"setting" -> context?.startActivity<ConfigActivity>( "setting" -> startActivity<ConfigActivity>(
Pair("configType", ConfigViewModel.TYPE_CONFIG) Pair("configType", ConfigViewModel.TYPE_CONFIG)
) )
"web_dav_setting" -> context?.startActivity<ConfigActivity>( "web_dav_setting" -> startActivity<ConfigActivity>(
Pair("configType", ConfigViewModel.TYPE_WEB_DAV_CONFIG) Pair("configType", ConfigViewModel.TYPE_WEB_DAV_CONFIG)
) )
"theme_setting" -> context?.startActivity<ConfigActivity>( "theme_setting" -> startActivity<ConfigActivity>(
Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG) Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG)
) )
"readRecord" -> context?.startActivity<ReadRecordActivity>() "readRecord" -> startActivity<ReadRecordActivity>()
"donate" -> context?.startActivity<DonateActivity>() "donate" -> startActivity<DonateActivity>()
"about" -> context?.startActivity<AboutActivity>() "about" -> startActivity<AboutActivity>()
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }

@ -16,21 +16,14 @@ import android.widget.TextView
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import io.legado.app.R import io.legado.app.R
class SearchView : SearchView { class SearchView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : SearchView(context, attrs, defStyleAttr) {
private var mSearchHintIcon: Drawable? = null private var mSearchHintIcon: Drawable? = null
private var textView: TextView? = null private var textView: TextView? = null
constructor(
context: Context,
attrs: AttributeSet? = null
) : super(context, attrs)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr)
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
override fun onLayout( override fun onLayout(
changed: Boolean, changed: Boolean,

@ -26,10 +26,7 @@ import kotlin.math.pow
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
class CircleImageView(context: Context, attrs: AttributeSet) : class CircleImageView(context: Context, attrs: AttributeSet) :
AppCompatImageView( AppCompatImageView(context, attrs) {
context,
attrs
) {
private val mDrawableRect = RectF() private val mDrawableRect = RectF()
private val mBorderRect = RectF() private val mBorderRect = RectF()

@ -20,7 +20,15 @@ import io.legado.app.utils.getPrefString
* 封面 * 封面
*/ */
@Suppress("unused") @Suppress("unused")
class CoverImageView : androidx.appcompat.widget.AppCompatImageView { class CoverImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatImageView(
context,
attrs,
defStyleAttr
) {
internal var width: Float = 0.toFloat() internal var width: Float = 0.toFloat()
internal var height: Float = 0.toFloat() internal var height: Float = 0.toFloat()
private var nameHeight = 0f private var nameHeight = 0f
@ -45,16 +53,6 @@ class CoverImageView : androidx.appcompat.widget.AppCompatImageView {
private var author: String? = null private var author: String? = null
private var loadFailed = false private var loadFailed = false
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val measuredWidth = MeasureSpec.getSize(widthMeasureSpec) val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
val measuredHeight = measuredWidth * 7 / 5 val measuredHeight = measuredWidth * 7 / 5

@ -10,7 +10,11 @@ import io.legado.app.R
import io.legado.app.utils.dp import io.legado.app.utils.dp
import kotlin.math.max import kotlin.math.max
class FilletImageView : AppCompatImageView { class FilletImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
internal var width: Float = 0.toFloat() internal var width: Float = 0.toFloat()
internal var height: Float = 0.toFloat() internal var height: Float = 0.toFloat()
private var leftTopRadius: Int = 0 private var leftTopRadius: Int = 0
@ -18,26 +22,29 @@ class FilletImageView : AppCompatImageView {
private var rightBottomRadius: Int = 0 private var rightBottomRadius: Int = 0
private var leftBottomRadius: Int = 0 private var leftBottomRadius: Int = 0
constructor(context: Context) : super(context) init {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet) {
// 读取配置 // 读取配置
val array = context.obtainStyledAttributes(attrs, R.styleable.FilletImageView) val array = context.obtainStyledAttributes(attrs, R.styleable.FilletImageView)
val defaultRadius = 5.dp val defaultRadius = 5.dp
val radius = array.getDimensionPixelOffset(R.styleable.FilletImageView_radius, defaultRadius) val radius =
leftTopRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_left_top_radius, defaultRadius) array.getDimensionPixelOffset(R.styleable.FilletImageView_radius, defaultRadius)
rightTopRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_right_top_radius, defaultRadius) leftTopRadius = array.getDimensionPixelOffset(
R.styleable.FilletImageView_left_top_radius,
defaultRadius
)
rightTopRadius = array.getDimensionPixelOffset(
R.styleable.FilletImageView_right_top_radius,
defaultRadius
)
rightBottomRadius = rightBottomRadius =
array.getDimensionPixelOffset(R.styleable.FilletImageView_right_bottom_radius, defaultRadius) array.getDimensionPixelOffset(
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_left_bottom_radius, defaultRadius) R.styleable.FilletImageView_right_bottom_radius,
defaultRadius
)
leftBottomRadius = array.getDimensionPixelOffset(
R.styleable.FilletImageView_left_bottom_radius,
defaultRadius
)
//如果四个角的值没有设置,那么就使用通用的radius的值。 //如果四个角的值没有设置,那么就使用通用的radius的值。
if (defaultRadius == leftTopRadius) { if (defaultRadius == leftTopRadius) {
@ -53,7 +60,6 @@ class FilletImageView : AppCompatImageView {
leftBottomRadius = radius leftBottomRadius = radius
} }
array.recycle() array.recycle()
} }
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {

@ -16,6 +16,7 @@ import android.view.animation.Interpolator
import android.widget.ImageView import android.widget.ImageView
import android.widget.OverScroller import android.widget.OverScroller
import android.widget.Scroller import android.widget.Scroller
import androidx.appcompat.widget.AppCompatImageView
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.image.photo.Info import io.legado.app.ui.widget.image.photo.Info
import io.legado.app.ui.widget.image.photo.OnRotateListener import io.legado.app.ui.widget.image.photo.OnRotateListener
@ -23,10 +24,12 @@ import io.legado.app.ui.widget.image.photo.RotateGestureDetector
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Suppress("UNUSED_PARAMETER", "unused", "MemberVisibilityCanBePrivate", "PropertyName")
@Suppress("UNUSED_PARAMETER", "unused", "MemberVisibilityCanBePrivate") class PhotoView @JvmOverloads constructor(
@SuppressLint("AppCompatCustomView") context: Context,
class PhotoView : ImageView { attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
val MIN_ROTATE = 35 val MIN_ROTATE = 35
val ANIMA_DURING = 340 val ANIMA_DURING = 340
val MAX_SCALE = 2.5f val MAX_SCALE = 2.5f
@ -45,9 +48,9 @@ class PhotoView : ImageView {
private val mSynthesisMatrix: Matrix = Matrix() private val mSynthesisMatrix: Matrix = Matrix()
private val mTmpMatrix: Matrix = Matrix() private val mTmpMatrix: Matrix = Matrix()
private var mRotateDetector: RotateGestureDetector? = null private val mRotateDetector: RotateGestureDetector
private var mDetector: GestureDetector? = null private val mDetector: GestureDetector
private var mScaleDetector: ScaleGestureDetector? = null private val mScaleDetector: ScaleGestureDetector
private var mClickListener: OnClickListener? = null private var mClickListener: OnClickListener? = null
private var mScaleType: ScaleType? = null private var mScaleType: ScaleType? = null
@ -100,23 +103,11 @@ class PhotoView : ImageView {
private var mLongClick: OnLongClickListener? = null private var mLongClick: OnLongClickListener? = null
constructor(context: Context) : super(context) { private val mRotateListener = RotateListener()
init() private val mGestureListener = GestureListener()
} private val mScaleListener = ScaleGestureListener()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init()
}
private fun init() {
super.setScaleType(ScaleType.MATRIX) super.setScaleType(ScaleType.MATRIX)
if (mScaleType == null) mScaleType = ScaleType.CENTER_INSIDE if (mScaleType == null) mScaleType = ScaleType.CENTER_INSIDE
mRotateDetector = RotateGestureDetector(mRotateListener) mRotateDetector = RotateGestureDetector(mRotateListener)
@ -484,11 +475,11 @@ class PhotoView : ImageView {
return if (isEnable) { return if (isEnable) {
val action = event.actionMasked val action = event.actionMasked
if (event.pointerCount >= 2) hasMultiTouch = true if (event.pointerCount >= 2) hasMultiTouch = true
mDetector!!.onTouchEvent(event) mDetector.onTouchEvent(event)
if (isRotateEnable) { if (isRotateEnable) {
mRotateDetector!!.onTouchEvent(event) mRotateDetector.onTouchEvent(event)
} }
mScaleDetector!!.onTouchEvent(event) mScaleDetector.onTouchEvent(event)
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) onUp() if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) onUp()
true true
} else { } else {
@ -566,49 +557,6 @@ class PhotoView : ImageView {
return abs(rect.left.roundToInt() - (mWidgetRect.width() - rect.width()) / 2) < 1 return abs(rect.left.roundToInt() - (mWidgetRect.width() - rect.width()) / 2) < 1
} }
private val mRotateListener: OnRotateListener = object :
OnRotateListener {
override fun onRotate(
degrees: Float,
focusX: Float,
focusY: Float
) {
mRotateFlag += degrees
if (canRotate) {
mDegrees += degrees
mAnimMatrix.postRotate(degrees, focusX, focusY)
} else {
if (abs(mRotateFlag) >= mMinRotate) {
canRotate = true
mRotateFlag = 0f
}
}
}
}
private val mScaleListener: OnScaleGestureListener = object : OnScaleGestureListener {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scaleFactor = detector.scaleFactor
if (java.lang.Float.isNaN(scaleFactor) || java.lang.Float.isInfinite(scaleFactor)) return false
mScale *= scaleFactor
//mScaleCenter.set(detector.getFocusX(), detector.getFocusY());
mAnimMatrix.postScale(
scaleFactor,
scaleFactor,
detector.focusX,
detector.focusY
)
executeTranslate()
return true
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) {}
}
private fun resistanceScrollByX( private fun resistanceScrollByX(
overScroll: Float, overScroll: Float,
detalX: Float detalX: Float
@ -652,147 +600,6 @@ class PhotoView : ImageView {
mClickListener?.onClick(this) mClickListener?.onClick(this)
} }
private val mGestureListener: GestureDetector.OnGestureListener =
object : SimpleOnGestureListener() {
override fun onLongPress(e: MotionEvent) {
mLongClick?.onLongClick(this@PhotoView)
}
override fun onDown(e: MotionEvent): Boolean {
hasOverTranslate = false
hasMultiTouch = false
canRotate = false
removeCallbacks(mClickRunnable)
return false
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (hasMultiTouch) return false
if (!imgLargeWidth && !imgLargeHeight) return false
if (mTranslate.isRunning) return false
var vx = velocityX
var vy = velocityY
if (mImgRect.left.roundToInt() >= mWidgetRect.left
|| mImgRect.right.roundToInt() <= mWidgetRect.right
) {
vx = 0f
}
if (mImgRect.top.roundToInt() >= mWidgetRect.top
|| mImgRect.bottom.roundToInt() <= mWidgetRect.bottom
) {
vy = 0f
}
if (canRotate || mDegrees % 90 != 0f) {
var toDegrees = (mDegrees / 90).toInt() * 90.toFloat()
val remainder = mDegrees % 90
if (remainder > 45) toDegrees += 90f else if (remainder < -45) toDegrees -= 90f
mTranslate.withRotate(mDegrees.toInt(), toDegrees.toInt())
mDegrees = toDegrees
}
doTranslateReset(mImgRect)
mTranslate.withFling(vx, vy)
mTranslate.start()
// onUp(e2);
return super.onFling(e1, e2, velocityX, velocityY)
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
var x = distanceX
var y = distanceY
if (mTranslate.isRunning) {
mTranslate.stop()
}
if (canScrollHorizontallySelf(x)) {
if (x < 0 && mImgRect.left - x > mWidgetRect.left)
x = mImgRect.left
if (x > 0 && mImgRect.right - x < mWidgetRect.right)
x = mImgRect.right - mWidgetRect.right
mAnimMatrix.postTranslate(-x, 0f)
mTranslateX -= x.toInt()
} else if (imgLargeWidth || hasMultiTouch || hasOverTranslate) {
checkRect()
if (!hasMultiTouch) {
if (x < 0 && mImgRect.left - x > mCommonRect.left) x =
resistanceScrollByX(mImgRect.left - mCommonRect.left, x)
if (x > 0 && mImgRect.right - x < mCommonRect.right) x =
resistanceScrollByX(mImgRect.right - mCommonRect.right, x)
}
mTranslateX -= x.toInt()
mAnimMatrix.postTranslate(-x, 0f)
hasOverTranslate = true
}
if (canScrollVerticallySelf(y)) {
if (y < 0 && mImgRect.top - y > mWidgetRect.top) y =
mImgRect.top
if (y > 0 && mImgRect.bottom - y < mWidgetRect.bottom) y =
mImgRect.bottom - mWidgetRect.bottom
mAnimMatrix.postTranslate(0f, -y)
mTranslateY -= y.toInt()
} else if (imgLargeHeight || hasOverTranslate || hasMultiTouch) {
checkRect()
if (!hasMultiTouch) {
if (y < 0 && mImgRect.top - y > mCommonRect.top) y =
resistanceScrollByY(mImgRect.top - mCommonRect.top, y)
if (y > 0 && mImgRect.bottom - y < mCommonRect.bottom) y =
resistanceScrollByY(mImgRect.bottom - mCommonRect.bottom, y)
}
mAnimMatrix.postTranslate(0f, -y)
mTranslateY -= y.toInt()
hasOverTranslate = true
}
executeTranslate()
return true
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
postDelayed(mClickRunnable, 250)
return false
}
override fun onDoubleTap(e: MotionEvent): Boolean {
mTranslate.stop()
val from: Float
val to: Float
val imgCx = mImgRect.left + mImgRect.width() / 2
val imgCy = mImgRect.top + mImgRect.height() / 2
mScaleCenter[imgCx] = imgCy
mRotateCenter[imgCx] = imgCy
mTranslateX = 0
mTranslateY = 0
if (isZoonUp) {
from = mScale
to = 1f
} else {
from = mScale
to = mMaxScale
mScaleCenter[e.x] = e.y
}
mTmpMatrix.reset()
mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top)
mTmpMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y)
mTmpMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight)
mTmpMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y)
mTmpMatrix.postScale(to, to, mScaleCenter.x, mScaleCenter.y)
mTmpMatrix.postTranslate(mTranslateX.toFloat(), mTranslateY.toFloat())
mTmpMatrix.mapRect(mTmpRect, mBaseRect)
doTranslateReset(mTmpRect)
isZoonUp = !isZoonUp
mTranslate.withScale(from, to)
mTranslate.start()
return false
}
}
fun canScrollHorizontallySelf(direction: Float): Boolean { fun canScrollHorizontallySelf(direction: Float): Boolean {
if (mImgRect.width() <= mWidgetRect.width()) if (mImgRect.width() <= mWidgetRect.width())
return false return false
@ -1268,4 +1075,185 @@ class PhotoView : ImageView {
executeTranslate() executeTranslate()
} }
inner class RotateListener : OnRotateListener {
override fun onRotate(
degrees: Float,
focusX: Float,
focusY: Float
) {
mRotateFlag += degrees
if (canRotate) {
mDegrees += degrees
mAnimMatrix.postRotate(degrees, focusX, focusY)
} else {
if (abs(mRotateFlag) >= mMinRotate) {
canRotate = true
mRotateFlag = 0f
}
}
}
}
inner class GestureListener : SimpleOnGestureListener() {
override fun onLongPress(e: MotionEvent) {
mLongClick?.onLongClick(this@PhotoView)
}
override fun onDown(e: MotionEvent): Boolean {
hasOverTranslate = false
hasMultiTouch = false
canRotate = false
removeCallbacks(mClickRunnable)
return false
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (hasMultiTouch) return false
if (!imgLargeWidth && !imgLargeHeight) return false
if (mTranslate.isRunning) return false
var vx = velocityX
var vy = velocityY
if (mImgRect.left.roundToInt() >= mWidgetRect.left
|| mImgRect.right.roundToInt() <= mWidgetRect.right
) {
vx = 0f
}
if (mImgRect.top.roundToInt() >= mWidgetRect.top
|| mImgRect.bottom.roundToInt() <= mWidgetRect.bottom
) {
vy = 0f
}
if (canRotate || mDegrees % 90 != 0f) {
var toDegrees = (mDegrees / 90).toInt() * 90.toFloat()
val remainder = mDegrees % 90
if (remainder > 45) toDegrees += 90f else if (remainder < -45) toDegrees -= 90f
mTranslate.withRotate(mDegrees.toInt(), toDegrees.toInt())
mDegrees = toDegrees
}
doTranslateReset(mImgRect)
mTranslate.withFling(vx, vy)
mTranslate.start()
// onUp(e2);
return super.onFling(e1, e2, velocityX, velocityY)
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
var x = distanceX
var y = distanceY
if (mTranslate.isRunning) {
mTranslate.stop()
}
if (canScrollHorizontallySelf(x)) {
if (x < 0 && mImgRect.left - x > mWidgetRect.left)
x = mImgRect.left
if (x > 0 && mImgRect.right - x < mWidgetRect.right)
x = mImgRect.right - mWidgetRect.right
mAnimMatrix.postTranslate(-x, 0f)
mTranslateX -= x.toInt()
} else if (imgLargeWidth || hasMultiTouch || hasOverTranslate) {
checkRect()
if (!hasMultiTouch) {
if (x < 0 && mImgRect.left - x > mCommonRect.left) x =
resistanceScrollByX(mImgRect.left - mCommonRect.left, x)
if (x > 0 && mImgRect.right - x < mCommonRect.right) x =
resistanceScrollByX(mImgRect.right - mCommonRect.right, x)
}
mTranslateX -= x.toInt()
mAnimMatrix.postTranslate(-x, 0f)
hasOverTranslate = true
}
if (canScrollVerticallySelf(y)) {
if (y < 0 && mImgRect.top - y > mWidgetRect.top) y =
mImgRect.top
if (y > 0 && mImgRect.bottom - y < mWidgetRect.bottom) y =
mImgRect.bottom - mWidgetRect.bottom
mAnimMatrix.postTranslate(0f, -y)
mTranslateY -= y.toInt()
} else if (imgLargeHeight || hasOverTranslate || hasMultiTouch) {
checkRect()
if (!hasMultiTouch) {
if (y < 0 && mImgRect.top - y > mCommonRect.top) y =
resistanceScrollByY(mImgRect.top - mCommonRect.top, y)
if (y > 0 && mImgRect.bottom - y < mCommonRect.bottom) y =
resistanceScrollByY(mImgRect.bottom - mCommonRect.bottom, y)
}
mAnimMatrix.postTranslate(0f, -y)
mTranslateY -= y.toInt()
hasOverTranslate = true
}
executeTranslate()
return true
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
postDelayed(mClickRunnable, 250)
return false
}
override fun onDoubleTap(e: MotionEvent): Boolean {
mTranslate.stop()
val from: Float
val to: Float
val imgCx = mImgRect.left + mImgRect.width() / 2
val imgCy = mImgRect.top + mImgRect.height() / 2
mScaleCenter[imgCx] = imgCy
mRotateCenter[imgCx] = imgCy
mTranslateX = 0
mTranslateY = 0
if (isZoonUp) {
from = mScale
to = 1f
} else {
from = mScale
to = mMaxScale
mScaleCenter[e.x] = e.y
}
mTmpMatrix.reset()
mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top)
mTmpMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y)
mTmpMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight)
mTmpMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y)
mTmpMatrix.postScale(to, to, mScaleCenter.x, mScaleCenter.y)
mTmpMatrix.postTranslate(mTranslateX.toFloat(), mTranslateY.toFloat())
mTmpMatrix.mapRect(mTmpRect, mBaseRect)
doTranslateReset(mTmpRect)
isZoonUp = !isZoonUp
mTranslate.withScale(from, to)
mTranslate.start()
return false
}
}
inner class ScaleGestureListener : OnScaleGestureListener {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scaleFactor = detector.scaleFactor
if (java.lang.Float.isNaN(scaleFactor) || java.lang.Float.isInfinite(scaleFactor)) return false
mScale *= scaleFactor
//mScaleCenter.set(detector.getFocusX(), detector.getFocusY());
mAnimMatrix.postScale(
scaleFactor,
scaleFactor,
detector.focusX,
detector.focusY
)
executeTranslate()
return true
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) {}
}
} }

@ -11,7 +11,7 @@ import io.legado.app.utils.ColorUtils
import io.legado.app.utils.dp import io.legado.app.utils.dp
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
class AccentBgTextView(context: Context, attrs: AttributeSet?) : class AccentBgTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
AppCompatTextView(context, attrs) { AppCompatTextView(context, attrs) {
private var radius = 0 private var radius = 0

@ -16,12 +16,12 @@ import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.item_1line_text_and_del.view.* import kotlinx.android.synthetic.main.item_1line_text_and_del.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@Suppress("unused")
class AutoCompleteTextView : AppCompatAutoCompleteTextView { class AutoCompleteTextView @JvmOverloads constructor(
context: Context,
constructor(context: Context) : super(context) attrs: AttributeSet? = null,
defStyleAttr: Int = 0
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) ) : AppCompatAutoCompleteTextView(context, attrs, defStyleAttr) {
var delCallBack: ((value: String) -> Unit)? = null var delCallBack: ((value: String) -> Unit)? = null
@ -33,7 +33,6 @@ class AutoCompleteTextView : AppCompatAutoCompleteTextView {
return true return true
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean { override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_DOWN) { if (event?.action == MotionEvent.ACTION_DOWN) {

@ -156,6 +156,16 @@ fun Context.getClipText(): String? {
return null return null
} }
fun Context.sendMail(mail: String) {
try {
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:$mail")
startActivity(intent)
} catch (e: Exception) {
toast(e.localizedMessage ?: "Error")
}
}
/** /**
* 系统是否暗色主题 * 系统是否暗色主题
*/ */

@ -35,6 +35,7 @@ import static android.text.TextUtils.isEmpty;
* @version 1.0 * @version 1.0
* @since Create on 2010-01-27 11:19:00 * @since Create on 2010-01-27 11:19:00
*/ */
@SuppressWarnings("ALL")
public class EncodingDetect { public class EncodingDetect {
public static String getHtmlEncode(@NonNull byte[] bytes) { public static String getHtmlEncode(@NonNull byte[] bytes) {
@ -117,6 +118,7 @@ public class EncodingDetect {
} }
@SuppressWarnings("ALL")
class BytesEncodingDetect extends Encoding { class BytesEncodingDetect extends Encoding {
// Frequency tables to hold the GB, Big5, and EUC-TW character // Frequency tables to hold the GB, Big5, and EUC-TW character
// frequencies // frequencies
@ -271,9 +273,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7
&& (byte) 0xA1 <= rawtext[i + 1] && (byte) 0xA1 <= rawtext[i + 1]
@ -312,9 +312,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7
&& // Original GB range && // Original GB range
@ -373,9 +371,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7 if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7
&& // Original GB range && // Original GB range
@ -516,9 +512,7 @@ class BytesEncodingDetect extends Encoding {
// Check to see if characters fit into acceptable ranges // Check to see if characters fit into acceptable ranges
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0xA1 <= rawtext[i] if ((byte) 0xA1 <= rawtext[i]
&& rawtext[i] <= (byte) 0xF9 && rawtext[i] <= (byte) 0xF9
@ -560,9 +554,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 128) { if (rawtext[i] < 128) {
// asciichars++;
} else {
dbchars++; dbchars++;
if (0xA1 <= rawtext[i] if (0xA1 <= rawtext[i]
&& rawtext[i] <= 0xF9 && rawtext[i] <= 0xF9
@ -626,9 +618,7 @@ class BytesEncodingDetect extends Encoding {
// and have expected frequency of use // and have expected frequency of use
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
if (rawtext[i] >= 0) { // in ASCII range if (rawtext[i] < 0) { // high bit set
// asciichars++;
} else { // high bit set
dbchars++; dbchars++;
if (i + 3 < rawtextlen && (byte) 0x8E == rawtext[i] if (i + 3 < rawtextlen && (byte) 0x8E == rawtext[i]
&& (byte) 0xA1 <= rawtext[i + 1] && (byte) 0xA1 <= rawtext[i + 1]
@ -846,9 +836,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE
&& (byte) 0xA1 <= rawtext[i + 1] && (byte) 0xA1 <= rawtext[i + 1]
@ -886,9 +874,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0x81 <= rawtext[i] if ((byte) 0x81 <= rawtext[i]
&& rawtext[i] <= (byte) 0xFE && rawtext[i] <= (byte) 0xFE
@ -945,9 +931,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xFE
&& (byte) 0xA1 <= rawtext[i + 1] && (byte) 0xA1 <= rawtext[i + 1]
@ -997,9 +981,7 @@ class BytesEncodingDetect extends Encoding {
rawtextlen = rawtext.length; rawtextlen = rawtext.length;
for (i = 0; i < rawtextlen - 1; i++) { for (i = 0; i < rawtextlen - 1; i++) {
// System.err.println(rawtext[i]); // System.err.println(rawtext[i]);
if (rawtext[i] >= 0) { if (rawtext[i] < 0) {
// asciichars++;
} else {
dbchars++; dbchars++;
if (i + 1 < rawtext.length if (i + 1 < rawtext.length
&& (((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0x9F) || ((byte) 0xE0 <= rawtext[i] && rawtext[i] <= (byte) 0xEF)) && (((byte) 0x81 <= rawtext[i] && rawtext[i] <= (byte) 0x9F) || ((byte) 0xE0 <= rawtext[i] && rawtext[i] <= (byte) 0xEF))
@ -4383,6 +4365,7 @@ class BytesEncodingDetect extends Encoding {
} }
} }
@SuppressWarnings("ALL")
class Encoding { class Encoding {
// Supported Encoding Types // Supported Encoding Types
static int GB2312 = 0; static int GB2312 = 0;

@ -1,3 +1,5 @@
@file:Suppress("unused")
package io.legado.app.utils package io.legado.app.utils
import android.widget.Toast import android.widget.Toast

Loading…
Cancel
Save