增加漫画单独配置
更新界面
This commit is contained in:
@@ -192,6 +192,7 @@ object PreferKey {
|
||||
const val showBottomView = "showBottomView"
|
||||
const val mangaScrollMode = "mangaScrollMode"
|
||||
const val webtoonSidePaddingDp = "webtoonSidePaddingDp"
|
||||
const val mangaVolumeKeyPage = "mangaVolumeKeyPage"
|
||||
|
||||
const val mangaClickActionTL = "mangaClickActionTopLeft"
|
||||
const val mangaClickActionTC = "mangaClickActionTopCenter"
|
||||
|
||||
@@ -402,8 +402,11 @@ data class Book(
|
||||
var readSimulating: Boolean = false,
|
||||
var startDate: LocalDate? = null,
|
||||
var startChapter: Int? = null, // 用户设置的起始章节
|
||||
var dailyChapters: Int = 3 // 用户设置的每日更新章节数
|
||||
var dailyChapters: Int = 3, // 用户设置的每日更新章节数
|
||||
|
||||
val mangaColorFilter: String? = null,
|
||||
var mangaScrollMode: Int? = null,
|
||||
var webtoonSidePaddingDp: Int? = null
|
||||
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@@ -743,29 +743,33 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
appCtx.putPrefBoolean(PreferKey.enableMangaEInk, value)
|
||||
}
|
||||
|
||||
//墨水屏阈值
|
||||
var mangaEInkThreshold
|
||||
get() = appCtx.getPrefInt(PreferKey.mangaEInkThreshold, 150)
|
||||
set(value) {
|
||||
appCtx.putPrefInt(PreferKey.mangaEInkThreshold, value)
|
||||
}
|
||||
|
||||
var disableHorizontalAnimator
|
||||
get() = appCtx.getPrefBoolean(PreferKey.disableHorizontalAnimator, false)
|
||||
set(value) {
|
||||
appCtx.putPrefBoolean(PreferKey.disableHorizontalAnimator, value)
|
||||
}
|
||||
|
||||
//漫画灰度
|
||||
var enableMangaGray
|
||||
get() = appCtx.getPrefBoolean(PreferKey.enableMangaGray, false)
|
||||
set(value) {
|
||||
appCtx.putPrefBoolean(PreferKey.enableMangaGray, value)
|
||||
}
|
||||
|
||||
//条漫侧边距
|
||||
var webtoonSidePaddingDp: Int
|
||||
get() = appCtx.getPrefInt(PreferKey.webtoonSidePaddingDp, 0)
|
||||
set(value) {
|
||||
appCtx.putPrefInt(PreferKey.webtoonSidePaddingDp, value)
|
||||
}
|
||||
|
||||
//漫画音量键翻页
|
||||
var MangaVolumeKeyPage: Boolean
|
||||
get() = appCtx.getPrefBoolean(PreferKey.mangaVolumeKeyPage, false)
|
||||
set(value) {
|
||||
appCtx.putPrefBoolean(PreferKey.mangaVolumeKeyPage, value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package io.legato.kazusa.ui.book.group
|
||||
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.viewModels
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseBottomSheetDialogFragment
|
||||
import io.legato.kazusa.data.entities.BookGroup
|
||||
import io.legato.kazusa.databinding.DialogBookGroupEditBinding
|
||||
import io.legato.kazusa.lib.dialogs.alert
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
import io.legato.kazusa.utils.FileUtils
|
||||
import io.legato.kazusa.utils.MD5Utils
|
||||
import io.legato.kazusa.utils.SelectImageContract
|
||||
@@ -19,7 +20,6 @@ import io.legato.kazusa.utils.launch
|
||||
import io.legato.kazusa.utils.readUri
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import io.legato.kazusa.utils.visible
|
||||
import splitties.init.appCtx
|
||||
import splitties.views.onClick
|
||||
import java.io.FileOutputStream
|
||||
@@ -32,6 +32,9 @@ class GroupEditDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_book_gro
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedSortIndex = -1
|
||||
private lateinit var sortOptions: List<String>
|
||||
|
||||
private val binding by viewBinding(DialogBookGroupEditBinding::bind)
|
||||
private val viewModel by viewModels<GroupViewModel>()
|
||||
private var bookGroup: BookGroup? = null
|
||||
@@ -62,21 +65,40 @@ class GroupEditDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_book_gro
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
//binding.toolBar.setBackgroundColor(primaryColor)
|
||||
sortOptions = resources.getStringArray(R.array.book_sort).toList()
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
bookGroup = arguments?.getParcelable("group")
|
||||
bookGroup?.let {
|
||||
binding.btnDelete.visible(it.groupId > 0 || it.groupId == Long.MIN_VALUE)
|
||||
binding.btnDelete.isEnabled = (it.groupId > 0 || it.groupId == Long.MIN_VALUE)
|
||||
binding.tieGroupName.setText(it.groupName)
|
||||
if (bookGroup?.cover != null)
|
||||
binding.ivCover.load(it.cover)
|
||||
binding.spSort.setSelection(it.bookSort + 1)
|
||||
binding.cbEnableRefresh.isChecked = it.enableRefresh
|
||||
if (it.bookSort in sortOptions.indices) {
|
||||
selectedSortIndex = it.bookSort
|
||||
binding.chipSortMenu.text = sortOptions[selectedSortIndex]
|
||||
}
|
||||
} ?: let {
|
||||
binding.toolBar.title = getString(R.string.add_group)
|
||||
binding.btnDelete.gone()
|
||||
binding.ivCover.load()
|
||||
}
|
||||
|
||||
binding.run {
|
||||
chipSortMenu.setOnClickListener { chip ->
|
||||
PopupMenu(requireContext(), chip).apply {
|
||||
sortOptions.forEachIndexed { index, title ->
|
||||
menu.add(0, index, index, title)
|
||||
}
|
||||
setOnMenuItemClickListener { item ->
|
||||
selectedSortIndex = item.itemId
|
||||
chipSortMenu.text = sortOptions[selectedSortIndex]
|
||||
true
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
ivCover.onClick {
|
||||
selectImage.launch()
|
||||
}
|
||||
@@ -88,15 +110,17 @@ class GroupEditDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_book_gro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btnCancel.onClick {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
btnOk.onClick {
|
||||
val groupName = tieGroupName.text?.toString()
|
||||
if (groupName.isNullOrEmpty()) {
|
||||
toastOnUi("分组名称不能为空")
|
||||
} else {
|
||||
val bookSort = binding.spSort.selectedItemPosition - 1
|
||||
val bookSort = selectedSortIndex
|
||||
val coverPath = binding.ivCover.bitmapPath
|
||||
val enableRefresh = binding.cbEnableRefresh.isChecked
|
||||
bookGroup?.let {
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -202,8 +201,12 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
enableMangaEInk(AppConfig.enableMangaEInk, AppConfig.mangaEInkThreshold)
|
||||
enableGray(AppConfig.enableMangaGray)
|
||||
}
|
||||
//setHorizontalScroll(AppConfig.enableMangaHorizontalScroll)
|
||||
setScrollMode(AppConfig.mangaScrollMode)
|
||||
|
||||
viewModel.initData(intent) {
|
||||
setScrollMode(viewModel.getEffectiveScrollMode())
|
||||
updateWebtoonSidePadding(viewModel.getEffectiveWebtoonSidePadding())
|
||||
}
|
||||
|
||||
binding.recyclerView.run {
|
||||
adapter = mAdapter
|
||||
itemAnimator = null
|
||||
@@ -212,7 +215,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
setDisableClickScroll(AppConfig.disableClickScroll)
|
||||
setDisableMangaScale(AppConfig.disableMangaScale)
|
||||
setRecyclerViewPreloader(AppConfig.mangaPreDownloadNum)
|
||||
updateWebtoonSidePadding(AppConfig.webtoonSidePaddingDp)
|
||||
setPreScrollListener { _, _, _, position ->
|
||||
if (mAdapter.isNotEmpty()) {
|
||||
val item = mAdapter.getItem(position)
|
||||
@@ -267,10 +269,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
Looper.myQueue().addIdleHandler {
|
||||
viewModel.initData(intent)
|
||||
false
|
||||
}
|
||||
// Looper.myQueue().addIdleHandler {
|
||||
// viewModel.initData(intent)
|
||||
// false
|
||||
// }
|
||||
justInitData = true
|
||||
}
|
||||
|
||||
@@ -563,6 +565,8 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
|
||||
override fun showScrollModeDialog() {
|
||||
showDialogFragment(MangaScrollModeDialog().apply {
|
||||
initialScrollMode = viewModel.getEffectiveScrollMode()
|
||||
initialWebtoonSidePadding = viewModel.getEffectiveWebtoonSidePadding()
|
||||
initialAutoPageEnabled = enableScroll
|
||||
callback = this@ReadMangaActivity
|
||||
})
|
||||
@@ -570,7 +574,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
|
||||
//漫画模式
|
||||
override fun onScrollModeChanged(mode: Int) {
|
||||
AppConfig.mangaScrollMode = mode
|
||||
//AppConfig.mangaScrollMode = mode
|
||||
viewModel.updateReadConfig {
|
||||
mangaScrollMode = mode
|
||||
}
|
||||
setScrollMode(mode)
|
||||
updateWebtoonSidePadding(AppConfig.webtoonSidePaddingDp)
|
||||
setAutoReadEnabled(false)
|
||||
@@ -590,7 +597,9 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
|
||||
//侧边留白
|
||||
override fun upSidePadding(paddingDp: Int) {
|
||||
AppConfig.webtoonSidePaddingDp = paddingDp
|
||||
viewModel.updateReadConfig {
|
||||
webtoonSidePaddingDp = paddingDp
|
||||
}
|
||||
updateWebtoonSidePadding(paddingDp)
|
||||
}
|
||||
|
||||
@@ -626,6 +635,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onVolumeKeyPageChanged(enable: Boolean) {
|
||||
AppConfig.MangaVolumeKeyPage = enable
|
||||
}
|
||||
|
||||
override fun onHideMangaTitleChanged(hide: Boolean) {
|
||||
AppConfig.hideMangaTitle = hide
|
||||
ReadManga.loadContent()
|
||||
@@ -907,17 +920,20 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
scrollToPrev()
|
||||
return true
|
||||
}
|
||||
if (AppConfig.MangaVolumeKeyPage) {
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
scrollToPrev()
|
||||
return true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
scrollToNext()
|
||||
return true
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
scrollToNext()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.legato.kazusa.constant.BookType
|
||||
import io.legato.kazusa.constant.EventBus
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.Book
|
||||
import io.legato.kazusa.data.entities.Book.ReadConfig
|
||||
import io.legato.kazusa.data.entities.BookChapter
|
||||
import io.legato.kazusa.data.entities.BookProgress
|
||||
import io.legato.kazusa.exception.NoStackTraceException
|
||||
@@ -273,6 +274,29 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getEffectiveScrollMode(): Int {
|
||||
return ReadManga.book?.readConfig?.mangaScrollMode ?: AppConfig.mangaScrollMode
|
||||
}
|
||||
|
||||
fun getEffectiveWebtoonSidePadding(): Int {
|
||||
return ReadManga.book?.readConfig?.webtoonSidePaddingDp ?: AppConfig.webtoonSidePaddingDp
|
||||
}
|
||||
|
||||
|
||||
fun updateReadConfig(update: ReadConfig.() -> Unit) {
|
||||
val book = ReadManga.book ?: return
|
||||
val config = book.readConfig ?: ReadConfig()
|
||||
config.update()
|
||||
book.readConfig = config
|
||||
|
||||
execute {
|
||||
appDb.bookDao.update(book)
|
||||
}.onError {
|
||||
AppLog.put("更新阅读配置失败\n${it.localizedMessage}", it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
changeSourceCoroutine?.cancel()
|
||||
|
||||
@@ -14,6 +14,9 @@ class MangaScrollModeDialog : BaseBottomSheetDialogFragment(R.layout.dialog_mang
|
||||
private val binding by viewBinding(DialogMangaScrollModeBinding::bind)
|
||||
|
||||
var initialAutoPageEnabled: Boolean = false
|
||||
var initialWebtoonSidePadding: Int = 0
|
||||
var initialScrollMode: Int = MangaScrollMode.PAGE_RIGHT_TO_LEFT
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -27,7 +30,7 @@ class MangaScrollModeDialog : BaseBottomSheetDialogFragment(R.layout.dialog_mang
|
||||
binding.chipGroupScrollMode.addView(Chip(requireContext()).apply {
|
||||
text = MangaScrollMode.labelOf(mode)
|
||||
isCheckable = true
|
||||
isChecked = (mode == AppConfig.mangaScrollMode)
|
||||
isChecked = (mode == initialScrollMode)
|
||||
|
||||
setOnClickListener {
|
||||
callback?.onScrollModeChanged(mode)
|
||||
@@ -65,9 +68,16 @@ class MangaScrollModeDialog : BaseBottomSheetDialogFragment(R.layout.dialog_mang
|
||||
}
|
||||
}
|
||||
|
||||
binding.checkboxVolumeKeyPage.apply {
|
||||
isChecked = AppConfig.MangaVolumeKeyPage
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
callback?.onVolumeKeyPageChanged(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.scvPadding.apply {
|
||||
valueFormat = { "$it %" }
|
||||
progress = AppConfig.webtoonSidePaddingDp
|
||||
progress = initialWebtoonSidePadding
|
||||
onChanged = { newValue ->
|
||||
callback?.upSidePadding(newValue)
|
||||
}
|
||||
@@ -96,6 +106,7 @@ class MangaScrollModeDialog : BaseBottomSheetDialogFragment(R.layout.dialog_mang
|
||||
fun onMangaScaleDisabledChanged(disabled: Boolean)
|
||||
fun upSidePadding(padding: Int)
|
||||
fun onHideMangaTitleChanged(hide: Boolean)
|
||||
fun onVolumeKeyPageChanged(enable: Boolean)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.legato.kazusa.ui.book.read.config
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.get
|
||||
@@ -15,6 +16,7 @@ import io.legato.kazusa.databinding.DialogReadBookStyleBinding
|
||||
import io.legato.kazusa.databinding.ItemReadStyleBinding
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.help.config.ReadBookConfig
|
||||
import io.legato.kazusa.help.config.ThemeConfig
|
||||
import io.legato.kazusa.lib.dialogs.selector
|
||||
import io.legato.kazusa.model.ReadBook
|
||||
import io.legato.kazusa.ui.book.read.ReadBookActivity
|
||||
@@ -22,9 +24,8 @@ import io.legato.kazusa.ui.font.FontSelectDialog
|
||||
import io.legato.kazusa.utils.ChineseUtils
|
||||
import io.legato.kazusa.utils.postEvent
|
||||
import io.legato.kazusa.utils.showDialogFragment
|
||||
import io.legato.kazusa.utils.themeColor
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import splitties.views.onLongClick
|
||||
import io.legato.kazusa.help.config.ThemeConfig
|
||||
|
||||
class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_style),
|
||||
FontSelectDialog.CallBack {
|
||||
@@ -229,32 +230,35 @@ class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
binding.apply {
|
||||
ivStyle.setText(item.name.ifBlank { "文字" })
|
||||
ivStyle.setTextColor(item.curTextColor())
|
||||
tvStyle.text = item.name.ifBlank { "文字" }
|
||||
tvStyle.setTextColor(item.curTextColor())
|
||||
ivStyle.setImageDrawable(item.curBgDrawable(100, 150))
|
||||
if (ReadBookConfig.styleSelect == holder.layoutPosition) {
|
||||
//ivStyle.borderColor = accentColor
|
||||
ivStyle.setTextBold(true)
|
||||
llStyle.gravity = Gravity.TOP
|
||||
cdStyle.strokeColor =
|
||||
context.themeColor(com.google.android.material.R.attr.colorTertiary)
|
||||
rvStyle.setCardBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorTertiary))
|
||||
//tvStyle.setTextBold(true)
|
||||
} else {
|
||||
ivStyle.borderColor = item.curTextColor()
|
||||
ivStyle.setTextBold(false)
|
||||
cdStyle.strokeColor = item.curTextColor()
|
||||
//tvStyle.setTextBold(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerListener(holder: ItemViewHolder, binding: ItemReadStyleBinding) {
|
||||
binding.apply {
|
||||
ivStyle.setOnClickListener {
|
||||
if (ivStyle.isInView) {
|
||||
changeBgTextConfig(holder.layoutPosition)
|
||||
}
|
||||
// ivStyle 现在是 MaterialCardView
|
||||
cdStyle.setOnClickListener {
|
||||
changeBgTextConfig(holder.layoutPosition)
|
||||
}
|
||||
ivStyle.onLongClick(ivStyle.isInView) {
|
||||
if (ivStyle.isInView) {
|
||||
showBgTextConfig(holder.layoutPosition)
|
||||
}
|
||||
|
||||
cdStyle.setOnLongClickListener {
|
||||
showBgTextConfig(holder.layoutPosition)
|
||||
true // 表示事件已消费
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
package io.legato.kazusa.ui.book.source.manage
|
||||
|
||||
//import io.legado.app.lib.theme.backgroundColor
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.adapter.ItemViewHolder
|
||||
import io.legato.kazusa.base.adapter.RecyclerAdapter
|
||||
import io.legato.kazusa.data.entities.BookSourcePart
|
||||
import io.legato.kazusa.databinding.ItemBookSourceBinding
|
||||
//import io.legado.app.lib.theme.backgroundColor
|
||||
import io.legato.kazusa.model.Debug
|
||||
import io.legato.kazusa.ui.login.SourceLoginActivity
|
||||
import io.legato.kazusa.ui.widget.recycler.DragSelectTouchHelper
|
||||
@@ -25,6 +24,7 @@ import io.legato.kazusa.utils.buildMainHandler
|
||||
import io.legato.kazusa.utils.gone
|
||||
import io.legato.kazusa.utils.invisible
|
||||
import io.legato.kazusa.utils.startActivity
|
||||
import io.legato.kazusa.utils.themeColor
|
||||
import io.legato.kazusa.utils.visible
|
||||
import java.util.Collections
|
||||
|
||||
@@ -208,20 +208,20 @@ class BookSourceAdapter(
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
private fun upShowExplore(iv: ImageView, source: BookSourcePart) {
|
||||
private fun upShowExplore(iv: MaterialCardView, source: BookSourcePart) {
|
||||
when {
|
||||
!source.hasExploreUrl -> {
|
||||
iv.invisible()
|
||||
}
|
||||
|
||||
source.enabledExplore -> {
|
||||
iv.setColorFilter(Color.GREEN)
|
||||
iv.setCardBackgroundColor(context.themeColor(androidx.appcompat.R.attr.colorPrimary))
|
||||
iv.visible()
|
||||
iv.contentDescription = context.getString(R.string.tag_explore_enabled)
|
||||
}
|
||||
|
||||
else -> {
|
||||
iv.setColorFilter(Color.RED)
|
||||
iv.setCardBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorOnError))
|
||||
iv.visible()
|
||||
iv.contentDescription = context.getString(R.string.tag_explore_disabled)
|
||||
}
|
||||
|
||||
@@ -1,445 +0,0 @@
|
||||
package io.legato.kazusa.ui.widget.image
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.utils.getCompatColor
|
||||
import io.legato.kazusa.utils.printOnDebug
|
||||
import io.legato.kazusa.utils.spToPx
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import androidx.core.graphics.createBitmap
|
||||
import io.legato.kazusa.utils.themeColor
|
||||
import androidx.core.content.withStyledAttributes
|
||||
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
class CircleImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : AppCompatImageView(context, attrs) {
|
||||
|
||||
private val mDrawableRect = RectF()
|
||||
private val mBorderRect = RectF()
|
||||
|
||||
private val mShaderMatrix = Matrix()
|
||||
private val mBitmapPaint = Paint()
|
||||
private val mBorderPaint = Paint()
|
||||
private val mCircleBackgroundPaint = Paint()
|
||||
private val textPaint by lazy {
|
||||
val textPaint = TextPaint()
|
||||
textPaint.isAntiAlias = true
|
||||
textPaint.textAlign = Paint.Align.CENTER
|
||||
textPaint
|
||||
}
|
||||
|
||||
private var mBorderColor = DEFAULT_BORDER_COLOR
|
||||
private var mBorderWidth = DEFAULT_BORDER_WIDTH
|
||||
private var mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR
|
||||
|
||||
private var mBitmap: Bitmap? = null
|
||||
private var mBitmapShader: BitmapShader? = null
|
||||
private var mBitmapWidth: Int = 0
|
||||
private var mBitmapHeight: Int = 0
|
||||
|
||||
private var mDrawableRadius: Float = 0.toFloat()
|
||||
private var mBorderRadius: Float = 0.toFloat()
|
||||
|
||||
private var mColorFilter: ColorFilter? = null
|
||||
|
||||
private var mReady: Boolean = false
|
||||
private var mSetupPending: Boolean = false
|
||||
private var mBorderOverlay: Boolean = false
|
||||
var isDisableCircularTransformation: Boolean = false
|
||||
set(disableCircularTransformation) {
|
||||
if (field == disableCircularTransformation) {
|
||||
return
|
||||
}
|
||||
field = disableCircularTransformation
|
||||
initializeBitmap()
|
||||
}
|
||||
|
||||
var borderColor: Int
|
||||
get() = mBorderColor
|
||||
set(@ColorInt borderColor) {
|
||||
if (borderColor == mBorderColor) {
|
||||
return
|
||||
}
|
||||
|
||||
mBorderColor = borderColor
|
||||
mBorderPaint.color = mBorderColor
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var circleBackgroundColor: Int
|
||||
get() = mCircleBackgroundColor
|
||||
set(@ColorInt circleBackgroundColor) {
|
||||
if (circleBackgroundColor == mCircleBackgroundColor) {
|
||||
return
|
||||
}
|
||||
mCircleBackgroundColor = circleBackgroundColor
|
||||
mCircleBackgroundPaint.color = circleBackgroundColor
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var borderWidth: Int
|
||||
get() = mBorderWidth
|
||||
set(borderWidth) {
|
||||
if (borderWidth == mBorderWidth) {
|
||||
return
|
||||
}
|
||||
|
||||
mBorderWidth = borderWidth
|
||||
setup()
|
||||
}
|
||||
|
||||
var isBorderOverlay: Boolean
|
||||
get() = mBorderOverlay
|
||||
set(borderOverlay) {
|
||||
if (borderOverlay == mBorderOverlay) {
|
||||
return
|
||||
}
|
||||
|
||||
mBorderOverlay = borderOverlay
|
||||
setup()
|
||||
}
|
||||
|
||||
private var text: String? = null
|
||||
|
||||
private var textColor = context.themeColor(com.google.android.material.R.attr.colorOnSurface)
|
||||
private var textBold = false
|
||||
var isInView = false
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs, R.styleable.CircleImageView) {
|
||||
mBorderWidth =
|
||||
getDimensionPixelSize(
|
||||
R.styleable.CircleImageView_civ_border_width,
|
||||
DEFAULT_BORDER_WIDTH
|
||||
)
|
||||
mBorderColor =
|
||||
getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR)
|
||||
mBorderOverlay =
|
||||
getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)
|
||||
mCircleBackgroundColor =
|
||||
getColor(
|
||||
R.styleable.CircleImageView_civ_circle_background_color,
|
||||
DEFAULT_CIRCLE_BACKGROUND_COLOR
|
||||
)
|
||||
text = getString(R.styleable.CircleImageView_text)
|
||||
contentDescription = text
|
||||
if (hasValue(R.styleable.CircleImageView_textColor)) {
|
||||
textColor = getColor(
|
||||
R.styleable.CircleImageView_textColor,
|
||||
context.themeColor(com.google.android.material.R.attr.colorOnSurface)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mReady = true
|
||||
|
||||
if (mSetupPending) {
|
||||
setup()
|
||||
mSetupPending = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
|
||||
if (adjustViewBounds) {
|
||||
throw IllegalArgumentException("adjustViewBounds not supported.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (isDisableCircularTransformation) {
|
||||
super.onDraw(canvas)
|
||||
return
|
||||
}
|
||||
if (mBitmap == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (mCircleBackgroundColor != Color.TRANSPARENT) {
|
||||
canvas.drawCircle(
|
||||
mDrawableRect.centerX(),
|
||||
mDrawableRect.centerY(),
|
||||
mDrawableRadius,
|
||||
mCircleBackgroundPaint
|
||||
)
|
||||
}
|
||||
canvas.drawCircle(
|
||||
mDrawableRect.centerX(),
|
||||
mDrawableRect.centerY(),
|
||||
mDrawableRadius,
|
||||
mBitmapPaint
|
||||
)
|
||||
if (mBorderWidth > 0) {
|
||||
canvas.drawCircle(
|
||||
mBorderRect.centerX(),
|
||||
mBorderRect.centerY(),
|
||||
mBorderRadius,
|
||||
mBorderPaint
|
||||
)
|
||||
}
|
||||
drawText(canvas)
|
||||
}
|
||||
|
||||
private fun drawText(canvas: Canvas) {
|
||||
text?.let {
|
||||
textPaint.color = textColor
|
||||
textPaint.isFakeBoldText = textBold
|
||||
textPaint.textSize = 15f.spToPx()
|
||||
val fm = textPaint.fontMetrics
|
||||
canvas.drawText(
|
||||
it,
|
||||
width * 0.5f,
|
||||
(height * 0.5f + (fm.bottom - fm.top) * 0.5f - fm.bottom),
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setText(text: String?) {
|
||||
this.text = text
|
||||
contentDescription = text
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setTextColor(@ColorInt textColor: Int) {
|
||||
this.textColor = textColor
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setTextBold(bold: Boolean) {
|
||||
this.textBold = bold
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
setup()
|
||||
}
|
||||
|
||||
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.setPadding(left, top, right, bottom)
|
||||
setup()
|
||||
}
|
||||
|
||||
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
|
||||
super.setPaddingRelative(start, top, end, bottom)
|
||||
setup()
|
||||
}
|
||||
|
||||
fun setCircleBackgroundColorResource(@ColorRes circleBackgroundRes: Int) {
|
||||
circleBackgroundColor = context.getCompatColor(circleBackgroundRes)
|
||||
}
|
||||
|
||||
override fun setImageBitmap(bm: Bitmap) {
|
||||
super.setImageBitmap(bm)
|
||||
initializeBitmap()
|
||||
}
|
||||
|
||||
override fun setImageDrawable(drawable: Drawable?) {
|
||||
super.setImageDrawable(drawable)
|
||||
initializeBitmap()
|
||||
}
|
||||
|
||||
override fun setImageResource(@DrawableRes resId: Int) {
|
||||
super.setImageResource(resId)
|
||||
initializeBitmap()
|
||||
}
|
||||
|
||||
override fun setImageURI(uri: Uri?) {
|
||||
super.setImageURI(uri)
|
||||
initializeBitmap()
|
||||
}
|
||||
|
||||
override fun setColorFilter(cf: ColorFilter) {
|
||||
if (cf === mColorFilter) {
|
||||
return
|
||||
}
|
||||
|
||||
mColorFilter = cf
|
||||
applyColorFilter()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun getColorFilter(): ColorFilter? {
|
||||
return mColorFilter
|
||||
}
|
||||
|
||||
private fun applyColorFilter() {
|
||||
mBitmapPaint.colorFilter = mColorFilter
|
||||
}
|
||||
|
||||
private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
|
||||
if (drawable == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (drawable is BitmapDrawable) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
|
||||
return try {
|
||||
val bitmap: Bitmap = if (drawable is ColorDrawable) {
|
||||
createBitmap(COLOR_DRAWABLE_DIMENSION, COLOR_DRAWABLE_DIMENSION, BITMAP_CONFIG)
|
||||
} else {
|
||||
createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, BITMAP_CONFIG)
|
||||
}
|
||||
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
bitmap
|
||||
} catch (e: Exception) {
|
||||
e.printOnDebug()
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun initializeBitmap() {
|
||||
mBitmap = if (isDisableCircularTransformation) {
|
||||
null
|
||||
} else {
|
||||
getBitmapFromDrawable(drawable)
|
||||
}
|
||||
setup()
|
||||
}
|
||||
|
||||
private fun setup() {
|
||||
if (!mReady) {
|
||||
mSetupPending = true
|
||||
return
|
||||
}
|
||||
|
||||
if (width == 0 && height == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (mBitmap == null) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
|
||||
|
||||
mBitmapPaint.isAntiAlias = true
|
||||
mBitmapPaint.shader = mBitmapShader
|
||||
|
||||
mBorderPaint.style = Paint.Style.STROKE
|
||||
mBorderPaint.isAntiAlias = true
|
||||
mBorderPaint.color = mBorderColor
|
||||
mBorderPaint.strokeWidth = mBorderWidth.toFloat()
|
||||
|
||||
mCircleBackgroundPaint.style = Paint.Style.FILL
|
||||
mCircleBackgroundPaint.isAntiAlias = true
|
||||
mCircleBackgroundPaint.color = mCircleBackgroundColor
|
||||
|
||||
mBitmapHeight = mBitmap!!.height
|
||||
mBitmapWidth = mBitmap!!.width
|
||||
|
||||
mBorderRect.set(calculateBounds())
|
||||
mBorderRadius =
|
||||
min(
|
||||
(mBorderRect.height() - mBorderWidth) / 2.0f,
|
||||
(mBorderRect.width() - mBorderWidth) / 2.0f
|
||||
)
|
||||
|
||||
mDrawableRect.set(mBorderRect)
|
||||
if (!mBorderOverlay && mBorderWidth > 0) {
|
||||
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f)
|
||||
}
|
||||
mDrawableRadius = min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f)
|
||||
|
||||
applyColorFilter()
|
||||
updateShaderMatrix()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun calculateBounds(): RectF {
|
||||
val availableWidth = width - paddingLeft - paddingRight
|
||||
val availableHeight = height - paddingTop - paddingBottom
|
||||
|
||||
val sideLength = min(availableWidth, availableHeight)
|
||||
|
||||
val left = paddingLeft + (availableWidth - sideLength) / 2f
|
||||
val top = paddingTop + (availableHeight - sideLength) / 2f
|
||||
|
||||
return RectF(left, top, left + sideLength, top + sideLength)
|
||||
}
|
||||
|
||||
private fun updateShaderMatrix() {
|
||||
val scale: Float
|
||||
var dx = 0f
|
||||
var dy = 0f
|
||||
|
||||
mShaderMatrix.set(null)
|
||||
|
||||
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
|
||||
scale = mDrawableRect.height() / mBitmapHeight.toFloat()
|
||||
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f
|
||||
} else {
|
||||
scale = mDrawableRect.width() / mBitmapWidth.toFloat()
|
||||
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f
|
||||
}
|
||||
|
||||
mShaderMatrix.setScale(scale, scale)
|
||||
mShaderMatrix.postTranslate(
|
||||
(dx + 0.5f).toInt() + mDrawableRect.left,
|
||||
(dy + 0.5f).toInt() + mDrawableRect.top
|
||||
)
|
||||
|
||||
mBitmapShader!!.setLocalMatrix(mShaderMatrix)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
isInView = (inTouchableArea(event.x, event.y))
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
private fun inTouchableArea(x: Float, y: Float): Boolean {
|
||||
return (x - mBorderRect.centerX()).toDouble()
|
||||
.pow(2.0) + (y - mBorderRect.centerY()).toDouble()
|
||||
.pow(2.0) <= mBorderRadius.toDouble().pow(2.0)
|
||||
}
|
||||
|
||||
private inner class OutlineProvider : ViewOutlineProvider() {
|
||||
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
val bounds = Rect()
|
||||
mBorderRect.roundOut(bounds)
|
||||
outline.setRoundRect(bounds, bounds.width() / 2.0f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
|
||||
private const val COLOR_DRAWABLE_DIMENSION = 2
|
||||
private const val DEFAULT_BORDER_WIDTH = 0
|
||||
private const val DEFAULT_BORDER_COLOR = Color.BLACK
|
||||
private const val DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT
|
||||
private const val DEFAULT_BORDER_OVERLAY = false
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user