diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index fdd365df..a3d5c4b6 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt index 4fb4a514..9bd6227a 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt @@ -19,7 +19,6 @@ package xyz.quaver.pupil.sources.composable import android.app.Application -import android.content.Intent import android.net.Uri import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.border @@ -35,20 +34,19 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.core.content.FileProvider import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import io.ktor.client.request.* -import io.ktor.client.utils.* import io.ktor.http.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.kodein.di.DIAware import org.kodein.di.android.closestDI import org.kodein.di.instance @@ -56,8 +54,8 @@ import xyz.quaver.graphics.subsampledimage.* import xyz.quaver.io.FileX import xyz.quaver.pupil.R import xyz.quaver.pupil.db.AppDatabase -import xyz.quaver.pupil.util.FileXImageSource import xyz.quaver.pupil.util.NetworkCache +import xyz.quaver.pupil.util.rememberFileXImageSource import kotlin.math.abs open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAware { @@ -78,10 +76,13 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar var imageCount by mutableStateOf(0) - private var images: List? = null val imageList = mutableStateListOf() val progressList = mutableStateListOf() + private val totalProgressMutex = Mutex() + var totalProgress by mutableStateOf(0) + private set + @OptIn(ExperimentalCoroutinesApi::class) fun load(urls: List, headerBuilder: HeadersBuilder.() -> Unit = { }) { viewModelScope.launch { @@ -89,25 +90,36 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar progressList.addAll(List(imageCount) { 0f }) imageList.addAll(List(imageCount) { null }) + totalProgressMutex.withLock { + totalProgress = 0 + } urls.forEachIndexed { index, url -> when (val scheme = url.takeWhile { it != ':' }) { "http", "https" -> { val (channel, file) = cache.load { url(url) - buildHeaders(headerBuilder) + headers(headerBuilder) } if (channel.isClosedForReceive) { imageList[index] = Uri.fromFile(file) + totalProgressMutex.withLock { + totalProgress++ + } } else { channel.invokeOnClose { e -> viewModelScope.launch { if (e == null) { imageList[index] = Uri.fromFile(file) + } else { error(index) } + imageList[index] = Uri.fromFile(file) + totalProgressMutex.withLock { + totalProgress++ + } } } @@ -143,35 +155,13 @@ fun ReaderBase( onToggleBookmark: () -> Unit = { } ) { val context = LocalContext.current + val haptic = LocalHapticFeedback.current var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) } - val imageSources = remember { mutableStateListOf() } - val states = remember { mutableStateListOf() } val scaffoldState = rememberScaffoldState() val snackbarCoroutineScope = rememberCoroutineScope() - LaunchedEffect(model.imageList.count { it != null }) { - if (imageSources.isEmpty() && model.imageList.isNotEmpty()) - imageSources.addAll(List(model.imageList.size) { null }) - - if (states.isEmpty() && model.imageList.isNotEmpty()) - states.addAll(List(model.imageList.size) { - SubSampledImageState(ScaleTypes.FIT_WIDTH, Bounds.FORCE_OVERLAP_OR_CENTER).apply { - isGestureEnabled = true - } - }) - - model.imageList.forEachIndexed { i, image -> - if (imageSources[i] == null && image != null) - imageSources[i] = kotlin.runCatching { - FileXImageSource(FileX(context, image)) - }.onFailure { - model.error(i) - }.getOrNull() - } - } - if (model.error) stringResource(R.string.reader_failed_to_find_gallery).let { snackbarCoroutineScope.launch { @@ -224,15 +214,17 @@ fun ReaderBase( Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(4.dp) ) { - itemsIndexed(imageSources) { i, imageSource -> + itemsIndexed(model.imageList) { i, uri -> + val state = rememberSubSampledImageState(ScaleTypes.FIT_WIDTH) + Box( Modifier - .wrapContentHeight(states[i], 500.dp) + .wrapContentHeight(state, 500.dp) .fillMaxWidth() .border(1.dp, Color.Gray), contentAlignment = Alignment.Center ) { - if (imageSource == null) + if (uri == null) model.progressList.getOrNull(i)?.let { progress -> if (progress < 0f) Icon(Icons.Filled.BrokenImage, null) @@ -245,55 +237,37 @@ fun ReaderBase( } } else { - val haptic = LocalHapticFeedback.current + val imageSource = kotlin.runCatching { + rememberFileXImageSource(FileX(context, uri)) + }.getOrNull() - SubSampledImage( - modifier = Modifier - .fillMaxSize() - .run { - if (model.isFullscreen) - doubleClickCycleZoom(states[i], 2f) - else - combinedClickable( - onLongClick = { - haptic.performHapticFeedback( - HapticFeedbackType.LongPress - ) + if (imageSource == null) + Icon(Icons.Default.BrokenImage, contentDescription = null) + else + SubSampledImage( + modifier = Modifier + .fillMaxSize() + .run { + if (model.isFullscreen) + doubleClickCycleZoom(state, 2f) + else + combinedClickable( + onLongClick = { - // TODO - val uri = FileProvider.getUriForFile( - context, - "xyz.quaver.pupil.fileprovider", - (imageSource as FileXImageSource).file - ) - context.startActivity( - Intent.createChooser( - Intent( - Intent.ACTION_SEND - ).apply { - type = "image/*" - putExtra( - Intent.EXTRA_STREAM, - uri - ) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - }, "Share image" - ) - ) + } + ) { + model.isFullscreen = true } - ) { - model.isFullscreen = true - } - }, - imageSource = imageSource, - state = states[i] - ) + }, + imageSource = imageSource, + state = state + ) } } } } - if (model.progressList.any { abs(it) != 1f }) + if (model.totalProgress != model.imageCount) LinearProgressIndicator( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt index e6c88ac2..432b344f 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt @@ -59,7 +59,7 @@ class Hitomi(app: Application) : Source(), DIAware { override fun MainScreen(navController: NavController) { navController.navigate("search/hitomi.la") { launchSingleTop = true - popUpTo("main") { inclusive = true } + navController.popBackStack() } }