Save and share manga cover #253
parent
1e1e9fabdc
commit
bf8838f943
@ -0,0 +1,68 @@
|
|||||||
|
package org.koitharu.kotatsu.image.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||||
|
import org.koitharu.kotatsu.local.data.isZipUri
|
||||||
|
|
||||||
|
class ImageMenuProvider(
|
||||||
|
private val activity: ComponentActivity,
|
||||||
|
private val snackbarHost: View,
|
||||||
|
private val viewModel: ImageViewModel,
|
||||||
|
) : MenuProvider {
|
||||||
|
|
||||||
|
private val permissionLauncher = activity.registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission(),
|
||||||
|
) { isGranted ->
|
||||||
|
if (isGranted) {
|
||||||
|
saveImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val saveLauncher = activity.registerForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument("image/png"),
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
viewModel.saveImage(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
menuInflater.inflate(R.menu.opt_image, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||||
|
R.id.action_save -> {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
} else {
|
||||||
|
saveImage()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveImage() {
|
||||||
|
val name = activity.intent.data?.let {
|
||||||
|
if (it.isZipUri()) {
|
||||||
|
it.fragment
|
||||||
|
} else {
|
||||||
|
it.lastPathSegment
|
||||||
|
}?.substringBeforeLast('.')?.plus(".png")
|
||||||
|
}
|
||||||
|
if (name == null || !saveLauncher.tryLaunch(name)) {
|
||||||
|
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package org.koitharu.kotatsu.image.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class ImageViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
private val coil: ImageLoader,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val onImageSaved = MutableEventFlow<Uri>()
|
||||||
|
|
||||||
|
fun saveImage(destination: Uri) {
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.memoryCachePolicy(CachePolicy.READ_ONLY)
|
||||||
|
.data(savedStateHandle.require<Uri>(BaseActivity.EXTRA_DATA))
|
||||||
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.source(savedStateHandle[ImageActivity.EXTRA_SOURCE])
|
||||||
|
.build()
|
||||||
|
val bitmap = coil.execute(request).getDrawableOrThrow().toBitmap()
|
||||||
|
runInterruptible(Dispatchers.IO) {
|
||||||
|
context.contentResolver.openOutputStream(destination)?.use { output ->
|
||||||
|
check(bitmap.compress(Bitmap.CompressFormat.PNG, 100, output))
|
||||||
|
} ?: error("Cannot open output stream")
|
||||||
|
}
|
||||||
|
onImageSaved.call(destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_save"
|
||||||
|
android:title="@string/save" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
Loading…
Reference in New Issue