新增漫画可保存图片
This commit is contained in:
@@ -39,19 +39,21 @@ import io.legato.kazusa.help.book.isImage
|
||||
import io.legato.kazusa.help.book.removeType
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.help.storage.Backup
|
||||
import io.legato.kazusa.lib.dialogs.SelectItem
|
||||
import io.legato.kazusa.lib.dialogs.alert
|
||||
import io.legato.kazusa.lib.dialogs.selector
|
||||
import io.legato.kazusa.model.ReadManga
|
||||
import io.legato.kazusa.receiver.NetworkChangedListener
|
||||
import io.legato.kazusa.ui.book.changesource.ChangeBookSourceDialog
|
||||
import io.legato.kazusa.ui.book.info.BookInfoActivity
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaAutoReadDialog
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaClickActionConfigDialog
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaColorFilterConfig
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaColorFilterDialog
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaFooterConfig
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaFooterSettingDialog
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaScrollMode
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaScrollModeDialog
|
||||
import io.legato.kazusa.ui.book.manga.entities.BaseMangaPage
|
||||
import io.legato.kazusa.ui.book.manga.entities.MangaFooterConfig
|
||||
import io.legato.kazusa.ui.book.manga.entities.MangaPage
|
||||
import io.legato.kazusa.ui.book.manga.recyclerview.MangaAdapter
|
||||
import io.legato.kazusa.ui.book.manga.recyclerview.MangaLayoutManager
|
||||
@@ -85,7 +87,7 @@ import kotlin.math.ceil
|
||||
class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewModel>(),
|
||||
ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack,
|
||||
MangaColorFilterDialog.Callback, ScrollTimer.ScrollCallback, MangaFooterSettingDialog.Callback,
|
||||
MangaScrollModeDialog.Callback {
|
||||
MangaAutoReadDialog.Callback {
|
||||
|
||||
private val mLayoutManager by lazy {
|
||||
MangaLayoutManager(this)
|
||||
@@ -161,21 +163,16 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
|
||||
addTarget(binding.root)
|
||||
duration = 700
|
||||
val transform = MaterialContainerTransform().apply {
|
||||
addTarget(binding.rootView)
|
||||
scrimColor = Color.TRANSPARENT
|
||||
fadeMode = MaterialContainerTransform.FADE_MODE_THROUGH
|
||||
}
|
||||
window.sharedElementReturnTransition = MaterialContainerTransform().apply {
|
||||
addTarget(binding.root)
|
||||
duration = 700
|
||||
scrimColor = Color.BLACK
|
||||
fadeMode = MaterialContainerTransform.FADE_MODE_THROUGH
|
||||
duration = 500
|
||||
}
|
||||
window.sharedElementEnterTransition = transform
|
||||
window.sharedElementReturnTransition = transform
|
||||
upLayoutInDisplayCutoutMode()
|
||||
super.onCreate(savedInstanceState)
|
||||
binding.root.transitionName = intent.getStringExtra("transitionName")
|
||||
binding.rootView.transitionName = intent.getStringExtra("transitionName")
|
||||
ReadManga.register(this)
|
||||
upSystemUiVisibility(false)
|
||||
initRecyclerView()
|
||||
@@ -279,6 +276,26 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
}
|
||||
|
||||
binding.webtoonFrame.longPressListener = {
|
||||
val centerPos = binding.recyclerView.findCenterViewPosition()
|
||||
val currentItem = mAdapter.getItem(centerPos)
|
||||
val page = currentItem as? MangaPage
|
||||
page?.let { item ->
|
||||
selector(
|
||||
arrayListOf(
|
||||
SelectItem("保存", "save"),
|
||||
SelectItem("分享", "share")
|
||||
)
|
||||
) { _, option, _ ->
|
||||
when (option.value) {
|
||||
"save" -> viewModel.saveImageToGallery(item.mImageUrl)
|
||||
// "share" -> shareImage(item.mImageUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@@ -585,7 +602,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
|
||||
override fun showScrollModeDialog() {
|
||||
showDialogFragment(MangaScrollModeDialog().apply {
|
||||
showDialogFragment(MangaAutoReadDialog().apply {
|
||||
initialAutoPageEnabled = enableScroll
|
||||
callback = this@ReadMangaActivity
|
||||
})
|
||||
@@ -638,9 +655,9 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
|
||||
//自动翻页
|
||||
override fun onAutoPageToggle(enabled: Boolean) {
|
||||
enableScroll = enabled
|
||||
setAutoReadEnabled(enabled)
|
||||
override fun onAutoPageToggle(enable: Boolean) {
|
||||
enableScroll = enable
|
||||
setAutoReadEnabled(enable)
|
||||
}
|
||||
|
||||
//自动翻页速度
|
||||
@@ -648,7 +665,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
setAutoReadEnabled(false)
|
||||
AppConfig.mangaAutoPageSpeed = speed
|
||||
mScrollTimer.setSpeed(speed)
|
||||
setAutoReadEnabled(true)
|
||||
setAutoReadEnabled(enableScroll)
|
||||
// if (enableAutoScrollPage) {
|
||||
// mScrollTimer.isEnabledPage = true
|
||||
// }
|
||||
@@ -708,23 +725,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
val keyCode = event.keyCode
|
||||
val action = event.action
|
||||
val isDown = action == 0
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
if (isDown && !binding.mangaMenu.canShowMenu) {
|
||||
binding.mangaMenu.runMenuIn()
|
||||
return true
|
||||
}
|
||||
if (!isDown && !binding.mangaMenu.canShowMenu) {
|
||||
binding.mangaMenu.canShowMenu = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整漫画类型
|
||||
|
||||
@@ -2,6 +2,9 @@ package io.legato.kazusa.ui.book.manga
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bumptech.glide.Glide
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseViewModel
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
@@ -24,9 +27,11 @@ import io.legato.kazusa.help.coroutine.Coroutine
|
||||
import io.legato.kazusa.model.ReadManga
|
||||
import io.legato.kazusa.model.localBook.LocalBook
|
||||
import io.legato.kazusa.model.webBook.WebBook
|
||||
import io.legato.kazusa.utils.ImageSaveUtils
|
||||
import io.legato.kazusa.utils.mapParallelSafe
|
||||
import io.legato.kazusa.utils.postEvent
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@@ -35,7 +40,10 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onEmpty
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import splitties.init.appCtx
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class ReadMangaViewModel(application: Application) : BaseViewModel(application) {
|
||||
|
||||
@@ -283,6 +291,39 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
|
||||
return ReadManga.book?.readConfig?.webtoonSidePaddingDp ?: AppConfig.webtoonSidePaddingDp
|
||||
}
|
||||
|
||||
fun saveImageToGallery(url: String, folderName: String = "Legado") {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val byteArray = withContext(Dispatchers.IO) {
|
||||
// 用 Glide 下载 Bitmap 转换成 ByteArray
|
||||
val bitmap = Glide.with(getApplication<Application>().applicationContext)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
val baos = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
|
||||
baos.toByteArray()
|
||||
}
|
||||
|
||||
val context = getApplication<Application>().applicationContext
|
||||
val success = ImageSaveUtils.saveImageToGallery(
|
||||
context,
|
||||
byteArray,
|
||||
folderName = folderName
|
||||
)
|
||||
|
||||
if (success) {
|
||||
context.toastOnUi("图片已保存到相册")
|
||||
} else {
|
||||
context.toastOnUi("保存失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
context.toastOnUi("保存出错: ${e.localizedMessage}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun updateReadConfig(update: ReadConfig.() -> Unit) {
|
||||
val book = ReadManga.book ?: return
|
||||
|
||||
+3
-3
@@ -4,13 +4,13 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseBottomSheetDialogFragment
|
||||
import io.legato.kazusa.databinding.DialogMangaScrollModeBinding
|
||||
import io.legato.kazusa.databinding.DialogMangaAutoReadBinding
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
|
||||
class MangaScrollModeDialog : BaseBottomSheetDialogFragment(R.layout.dialog_manga_scroll_mode) {
|
||||
class MangaAutoReadDialog : BaseBottomSheetDialogFragment(R.layout.dialog_manga_auto_read) {
|
||||
|
||||
private val binding by viewBinding(DialogMangaScrollModeBinding::bind)
|
||||
private val binding by viewBinding(DialogMangaAutoReadBinding::bind)
|
||||
|
||||
var initialAutoPageEnabled: Boolean = false
|
||||
|
||||
+10
-1
@@ -4,12 +4,15 @@ import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseBottomSheetDialogFragment
|
||||
import io.legato.kazusa.constant.EventBus
|
||||
import io.legato.kazusa.databinding.DialogMangaFooterSettingBinding
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.ui.book.manga.entities.MangaFooterConfig
|
||||
import io.legato.kazusa.ui.widget.ReaderInfoBarView
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
@@ -46,10 +49,17 @@ class MangaFooterSettingDialog :
|
||||
|
||||
setOnClickListener {
|
||||
callback?.onScrollModeChanged(mode)
|
||||
TransitionManager.beginDelayedTransition(binding.rootView)
|
||||
binding.llWebtoon.isVisible = mode == MangaScrollMode.WEBTOON ||
|
||||
mode == MangaScrollMode.WEBTOON_WITH_GAP
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
binding.llWebtoon.isVisible = initialScrollMode == MangaScrollMode.WEBTOON ||
|
||||
initialScrollMode == MangaScrollMode.WEBTOON_WITH_GAP
|
||||
|
||||
|
||||
binding.scvPadding.apply {
|
||||
valueFormat = { "$it %" }
|
||||
progress = initialWebtoonSidePadding
|
||||
@@ -200,7 +210,6 @@ class MangaFooterSettingDialog :
|
||||
}
|
||||
|
||||
private fun updateChapterText() {
|
||||
|
||||
if (config.hideFooter) {
|
||||
binding.tvChapter.visibility = View.INVISIBLE
|
||||
return
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package io.legato.kazusa.ui.book.manga.config
|
||||
package io.legato.kazusa.ui.book.manga.entities
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import io.legato.kazusa.ui.widget.ReaderInfoBarView
|
||||
@@ -11,7 +11,7 @@ data class MangaFooterConfig(
|
||||
var hidePageNumber: Boolean = false,
|
||||
var hideProgressRatioLabel: Boolean = false,
|
||||
var hideProgressRatio: Boolean = false,
|
||||
var footerOrientation: Int = ReaderInfoBarView.ALIGN_LEFT,//默认靠左
|
||||
var footerOrientation: Int = ReaderInfoBarView.Companion.ALIGN_LEFT,//默认靠左
|
||||
var hideFooter: Boolean = false,
|
||||
var hideChapterName:Boolean=false,
|
||||
)
|
||||
@@ -8,7 +8,9 @@ import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.ScaleGestureDetector
|
||||
import android.widget.FrameLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.utils.findCenterViewPosition
|
||||
|
||||
class WebtoonFrame : FrameLayout {
|
||||
|
||||
@@ -20,6 +22,9 @@ class WebtoonFrame : FrameLayout {
|
||||
defStyle
|
||||
)
|
||||
|
||||
/** 长按回调,返回手指坐标 */
|
||||
var longPressListener: ((centerPosition: Int) -> Unit)? = null
|
||||
|
||||
private val scaleDetector = ScaleGestureDetector(context, ScaleListener())
|
||||
private val flingDetector = GestureDetector(context, FlingListener())
|
||||
private val regionRects = mutableMapOf<String, RectF>()
|
||||
@@ -37,9 +42,22 @@ class WebtoonFrame : FrameLayout {
|
||||
get() = getChildAt(0) as? WebtoonRecyclerView
|
||||
|
||||
var disabledClickScroll = false
|
||||
|
||||
var actionHandler: ClickActionHandler? = null
|
||||
|
||||
private val gestureDetector =
|
||||
GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
recycler?.let {
|
||||
val centerPos = it.findCenterViewPosition()
|
||||
if (centerPos != RecyclerView.NO_POSITION) {
|
||||
longPressListener?.invoke(centerPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDown(e: MotionEvent): Boolean = true
|
||||
})
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
recycler?.tapListener = tapListener@{ ev ->
|
||||
@@ -63,7 +81,6 @@ class WebtoonFrame : FrameLayout {
|
||||
|
||||
performClickAction(action)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
@@ -84,8 +101,9 @@ class WebtoonFrame : FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
gestureDetector.onTouchEvent(ev) // 捕获长按
|
||||
|
||||
if (!disableMangaScale) {
|
||||
scaleDetector.onTouchEvent(ev)
|
||||
flingDetector.onTouchEvent(ev)
|
||||
@@ -99,7 +117,7 @@ class WebtoonFrame : FrameLayout {
|
||||
|
||||
ev.setLocation(
|
||||
ev.x.coerceIn(recyclerRect.left.toFloat(), recyclerRect.right.toFloat()),
|
||||
ev.y.coerceIn(recyclerRect.top.toFloat(), recyclerRect.bottom.toFloat()),
|
||||
ev.y.coerceIn(recyclerRect.top.toFloat(), recyclerRect.bottom.toFloat())
|
||||
)
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
@@ -134,15 +152,13 @@ class WebtoonFrame : FrameLayout {
|
||||
}
|
||||
|
||||
inner class FlingListener : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onDown(e: MotionEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun onDown(e: MotionEvent): Boolean = true
|
||||
|
||||
override fun onFling(
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float,
|
||||
velocityY: Float
|
||||
): Boolean {
|
||||
return recycler?.zoomFling(velocityX.toInt(), velocityY.toInt()) ?: false
|
||||
}
|
||||
@@ -155,5 +171,4 @@ class WebtoonFrame : FrameLayout {
|
||||
fun nextChapter()
|
||||
fun prevChapter()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user