[Reader] Improved layout config
This commit is contained in:
@@ -137,7 +137,7 @@ dependencies {
|
|||||||
implementation("ru.noties.markwon:core:3.1.0")
|
implementation("ru.noties.markwon:core:3.1.0")
|
||||||
|
|
||||||
implementation("xyz.quaver:documentfilex:0.7.1")
|
implementation("xyz.quaver:documentfilex:0.7.1")
|
||||||
implementation("xyz.quaver:subsampledimage:0.0.1-alpha14-SNAPSHOT-DEV01")
|
implementation("xyz.quaver:subsampledimage:0.0.1-alpha15-SNAPSHOT")
|
||||||
|
|
||||||
implementation("com.google.guava:guava:31.0.1-jre")
|
implementation("com.google.guava:guava:31.0.1-jre")
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ package xyz.quaver.pupil.sources.composable
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.MotionEvent
|
||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.*
|
import androidx.compose.foundation.lazy.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
@@ -34,16 +38,21 @@ import androidx.compose.material.icons.materialIcon
|
|||||||
import androidx.compose.material.icons.materialPath
|
import androidx.compose.material.icons.materialPath
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
@@ -70,12 +79,15 @@ import kotlinx.coroutines.flow.takeWhile
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import org.kodein.log.LoggerFactory
|
import org.kodein.log.LoggerFactory
|
||||||
import org.kodein.log.newLogger
|
import org.kodein.log.newLogger
|
||||||
import xyz.quaver.graphics.subsampledimage.*
|
import xyz.quaver.graphics.subsampledimage.*
|
||||||
|
import xyz.quaver.graphics.subsampledimage.ScaleTypes.CENTER_INSIDE
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.db.AppDatabase
|
import xyz.quaver.pupil.db.AppDatabase
|
||||||
@@ -472,25 +484,11 @@ fun BoxScope.ReaderLazyList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
data class ReaderItemData(
|
||||||
fun ReaderLayoutItem(
|
val index: Int,
|
||||||
modifier: Modifier = Modifier,
|
val size: Size?,
|
||||||
isVertical: Boolean,
|
val imageSource: ImageSource?
|
||||||
content: @Composable (Modifier) -> Unit
|
)
|
||||||
) {
|
|
||||||
if (isVertical)
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
content(modifier.weight(1f))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxHeight()
|
|
||||||
) {
|
|
||||||
content(modifier.weight(1f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@Composable
|
@Composable
|
||||||
@@ -498,37 +496,35 @@ fun ReaderItem(
|
|||||||
model: ReaderBaseViewModel,
|
model: ReaderBaseViewModel,
|
||||||
readerOptions: ReaderOptions,
|
readerOptions: ReaderOptions,
|
||||||
listSize: Size,
|
listSize: Size,
|
||||||
imageSources: List<ImageSource?>,
|
images: List<ReaderItemData>,
|
||||||
|
onTap: () -> Unit = { }
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val (widthDp, heightDp) = LocalDensity.current.run { listSize.width.toDp() to listSize.height.toDp() }
|
||||||
val state = rememberSubSampledImageState(
|
|
||||||
when {
|
|
||||||
readerOptions.padding -> ScaleTypes.CENTER_INSIDE
|
|
||||||
readerOptions.orientation.isVertical -> ScaleTypes.FIT_WIDTH
|
|
||||||
else -> ScaleTypes.FIT_HEIGHT
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val listSizeDp = LocalDensity.current.run { listSize.width.toDp() to listSize.height.toDp() }
|
Row(
|
||||||
|
modifier = when {
|
||||||
|
readerOptions.padding -> Modifier.size(widthDp, heightDp)
|
||||||
|
readerOptions.orientation.isVertical -> Modifier.fillMaxWidth()
|
||||||
|
else -> Modifier.fillMaxHeight()
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
images.let { if (readerOptions.orientation.isReverse) it.reversed() else it }.forEach { (index, imageSize, imageSource) ->
|
||||||
|
val state = rememberSubSampledImageState()
|
||||||
|
|
||||||
val modifier = when {
|
val modifier = when {
|
||||||
readerOptions.padding -> Modifier.size(listSizeDp.first, listSizeDp.second)
|
imageSize == null -> Modifier.weight(1f).height(heightDp)
|
||||||
readerOptions.orientation.isVertical -> Modifier
|
readerOptions.padding -> Modifier.fillMaxHeight().widthIn(0.dp, widthDp/images.size).aspectRatio(imageSize.width/imageSize.height)
|
||||||
.wrapContentHeight(state, listSizeDp.second)
|
else -> Modifier.aspectRatio(imageSize.width/imageSize.height).weight(1f)
|
||||||
.fillMaxWidth()
|
|
||||||
else -> Modifier
|
|
||||||
.wrapContentWidth(state, listSizeDp.first)
|
|
||||||
.fillMaxHeight()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReaderLayoutItem(modifier, readerOptions.orientation.isVertical) { modifier ->
|
|
||||||
indices.forEach { index ->
|
|
||||||
Box(
|
Box(
|
||||||
modifier.border(1.dp, Color.Gray),
|
modifier,
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
val progress = model.progressList.getOrNull(index) ?: 0f
|
val progress = model.progressList.getOrNull(index) ?: 0f
|
||||||
val uri = model.imageList.getOrNull(index)
|
|
||||||
|
|
||||||
if (progress == Float.NEGATIVE_INFINITY)
|
if (progress == Float.NEGATIVE_INFINITY)
|
||||||
Icon(Icons.Filled.BrokenImage, null, tint = Orange500)
|
Icon(Icons.Filled.BrokenImage, null, tint = Orange500)
|
||||||
@@ -539,18 +535,13 @@ fun ReaderItem(
|
|||||||
LinearProgressIndicator(progress)
|
LinearProgressIndicator(progress)
|
||||||
Text((index + 1).toString())
|
Text((index + 1).toString())
|
||||||
}
|
}
|
||||||
else if (uri != null && progress == Float.POSITIVE_INFINITY) {
|
else if (progress == Float.POSITIVE_INFINITY) {
|
||||||
val imageSource = kotlin.runCatching {
|
|
||||||
rememberFileXImageSource(FileX(context, uri))
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
if (imageSource != null)
|
|
||||||
SubSampledImage(
|
SubSampledImage(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.run {
|
.run {
|
||||||
if (model.fullscreen)
|
if (model.fullscreen)
|
||||||
doubleClickCycleZoom(state, 2f)
|
doubleClickCycleZoom(state, 2f, onTap = onTap)
|
||||||
else
|
else
|
||||||
combinedClickable(
|
combinedClickable(
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
@@ -578,24 +569,51 @@ fun LazyListScope.ReaderLazyListContent(
|
|||||||
listSize: Size,
|
listSize: Size,
|
||||||
imageSources: List<ImageSource?>,
|
imageSources: List<ImageSource?>,
|
||||||
imageSizes: List<Size?>,
|
imageSizes: List<Size?>,
|
||||||
readerOptions: ReaderOptions
|
readerOptions: ReaderOptions,
|
||||||
|
onTap: () -> Unit = { }
|
||||||
) {
|
) {
|
||||||
when {
|
when (readerOptions.layout) {
|
||||||
readerOptions.layout == ReaderOptions.Layout.SINGLE_PAGE ->
|
ReaderOptions.Layout.SINGLE_PAGE ->
|
||||||
items(imageSources) { source ->
|
itemsIndexed(imageSources) { index, source ->
|
||||||
ReaderItem(model, readerOptions, listSize, listOf(source))
|
ReaderItem(model, readerOptions, listSize, listOf(ReaderItemData(index, imageSizes[index], source)))
|
||||||
}
|
}
|
||||||
readerOptions.layout == ReaderOptions.Layout.DOUBLE_PAGE ->
|
ReaderOptions.Layout.DOUBLE_PAGE ->
|
||||||
items(imageSources.size/2 + (imageSources.size and 0x1)) { i ->
|
itemsIndexed(imageSources.chunked(2), key = { i, _ -> i*2 }) { chunkIndex, sourceList ->
|
||||||
ReaderItem(model, readerOptions, listSize, imageSources.subList(2*i, 2*i+2))
|
ReaderItem(model, readerOptions, listSize, sourceList.mapIndexed { i, it ->
|
||||||
|
val index = chunkIndex*2+i
|
||||||
|
ReaderItemData(index, imageSizes[index], it)
|
||||||
|
}, onTap)
|
||||||
}
|
}
|
||||||
else ->
|
ReaderOptions.Layout.AUTO -> {
|
||||||
items(imageSources) { source ->
|
val images = mutableListOf<List<Int>>()
|
||||||
ReaderItem(model, readerOptions, listSize, listOf(source))
|
|
||||||
|
var i = 0
|
||||||
|
while (i < imageSizes.size) {
|
||||||
|
val list = mutableListOf(i)
|
||||||
|
|
||||||
|
if (
|
||||||
|
imageSizes[i] != null &&
|
||||||
|
imageSizes.getOrNull(i+1) != null &&
|
||||||
|
listSize != Size.Zero &&
|
||||||
|
imageSizes[i]!!.width*listSize.height/imageSizes[i]!!.height +
|
||||||
|
imageSizes[i+1]!!.width*listSize.height/imageSizes[i+1]!!.height < listSize.width
|
||||||
|
) list.add(++i)
|
||||||
|
|
||||||
|
images.add(list)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
items(images, key = { it.first() }) { images ->
|
||||||
|
ReaderItem(model, readerOptions, listSize, images.map { ReaderItemData(it, imageSizes[it], imageSources[it]) }, onTap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> itemsIndexed(imageSources) { index, source ->
|
||||||
|
ReaderItem(model, readerOptions, listSize, listOf(ReaderItemData(index, imageSizes[index], source)), onTap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalComposeUiApi
|
||||||
@ExperimentalMaterialApi
|
@ExperimentalMaterialApi
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@Composable
|
@Composable
|
||||||
@@ -666,7 +684,10 @@ fun ReaderBase(
|
|||||||
|
|
||||||
val nestedScrollConnection = remember { object: NestedScrollConnection {
|
val nestedScrollConnection = remember { object: NestedScrollConnection {
|
||||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||||
return if (mainReaderOptions.snap) {
|
return if (
|
||||||
|
mainReaderOptions.snap &&
|
||||||
|
listState.layoutInfo.visibleItemsInfo.size > 1
|
||||||
|
) {
|
||||||
val velocity = when (mainReaderOptions.orientation) {
|
val velocity = when (mainReaderOptions.orientation) {
|
||||||
ReaderOptions.Orientation.VERTICAL_DOWN -> available.y
|
ReaderOptions.Orientation.VERTICAL_DOWN -> available.y
|
||||||
ReaderOptions.Orientation.VERTICAL_UP -> -(available.y)
|
ReaderOptions.Orientation.VERTICAL_UP -> -(available.y)
|
||||||
@@ -692,19 +713,23 @@ fun ReaderBase(
|
|||||||
val imageSources = remember { mutableStateListOf<ImageSource?>() }
|
val imageSources = remember { mutableStateListOf<ImageSource?>() }
|
||||||
val imageSizes = remember { mutableStateListOf<Size?>() }
|
val imageSizes = remember { mutableStateListOf<Size?>() }
|
||||||
|
|
||||||
LaunchedEffect(model.imageList.count { it != null }) {
|
LaunchedEffect(model.progressList.count { it.isFinite() }) {
|
||||||
if (imageSources.size != model.imageList.size)
|
val size = model.progressList.size
|
||||||
imageSources.addAll(List (model.imageList.size-imageSources.size) { null })
|
|
||||||
|
|
||||||
if (imageSizes.size != model.imageList.size)
|
if (imageSources.size != size)
|
||||||
imageSizes.addAll(List (model.imageList.size-imageSources.size) { null })
|
imageSources.addAll(List (size-imageSources.size) { null })
|
||||||
|
|
||||||
|
if (imageSizes.size != size)
|
||||||
|
imageSizes.addAll(List (size-imageSizes.size) { null })
|
||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
model.imageList.forEachIndexed { i, uri ->
|
repeat(size) { i ->
|
||||||
|
val uri = model.imageList[i]
|
||||||
|
|
||||||
if (imageSources[i] == null && uri != null)
|
if (imageSources[i] == null && uri != null)
|
||||||
imageSources[i] = FileXImageSource(FileX(context, uri))
|
imageSources[i] = FileXImageSource(FileX(context, uri))
|
||||||
|
|
||||||
if (imageSizes[i] == null)
|
if (imageSizes[i] == null && model.progressList[i] == Float.POSITIVE_INFINITY)
|
||||||
imageSources[i]?.let {
|
imageSources[i]?.let {
|
||||||
imageSizes[i] = it.imageSize
|
imageSizes[i] = it.imageSize
|
||||||
}
|
}
|
||||||
@@ -726,7 +751,11 @@ fun ReaderBase(
|
|||||||
imageSources,
|
imageSources,
|
||||||
imageSizes,
|
imageSizes,
|
||||||
mainReaderOptions
|
mainReaderOptions
|
||||||
)
|
) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
listState.scrollToItem(listState.firstVisibleItemIndex + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.progressList.any { it.isFinite() })
|
if (model.progressList.any { it.isFinite() })
|
||||||
@@ -746,3 +775,43 @@ fun ReaderBase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Modifier.doubleClickCycleZoom(
|
||||||
|
state: SubSampledImageState,
|
||||||
|
scale: Float = 2f,
|
||||||
|
animationSpec: AnimationSpec<Rect> = spring(),
|
||||||
|
onTap: () -> Unit = { },
|
||||||
|
) = composed {
|
||||||
|
val initialImageRect by produceState<Rect?>(null, state.canvasSize, state.imageSize) {
|
||||||
|
state.canvasSize?.let { canvasSize ->
|
||||||
|
state.imageSize?.let { imageSize ->
|
||||||
|
value = state.bound(state.scaleType(canvasSize, imageSize), canvasSize)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onTap = { onTap() },
|
||||||
|
onDoubleTap = { centroid ->
|
||||||
|
val imageRect = state.imageRect
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (imageRect == null || imageRect != initialImageRect)
|
||||||
|
state.resetImageRect(animationSpec)
|
||||||
|
else {
|
||||||
|
state.setImageRectWithBound(
|
||||||
|
Rect(
|
||||||
|
Offset(
|
||||||
|
centroid.x - (centroid.x - imageRect.left) * scale,
|
||||||
|
centroid.y - (centroid.y - imageRect.top) * scale
|
||||||
|
),
|
||||||
|
imageRect.size * scale
|
||||||
|
), animationSpec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.compose.material.icons.filled.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -67,7 +68,7 @@ import xyz.quaver.pupil.sources.hitomi.lib.getReferer
|
|||||||
import xyz.quaver.pupil.sources.hitomi.lib.imageUrlFromImage
|
import xyz.quaver.pupil.sources.hitomi.lib.imageUrlFromImage
|
||||||
import xyz.quaver.pupil.ui.theme.Orange500
|
import xyz.quaver.pupil.ui.theme.Orange500
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||||
class Hitomi(app: Application) : Source(), DIAware {
|
class Hitomi(app: Application) : Source(), DIAware {
|
||||||
override val di by closestDI(app)
|
override val di by closestDI(app)
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
@@ -93,7 +94,12 @@ import kotlin.math.sign
|
|||||||
|
|
||||||
private val imageUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36"
|
private val imageUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36"
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, ExperimentalAnimationApi::class)
|
@OptIn(
|
||||||
|
ExperimentalMaterialApi::class,
|
||||||
|
ExperimentalFoundationApi::class,
|
||||||
|
ExperimentalAnimationApi::class,
|
||||||
|
ExperimentalComposeUiApi::class
|
||||||
|
)
|
||||||
class Manatoki(app: Application) : Source(), DIAware {
|
class Manatoki(app: Application) : Source(), DIAware {
|
||||||
override val di by closestDI(app)
|
override val di by closestDI(app)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user