This commit is contained in:
Finnley Somdahl 2023-12-01 01:22:15 -06:00
parent 1df528c0dc
commit afa960c808
171 changed files with 3458 additions and 1915 deletions

View file

@ -14,8 +14,8 @@ import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.*
import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.settings.CurrentReaderSettings
import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.Glide
@ -23,12 +23,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ani.dantotsu.media.manga.MangaCache
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -118,7 +115,10 @@ abstract class BaseImageAdapter(
abstract suspend fun loadImage(position: Int, parent: View): Boolean
companion object {
suspend fun Context.loadBitmap_old(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? { //still used in some places
suspend fun Context.loadBitmap_old(
link: FileUrl,
transforms: List<BitmapTransformation>
): Bitmap? { //still used in some places
return tryWithSuspend {
withContext(Dispatchers.IO) {
Glide.with(this@loadBitmap_old)
@ -135,8 +135,7 @@ abstract class BaseImageAdapter(
.let {
if (transforms.isNotEmpty()) {
it.transform(*transforms.toTypedArray())
}
else {
} else {
it
}
}
@ -146,7 +145,10 @@ abstract class BaseImageAdapter(
}
}
suspend fun Context.loadBitmap(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? {
suspend fun Context.loadBitmap(
link: FileUrl,
transforms: List<BitmapTransformation>
): Bitmap? {
return tryWithSuspend {
val mangaCache = uy.kohesive.injekt.Injekt.get<MangaCache>()
withContext(Dispatchers.IO) {
@ -161,7 +163,11 @@ abstract class BaseImageAdapter(
.diskCacheStrategy(DiskCacheStrategy.NONE)
} else {
mangaCache.get(link.url)?.let { imageData ->
val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source, context = this@loadBitmap)
val bitmap = imageData.fetchAndProcessImage(
imageData.page,
imageData.source,
context = this@loadBitmap
)
it.load(bitmap)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)

View file

@ -14,9 +14,9 @@ import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.R
import ani.dantotsu.currActivity
import ani.dantotsu.databinding.BottomSheetSelectorBinding
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.others.getSerialized
import ani.dantotsu.tryWith
import kotlinx.coroutines.Dispatchers
@ -29,8 +29,8 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
val model: MediaDetailsViewModel by activityViewModels()
private val launch : Boolean by lazy { arguments?.getBoolean("launch", false) ?: false }
private val chp : MangaChapter by lazy { arguments?.getSerialized("next")!! }
private val launch: Boolean by lazy { arguments?.getBoolean("launch", false) ?: false }
private val chp: MangaChapter by lazy { arguments?.getSerialized("next")!! }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var loaded = false
@ -47,13 +47,21 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
loaded = true
binding.selectorAutoText.text = chp.title
lifecycleScope.launch(Dispatchers.IO) {
if(model.loadMangaChapterImages(chp, m.selected!!, m.nameMAL?:m.nameRomaji)) {
if (model.loadMangaChapterImages(
chp,
m.selected!!,
m.nameMAL ?: m.nameRomaji
)
) {
val activity = currActivity()
activity?.runOnUiThread {
tryWith { dismiss() }
if(launch) {
if (launch) {
MediaSingleton.media = m
val intent = Intent(activity, MangaReaderActivity::class.java)//.apply { putExtra("media", m) }
val intent = Intent(
activity,
MangaReaderActivity::class.java
)//.apply { putExtra("media", m) }
activity.startActivity(intent)
}
}
@ -63,7 +71,11 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
val window = dialog?.window
window?.statusBarColor = Color.TRANSPARENT

View file

@ -30,15 +30,15 @@ open class ImageAdapter(
inner class ImageViewHolder(binding: ItemImageBinding) : RecyclerView.ViewHolder(binding.root)
open suspend fun loadBitmap(position: Int, parent: View) : Bitmap? {
open suspend fun loadBitmap(position: Int, parent: View): Bitmap? {
val link = images.getOrNull(position)?.url ?: return null
if (link.url.isEmpty()) return null
val transforms = mutableListOf<BitmapTransformation>()
val parserTransformation = activity.getTransformation(images[position])
if(parserTransformation!=null) transforms.add(parserTransformation)
if(settings.cropBorders) {
if (parserTransformation != null) transforms.add(parserTransformation)
if (settings.cropBorders) {
transforms.add(RemoveBordersTransformation(true, settings.cropBorderThreshold))
transforms.add(RemoveBordersTransformation(false, settings.cropBorderThreshold))
}
@ -47,7 +47,8 @@ open class ImageAdapter(
}
override suspend fun loadImage(position: Int, parent: View): Boolean {
val imageView = parent.findViewById<SubsamplingScaleImageView>(R.id.imgProgImageNoGestures) ?: return false
val imageView = parent.findViewById<SubsamplingScaleImageView>(R.id.imgProgImageNoGestures)
?: return false
val progress = parent.findViewById<View>(R.id.imgProgProgress) ?: return false
imageView.recycle()
imageView.visibility = View.GONE
@ -60,10 +61,12 @@ open class ImageAdapter(
if (settings.layout != PAGED)
parent.updateLayoutParams {
if (settings.direction != LEFT_TO_RIGHT && settings.direction != RIGHT_TO_LEFT) {
sHeight = if (settings.wrapImages) bitmap.height else (sWidth * bitmap.height * 1f / bitmap.width).toInt()
sHeight =
if (settings.wrapImages) bitmap.height else (sWidth * bitmap.height * 1f / bitmap.width).toInt()
height = sHeight
} else {
sWidth = if (settings.wrapImages) bitmap.width else (sHeight * bitmap.width * 1f / bitmap.height).toInt()
sWidth =
if (settings.wrapImages) bitmap.width else (sHeight * bitmap.width * 1f / bitmap.height).toInt()
width = sWidth
}
}
@ -73,7 +76,8 @@ open class ImageAdapter(
val parentArea = sWidth * sHeight * 1f
val bitmapArea = bitmap.width * bitmap.height * 1f
val scale = if (parentArea < bitmapArea) (bitmapArea / parentArea) else (parentArea / bitmapArea)
val scale =
if (parentArea < bitmapArea) (bitmapArea / parentArea) else (parentArea / bitmapArea)
imageView.maxScale = scale * 1.1f
imageView.minScale = scale

View file

@ -34,12 +34,11 @@ import ani.dantotsu.databinding.ActivityMangaReaderBinding
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.media.anime.ExoplayerView
import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.manga.MangaNameAdapter
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.getSerialized
import ani.dantotsu.others.LangSet
import ani.dantotsu.parsers.HMangaSources
import ani.dantotsu.parsers.MangaImage
import ani.dantotsu.parsers.MangaSources
@ -50,7 +49,6 @@ import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
import ani.dantotsu.settings.ReaderSettings
import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.others.LangSet
import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
@ -58,8 +56,6 @@ import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -262,16 +258,16 @@ class MangaReaderActivity : AppCompatActivity() {
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.title_update_progress)).apply {
setMultiChoiceItems(
arrayOf(getString(R.string.dont_ask_again, media.userPreferredName)),
booleanArrayOf(false)
) { _, _, isChecked ->
if (isChecked) progressDialog = null
saveData("${media.id}_progressDialog", isChecked)
showProgressDialog = isChecked
setMultiChoiceItems(
arrayOf(getString(R.string.dont_ask_again, media.userPreferredName)),
booleanArrayOf(false)
) { _, _, isChecked ->
if (isChecked) progressDialog = null
saveData("${media.id}_progressDialog", isChecked)
showProgressDialog = isChecked
}
setOnCancelListener { hideBars() }
}
setOnCancelListener { hideBars() }
}
else null
//Chapter Change

View file

@ -10,14 +10,15 @@ import kotlin.math.max
class PreloadLinearLayoutManager(context: Context, orientation: Int, reverseLayout: Boolean) :
LinearLayoutManager(context, orientation, reverseLayout) {
private val mOrientationHelper: OrientationHelper = OrientationHelper.createOrientationHelper(this, orientation)
private val mOrientationHelper: OrientationHelper =
OrientationHelper.createOrientationHelper(this, orientation)
/**
* As [LinearLayoutManager.collectAdjacentPrefetchPositions] will prefetch one view for us,
* we only need to prefetch additional ones.
*/
var preloadItemCount = 1
set(count){
set(count) {
require(count >= 1) { "preloadItemCount must not be smaller than 1!" }
field = count - 1
}
@ -37,7 +38,8 @@ class PreloadLinearLayoutManager(context: Context, orientation: Int, reverseLayo
val currentPosition: Int = getPosition(child ?: return) + layoutDirection
if (layoutDirection == 1) {
val scrollingOffset = (mOrientationHelper.getDecoratedEnd(child) - mOrientationHelper.endAfterPadding)
val scrollingOffset =
(mOrientationHelper.getDecoratedEnd(child) - mOrientationHelper.endAfterPadding)
((currentPosition + 1) until (currentPosition + preloadItemCount + 1)).forEach {
if (it >= 0 && it < state.itemCount) {
layoutPrefetchRegistry.addPosition(it, max(0, scrollingOffset))

View file

@ -14,7 +14,11 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetCurrentReaderSettingsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = BottomSheetCurrentReaderSettingsBinding.inflate(inflater, container, false)
return binding.root
}
@ -24,11 +28,14 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
val activity = requireActivity() as MangaReaderActivity
val settings = activity.settings.default
binding.readerDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
binding.readerDirectionText.text =
resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
binding.readerDirection.rotation = 90f * (settings.direction.ordinal)
binding.readerDirection.setOnClickListener {
settings.direction = Directions[settings.direction.ordinal + 1] ?: Directions.TOP_TO_BOTTOM
binding.readerDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
settings.direction =
Directions[settings.direction.ordinal + 1] ?: Directions.TOP_TO_BOTTOM
binding.readerDirectionText.text =
resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
binding.readerDirection.rotation = 90f * (settings.direction.ordinal)
activity.applySettings()
}
@ -39,36 +46,39 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
binding.readerContinuous
)
binding.readerPadding.isEnabled = settings.layout.ordinal!=0
fun paddingAvailable(enable:Boolean){
binding.readerPadding.isEnabled = settings.layout.ordinal != 0
fun paddingAvailable(enable: Boolean) {
binding.readerPadding.isEnabled = enable
}
binding.readerPadding.isChecked = settings.padding
binding.readerPadding.setOnCheckedChangeListener { _,isChecked ->
binding.readerPadding.setOnCheckedChangeListener { _, isChecked ->
settings.padding = isChecked
activity.applySettings()
}
binding.readerCropBorders.isChecked = settings.cropBorders
binding.readerCropBorders.setOnCheckedChangeListener { _,isChecked ->
binding.readerCropBorders.setOnCheckedChangeListener { _, isChecked ->
settings.cropBorders = isChecked
activity.applySettings()
}
binding.readerLayoutText.text = resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
binding.readerLayoutText.text =
resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
var selected = list[settings.layout.ordinal]
selected.alpha = 1f
list.forEachIndexed { index , imageButton ->
list.forEachIndexed { index, imageButton ->
imageButton.setOnClickListener {
selected.alpha = 0.33f
selected = imageButton
selected.alpha = 1f
settings.layout = CurrentReaderSettings.Layouts[index]?:CurrentReaderSettings.Layouts.CONTINUOUS
binding.readerLayoutText.text = resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
settings.layout =
CurrentReaderSettings.Layouts[index] ?: CurrentReaderSettings.Layouts.CONTINUOUS
binding.readerLayoutText.text =
resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
activity.applySettings()
paddingAvailable(settings.layout.ordinal!=0)
paddingAvailable(settings.layout.ordinal != 0)
}
}
@ -87,7 +97,8 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
selectedDual.alpha = 0.33f
selectedDual = imageButton
selectedDual.alpha = 1f
settings.dualPageMode = CurrentReaderSettings.DualPageModes[index] ?: CurrentReaderSettings.DualPageModes.Automatic
settings.dualPageMode = CurrentReaderSettings.DualPageModes[index]
?: CurrentReaderSettings.DualPageModes.Automatic
binding.readerDualPageText.text = settings.dualPageMode.toString()
activity.applySettings()
}
@ -111,37 +122,37 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
}
binding.readerKeepScreenOn.isChecked = settings.keepScreenOn
binding.readerKeepScreenOn.setOnCheckedChangeListener { _,isChecked ->
binding.readerKeepScreenOn.setOnCheckedChangeListener { _, isChecked ->
settings.keepScreenOn = isChecked
activity.applySettings()
}
binding.readerHidePageNumbers.isChecked = settings.hidePageNumbers
binding.readerHidePageNumbers.setOnCheckedChangeListener { _,isChecked ->
binding.readerHidePageNumbers.setOnCheckedChangeListener { _, isChecked ->
settings.hidePageNumbers = isChecked
activity.applySettings()
}
binding.readerOverscroll.isChecked = settings.overScrollMode
binding.readerOverscroll.setOnCheckedChangeListener { _,isChecked ->
binding.readerOverscroll.setOnCheckedChangeListener { _, isChecked ->
settings.overScrollMode = isChecked
activity.applySettings()
}
binding.readerVolumeButton.isChecked = settings.volumeButtons
binding.readerVolumeButton.setOnCheckedChangeListener { _,isChecked ->
binding.readerVolumeButton.setOnCheckedChangeListener { _, isChecked ->
settings.volumeButtons = isChecked
activity.applySettings()
}
binding.readerWrapImage.isChecked = settings.wrapImages
binding.readerWrapImage.setOnCheckedChangeListener { _,isChecked ->
binding.readerWrapImage.setOnCheckedChangeListener { _, isChecked ->
settings.wrapImages = isChecked
activity.applySettings()
}
binding.readerLongClickImage.isChecked = settings.longClickImage
binding.readerLongClickImage.setOnCheckedChangeListener { _,isChecked ->
binding.readerLongClickImage.setOnCheckedChangeListener { _, isChecked ->
settings.longClickImage = isChecked
activity.applySettings()
}
@ -152,7 +163,7 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
super.onDestroy()
}
companion object{
companion object {
fun newInstance() = ReaderSettingsDialogFragment()
}
}

View file

@ -6,7 +6,8 @@ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest
class RemoveBordersTransformation(private val white:Boolean, private val threshHold:Int) : BitmapTransformation() {
class RemoveBordersTransformation(private val white: Boolean, private val threshHold: Int) :
BitmapTransformation() {
override fun transform(
pool: BitmapPool,
@ -95,6 +96,6 @@ class RemoveBordersTransformation(private val white:Boolean, private val threshH
private fun isPixelNotWhite(pixel: Int): Boolean {
val brightness = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel)
return if(white) brightness < (255-threshHold) else brightness > threshHold
return if (white) brightness < (255 - threshHold) else brightness > threshHold
}
}

View file

@ -13,7 +13,7 @@ class Swipy @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
var dragDivider : Int = 5
var dragDivider: Int = 5
var vertical = true
//public, in case a different sub child needs to be considered
@ -100,7 +100,7 @@ class Swipy @JvmOverloads constructor(
}
when (action) {
MotionEvent.ACTION_DOWN -> {
MotionEvent.ACTION_DOWN -> {
activePointerId = ev.getPointerId(0)
isBeingDragged = false
pointerIndex = ev.findPointerIndex(activePointerId)
@ -109,7 +109,8 @@ class Swipy @JvmOverloads constructor(
}
initialDown = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
}
MotionEvent.ACTION_MOVE -> {
MotionEvent.ACTION_MOVE -> {
if (activePointerId == INVALID_POINTER) {
//("Got ACTION_MOVE event but don't have an active pointer id.")
return false
@ -121,7 +122,8 @@ class Swipy @JvmOverloads constructor(
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
startDragging(pos)
}
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isBeingDragged = false
activePointerId = INVALID_POINTER
@ -138,11 +140,12 @@ class Swipy @JvmOverloads constructor(
return false
}
when (action) {
MotionEvent.ACTION_DOWN -> {
MotionEvent.ACTION_DOWN -> {
activePointerId = ev.getPointerId(0)
isBeingDragged = false
}
MotionEvent.ACTION_MOVE -> {
MotionEvent.ACTION_MOVE -> {
pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex < 0) {
//("Got ACTION_MOVE event but have an invalid active pointer id.")
@ -160,16 +163,16 @@ class Swipy @JvmOverloads constructor(
if (overscroll > 0) {
parent.requestDisallowInterceptTouchEvent(true)
if (vertical){
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
if (vertical) {
val totalDragDistance =
Resources.getSystem().displayMetrics.heightPixels / dragDivider
if (verticalPos == VerticalPosition.Top)
topBeingSwiped.invoke(overscroll / totalDragDistance)
else
bottomBeingSwiped.invoke(overscroll / totalDragDistance)
}
else {
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
} else {
val totalDragDistance =
Resources.getSystem().displayMetrics.widthPixels / dragDivider
if (horizontalPos == HorizontalPosition.Left)
leftBeingSwiped.invoke(overscroll / totalDragDistance)
else
@ -180,6 +183,7 @@ class Swipy @JvmOverloads constructor(
}
}
}
MotionEvent.ACTION_POINTER_DOWN -> {
pointerIndex = ev.actionIndex
if (pointerIndex < 0) {
@ -188,8 +192,9 @@ class Swipy @JvmOverloads constructor(
}
activePointerId = ev.getPointerId(pointerIndex)
}
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP -> {
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP -> {
if (vertical) {
topBeingSwiped.invoke(0f)
bottomBeingSwiped.invoke(0f)
@ -216,7 +221,8 @@ class Swipy @JvmOverloads constructor(
activePointerId = INVALID_POINTER
return false
}
MotionEvent.ACTION_CANCEL -> return false
MotionEvent.ACTION_CANCEL -> return false
}
return true
}
@ -235,21 +241,20 @@ class Swipy @JvmOverloads constructor(
private fun finishSpinner(overscrollDistance: Float) {
if (vertical) {
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
if (overscrollDistance > totalDragDistance)
if (verticalPos == VerticalPosition.Top)
onTopSwiped.invoke()
else
onBottomSwiped.invoke()
}
else {
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
if (overscrollDistance > totalDragDistance)
if (vertical) {
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
if (overscrollDistance > totalDragDistance)
if (verticalPos == VerticalPosition.Top)
onTopSwiped.invoke()
else
onBottomSwiped.invoke()
} else {
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
if (overscrollDistance > totalDragDistance)
if (horizontalPos == HorizontalPosition.Left)
onLeftSwiped.invoke()
else
onRightSwiped.invoke()
}
}
}
}