Improve manga link sharing

master
Koitharu 1 year ago
parent b3028258ca
commit f689bf0cf7
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -6,6 +6,7 @@ import android.text.SpannableStringBuilder
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.collection.MutableObjectIntMap import androidx.collection.MutableObjectIntMap
import androidx.core.net.toUri
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.strikeThrough import androidx.core.text.strikeThrough
@ -125,7 +126,8 @@ val Manga.isBroken: Boolean
get() = source == UnknownMangaSource get() = source == UnknownMangaSource
val Manga.appUrl: Uri val Manga.appUrl: Uri
get() = Uri.parse("https://kotatsu.app/manga").buildUpon() get() = "https://kotatsu.app/manga".toUri()
.buildUpon()
.appendQueryParameter("source", source.name) .appendQueryParameter("source", source.name)
.appendQueryParameter("name", title) .appendQueryParameter("name", title)
.appendQueryParameter("url", url) .appendQueryParameter("url", url)

@ -12,6 +12,8 @@ import android.provider.Settings
import android.view.View import android.view.View
import androidx.annotation.CheckResult import androidx.annotation.CheckResult
import androidx.annotation.UiContext import androidx.annotation.UiContext
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -29,7 +31,10 @@ import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.MangaSourceInfo import org.koitharu.kotatsu.core.model.MangaSourceInfo
import org.koitharu.kotatsu.core.model.appUrl
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.isBroken
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaListFilter import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaListFilter
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage
@ -43,6 +48,8 @@ import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.core.util.ext.connectivityManager import org.koitharu.kotatsu.core.util.ext.connectivityManager
import org.koitharu.kotatsu.core.util.ext.findActivity import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.getThemeDrawable
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
import org.koitharu.kotatsu.core.util.ext.toUriOrNull import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@ -72,6 +79,7 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.parsers.util.isNullOrEmpty import org.koitharu.kotatsu.parsers.util.isNullOrEmpty
import org.koitharu.kotatsu.parsers.util.mapToArray import org.koitharu.kotatsu.parsers.util.mapToArray
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
@ -96,6 +104,8 @@ import org.koitharu.kotatsu.stats.ui.StatsActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
import java.io.File
import com.google.android.material.R as materialR
class AppRouter private constructor( class AppRouter private constructor(
private val activity: FragmentActivity?, private val activity: FragmentActivity?,
@ -391,6 +401,37 @@ class AppRouter private constructor(
}.show() }.show()
} }
fun showShareDialog(manga: Manga) {
if (manga.isBroken) {
return
}
if (manga.isLocal) {
manga.url.toUri().toFileOrNull()?.let {
shareFile(it)
}
return
}
buildAlertDialog(contextOrNull() ?: return) {
setIcon(context.getThemeDrawable(materialR.attr.actionModeShareDrawable))
setTitle(R.string.share)
setItems(
arrayOf(
context.getString(R.string.link_to_manga_in_app),
context.getString(R.string.link_to_manga_on_s, manga.source.getTitle(context)),
),
) { _, which ->
val link = when (which) {
0 -> manga.appUrl.toString()
1 -> manga.publicUrl
else -> return@setItems
}
shareLink(link, manga.title)
}
setNegativeButton(android.R.string.cancel, null)
setCancelable(true)
}.show()
}
fun showErrorDialog(error: Throwable, url: String? = null) { fun showErrorDialog(error: Throwable, url: String? = null) {
ErrorDetailsDialog().withArgs(2) { ErrorDetailsDialog().withArgs(2) {
putSerializable(KEY_ERROR, error) putSerializable(KEY_ERROR, error)
@ -565,6 +606,25 @@ class AppRouter private constructor(
return fragment?.childFragmentManager ?: activity?.supportFragmentManager return fragment?.childFragmentManager ?: activity?.supportFragmentManager
} }
private fun shareLink(link: String, title: String) {
val context = contextOrNull() ?: return
ShareCompat.IntentBuilder(context)
.setText(link)
.setType(TYPE_TEXT)
.setChooserTitle(context.getString(R.string.share_s, title.ellipsize(12)))
.startChooser()
}
private fun shareFile(file: File) { // TODO directory sharing support
val context = contextOrNull() ?: return
val intentBuilder = ShareCompat.IntentBuilder(context)
.setType(TYPE_CBZ)
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", file)
intentBuilder.addStream(uri)
intentBuilder.setChooserTitle(context.getString(R.string.share_s, file.name))
intentBuilder.startChooser()
}
@UiContext @UiContext
private fun contextOrNull(): Context? = activity ?: fragment?.context private fun contextOrNull(): Context? = activity ?: fragment?.context
@ -726,6 +786,10 @@ class AppRouter private constructor(
private const val ACTION_ACCOUNT_SYNC_SETTINGS = "android.settings.ACCOUNT_SYNC_SETTINGS" private const val ACTION_ACCOUNT_SYNC_SETTINGS = "android.settings.ACCOUNT_SYNC_SETTINGS"
private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args" private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
private const val TYPE_TEXT = "text/plain"
private const val TYPE_IMAGE = "image/*"
private const val TYPE_CBZ = "application/x-cbz"
private fun Class<out Fragment>.fragmentTag() = name // TODO private fun Class<out Fragment>.fragmentTag() = name // TODO
private inline fun <reified F : Fragment> fragmentTag() = F::class.java.fragmentTag() private inline fun <reified F : Fragment> fragmentTag() = F::class.java.fragmentTag()

@ -15,6 +15,7 @@ private const val TYPE_TEXT = "text/plain"
private const val TYPE_IMAGE = "image/*" private const val TYPE_IMAGE = "image/*"
private const val TYPE_CBZ = "application/x-cbz" private const val TYPE_CBZ = "application/x-cbz"
@Deprecated("")
class ShareHelper(private val context: Context) { class ShareHelper(private val context: Context) {
fun shareMangaLink(manga: Manga) { fun shareMangaLink(manga: Manga) {

@ -5,20 +5,16 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
class DetailsMenuProvider( class DetailsMenuProvider(
private val activity: FragmentActivity, private val activity: FragmentActivity,
@ -47,23 +43,16 @@ class DetailsMenuProvider(
val manga = viewModel.getMangaOrNull() ?: return false val manga = viewModel.getMangaOrNull() ?: return false
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_share -> { R.id.action_share -> {
val shareHelper = ShareHelper(activity) activity.router.showShareDialog(manga)
if (manga.isLocal) {
shareHelper.shareCbz(listOf(manga.url.toUri().toFile()))
} else {
shareHelper.shareMangaLink(manga)
}
} }
R.id.action_delete -> { R.id.action_delete -> {
MaterialAlertDialogBuilder(activity) buildAlertDialog(activity) {
.setTitle(R.string.delete_manga) setTitle(R.string.delete_manga)
.setMessage(activity.getString(R.string.text_delete_local_manga, manga.title)) setMessage(activity.getString(R.string.text_delete_local_manga, manga.title))
.setPositiveButton(R.string.delete) { _, _ -> setPositiveButton(R.string.delete) { _, _ -> viewModel.deleteLocal() }
viewModel.deleteLocal() setNegativeButton(android.R.string.cancel, null)
} }.show()
.setNegativeButton(android.R.string.cancel, null)
.show()
} }
R.id.action_save -> { R.id.action_save -> {

@ -814,4 +814,6 @@
<string name="error_disclaimer_manga">Try to open manga in a web browser to ensure it is available on its source.</string> <string name="error_disclaimer_manga">Try to open manga in a web browser to ensure it is available on its source.</string>
<string name="error_disclaimer_app_outdated">It looks like your version of Kotatsu is out of date. Please install the latest version to get all available fixes.</string> <string name="error_disclaimer_app_outdated">It looks like your version of Kotatsu is out of date. Please install the latest version to get all available fixes.</string>
<string name="error_disclaimer_report">You can submit a bug report to the developers. This will help us investigate and fix the issue.</string> <string name="error_disclaimer_report">You can submit a bug report to the developers. This will help us investigate and fix the issue.</string>
<string name="link_to_manga_on_s">Link to manga on %s</string>
<string name="link_to_manga_in_app">Link to manga in Kotatsu</string>
</resources> </resources>

@ -31,7 +31,7 @@ material = "1.13.0-alpha11"
moshi = "1.15.2" moshi = "1.15.2"
okhttp = "4.12.0" okhttp = "4.12.0"
okio = "3.10.2" okio = "3.10.2"
parsers = "d5a4cf68c6" parsers = "e83636edc0"
preference = "1.2.1" preference = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
room = "2.6.1" room = "2.6.1"

Loading…
Cancel
Save