Refactor navigation

master
Koitharu 1 year ago
parent a5199e2f06
commit f675c606a2
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.alternatives.ui package org.koitharu.kotatsu.alternatives.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
@ -13,8 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
@ -22,7 +19,6 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivityAlternativesBinding import org.koitharu.kotatsu.databinding.ActivityAlternativesBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
@ -30,8 +26,6 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -65,7 +59,7 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
viewModel.content.observe(this, listAdapter) viewModel.content.observe(this, listAdapter)
viewModel.onMigrated.observeEvent(this) { viewModel.onMigrated.observeEvent(this) {
Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show()
startActivity(DetailsActivity.newIntent(this, it)) router.openDetails(it)
finishAfterTransition() finishAfterTransition()
} }
} }
@ -82,16 +76,9 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
override fun onItemClick(item: MangaAlternativeModel, view: View) { override fun onItemClick(item: MangaAlternativeModel, view: View) {
when (view.id) { when (view.id) {
R.id.chip_source -> startActivity( R.id.chip_source -> router.openSearch(item.manga.source, viewModel.manga.title)
MangaListActivity.newIntent(
this,
item.manga.source,
MangaListFilter(query = viewModel.manga.title),
),
)
R.id.button_migrate -> confirmMigration(item.manga) R.id.button_migrate -> confirmMigration(item.manga)
else -> startActivity(DetailsActivity.newIntent(this, item.manga)) else -> router.openDetails(item.manga)
} }
} }
@ -114,10 +101,4 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
} }
}.show() }.show()
} }
companion object {
fun newIntent(context: Context, manga: Manga) = Intent(context, AlternativesActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
} }

@ -13,7 +13,7 @@ import org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase
import org.koitharu.kotatsu.alternatives.domain.MigrateUseCase import org.koitharu.kotatsu.alternatives.domain.MigrateUseCase
import org.koitharu.kotatsu.core.model.chaptersCount import org.koitharu.kotatsu.core.model.chaptersCount
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -40,7 +40,7 @@ class AlternativesViewModel @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
val onMigrated = MutableEventFlow<Manga>() val onMigrated = MutableEventFlow<Manga>()
val content = MutableStateFlow<List<ListModel>>(listOf(LoadingState)) val content = MutableStateFlow<List<ListModel>>(listOf(LoadingState))

@ -10,7 +10,6 @@ import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat import androidx.core.app.PendingIntentCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import coil3.ImageLoader import coil3.ImageLoader
import coil3.request.ImageRequest import coil3.request.ImageRequest
@ -20,13 +19,13 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase import org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase
import org.koitharu.kotatsu.core.ErrorReporterReceiver import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.CoroutineIntentService import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject import javax.inject.Inject
@ -122,7 +121,7 @@ class AutoFixService : CoroutineIntentService() {
).toBitmapOrNull(), ).toBitmapOrNull(),
) )
notification.setSubText(replacement.title) notification.setSubText(replacement.title)
val intent = DetailsActivity.newIntent(applicationContext, replacement) val intent = AppRouter.detailsIntent(applicationContext, replacement)
notification.setContentIntent( notification.setContentIntent(
PendingIntentCompat.getActivity( PendingIntentCompat.getActivity(
applicationContext, applicationContext,

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.bookmarks.ui package org.koitharu.kotatsu.bookmarks.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
@ -46,9 +44,4 @@ class AllBookmarksActivity :
right = insets.right, right = insets.right,
) )
} }
companion object {
fun newIntent(context: Context) = Intent(context, AllBookmarksActivity::class.java)
}
} }

@ -20,6 +20,8 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
@ -30,7 +32,6 @@ import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentListSimpleBinding import org.koitharu.kotatsu.databinding.FragmentListSimpleBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.GridSpanResolver import org.koitharu.kotatsu.list.ui.GridSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener import org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
@ -39,7 +40,6 @@ import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -115,26 +115,26 @@ class AllBookmarksFragment :
override fun onItemClick(item: Bookmark, view: View) { override fun onItemClick(item: Bookmark, view: View) {
if (selectionController?.onItemClick(item.pageId) != true) { if (selectionController?.onItemClick(item.pageId) != true) {
val intent = ReaderActivity.IntentBuilder(view.context) val intent = ReaderIntent.Builder(view.context)
.bookmark(item) .bookmark(item)
.incognito(true) .incognito(true)
.build() .build()
startActivity(intent) router.openReader(intent)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show() Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
} }
} }
override fun onListHeaderClick(item: ListHeader, view: View) { override fun onListHeaderClick(item: ListHeader, view: View) {
val manga = item.payload as? Manga ?: return val manga = item.payload as? Manga ?: return
startActivity(DetailsActivity.newIntent(view.context, manga)) router.openDetails(manga)
} }
override fun onItemLongClick(item: Bookmark, view: View): Boolean { override fun onItemLongClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemLongClick(view, item.pageId) ?: false return selectionController?.onItemLongClick(view, item.pageId) == true
} }
override fun onItemContextClick(item: Bookmark, view: View): Boolean { override fun onItemContextClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.pageId) ?: false return selectionController?.onItemContextClick(view, item.pageId) == true
} }
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
@ -208,16 +208,4 @@ class AllBookmarksFragment :
invalidateSpanIndexCache() invalidateSpanIndexCache()
} }
} }
companion object {
@Deprecated(
"",
ReplaceWith(
"BookmarksFragment()",
"org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment",
),
)
fun newInstance() = AllBookmarksFragment()
}
} }

@ -1,9 +1,5 @@
package org.koitharu.kotatsu.browser package org.koitharu.kotatsu.browser
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -11,17 +7,18 @@ import android.webkit.CookieManager
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -42,7 +39,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
setDisplayHomeAsUpEnabled(true) setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material) setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
} }
val mangaSource = MangaSource(intent?.getStringExtra(EXTRA_SOURCE)) val mangaSource = MangaSource(intent?.getStringExtra(AppRouter.KEY_SOURCE))
val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository
val userAgent = repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT) val userAgent = repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)
viewBinding.webView.configureForParser(userAgent) viewBinding.webView.configureForParser(userAgent)
@ -59,7 +56,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
finishAfterTransition() finishAfterTransition()
} else { } else {
onTitleChanged( onTitleChanged(
intent?.getStringExtra(EXTRA_TITLE) ?: getString(R.string.loading_), intent?.getStringExtra(AppRouter.KEY_TITLE) ?: getString(R.string.loading_),
url, url,
) )
viewBinding.webView.loadUrl(url) viewBinding.webView.loadUrl(url)
@ -80,14 +77,8 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
} }
R.id.action_browser -> { R.id.action_browser -> {
val url = viewBinding.webView.url?.toUriOrNull() if (!router.openExternalBrowser(viewBinding.webView.url.orEmpty(), item.title)) {
if (url != null) { Snackbar.make(viewBinding.webView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
val intent = Intent(Intent.ACTION_VIEW)
intent.data = url
try {
startActivity(Intent.createChooser(intent, item.title))
} catch (_: ActivityNotFoundException) {
}
} }
true true
} }
@ -136,17 +127,4 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
bottom = insets.bottom, bottom = insets.bottom,
) )
} }
companion object {
private const val EXTRA_TITLE = "title"
private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, url: String, source: MangaSource?, title: String?): Intent {
return Intent(context, BrowserActivity::class.java)
.setData(Uri.parse(url))
.putExtra(EXTRA_TITLE, title)
.putExtra(EXTRA_SOURCE, source?.name)
}
}
} }

@ -17,6 +17,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
@ -38,7 +39,7 @@ class CaptchaNotifier(
.build() .build()
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
val intent = CloudFlareActivity.newIntent(context, exception) val intent = AppRouter.cloudFlareResolveIntent(context, exception)
.setData(exception.url.toUri()) .setData(exception.url.toUri())
val notification = NotificationCompat.Builder(context, CHANNEL_ID) val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(channel.name) .setContentTitle(channel.name)

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.browser.cloudflare package org.koitharu.kotatsu.browser.cloudflare
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -9,7 +8,6 @@ import android.view.MenuItem
import android.webkit.CookieManager import android.webkit.CookieManager
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.net.toUri
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -19,19 +17,17 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -62,7 +58,7 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
return return
} }
cfClient = CloudFlareClient(cookieJar, this, url) cfClient = CloudFlareClient(cookieJar, this, url)
viewBinding.webView.configureForParser(intent?.getStringExtra(ARG_UA)) viewBinding.webView.configureForParser(intent?.getStringExtra(AppRouter.KEY_USER_AGENT))
viewBinding.webView.webViewClient = cfClient viewBinding.webView.webViewClient = cfClient
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also { onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also {
onBackPressedDispatcher.addCallback(it) onBackPressedDispatcher.addCallback(it)
@ -140,7 +136,7 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
override fun onCheckPassed() { override fun onCheckPassed() {
pendingResult = RESULT_OK pendingResult = RESULT_OK
val source = intent?.getStringExtra(ARG_SOURCE) val source = intent?.getStringExtra(AppRouter.KEY_SOURCE)
if (source != null) { if (source != null) {
CaptchaNotifier(this).dismiss(MangaSource(source)) CaptchaNotifier(this).dismiss(MangaSource(source))
} }
@ -182,38 +178,16 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
class Contract : ActivityResultContract<CloudFlareProtectedException, Boolean>() { class Contract : ActivityResultContract<CloudFlareProtectedException, Boolean>() {
override fun createIntent(context: Context, input: CloudFlareProtectedException): Intent { override fun createIntent(context: Context, input: CloudFlareProtectedException): Intent {
return newIntent(context, input) return AppRouter.cloudFlareResolveIntent(context, input)
} }
override fun parseResult(resultCode: Int, intent: Intent?): Boolean { override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
return resultCode == Activity.RESULT_OK return resultCode == RESULT_OK
} }
} }
companion object { companion object {
const val TAG = "CloudFlareActivity" const val TAG = "CloudFlareActivity"
private const val ARG_UA = "ua"
private const val ARG_SOURCE = "_source"
fun newIntent(context: Context, exception: CloudFlareProtectedException) = newIntent(
context = context,
url = exception.url,
source = exception.source,
headers = exception.headers,
)
private fun newIntent(
context: Context,
url: String,
source: MangaSource?,
headers: Headers?,
) = Intent(context, CloudFlareActivity::class.java).apply {
data = url.toUri()
putExtra(ARG_SOURCE, source?.name)
headers?.get(CommonHeaders.USER_AGENT)?.let {
putExtra(ARG_UA, it)
}
}
} }
} }

@ -8,6 +8,7 @@ import android.net.Uri
import android.os.BadParcelableException import android.os.BadParcelableException
import androidx.core.app.PendingIntentCompat import androidx.core.app.PendingIntentCompat
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.report import org.koitharu.kotatsu.core.util.ext.report
@ -15,20 +16,19 @@ import org.koitharu.kotatsu.core.util.ext.report
class ErrorReporterReceiver : BroadcastReceiver() { class ErrorReporterReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
val e = intent?.getSerializableExtraCompat<Throwable>(EXTRA_ERROR) ?: return val e = intent?.getSerializableExtraCompat<Throwable>(AppRouter.KEY_ERROR) ?: return
e.report() e.report()
} }
companion object { companion object {
private const val EXTRA_ERROR = "err"
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR" private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = try { fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = try {
val intent = Intent(context, ErrorReporterReceiver::class.java) val intent = Intent(context, ErrorReporterReceiver::class.java)
intent.setAction(ACTION_REPORT) intent.setAction(ACTION_REPORT)
intent.setData(Uri.parse("err://${e.hashCode()}")) intent.setData(Uri.parse("err://${e.hashCode()}"))
intent.putExtra(EXTRA_ERROR, e) intent.putExtra(AppRouter.KEY_ERROR, e)
PendingIntentCompat.getBroadcast(context, 0, intent, 0, false) PendingIntentCompat.getBroadcast(context, 0, intent, 0, false)
} catch (e: BadParcelableException) { } catch (e: BadParcelableException) {
e.printStackTraceDebug() e.printStackTraceDebug()

@ -1,11 +1,7 @@
package org.koitharu.kotatsu.core.backup package org.koitharu.kotatsu.core.backup
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import androidx.annotation.CheckResult
import android.widget.Toast
import androidx.annotation.UiContext
import androidx.core.net.toUri
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -17,6 +13,7 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.closeQuietly import okhttp3.internal.closeQuietly
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.network.BaseHttpClient import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.parsers.util.await
@ -56,16 +53,11 @@ class TelegramBackupUploader @Inject constructor(
sendMessage(context.getString(R.string.backup_tg_echo)) sendMessage(context.getString(R.string.backup_tg_echo))
} }
@SuppressLint("UnsafeImplicitIntentLaunch") @CheckResult
fun openBotInApp(@UiContext context: Context): Boolean { fun openBotInApp(router: AppRouter): Boolean {
val botUsername = context.getString(R.string.tg_backup_bot_name) val botUsername = context.getString(R.string.tg_backup_bot_name)
return runCatching { return router.openExternalBrowser("tg://resolve?domain=$botUsername") ||
context.startActivity(Intent(Intent.ACTION_VIEW, "tg://resolve?domain=$botUsername".toUri())) router.openExternalBrowser("https://t.me/$botUsername")
}.recoverCatching {
context.startActivity(Intent(Intent.ACTION_VIEW, "https://t.me/$botUsername".toUri()))
}.onFailure {
Toast.makeText(context, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
}.isSuccess
} }
private suspend fun sendMessage(message: String) { private suspend fun sendMessage(message: String) {

@ -6,7 +6,6 @@ import androidx.core.util.Consumer
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
@ -32,10 +31,10 @@ class DialogErrorObserver(
if (canResolve(value)) { if (canResolve(value)) {
dialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(value), listener) dialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(value), listener)
} else if (value is ParseException) { } else if (value is ParseException) {
val fm = fragmentManager val router = router()
if (fm != null && value.isSerializable()) { if (router != null && value.isSerializable()) {
dialogBuilder.setPositiveButton(R.string.details) { _, _ -> dialogBuilder.setPositiveButton(R.string.details) { _, _ ->
ErrorDetailsDialog.show(fm, value, value.url) router.showErrorDialog(value)
} }
} }
} }

@ -4,6 +4,7 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.util.Consumer import androidx.core.util.Consumer
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -11,6 +12,7 @@ import androidx.lifecycle.coroutineScope
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.util.ext.findActivity import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
@ -33,6 +35,8 @@ abstract class ErrorObserver(
return resolver != null && ExceptionResolver.canResolve(error) return resolver != null && ExceptionResolver.canResolve(error)
} }
protected fun router() = fragment?.router ?: (activity as? FragmentActivity)?.router
private fun isAlive(): Boolean { private fun isAlive(): Boolean {
return when { return when {
fragment != null -> fragment.view != null fragment != null -> fragment.view != null
@ -44,7 +48,7 @@ abstract class ErrorObserver(
protected fun resolve(error: Throwable) { protected fun resolve(error: Throwable) {
if (isAlive()) { if (isAlive()) {
lifecycleScope.launch { lifecycleScope.launch {
val isResolved = resolver?.resolve(error) ?: false val isResolved = resolver?.resolve(error) == true
if (isActive) { if (isActive) {
onResolved?.accept(isResolved) onResolved?.accept(isResolved)
} }

@ -5,19 +5,20 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultCaller import androidx.activity.result.ActivityResultCaller
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.collection.MutableScatterMap import androidx.collection.MutableScatterMap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity 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.exceptions.ProxyConfigException import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
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.restartApplication import org.koitharu.kotatsu.core.util.ext.restartApplication
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
@ -26,7 +27,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException
import org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper import org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import java.security.cert.CertPathValidatorException import java.security.cert.CertPathValidatorException
import javax.inject.Provider import javax.inject.Provider
@ -49,8 +49,8 @@ class ExceptionResolver @AssistedInject constructor(
handleActivityResult(CloudFlareActivity.TAG, it) handleActivityResult(CloudFlareActivity.TAG, it)
} }
fun showDetails(e: Throwable, url: String?) { fun showErrorDetails(e: Throwable, url: String? = null) {
ErrorDetailsDialog.show(host.getChildFragmentManager(), e, url) host.router()?.showErrorDialog(e, url)
} }
suspend fun resolve(e: Throwable): Boolean = when (e) { suspend fun resolve(e: Throwable): Boolean = when (e) {
@ -63,9 +63,7 @@ class ExceptionResolver @AssistedInject constructor(
} }
is ProxyConfigException -> { is ProxyConfigException -> {
host.withContext { host.router()?.openProxySettings()
startActivity(SettingsActivity.newProxySettingsIntent(this))
}
false false
} }
@ -85,9 +83,7 @@ class ExceptionResolver @AssistedInject constructor(
true true
} else { } else {
host.withContext { host.withContext {
authHelper.startAuth(this, e.scrobbler).onFailure { authHelper.startAuth(this, e.scrobbler).onFailure(::showErrorDetails)
showDetails(it, null)
}
} }
false false
} }
@ -106,12 +102,12 @@ class ExceptionResolver @AssistedInject constructor(
sourceAuthContract.launch(source) sourceAuthContract.launch(source)
} }
private fun openInBrowser(url: String) = host.withContext { private fun openInBrowser(url: String) {
startActivity(BrowserActivity.newIntent(this, url, null, null)) host.router()?.openBrowser(url, null, null)
} }
private fun openAlternatives(manga: Manga) = host.withContext { private fun openAlternatives(manga: Manga) {
startActivity(AlternativesActivity.newIntent(this, manga)) host.router()?.openAlternatives(manga)
} }
private fun handleActivityResult(tag: String, result: Boolean) { private fun handleActivityResult(tag: String, result: Boolean) {
@ -140,6 +136,12 @@ class ExceptionResolver @AssistedInject constructor(
getContext()?.apply(block) getContext()?.apply(block)
} }
private fun Host.router(): AppRouter? = when (this) {
is FragmentActivity -> router
is Fragment -> router
else -> null
}
interface Host : ActivityResultCaller { interface Host : ActivityResultCaller {
fun getChildFragmentManager(): FragmentManager fun getChildFragmentManager(): FragmentManager

@ -5,7 +5,6 @@ import androidx.core.util.Consumer
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
@ -33,10 +32,10 @@ class SnackbarErrorObserver(
resolve(value) resolve(value)
} }
} else if (value is ParseException) { } else if (value is ParseException) {
val fm = fragmentManager val router = router()
if (fm != null && value.isSerializable()) { if (router != null && value.isSerializable()) {
snackbar.setAction(R.string.details) { snackbar.setAction(R.string.details) {
ErrorDetailsDialog.show(fm, value, value.url) router.showErrorDialog(value)
} }
} }
} }

@ -0,0 +1,602 @@
package org.koitharu.kotatsu.core.nav
import android.accounts.Account
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.view.View
import androidx.annotation.CheckResult
import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.findFragment
import androidx.lifecycle.LifecycleOwner
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.MangaSourceInfo
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaListFilter
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.mapToArray
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
import org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingInfoSheet
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteDialog
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment
import org.koitharu.kotatsu.filter.ui.tags.TagsCatalogSheet
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.ui.config.ListConfigBottomSheet
import org.koitharu.kotatsu.list.ui.config.ListConfigSection
import org.koitharu.kotatsu.local.ui.ImportDialogFragment
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.main.ui.welcome.WelcomeSheet
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.isNullOrEmpty
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.ui.config.ScrobblerConfigActivity
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.multi.SearchActivity
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.about.AppUpdateActivity
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
import org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity
import org.koitharu.kotatsu.settings.storage.MangaDirectorySelectDialog
import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity
import org.koitharu.kotatsu.settings.tracker.categories.TrackerCategoriesConfigSheet
import org.koitharu.kotatsu.stats.ui.StatsActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
class AppRouter private constructor(
private val activity: FragmentActivity?,
private val fragment: Fragment?,
) {
constructor(activity: FragmentActivity) : this(activity, null)
constructor(fragment: Fragment) : this(null, fragment)
/** Activities **/
fun openList(source: MangaSource, filter: MangaListFilter?) {
startActivity(listIntent(contextOrNull() ?: return, source, filter))
}
fun openList(tag: MangaTag) = openList(tag.source, MangaListFilter(tags = setOf(tag)))
fun openSearch(query: String) {
startActivity(
Intent(contextOrNull() ?: return, SearchActivity::class.java)
.putExtra(KEY_QUERY, query),
)
}
fun openSearch(source: MangaSource, query: String) = openList(source, MangaListFilter(query = query))
fun openDetails(manga: Manga) {
startActivity(detailsIntent(contextOrNull() ?: return, manga))
}
fun openDetails(mangaId: Long) {
startActivity(detailsIntent(contextOrNull() ?: return, mangaId))
}
fun openReader(manga: Manga, anchor: View? = null) {
openReader(
ReaderIntent.Builder(contextOrNull() ?: return)
.manga(manga)
.build(),
anchor,
)
}
fun openReader(intent: ReaderIntent, anchor: View? = null) {
startActivity(intent.intent, anchor?.let { view -> scaleUpActivityOptionsOf(view) })
}
fun openAlternatives(manga: Manga) {
startActivity(
Intent(contextOrNull() ?: return, AlternativesActivity::class.java)
.putExtra(KEY_MANGA, ParcelableManga(manga)),
)
}
fun openRelated(manga: Manga) {
startActivity(
Intent(contextOrNull(), RelatedMangaActivity::class.java)
.putExtra(KEY_MANGA, ParcelableManga(manga)),
)
}
fun openImage(url: String, source: MangaSource?, anchor: View? = null) {
startActivity(
Intent(contextOrNull(), ImageActivity::class.java)
.setData(url.toUri())
.putExtra(KEY_SOURCE, source?.name),
anchor?.let { scaleUpActivityOptionsOf(it) },
)
}
fun openBookmarks() = startActivity(AllBookmarksActivity::class.java)
fun openAppUpdate() = startActivity(AppUpdateActivity::class.java)
fun openSuggestions() {
startActivity(suggestionsIntent(contextOrNull() ?: return))
}
fun openSourcesCatalog() = startActivity(SourcesCatalogActivity::class.java)
fun openDownloads() = startActivity(DownloadsActivity::class.java)
fun openDirectoriesSettings() = startActivity(MangaDirectoriesActivity::class.java)
fun openBrowser(url: String, source: MangaSource?, title: String?) {
startActivity(
Intent(contextOrNull() ?: return, BrowserActivity::class.java)
.setData(url.toUri())
.putExtra(KEY_TITLE, title)
.putExtra(KEY_SOURCE, source?.name),
)
}
fun openColorFilterConfig(manga: Manga, page: MangaPage) {
startActivity(
Intent(contextOrNull(), ColorFilterConfigActivity::class.java)
.putExtra(KEY_MANGA, ParcelableManga(manga))
.putExtra(KEY_PAGES, ParcelableMangaPage(page)),
)
}
fun openHistory() = startActivity(HistoryActivity::class.java)
fun openFavorites() = startActivity(FavouritesActivity::class.java)
fun openFavorites(category: FavouriteCategory) {
startActivity(
Intent(contextOrNull() ?: return, FavouritesActivity::class.java)
.putExtra(KEY_ID, category.id)
.putExtra(KEY_TITLE, category.title),
)
}
fun openFavoriteCategories() = startActivity(FavouriteCategoriesActivity::class.java)
fun openFavoriteCategoryEdit(categoryId: Long) {
startActivity(
Intent(contextOrNull() ?: return, FavouritesCategoryEditActivity::class.java)
.putExtra(KEY_ID, categoryId),
)
}
fun openFavoriteCategoryCreate() = openFavoriteCategoryEdit(FavouritesCategoryEditActivity.NO_ID)
fun openMangaUpdates() {
startActivity(mangaUpdatesIntent(contextOrNull() ?: return))
}
fun openSettings() = startActivity(SettingsActivity::class.java)
fun openReaderSettings() {
startActivity(readerSettingsIntent(contextOrNull() ?: return))
}
fun openProxySettings() {
startActivity(proxySettingsIntent(contextOrNull() ?: return))
}
fun openDownloadsSetting() {
startActivity(downloadsSettingsIntent(contextOrNull() ?: return))
}
fun openSourceSettings(source: MangaSource) {
startActivity(sourceSettingsIntent(contextOrNull() ?: return, source))
}
fun openSuggestionsSettings() {
startActivity(suggestionsSettingsIntent(contextOrNull() ?: return))
}
fun openSourcesSettings() {
startActivity(sourcesSettingsIntent(contextOrNull() ?: return))
}
fun openReaderTapGridSettings() = startActivity(ReaderTapGridConfigActivity::class.java)
fun openScrobblerSettings(scrobbler: ScrobblerService) {
startActivity(
Intent(contextOrNull() ?: return, ScrobblerConfigActivity::class.java)
.putExtra(KEY_ID, scrobbler.id),
)
}
fun openSourceAuth(source: MangaSource) {
startActivity(sourceAuthIntent(contextOrNull() ?: return, source))
}
fun openManageSources() {
startActivity(
manageSourcesIntent(contextOrNull() ?: return),
)
}
fun openStatistic() = startActivity(StatsActivity::class.java)
@CheckResult
fun openExternalBrowser(url: String, chooserTitle: CharSequence? = null): Boolean {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = url.toUriOrNull() ?: return false
return startActivitySafe(
if (!chooserTitle.isNullOrEmpty()) {
Intent.createChooser(intent, chooserTitle)
} else {
intent
},
)
}
@CheckResult
fun openSystemSyncSettings(account: Account): Boolean {
val args = Bundle(1)
args.putParcelable(ACCOUNT_KEY, account)
val intent = Intent(ACTION_ACCOUNT_SYNC_SETTINGS)
intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args)
return startActivitySafe(intent)
}
/** Dialogs **/
fun showDownloadDialog(manga: Manga, snackbarHost: View?) = showDownloadDialog(setOf(manga), snackbarHost)
fun showDownloadDialog(manga: Collection<Manga>, snackbarHost: View?) {
if (manga.isEmpty()) {
return
}
val fm = getFragmentManager() ?: return
if (snackbarHost != null) {
getLifecycleOwner()?.let { lifecycleOwner ->
DownloadDialogFragment.registerCallback(fm, lifecycleOwner, snackbarHost)
}
} else {
DownloadDialogFragment.unregisterCallback(fm)
}
DownloadDialogFragment().withArgs(1) {
putParcelableArray(KEY_MANGA, manga.mapToArray { ParcelableManga(it) })
}.showDistinct()
}
fun showLocalInfoDialog(manga: Manga) {
LocalInfoDialog().withArgs(1) {
putParcelable(KEY_MANGA, ParcelableManga(manga))
}.showDistinct()
}
fun showDirectorySelectDialog() {
MangaDirectorySelectDialog().showDistinct()
}
fun showFavoriteDialog(manga: Manga) = showFavoriteDialog(setOf(manga))
fun showFavoriteDialog(manga: Collection<Manga>) {
if (manga.isEmpty()) {
return
}
FavoriteDialog().withArgs(1) {
putParcelableArrayList(
KEY_MANGA_LIST,
manga.mapTo(ArrayList(manga.size), ::ParcelableManga),
)
}.showDistinct()
}
fun showErrorDialog(error: Throwable, url: String? = null) {
ErrorDetailsDialog().withArgs(2) {
putSerializable(KEY_ERROR, error)
putString(KEY_URL, url)
}.show()
}
fun showBackupRestoreDialog(fileUri: Uri) {
RestoreDialogFragment().withArgs(1) {
putString(KEY_FILE, fileUri.toString())
}.show()
}
fun showBackupCreateDialog() {
BackupDialogFragment().show()
}
fun showImportDialog() {
ImportDialogFragment().showDistinct()
}
fun showFilterSheet(): Boolean = if (isFilterSupported()) {
FilterSheetFragment().showDistinct()
} else {
false
}
fun showTagsCatalogSheet(excludeMode: Boolean) {
if (!isFilterSupported()) {
return
}
TagsCatalogSheet().withArgs(1) {
putBoolean(KEY_EXCLUDE, excludeMode)
}.showDistinct()
}
fun showListConfigSheet(section: ListConfigSection) {
ListConfigBottomSheet().withArgs(1) {
putParcelable(KEY_LIST_SECTION, section)
}.showDistinct()
}
fun showStatisticSheet(manga: Manga) {
MangaStatsSheet().withArgs(1) {
putParcelable(KEY_MANGA, ParcelableManga(manga))
}.showDistinct()
}
fun showReaderConfigSheet(mode: ReaderMode) {
ReaderConfigSheet().withArgs(1) {
putInt(KEY_READER_MODE, mode.id)
}.showDistinct()
}
fun showWelcomeSheet() {
WelcomeSheet().showDistinct()
}
fun showChapterPagesSheet() {
ChaptersPagesSheet().showDistinct()
}
fun showChapterPagesSheet(defaultTab: Int) {
ChaptersPagesSheet().withArgs(1) {
putInt(KEY_TAB, defaultTab)
}.showDistinct()
}
fun showScrobblingSelectorSheet(manga: Manga, scrobblerService: ScrobblerService?) {
ScrobblingSelectorSheet().withArgs(2) {
putParcelable(KEY_MANGA, ParcelableManga(manga))
if (scrobblerService != null) {
putInt(KEY_ID, scrobblerService.id)
}
}.show()
}
fun showScrobblingInfoSheet(index: Int) {
ScrobblingInfoSheet().withArgs(1) {
putInt(KEY_INDEX, index)
}.showDistinct()
}
fun showTrackerCategoriesConfigSheet() {
TrackerCategoriesConfigSheet().showDistinct()
}
/** Public utils **/
fun isFilterSupported(): Boolean = when {
fragment != null -> fragment.activity is FilterCoordinator.Owner
activity != null -> activity is FilterCoordinator.Owner
else -> false
}
fun isChapterPagesSheetShown(): Boolean {
val sheet = getFragmentManager()?.findFragmentByTag(fragmentTag<ChaptersPagesSheet>()) as? ChaptersPagesSheet
return sheet?.dialog?.isShowing == true
}
fun closeWelcomeSheet(): Boolean {
val fm = fragment?.parentFragmentManager ?: activity?.supportFragmentManager ?: return false
val sheet = fm.findFragmentByTag(fragmentTag<WelcomeSheet>()) as? WelcomeSheet ?: return false
sheet.dismissAllowingStateLoss()
return true
}
/** Private utils **/
private fun startActivity(intent: Intent, options: Bundle? = null) {
fragment?.startActivity(intent, options)
?: activity?.startActivity(intent, options)
}
private fun startActivitySafe(intent: Intent): Boolean = try {
startActivity(intent)
true
} catch (_: ActivityNotFoundException) {
false
}
private fun startActivity(activityClass: Class<out Activity>) {
startActivity(Intent(contextOrNull() ?: return, activityClass))
}
private fun getFragmentManager(): FragmentManager? {
return fragment?.childFragmentManager ?: activity?.supportFragmentManager
}
private fun contextOrNull(): Context? = activity ?: fragment?.context
private fun getLifecycleOwner(): LifecycleOwner? = activity ?: fragment?.viewLifecycleOwner
private fun DialogFragment.showDistinct(): Boolean {
val fm = this@AppRouter.getFragmentManager() ?: return false
val tag = javaClass.fragmentTag()
val existing = fm.findFragmentByTag(tag) as? DialogFragment?
if (existing != null && existing.isVisible && existing.arguments == this.arguments) {
return false
}
show(fm, tag)
return true
}
private fun DialogFragment.show() {
show(
this@AppRouter.getFragmentManager() ?: return,
javaClass.fragmentTag(),
)
}
companion object {
fun from(view: View): AppRouter? = runCatching {
AppRouter(view.findFragment<Fragment>())
}.getOrElse {
(view.context.findActivity() as? FragmentActivity)?.let(::AppRouter)
}
fun detailsIntent(context: Context, manga: Manga) = Intent(context, DetailsActivity::class.java)
.putExtra(KEY_MANGA, ParcelableManga(manga))
fun detailsIntent(context: Context, mangaId: Long) = Intent(context, DetailsActivity::class.java)
.putExtra(KEY_ID, mangaId)
fun listIntent(context: Context, source: MangaSource, filter: MangaListFilter?): Intent =
Intent(context, MangaListActivity::class.java)
.setAction(ACTION_MANGA_EXPLORE)
.putExtra(KEY_SOURCE, source.name)
.apply {
if (!filter.isNullOrEmpty()) {
putExtra(KEY_FILTER, ParcelableMangaListFilter(filter))
}
}
fun cloudFlareResolveIntent(context: Context, exception: CloudFlareProtectedException): Intent =
Intent(context, CloudFlareActivity::class.java).apply {
data = exception.url.toUri()
putExtra(KEY_SOURCE, exception.source?.name)
exception.headers.get(CommonHeaders.USER_AGENT)?.let {
putExtra(KEY_USER_AGENT, it)
}
}
fun suggestionsIntent(context: Context) = Intent(context, SuggestionsActivity::class.java)
fun mangaUpdatesIntent(context: Context) = Intent(context, UpdatesActivity::class.java)
fun readerSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_READER)
fun suggestionsSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SUGGESTIONS)
fun trackerSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_TRACKER)
fun proxySettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_PROXY)
fun historySettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_HISTORY)
fun sourcesSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SOURCES)
fun manageSourcesIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_MANAGE_SOURCES)
fun downloadsSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_MANAGE_DOWNLOADS)
fun sourceSettingsIntent(context: Context, source: MangaSource): Intent = when (source) {
is MangaSourceInfo -> sourceSettingsIntent(context, source.mangaSource)
is ExternalMangaSource -> Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", source.packageName, null))
else -> Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SOURCE)
.putExtra(KEY_SOURCE, source.name)
}
fun sourceAuthIntent(context: Context, source: MangaSource): Intent {
return Intent(context, SourceAuthActivity::class.java)
.putExtra(KEY_SOURCE, source.name)
}
const val KEY_EXCLUDE = "exclude"
const val KEY_FILTER = "filter"
const val KEY_ID = "id"
const val KEY_LIST_SECTION = "list_section"
const val KEY_MANGA = "manga"
const val KEY_MANGA_LIST = "manga_list"
const val KEY_PAGES = "pages"
const val KEY_QUERY = "query"
const val KEY_READER_MODE = "reader_mode"
const val KEY_SOURCE = "source"
const val KEY_TAB = "tab"
const val KEY_TITLE = "title"
const val KEY_USER_AGENT = "user_agent"
const val KEY_URL = "url"
const val KEY_ERROR = "error"
const val KEY_FILE = "file"
const val KEY_INDEX = "index"
const val KEY_DATA = "data"
const val ACTION_HISTORY = "${BuildConfig.APPLICATION_ID}.action.MANAGE_HISTORY"
const val ACTION_MANAGE_DOWNLOADS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_DOWNLOADS"
const val ACTION_MANAGE_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES_LIST"
const val ACTION_MANGA_EXPLORE = "${BuildConfig.APPLICATION_ID}.action.EXPLORE_MANGA"
const val ACTION_PROXY = "${BuildConfig.APPLICATION_ID}.action.MANAGE_PROXY"
const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
const val ACTION_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES"
const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
private const val ACCOUNT_KEY = "account"
private const val ACTION_ACCOUNT_SYNC_SETTINGS = "android.settings.ACCOUNT_SYNC_SETTINGS"
private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
private fun Class<out Fragment>.fragmentTag() = name // TODO
private inline fun <reified F : Fragment> fragmentTag() = F::class.java.fragmentTag()
}
}

@ -1,11 +1,12 @@
package org.koitharu.kotatsu.core.parser package org.koitharu.kotatsu.core.nav
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.nav.AppRouter.Companion.KEY_ID
import org.koitharu.kotatsu.core.nav.AppRouter.Companion.KEY_MANGA
import org.koitharu.kotatsu.core.util.ext.getParcelableCompat import org.koitharu.kotatsu.core.util.ext.getParcelableCompat
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@ -25,7 +26,7 @@ class MangaIntent private constructor(
constructor(savedStateHandle: SavedStateHandle) : this( constructor(savedStateHandle: SavedStateHandle) : this(
manga = savedStateHandle.get<ParcelableManga>(KEY_MANGA)?.manga, manga = savedStateHandle.get<ParcelableManga>(KEY_MANGA)?.manga,
id = savedStateHandle[KEY_ID] ?: ID_NONE, id = savedStateHandle[KEY_ID] ?: ID_NONE,
uri = savedStateHandle[BaseActivity.EXTRA_DATA], uri = savedStateHandle[AppRouter.KEY_DATA],
) )
constructor(args: Bundle?) : this( constructor(args: Bundle?) : this(
@ -41,9 +42,6 @@ class MangaIntent private constructor(
const val ID_NONE = 0L const val ID_NONE = 0L
const val KEY_MANGA = "manga"
const val KEY_ID = "id"
fun of(manga: Manga) = MangaIntent(manga, manga.id, null) fun of(manga: Manga) = MangaIntent(manga, manga.id, null)
} }
} }

@ -0,0 +1,39 @@
package org.koitharu.kotatsu.core.nav
import android.app.ActivityOptions
import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
inline val FragmentActivity.router: AppRouter
get() = AppRouter(this)
inline val Fragment.router: AppRouter
get() = AppRouter(this)
tailrec fun Fragment.dismissParentDialog(): Boolean {
return when (val parent = parentFragment) {
null -> return false
is DialogFragment -> {
parent.dismiss()
true
}
else -> parent.dismissParentDialog()
}
}
fun scaleUpActivityOptionsOf(view: View): Bundle? = if (view.context.isAnimationsEnabled) {
ActivityOptions.makeScaleUpAnimation(
view,
0,
0,
view.width,
view.height,
).toBundle()
} else {
null
}

@ -0,0 +1,61 @@
package org.koitharu.kotatsu.core.nav
import android.content.Context
import android.content.Intent
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
@JvmInline
value class ReaderIntent private constructor(
val intent: Intent,
) {
class Builder(context: Context) {
private val intent = Intent(context, ReaderActivity::class.java)
.setAction(ACTION_MANGA_READ)
fun manga(manga: Manga) = apply {
intent.putExtra(AppRouter.KEY_MANGA, ParcelableManga(manga))
}
fun mangaId(mangaId: Long) = apply {
intent.putExtra(AppRouter.KEY_ID, mangaId)
}
fun incognito(incognito: Boolean) = apply {
intent.putExtra(EXTRA_INCOGNITO, incognito)
}
fun branch(branch: String?) = apply {
intent.putExtra(EXTRA_BRANCH, branch)
}
fun state(state: ReaderState?) = apply {
intent.putExtra(EXTRA_STATE, state)
}
fun bookmark(bookmark: Bookmark) = manga(
bookmark.manga,
).state(
ReaderState(
chapterId = bookmark.chapterId,
page = bookmark.page,
scroll = bookmark.scroll,
),
)
fun build() = ReaderIntent(intent)
}
companion object {
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"
const val EXTRA_STATE = "state"
const val EXTRA_BRANCH = "branch"
const val EXTRA_INCOGNITO = "incognito"
}
}

@ -23,6 +23,8 @@ import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.TABLE_HISTORY import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -36,8 +38,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -155,9 +155,10 @@ class AppShortcutManager @Inject constructor(
.setIcon(icon) .setIcon(icon)
.setLongLived(true) .setLongLived(true)
.setIntent( .setIntent(
ReaderActivity.IntentBuilder(context) ReaderIntent.Builder(context)
.mangaId(manga.id) .mangaId(manga.id)
.build(), .build()
.intent,
) )
.build() .build()
} }
@ -181,7 +182,7 @@ class AppShortcutManager @Inject constructor(
.setLongLabel(title) .setLongLabel(title)
.setIcon(icon) .setIcon(icon)
.setLongLived(true) .setLongLived(true)
.setIntent(MangaListActivity.newIntent(context, source, null)) .setIntent(AppRouter.listIntent(context, source, null))
.build() .build()
} }
} }

@ -14,6 +14,7 @@ import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.core.db.entity.toMangaTags
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.model.isLocal
import org.koitharu.kotatsu.core.nav.MangaIntent
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.util.ext.toFileOrNull import org.koitharu.kotatsu.core.util.ext.toFileOrNull
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@ -45,8 +46,8 @@ class MangaDataRepository @Inject constructor(
entity.copy( entity.copy(
cfBrightness = colorFilter?.brightness ?: 0f, cfBrightness = colorFilter?.brightness ?: 0f,
cfContrast = colorFilter?.contrast ?: 0f, cfContrast = colorFilter?.contrast ?: 0f,
cfInvert = colorFilter?.isInverted ?: false, cfInvert = colorFilter?.isInverted == true,
cfGrayscale = colorFilter?.isGrayscale ?: false, cfGrayscale = colorFilter?.isGrayscale == true,
), ),
) )
} }

@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.flowOf
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.util.ext.isWebViewUnavailable import org.koitharu.kotatsu.core.util.ext.isWebViewUnavailable
@ -159,7 +160,7 @@ abstract class BaseActivity<B : ViewBinding> :
override fun isNsfwContent(): Flow<Boolean> = flowOf(false) override fun isNsfwContent(): Flow<Boolean> = flowOf(false)
private fun putDataToExtras(intent: Intent?) { private fun putDataToExtras(intent: Intent?) {
intent?.putExtra(EXTRA_DATA, intent.data) intent?.putExtra(AppRouter.KEY_DATA, intent.data)
} }
protected fun setContentViewWebViewSafe(viewBindingProducer: () -> B): Boolean { protected fun setContentViewWebViewSafe(viewBindingProducer: () -> B): Boolean {
@ -178,9 +179,4 @@ abstract class BaseActivity<B : ViewBinding> :
} }
protected fun hasViewBinding() = ::viewBinding.isInitialized protected fun hasViewBinding() = ::viewBinding.isInitialized
companion object {
const val EXTRA_DATA = "data"
}
} }

@ -1,8 +1,6 @@
package org.koitharu.kotatsu.core.ui package org.koitharu.kotatsu.core.ui
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
@ -14,10 +12,8 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.get import androidx.preference.get
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
@ -89,14 +85,6 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
(activity as? SettingsActivity)?.setSectionTitle(title) (activity as? SettingsActivity)?.setSectionTitle(title)
} }
protected fun startActivitySafe(intent: Intent): Boolean = try {
startActivity(intent)
true
} catch (_: ActivityNotFoundException) {
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
false
}
private fun focusPreference(key: String) { private fun focusPreference(key: String) {
val pref = findPreference<Preference>(key) val pref = findPreference<Preference>(key)
if (pref == null) { if (pref == null) {

@ -10,14 +10,14 @@ import androidx.core.text.HtmlCompat
import androidx.core.text.htmlEncode import androidx.core.text.htmlEncode
import androidx.core.text.method.LinkMovementMethodCompat import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.util.ext.getCauseUrl
import org.koitharu.kotatsu.core.util.ext.isReportable import org.koitharu.kotatsu.core.util.ext.isReportable
import org.koitharu.kotatsu.core.util.ext.report import org.koitharu.kotatsu.core.util.ext.report
import org.koitharu.kotatsu.core.util.ext.requireSerializable import org.koitharu.kotatsu.core.util.ext.requireSerializable
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.DialogErrorDetailsBinding import org.koitharu.kotatsu.databinding.DialogErrorDetailsBinding
class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() { class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
@ -27,7 +27,7 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val args = requireArguments() val args = requireArguments()
exception = args.requireSerializable(ARG_ERROR) exception = args.requireSerializable(AppRouter.KEY_ERROR)
} }
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogErrorDetailsBinding { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogErrorDetailsBinding {
@ -41,7 +41,7 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
text = context.getString( text = context.getString(
R.string.manga_error_description_pattern, R.string.manga_error_description_pattern,
exception.message?.htmlEncode().orEmpty(), exception.message?.htmlEncode().orEmpty(),
arguments?.getString(ARG_URL), arguments?.getString(AppRouter.KEY_URL) ?: exception.getCauseUrl(),
).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY) ).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY)
} }
} }
@ -71,16 +71,4 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
ClipData.newPlainText(getString(R.string.error), exception.stackTraceToString()), ClipData.newPlainText(getString(R.string.error), exception.stackTraceToString()),
) )
} }
companion object {
private const val TAG = "ErrorDetailsDialog"
private const val ARG_ERROR = "error"
private const val ARG_URL = "url"
fun show(fm: FragmentManager, error: Throwable, url: String?) = ErrorDetailsDialog().withArgs(2) {
putSerializable(ARG_ERROR, error)
putString(ARG_URL, url)
}.show(fm, TAG)
}
} }

@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.app.ActivityManager.MemoryInfo import android.app.ActivityManager.MemoryInfo
import android.app.ActivityOptions
import android.app.LocaleConfig import android.app.LocaleConfig
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
@ -23,19 +22,17 @@ import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.os.Bundle
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings import android.provider.Settings
import android.view.View
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import android.view.Window import android.view.Window
import android.webkit.WebView import android.webkit.WebView
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.CheckResult
import androidx.annotation.IntegerRes import androidx.annotation.IntegerRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDialog import androidx.appcompat.app.AppCompatDialog
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -86,12 +83,14 @@ suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatchingCancellable
setForeground(info) setForeground(info)
}.isSuccess }.isSuccess
@CheckResult
fun <I> ActivityResultLauncher<I>.resolve(context: Context, input: I): ResolveInfo? { fun <I> ActivityResultLauncher<I>.resolve(context: Context, input: I): ResolveInfo? {
val pm = context.packageManager val pm = context.packageManager
val intent = contract.createIntent(context, input) val intent = contract.createIntent(context, input)
return pm.resolveActivity(intent, 0) return pm.resolveActivity(intent, 0)
} }
@CheckResult
fun <I> ActivityResultLauncher<I>.tryLaunch( fun <I> ActivityResultLauncher<I>.tryLaunch(
input: I, input: I,
options: ActivityOptionsCompat? = null, options: ActivityOptionsCompat? = null,
@ -171,7 +170,7 @@ fun Context.getAnimationDuration(@IntegerRes resId: Int): Long {
} }
fun Context.isLowRamDevice(): Boolean { fun Context.isLowRamDevice(): Boolean {
return activityManager?.isLowRamDevice ?: false return activityManager?.isLowRamDevice == true
} }
fun Context.isPowerSaveMode(): Boolean { fun Context.isPowerSaveMode(): Boolean {
@ -185,18 +184,6 @@ val Context.ramAvailable: Long
return result.availMem return result.availMem
} }
fun scaleUpActivityOptionsOf(view: View): Bundle? = if (view.context.isAnimationsEnabled) {
ActivityOptions.makeScaleUpAnimation(
view,
0,
0,
view.width,
view.height,
).toBundle()
} else {
null
}
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
fun Context.getLocalesConfig(): LocaleListCompat { fun Context.getLocalesConfig(): LocaleListCompat {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

@ -110,3 +110,5 @@ fun <T : Parcelable> Parcelable.Creator<T>.unmarshall(bytes: ByteArray): T {
parcel.recycle() parcel.recycle()
} }
} }
inline fun buildBundle(capacity: Int, block: Bundle.() -> Unit): Bundle = Bundle(capacity).apply(block)

@ -2,9 +2,7 @@ package org.koitharu.kotatsu.core.util.ext
import android.os.Bundle import android.os.Bundle
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
@ -18,36 +16,10 @@ inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
val Fragment.viewLifecycleScope val Fragment.viewLifecycleScope
inline get() = viewLifecycleOwner.lifecycle.coroutineScope inline get() = viewLifecycleOwner.lifecycle.coroutineScope
fun DialogFragment.showAllowStateLoss(manager: FragmentManager, tag: String?) {
if (!manager.isStateSaved) {
show(manager, tag)
}
}
fun Fragment.addMenuProvider(provider: MenuProvider) { fun Fragment.addMenuProvider(provider: MenuProvider) {
requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED) requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED)
} }
fun DialogFragment.showDistinct(fm: FragmentManager, tag: String) {
val existing = fm.findFragmentByTag(tag) as? DialogFragment?
if (existing != null && existing.isVisible && existing.arguments == this.arguments) {
return
}
show(fm, tag)
}
tailrec fun Fragment.dismissParentDialog(): Boolean {
return when (val parent = parentFragment) {
null -> return false
is DialogFragment -> {
parent.dismiss()
true
}
else -> parent.dismissParentDialog()
}
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
tailrec fun <T> Fragment.findParentCallback(cls: Class<T>): T? { tailrec fun <T> Fragment.findParentCallback(cls: Class<T>): T? {
val parent = parentFragment val parent = parentFragment

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.core.util.iterator package org.koitharu.kotatsu.core.util.iterator
import org.koitharu.kotatsu.R
class MappingIterator<T, R>( class MappingIterator<T, R>(
private val upstream: Iterator<T>, private val upstream: Iterator<T>,
private val mapper: (T) -> R, private val mapper: (T) -> R,

@ -14,8 +14,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import okio.IOException import okio.IOException
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.nav.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.ext.peek import org.koitharu.kotatsu.core.util.ext.peek
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.details.ui package org.koitharu.kotatsu.details.ui
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.transition.TransitionManager import android.transition.TransitionManager
import android.view.Gravity import android.view.Gravity
@ -50,10 +49,10 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.UnknownMangaSource import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.titleResId import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.nav.ReaderIntent
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.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
@ -78,7 +77,6 @@ import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.parentView import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
@ -88,28 +86,18 @@ import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet
import org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration
import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteDialog
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.domain.MangaListMapper import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.roundToInt import kotlin.math.roundToInt
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -164,13 +152,14 @@ class DetailsActivity :
} }
TitleExpandListener(viewBinding.textViewTitle).attach() TitleExpandListener(viewBinding.textViewTitle).attach()
val appRouter = router
viewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated) viewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved) viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)
viewModel.onError viewModel.onError
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } .filterNot { appRouter.isChapterPagesSheetShown() }
.observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver)) .observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver))
viewModel.onActionDone viewModel.onActionDone
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } .filterNot { appRouter.isChapterPagesSheetShown() }
.observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null)) .observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null))
combine(viewModel.historyInfo, viewModel.isLoading, ::Pair).observe(this) { combine(viewModel.historyInfo, viewModel.isLoading, ::Pair).observe(this) {
onHistoryChanged(it.first, it.second) onHistoryChanged(it.first, it.second)
@ -189,10 +178,8 @@ class DetailsActivity :
} }
viewModel.chapters.observe(this, PrefetchObserver(this)) viewModel.chapters.observe(this, PrefetchObserver(this))
viewModel.onDownloadStarted viewModel.onDownloadStarted
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) } .filterNot { appRouter.isChapterPagesSheetShown() }
.observeEvent(this, DownloadStartedObserver(viewBinding.scrollView)) .observeEvent(this, DownloadStartedObserver(viewBinding.scrollView))
DownloadDialogFragment.registerCallback(this, viewBinding.scrollView)
menuProvider = DetailsMenuProvider( menuProvider = DetailsMenuProvider(
activity = this, activity = this,
viewModel = viewModel, viewModel = viewModel,
@ -210,54 +197,30 @@ class DetailsActivity :
R.id.textView_author -> { R.id.textView_author -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
startActivity( router.openSearch(manga.source, manga.author ?: return)
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
filter = MangaListFilter(query = manga.author),
),
)
} }
R.id.textView_source -> { R.id.textView_source -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
startActivity( router.openList(manga.source, null)
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
filter = null,
),
)
} }
R.id.textView_local -> { R.id.textView_local -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
LocalInfoDialog.show(supportFragmentManager, manga) router.showLocalInfoDialog(manga)
} }
R.id.chip_favorite -> { R.id.chip_favorite -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
FavoriteDialog.show(supportFragmentManager, manga) router.showFavoriteDialog(manga)
} }
// R.id.chip_time -> {
// if (viewModel.isStatsAvailable.value) {
// val manga = viewModel.manga.value ?: return
// MangaStatsSheet.show(supportFragmentManager, manga)
// } else {
// // TODO
// }
// }
R.id.imageView_cover -> { R.id.imageView_cover -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
startActivity( router.openImage(
ImageActivity.newIntent( url = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
v.context, source = manga.source,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }, anchor = v,
manga.source,
),
scaleUpActivityOptionsOf(v),
) )
} }
@ -273,12 +236,12 @@ class DetailsActivity :
R.id.button_scrobbling_more -> { R.id.button_scrobbling_more -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
ScrobblingSelectorSheet.show(supportFragmentManager, manga, null) router.showScrobblingSelectorSheet(manga, null)
} }
R.id.button_related_more -> { R.id.button_related_more -> {
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
startActivity(RelatedMangaActivity.newIntent(v.context, manga)) router.openRelated(manga)
} }
} }
} }
@ -286,7 +249,7 @@ class DetailsActivity :
override fun onChipClick(chip: Chip, data: Any?) { override fun onChipClick(chip: Chip, data: Any?) {
val tag = data as? MangaTag ?: return val tag = data as? MangaTag ?: return
// TODO dialog // TODO dialog
startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag)))) router.openList(tag)
} }
override fun onContextClick(v: View): Boolean = onLongClick(v) override fun onContextClick(v: View): Boolean = onLongClick(v)
@ -324,9 +287,7 @@ class DetailsActivity :
} }
override fun onItemClick(item: Bookmark, view: View) { override fun onItemClick(item: Bookmark, view: View) {
startActivity( router.openReader(ReaderIntent.Builder(view.context).bookmark(item).incognito(true).build())
ReaderActivity.IntentBuilder(view.context).bookmark(item).incognito(true).build(),
)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show() Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
} }
@ -393,7 +354,7 @@ class DetailsActivity :
coil, this, coil, this,
StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)), StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),
) { item, view -> ) { item, view ->
startActivity(newIntent(view.context, item)) router.openDetails(item)
}, },
).also { rv.adapter = it } ).also { rv.adapter = it }
adapter.items = related adapter.items = related
@ -410,7 +371,7 @@ class DetailsActivity :
if (adapter != null) { if (adapter != null) {
adapter.items = scrobblings adapter.items = scrobblings
} else { } else {
adapter = ScrollingInfoAdapter(this, coil, supportFragmentManager) adapter = ScrollingInfoAdapter(this, coil, router)
adapter.items = scrobblings adapter.items = scrobblings
viewBinding.recyclerViewScrobbling.adapter = adapter viewBinding.recyclerViewScrobbling.adapter = adapter
viewBinding.recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration()) viewBinding.recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration())
@ -531,8 +492,8 @@ class DetailsActivity :
Snackbar.make(viewBinding.scrollView, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT) Snackbar.make(viewBinding.scrollView, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
.show() .show()
} else { } else {
startActivity( router.openReader(
ReaderActivity.IntentBuilder(this) ReaderIntent.Builder(this)
.manga(manga) .manga(manga)
.branch(viewModel.selectedBranchValue) .branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode) .incognito(isIncognitoMode)
@ -604,15 +565,5 @@ class DetailsActivity :
companion object { companion object {
private const val FAV_LABEL_LIMIT = 16 private const val FAV_LABEL_LIMIT = 16
fun newIntent(context: Context, manga: Manga): Intent {
return Intent(context, DetailsActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
fun newIntent(context: Context, mangaId: Long): Intent {
return Intent(context, DetailsActivity::class.java)
.putExtra(MangaIntent.KEY_ID, mangaId)
}
} }
} }

@ -5,7 +5,6 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
import org.koitharu.kotatsu.core.exceptions.resolve.ErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.ErrorObserver
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isNetworkError import org.koitharu.kotatsu.core.util.ext.isNetworkError
import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.core.util.ext.isSerializable
@ -38,10 +37,10 @@ class DetailsErrorObserver(
} }
value is ParseException -> { value is ParseException -> {
val fm = fragmentManager val router = router()
if (fm != null && value.isSerializable()) { if (router != null && value.isSerializable()) {
snackbar.setAction(R.string.details) { snackbar.setAction(R.string.details) {
ErrorDetailsDialog.show(fm, value, value.url) router.showErrorDialog(value)
} }
} }
} }

@ -14,16 +14,11 @@ 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.alternatives.ui.AlternativesActivity
import org.koitharu.kotatsu.browser.BrowserActivity
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.model.isLocal
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.util.ShareHelper
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.multi.SearchActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
class DetailsMenuProvider( class DetailsMenuProvider(
private val activity: FragmentActivity, private val activity: FragmentActivity,
@ -49,23 +44,21 @@ class DetailsMenuProvider(
} }
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
val manga = viewModel.getMangaOrNull() ?: return false
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_share -> { R.id.action_share -> {
viewModel.manga.value?.let { val shareHelper = ShareHelper(activity)
val shareHelper = ShareHelper(activity) if (manga.isLocal) {
if (it.isLocal) { shareHelper.shareCbz(listOf(manga.url.toUri().toFile()))
shareHelper.shareCbz(listOf(it.url.toUri().toFile())) } else {
} else { shareHelper.shareMangaLink(manga)
shareHelper.shareMangaLink(it)
}
} }
} }
R.id.action_delete -> { R.id.action_delete -> {
val title = viewModel.manga.value?.title.orEmpty()
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setTitle(R.string.delete_manga) .setTitle(R.string.delete_manga)
.setMessage(activity.getString(R.string.text_delete_local_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()
} }
@ -74,52 +67,38 @@ class DetailsMenuProvider(
} }
R.id.action_save -> { R.id.action_save -> {
DownloadDialogFragment.show(activity.supportFragmentManager, listOfNotNull(viewModel.manga.value)) activity.router.showDownloadDialog(manga, snackbarHost)
} }
R.id.action_browser -> { R.id.action_browser -> {
viewModel.manga.value?.let { activity.router.openBrowser(url = manga.publicUrl, source = manga.source, title = manga.title)
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.source, it.title))
}
} }
R.id.action_online -> { R.id.action_online -> {
viewModel.remoteManga.value?.let { activity.router.openDetails(manga)
activity.startActivity(DetailsActivity.newIntent(activity, it))
}
} }
R.id.action_related -> { R.id.action_related -> {
viewModel.manga.value?.let { activity.router.openSearch(manga.title)
activity.startActivity(SearchActivity.newIntent(activity, it.title))
}
} }
R.id.action_alternatives -> { R.id.action_alternatives -> {
viewModel.manga.value?.let { activity.router.openAlternatives(manga)
activity.startActivity(AlternativesActivity.newIntent(activity, it))
}
} }
R.id.action_stats -> { R.id.action_stats -> {
viewModel.manga.value?.let { activity.router.showStatisticSheet(manga)
MangaStatsSheet.show(activity.supportFragmentManager, it)
}
} }
R.id.action_scrobbling -> { R.id.action_scrobbling -> {
viewModel.manga.value?.let { activity.router.showScrobblingSelectorSheet(manga, null)
ScrobblingSelectorSheet.show(activity.supportFragmentManager, it, null)
}
} }
R.id.action_shortcut -> { R.id.action_shortcut -> {
viewModel.manga.value?.let { activity.lifecycleScope.launch {
activity.lifecycleScope.launch { if (!appShortcutManager.requestPinShortcut(manga)) {
if (!appShortcutManager.requestPinShortcut(it)) { Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT) .show()
.show()
}
} }
} }
} }

@ -21,7 +21,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.nav.MangaIntent
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.util.ReversibleAction import org.koitharu.kotatsu.core.ui.util.ReversibleAction

@ -15,7 +15,6 @@ import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans import androidx.core.text.inSpans
import androidx.core.view.MenuCompat import androidx.core.view.MenuCompat
import androidx.core.view.get import androidx.core.view.get
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialSplitButton import com.google.android.material.button.MaterialSplitButton
@ -23,16 +22,16 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.util.ext.findActivity import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.details.ui.model.HistoryInfo import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.reader.ui.ReaderActivity
class ReadButtonDelegate( class ReadButtonDelegate(
private val splitButton: MaterialSplitButton, private val splitButton: MaterialSplitButton,
private val viewModel: DetailsViewModel, private val viewModel: DetailsViewModel,
private val router: AppRouter,
) : View.OnClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { ) : View.OnClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
private val buttonRead = splitButton[0] as MaterialButton private val buttonRead = splitButton[0] as MaterialButton
@ -52,10 +51,12 @@ class ReadButtonDelegate(
when (item.itemId) { when (item.itemId) {
R.id.action_incognito -> openReader(isIncognitoMode = true) R.id.action_incognito -> openReader(isIncognitoMode = true)
R.id.action_forget -> viewModel.removeFromHistory() R.id.action_forget -> viewModel.removeFromHistory()
R.id.action_download -> DownloadDialogFragment.show( R.id.action_download -> {
fm = (context.findActivity() as? FragmentActivity)?.supportFragmentManager ?: return false, router.showDownloadDialog(
manga = setOf(viewModel.getMangaOrNull() ?: return false), manga = setOf(viewModel.getMangaOrNull() ?: return false),
) snackbarHost = splitButton,
)
}
Menu.NONE -> { Menu.NONE -> {
val branch = viewModel.branches.value.getOrNull(item.order) ?: return false val branch = viewModel.branches.value.getOrNull(item.order) ?: return false
@ -106,8 +107,8 @@ class ReadButtonDelegate(
Snackbar.make(buttonRead, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT) Snackbar.make(buttonRead, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
.show() // TODO .show() // TODO
} else { } else {
context.startActivity( router.openReader(
ReaderActivity.IntentBuilder(context) ReaderIntent.Builder(context)
.manga(manga) .manga(manga)
.branch(viewModel.selectedBranchValue) .branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode) .incognito(isIncognitoMode)

@ -6,10 +6,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_COLLAPSED import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_COLLAPSED
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_DRAGGING import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_DRAGGING
@ -26,8 +27,6 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.recyclerView import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding import org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding
import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.DetailsViewModel
import org.koitharu.kotatsu.details.ui.ReadButtonDelegate import org.koitharu.kotatsu.details.ui.ReadButtonDelegate
@ -51,13 +50,13 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
disableFitToContents() disableFitToContents()
val args = arguments ?: Bundle.EMPTY val args = arguments ?: Bundle.EMPTY
var defaultTab = args.getInt(ARG_TAB, settings.defaultDetailsTab) var defaultTab = args.getInt(AppRouter.KEY_TAB, settings.defaultDetailsTab)
val adapter = ChaptersPagesAdapter(this, settings.isPagesTabEnabled) val adapter = ChaptersPagesAdapter(this, settings.isPagesTabEnabled)
if (!adapter.isPagesTabEnabled) { if (!adapter.isPagesTabEnabled) {
defaultTab = (defaultTab - 1).coerceAtLeast(TAB_CHAPTERS) defaultTab = (defaultTab - 1).coerceAtLeast(TAB_CHAPTERS)
} }
(viewModel as? DetailsViewModel)?.let { dvm -> (viewModel as? DetailsViewModel)?.let { dvm ->
ReadButtonDelegate(binding.splitButtonRead, dvm).attach(viewLifecycleOwner) ReadButtonDelegate(binding.splitButtonRead, dvm, router).attach(viewLifecycleOwner)
} }
binding.pager.offscreenPageLimit = adapter.itemCount binding.pager.offscreenPageLimit = adapter.itemCount
binding.pager.recyclerView?.isNestedScrollingEnabled = false binding.pager.recyclerView?.isNestedScrollingEnabled = false
@ -145,22 +144,5 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
const val TAB_CHAPTERS = 0 const val TAB_CHAPTERS = 0
const val TAB_PAGES = 1 const val TAB_PAGES = 1
const val TAB_BOOKMARKS = 2 const val TAB_BOOKMARKS = 2
private const val ARG_TAB = "tag"
private const val TAG = "ChaptersPagesSheet"
fun show(fm: FragmentManager) {
ChaptersPagesSheet().showDistinct(fm, TAG)
}
fun show(fm: FragmentManager, defaultTab: Int) {
ChaptersPagesSheet().withArgs(1) {
putInt(ARG_TAB, defaultTab)
}.showDistinct(fm, TAG)
}
fun isShown(fm: FragmentManager): Boolean {
val sheet = fm.findFragmentByTag(TAG) as? ChaptersPagesSheet
return sheet?.dialog?.isShowing == true
}
} }
} }

@ -18,13 +18,15 @@ import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.BookmarksSelectionDecoration import org.koitharu.kotatsu.bookmarks.ui.BookmarksSelectionDecoration
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.nav.dismissParentDialog
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@ -34,7 +36,6 @@ import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel
import org.koitharu.kotatsu.list.ui.GridSpanResolver import org.koitharu.kotatsu.list.ui.GridSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import javax.inject.Inject import javax.inject.Inject
@ -124,21 +125,21 @@ class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
if (listener != null && listener.onBookmarkSelected(item)) { if (listener != null && listener.onBookmarkSelected(item)) {
dismissParentDialog() dismissParentDialog()
} else { } else {
val intent = IntentBuilder(view.context) val intent = ReaderIntent.Builder(view.context)
.manga(activityViewModel.getMangaOrNull() ?: return) .manga(activityViewModel.getMangaOrNull() ?: return)
.bookmark(item) .bookmark(item)
.incognito(true) .incognito(true)
.build() .build()
startActivity(intent) router.openReader(intent)
} }
} }
override fun onItemLongClick(item: Bookmark, view: View): Boolean { override fun onItemLongClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemLongClick(view, item.pageId) ?: false return selectionController?.onItemLongClick(view, item.pageId) == true
} }
override fun onItemContextClick(item: Bookmark, view: View): Boolean { override fun onItemContextClick(item: Bookmark, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.pageId) ?: false return selectionController?.onItemContextClick(view, item.pageId) == true
} }
override fun onSelectionChanged(controller: ListSelectionController, count: Int) { override fun onSelectionChanged(controller: ListSelectionController, count: Int) {

@ -15,6 +15,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.nav.dismissParentDialog
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
@ -22,7 +25,6 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@ -35,7 +37,6 @@ import org.koitharu.kotatsu.details.ui.withVolumeHeaders
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import javax.inject.Inject import javax.inject.Inject
@ -111,8 +112,8 @@ class ChaptersFragment :
if (listener != null && listener.onChapterSelected(item.chapter)) { if (listener != null && listener.onChapterSelected(item.chapter)) {
dismissParentDialog() dismissParentDialog()
} else { } else {
startActivity( router.openReader(
IntentBuilder(view.context) ReaderIntent.Builder(view.context)
.manga(viewModel.getMangaOrNull() ?: return) .manga(viewModel.getMangaOrNull() ?: return)
.state(ReaderState(item.chapter.id, 0, 0)) .state(ReaderState(item.chapter.id, 0, 0))
.build(), .build(),

@ -22,6 +22,9 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.nav.dismissParentDialog
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener
@ -29,7 +32,6 @@ import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@ -42,7 +44,6 @@ import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.reader.ui.PageSaveHelper import org.koitharu.kotatsu.reader.ui.PageSaveHelper
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
@ -151,8 +152,8 @@ class PagesFragment :
if (listener != null && listener.onPageSelected(item.page)) { if (listener != null && listener.onPageSelected(item.page)) {
dismissParentDialog() dismissParentDialog()
} else { } else {
startActivity( router.openReader(
IntentBuilder(view.context) ReaderIntent.Builder(view.context)
.manga(parentViewModel.getMangaOrNull() ?: return) .manga(parentViewModel.getMangaOrNull() ?: return)
.state(ReaderState(item.page.chapterId, item.page.index, 0)) .state(ReaderState(item.page.chapterId, item.page.index, 0))
.build(), .build(),
@ -161,11 +162,11 @@ class PagesFragment :
} }
override fun onItemLongClick(item: PageThumbnail, view: View): Boolean { override fun onItemLongClick(item: PageThumbnail, view: View): Boolean {
return selectionController?.onItemLongClick(view, item.page.id) ?: false return selectionController?.onItemLongClick(view, item.page.id) == true
} }
override fun onItemContextClick(item: PageThumbnail, view: View): Boolean { override fun onItemContextClick(item: PageThumbnail, view: View): Boolean {
return selectionController?.onItemContextClick(view, item.page.id) ?: false return selectionController?.onItemContextClick(view, item.page.id) == true
} }
override fun onSelectionChanged(controller: ListSelectionController, count: Int) { override fun onSelectionChanged(controller: ListSelectionController, count: Int) {

@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
@ -37,7 +37,7 @@ class RelatedListViewModel @Inject constructor(
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) { ) : MangaListViewModel(settings, downloadScheduler) {
private val seed = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga private val seed = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
private val repository = mangaRepositoryFactory.create(seed.source) private val repository = mangaRepositoryFactory.create(seed.source)
private val mangaList = MutableStateFlow<List<Manga>?>(null) private val mangaList = MutableStateFlow<List<Manga>?>(null)
private val listError = MutableStateFlow<Throwable?>(null) private val listError = MutableStateFlow<Throwable?>(null)

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.details.ui.related package org.koitharu.kotatsu.details.ui.related
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -9,12 +7,9 @@ import androidx.fragment.app.commit
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga
@AndroidEntryPoint @AndroidEntryPoint
class RelatedMangaActivity : BaseActivity<ActivityContainerBinding>(), AppBarOwner { class RelatedMangaActivity : BaseActivity<ActivityContainerBinding>(), AppBarOwner {
@ -41,10 +36,4 @@ class RelatedMangaActivity : BaseActivity<ActivityContainerBinding>(), AppBarOwn
right = insets.right, right = insets.right,
) )
} }
companion object {
fun newIntent(context: Context, seed: Manga) = Intent(context, RelatedMangaActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(seed))
}
} }

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.details.ui.scrobbling package org.koitharu.kotatsu.details.ui.scrobbling
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil3.ImageLoader import coil3.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
@ -15,12 +15,12 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
fun scrobblingInfoAD( fun scrobblingInfoAD(
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
coil: ImageLoader, coil: ImageLoader,
fragmentManager: FragmentManager, router: AppRouter,
) = adapterDelegateViewBinding<ScrobblingInfo, ListModel, ItemScrobblingInfoBinding>( ) = adapterDelegateViewBinding<ScrobblingInfo, ListModel, ItemScrobblingInfoBinding>(
{ layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) },
) { ) {
binding.root.setOnClickListener { binding.root.setOnClickListener {
ScrobblingInfoSheet.show(fragmentManager, bindingAdapterPosition) router.showScrobblingInfoSheet(bindingAdapterPosition)
} }
bind { bind {

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.details.ui.scrobbling package org.koitharu.kotatsu.details.ui.scrobbling
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
@ -10,13 +9,14 @@ import android.widget.AdapterView
import android.widget.RatingBar import android.widget.RatingBar
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.net.toUri
import androidx.core.text.method.LinkMovementMethodCompat import androidx.core.text.method.LinkMovementMethodCompat
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import coil3.ImageLoader import coil3.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
@ -25,14 +25,10 @@ import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.sanitize import org.koitharu.kotatsu.core.util.ext.sanitize
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetScrobblingBinding import org.koitharu.kotatsu.databinding.SheetScrobblingBinding
import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.DetailsViewModel
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -53,7 +49,7 @@ class ScrobblingInfoSheet :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
scrobblerIndex = requireArguments().getInt(ARG_INDEX, scrobblerIndex) scrobblerIndex = requireArguments().getInt(AppRouter.KEY_INDEX, scrobblerIndex)
} }
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingBinding { override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingBinding {
@ -108,11 +104,11 @@ class ScrobblingInfoSheet :
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_menu -> menu?.show() R.id.button_menu -> menu?.show()
R.id.imageView_cover -> { R.id.imageView_cover -> router.openImage(
val coverUrl = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.coverUrl ?: return url = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.coverUrl ?: return,
val options = scaleUpActivityOptionsOf(v) source = null,
startActivity(ImageActivity.newIntent(v.context, coverUrl, null), options) anchor = v,
} )
} }
} }
@ -139,10 +135,13 @@ class ScrobblingInfoSheet :
when (item.itemId) { when (item.itemId) {
R.id.action_browser -> { R.id.action_browser -> {
val url = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.externalUrl ?: return false val url = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.externalUrl ?: return false
val intent = Intent(Intent.ACTION_VIEW, url.toUri()) if (!router.openExternalBrowser(url, getString(R.string.open_in_browser))) {
startActivity( Snackbar.make(
Intent.createChooser(intent, getString(R.string.open_in_browser)), viewBinding?.textViewDescription ?: return false,
) R.string.operation_not_supported,
Snackbar.LENGTH_SHORT,
).show()
}
} }
R.id.action_unregister -> { R.id.action_unregister -> {
@ -153,20 +152,10 @@ class ScrobblingInfoSheet :
R.id.action_edit -> { R.id.action_edit -> {
val manga = viewModel.manga.value ?: return false val manga = viewModel.manga.value ?: return false
val scrobblerService = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.scrobbler val scrobblerService = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.scrobbler
ScrobblingSelectorSheet.show(parentFragmentManager, manga, scrobblerService) router.showScrobblingSelectorSheet(manga, scrobblerService)
dismiss() dismiss()
} }
} }
return true return true
} }
companion object {
private const val TAG = "ScrobblingInfoBottomSheet"
private const val ARG_INDEX = "index"
fun show(fm: FragmentManager, index: Int) = ScrobblingInfoSheet().withArgs(1) {
putInt(ARG_INDEX, index)
}.show(fm, TAG)
}
} }

@ -1,18 +1,18 @@
package org.koitharu.kotatsu.details.ui.scrobbling package org.koitharu.kotatsu.details.ui.scrobbling
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil3.ImageLoader import coil3.ImageLoader
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
class ScrollingInfoAdapter( class ScrollingInfoAdapter(
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
coil: ImageLoader, coil: ImageLoader,
fragmentManager: FragmentManager, router: AppRouter,
) : BaseListAdapter<ListModel>() { ) : BaseListAdapter<ListModel>() {
init { init {
delegatesManager.addDelegate(scrobblingInfoAD(lifecycleOwner, coil, fragmentManager)) delegatesManager.addDelegate(scrobblingInfoAD(lifecycleOwner, coil, router))
} }
} }

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.download.ui.dialog package org.koitharu.kotatsu.download.ui.dialog
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -9,17 +8,16 @@ import android.view.ViewGroup
import android.widget.Spinner import android.widget.Spinner
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentResultListener import androidx.fragment.app.FragmentResultListener
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.DownloadFormat import org.koitharu.kotatsu.core.prefs.DownloadFormat
import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs
@ -27,17 +25,12 @@ import org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView
import org.koitharu.kotatsu.core.util.ext.findActivity import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
import org.koitharu.kotatsu.core.util.ext.mapToArray
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.parentView import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.showOrHide import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.DialogDownloadBinding import org.koitharu.kotatsu.databinding.DialogDownloadBinding
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.settings.storage.DirectoryModel import org.koitharu.kotatsu.settings.storage.DirectoryModel
import javax.inject.Inject import javax.inject.Inject
@ -325,7 +318,9 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
} }
} }
private class SnackbarResultListener(private val host: View) : FragmentResultListener { private class SnackbarResultListener(
private val host: View,
) : FragmentResultListener {
override fun onFragmentResult(requestKey: String, result: Bundle) { override fun onFragmentResult(requestKey: String, result: Bundle) {
val isStarted = result.getBoolean(ARG_STARTED, true) val isStarted = result.getBoolean(ARG_STARTED, true)
@ -337,8 +332,9 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
(host.context.findActivity() as? BottomNavOwner)?.let { (host.context.findActivity() as? BottomNavOwner)?.let {
snackbar.anchorView = it.bottomNav snackbar.anchorView = it.bottomNav
} }
snackbar.setAction(R.string.details) { val router = AppRouter.from(host)
it.context.startActivity(Intent(it.context, DownloadsActivity::class.java)) if (router != null) {
snackbar.setAction(R.string.details) { router.openDownloads() }
} }
snackbar.show() snackbar.show()
} }
@ -346,28 +342,16 @@ class DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), Vie
companion object { companion object {
private const val TAG = "DownloadDialogFragment"
private const val RESULT_KEY = "DOWNLOAD_STARTED" private const val RESULT_KEY = "DOWNLOAD_STARTED"
private const val ARG_STARTED = "started" private const val ARG_STARTED = "started"
private const val KEY_CHECKED_OPTION = "checked_opt" private const val KEY_CHECKED_OPTION = "checked_opt"
const val ARG_MANGA = "manga"
fun show(fm: FragmentManager, manga: Collection<Manga>) = DownloadDialogFragment().withArgs(1) { fun registerCallback(
putParcelableArray(ARG_MANGA, manga.mapToArray { ParcelableManga(it) }) fm: FragmentManager,
}.showDistinct(fm, TAG) lifecycleOwner: LifecycleOwner,
snackbarHost: View
) = fm.setFragmentResultListener(RESULT_KEY, lifecycleOwner, SnackbarResultListener(snackbarHost))
fun registerCallback(activity: FragmentActivity, snackbarHost: View) = fun unregisterCallback(fm: FragmentManager) = fm.clearFragmentResultListener(RESULT_KEY)
activity.supportFragmentManager.setFragmentResultListener(
RESULT_KEY,
activity,
SnackbarResultListener(snackbarHost),
)
fun registerCallback(fragment: Fragment, snackbarHost: View) =
fragment.childFragmentManager.setFragmentResultListener(
RESULT_KEY,
fragment.viewLifecycleOwner,
SnackbarResultListener(snackbarHost),
)
} }
} }

@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.DownloadFormat import org.koitharu.kotatsu.core.prefs.DownloadFormat
@ -45,7 +46,7 @@ class DownloadDialogViewModel @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
val manga = savedStateHandle.require<Array<ParcelableManga>>(DownloadDialogFragment.ARG_MANGA).map { val manga = savedStateHandle.require<Array<ParcelableManga>>(AppRouter.KEY_MANGA).map {
it.manga it.manga
} }
private val mangaDetails = suspendLazy { private val mangaDetails = suspendLazy {

@ -12,6 +12,7 @@ import androidx.core.view.updatePadding
import coil3.ImageLoader import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
@ -20,7 +21,6 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import javax.inject.Inject import javax.inject.Inject
@ -84,7 +84,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
if (selectionController.onItemClick(item.id.mostSignificantBits)) { if (selectionController.onItemClick(item.id.mostSignificantBits)) {
return return
} }
startActivity(DetailsActivity.newIntent(view.context, item.manga ?: return)) router.openDetails(item.manga ?: return)
} }
override fun onItemLongClick(item: DownloadItemModel, view: View): Boolean { override fun onItemLongClick(item: DownloadItemModel, view: View): Boolean {

@ -1,16 +1,16 @@
package org.koitharu.kotatsu.download.ui.list package org.koitharu.kotatsu.download.ui.list
import android.content.Context
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentActivity
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.settings.SettingsActivity
class DownloadsMenuProvider( class DownloadsMenuProvider(
private val context: Context, private val activity: FragmentActivity,
private val viewModel: DownloadsViewModel, private val viewModel: DownloadsViewModel,
) : MenuProvider { ) : MenuProvider {
@ -24,10 +24,7 @@ class DownloadsMenuProvider(
R.id.action_resume -> viewModel.resumeAll() R.id.action_resume -> viewModel.resumeAll()
R.id.action_cancel_all -> confirmCancelAll() R.id.action_cancel_all -> confirmCancelAll()
R.id.action_remove_completed -> confirmRemoveCompleted() R.id.action_remove_completed -> confirmRemoveCompleted()
R.id.action_settings -> { R.id.action_settings -> activity.router.openDownloadsSetting()
context.startActivity(SettingsActivity.newDownloadsSettingsIntent(context))
}
else -> return false else -> return false
} }
return true return true
@ -41,7 +38,7 @@ class DownloadsMenuProvider(
} }
private fun confirmCancelAll() { private fun confirmCancelAll() {
buildAlertDialog(context, isCentered = true) { buildAlertDialog(activity, isCentered = true) {
setTitle(R.string.cancel_all) setTitle(R.string.cancel_all)
setMessage(R.string.cancel_all_downloads_confirm) setMessage(R.string.cancel_all_downloads_confirm)
setIcon(R.drawable.ic_cancel_multiple) setIcon(R.drawable.ic_cancel_multiple)
@ -51,7 +48,7 @@ class DownloadsMenuProvider(
} }
private fun confirmRemoveCompleted() { private fun confirmRemoveCompleted() {
buildAlertDialog(context, isCentered = true) { buildAlertDialog(activity, isCentered = true) {
setTitle(R.string.remove_completed) setTitle(R.string.remove_completed)
setMessage(R.string.remove_completed_downloads_confirm) setMessage(R.string.remove_completed_downloads_confirm)
setIcon(R.drawable.ic_clear_all) setIcon(R.drawable.ic_clear_all)

@ -25,17 +25,16 @@ import kotlinx.coroutines.sync.withLock
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
import org.koitharu.kotatsu.core.util.ext.isReportable import org.koitharu.kotatsu.core.util.ext.isReportable
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.domain.DownloadState
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.search.ui.MangaListActivity
import java.util.UUID import java.util.UUID
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -267,9 +266,9 @@ class DownloadNotificationFactory @AssistedInject constructor(
context, context,
manga.hashCode(), manga.hashCode(),
if (manga != null) { if (manga != null) {
DetailsActivity.newIntent(context, manga) AppRouter.detailsIntent(context, manga)
} else { } else {
MangaListActivity.newIntent(context, LocalMangaSource, null) AppRouter.listIntent(context, LocalMangaSource, null)
}, },
PendingIntent.FLAG_CANCEL_CURRENT, PendingIntent.FLAG_CANCEL_CURRENT,
false, false,

@ -1,12 +1,11 @@
package org.koitharu.kotatsu.download.ui.worker package org.koitharu.kotatsu.download.ui.worker
import android.content.Intent
import android.view.View import android.view.View
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.util.ext.findActivity import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
class DownloadStartedObserver( class DownloadStartedObserver(
@ -18,8 +17,9 @@ class DownloadStartedObserver(
(snackbarHost.context.findActivity() as? BottomNavOwner)?.let { (snackbarHost.context.findActivity() as? BottomNavOwner)?.let {
snackbar.anchorView = it.bottomNav snackbar.anchorView = it.bottomNav
} }
snackbar.setAction(R.string.details) { val router = AppRouter.from(snackbarHost)
it.context.startActivity(Intent(it.context, DownloadsActivity::class.java)) if (router != null) {
snackbar.setAction(R.string.details) { router.openDownloads() }
} }
snackbar.show() snackbar.show()
} }

@ -21,9 +21,9 @@ import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.LocalMangaSource import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.dialog.BigButtonsAlertDialog import org.koitharu.kotatsu.core.ui.dialog.BigButtonsAlertDialog
@ -37,8 +37,6 @@ import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentExploreBinding import org.koitharu.kotatsu.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
@ -46,10 +44,6 @@ import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -76,7 +70,7 @@ class ExploreFragment :
override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this) { manga, view -> exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this) { manga, view ->
startActivity(DetailsActivity.newIntent(view.context, manga)) router.openDetails(manga)
} }
sourceSelectionController = ListSelectionController( sourceSelectionController = ListSelectionController(
appCompatDelegate = checkNotNull(findAppCompatDelegate()), appCompatDelegate = checkNotNull(findAppCompatDelegate()),
@ -91,7 +85,7 @@ class ExploreFragment :
addItemDecoration(TypedListSpacingDecoration(context, false)) addItemDecoration(TypedListSpacingDecoration(context, false))
checkNotNull(sourceSelectionController).attachToRecyclerView(this) checkNotNull(sourceSelectionController).attachToRecyclerView(this)
} }
addMenuProvider(ExploreMenuProvider(binding.root.context)) addMenuProvider(ExploreMenuProvider(router))
viewModel.content.observe(viewLifecycleOwner, checkNotNull(exploreAdapter)) viewModel.content.observe(viewLifecycleOwner, checkNotNull(exploreAdapter))
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onOpenManga.observeEvent(viewLifecycleOwner, ::onOpenManga) viewModel.onOpenManga.observeEvent(viewLifecycleOwner, ::onOpenManga)
@ -117,49 +111,40 @@ class ExploreFragment :
override fun onListHeaderClick(item: ListHeader, view: View) { override fun onListHeaderClick(item: ListHeader, view: View) {
if (item.payload == R.id.nav_suggestions) { if (item.payload == R.id.nav_suggestions) {
startActivity(SuggestionsActivity.newIntent(view.context)) router.openSuggestions()
} else { } else {
startActivity(Intent(view.context, SourcesCatalogActivity::class.java)) router.openSourcesCatalog()
} }
} }
override fun onClick(v: View) { override fun onClick(v: View) {
val intent = when (v.id) { when (v.id) {
R.id.button_local -> MangaListActivity.newIntent(v.context, LocalMangaSource, null) R.id.button_local -> router.openList(LocalMangaSource, null)
R.id.button_bookmarks -> AllBookmarksActivity.newIntent(v.context) R.id.button_bookmarks -> router.openBookmarks()
R.id.button_more -> SuggestionsActivity.newIntent(v.context) R.id.button_more -> router.openSuggestions()
R.id.button_downloads -> Intent(v.context, DownloadsActivity::class.java) R.id.button_downloads -> router.openDownloads()
R.id.button_random -> { R.id.button_random -> viewModel.openRandom()
viewModel.openRandom()
return
}
else -> return
} }
startActivity(intent)
} }
override fun onItemClick(item: MangaSourceItem, view: View) { override fun onItemClick(item: MangaSourceItem, view: View) {
if (sourceSelectionController?.onItemClick(item.id) == true) { if (sourceSelectionController?.onItemClick(item.id) == true) {
return return
} }
val intent = MangaListActivity.newIntent(view.context, item.source, null) router.openList(item.source, null)
startActivity(intent)
} }
override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean { override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {
return sourceSelectionController?.onItemLongClick(view, item.id) ?: false return sourceSelectionController?.onItemLongClick(view, item.id) == true
} }
override fun onItemContextClick(item: MangaSourceItem, view: View): Boolean { override fun onItemContextClick(item: MangaSourceItem, view: View): Boolean {
return sourceSelectionController?.onItemContextClick(view, item.id) ?: false return sourceSelectionController?.onItemContextClick(view, item.id) == true
} }
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() { override fun onEmptyActionClick() = router.openSourcesCatalog()
startActivity(Intent(context ?: return, SourcesCatalogActivity::class.java))
}
override fun onSelectionChanged(controller: ListSelectionController, count: Int) { override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
viewBinding?.recyclerView?.invalidateItemDecorations() viewBinding?.recyclerView?.invalidateItemDecorations()
@ -194,7 +179,7 @@ class ExploreFragment :
when (item.itemId) { when (item.itemId) {
R.id.action_settings -> { R.id.action_settings -> {
val source = selectedSources.singleOrNull() ?: return false val source = selectedSources.singleOrNull() ?: return false
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), source)) router.openSourceSettings(source)
mode?.finish() mode?.finish()
} }
@ -232,8 +217,7 @@ class ExploreFragment :
} }
private fun onOpenManga(manga: Manga) { private fun onOpenManga(manga: Manga) {
val intent = DetailsActivity.newIntent(context ?: return, manga) router.openDetails(manga)
startActivity(intent)
} }
private fun onGridModeChanged(isGrid: Boolean) { private fun onGridModeChanged(isGrid: Boolean) {

@ -1,15 +1,14 @@
package org.koitharu.kotatsu.explore.ui package org.koitharu.kotatsu.explore.ui
import android.content.Context
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.core.nav.AppRouter
class ExploreMenuProvider( class ExploreMenuProvider(
private val context: Context, private val router: AppRouter,
) : MenuProvider { ) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
@ -19,7 +18,7 @@ class ExploreMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_manage -> { R.id.action_manage -> {
context.startActivity(SettingsActivity.newSourcesSettingsIntent(context)) router.openSourcesSettings()
true true
} }

@ -1,14 +1,12 @@
package org.koitharu.kotatsu.favourites.ui package org.koitharu.kotatsu.favourites.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
@ -21,7 +19,7 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityContainerBinding.inflate(layoutInflater)) setContentView(ActivityContainerBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val categoryTitle = intent.getStringExtra(EXTRA_TITLE) val categoryTitle = intent.getStringExtra(AppRouter.KEY_TITLE)
if (categoryTitle != null) { if (categoryTitle != null) {
title = categoryTitle title = categoryTitle
} }
@ -29,7 +27,7 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
if (fm.findFragmentById(R.id.container) == null) { if (fm.findFragmentById(R.id.container) == null) {
fm.commit { fm.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
val fragment = FavouritesListFragment.newInstance(intent.getLongExtra(EXTRA_CATEGORY_ID, NO_ID)) val fragment = FavouritesListFragment.newInstance(intent.getLongExtra(AppRouter.KEY_ID, NO_ID))
replace(R.id.container, fragment) replace(R.id.container, fragment)
} }
} }
@ -41,16 +39,4 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
right = insets.right, right = insets.right,
) )
} }
companion object {
private const val EXTRA_CATEGORY_ID = "cat_id"
private const val EXTRA_TITLE = "title"
fun newIntent(context: Context) = Intent(context, FavouritesActivity::class.java)
fun newIntent(context: Context, category: FavouriteCategory) = Intent(context, FavouritesActivity::class.java)
.putExtra(EXTRA_CATEGORY_ID, category.id)
.putExtra(EXTRA_TITLE, category.title)
}
} }

@ -46,16 +46,6 @@ class CategoriesSelectionCallback(
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
/*R.id.action_view -> {
val id = controller.peekCheckedIds().singleOrNull() ?: return false
val context = recyclerView.context
val category = viewModel.getCategory(id) ?: return false
val intent = FavouritesActivity.newIntent(context, category)
context.startActivity(intent)
mode.finish()
true
}*/
R.id.action_show -> { R.id.action_show -> {
viewModel.setIsVisible(controller.snapshot(), true) viewModel.setIsVisible(controller.snapshot(), true)
mode?.finish() mode?.finish()

@ -15,14 +15,13 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
@ -71,30 +70,28 @@ class FavouriteCategoriesActivity :
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.fab_add -> startActivity(FavouritesCategoryEditActivity.newIntent(this)) R.id.fab_add -> router.openFavoriteCategoryCreate()
} }
} }
override fun onItemClick(item: FavouriteCategory?, view: View) { override fun onItemClick(item: FavouriteCategory?, view: View) {
if (item == null) { if (item == null) {
if (selectionController.count == 0) { if (selectionController.count == 0) {
startActivity(FavouritesActivity.newIntent(view.context)) router.openFavorites()
} }
return return
} }
if (selectionController.onItemClick(item.id)) { if (selectionController.onItemClick(item.id)) {
return return
} }
val intent = FavouritesActivity.newIntent(view.context, item) router.openFavorites(item)
startActivity(intent)
} }
override fun onEditClick(item: FavouriteCategory, view: View) { override fun onEditClick(item: FavouriteCategory, view: View) {
if (selectionController.onItemClick(item.id)) { if (selectionController.onItemClick(item.id)) {
return return
} }
val intent = FavouritesCategoryEditActivity.newIntent(view.context, item.id) router.openFavoriteCategoryEdit(item.id)
startActivity(intent)
} }
override fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean {

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.favourites.ui.categories.edit package org.koitharu.kotatsu.favourites.ui.categories.edit
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.view.View import android.view.View
@ -69,7 +68,7 @@ class FavouritesCategoryEditActivity :
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
savedInstanceState.getSerializableCompat<ListSortOrder>(KEY_SORT_ORDER)?.let { savedInstanceState.getSerializableCompat<ListSortOrder>(KEY_SORT_ORDER)?.let {
selectedSortOrder = it selectedSortOrder = it
} }
} }
@ -114,8 +113,8 @@ class FavouritesCategoryEditActivity :
selectedSortOrder = category?.order selectedSortOrder = category?.order
val sortText = getString((category?.order ?: ListSortOrder.NEWEST).titleResId) val sortText = getString((category?.order ?: ListSortOrder.NEWEST).titleResId)
viewBinding.editSort.setText(sortText, false) viewBinding.editSort.setText(sortText, false)
viewBinding.switchTracker.setChecked(category?.isTrackingEnabled ?: true, false) viewBinding.switchTracker.setChecked(category?.isTrackingEnabled != false, false)
viewBinding.switchShelf.setChecked(category?.isVisibleInLibrary ?: true, false) viewBinding.switchShelf.setChecked(category?.isVisibleInLibrary != false, false)
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
@ -162,13 +161,7 @@ class FavouritesCategoryEditActivity :
companion object { companion object {
const val EXTRA_ID = "id"
const val NO_ID = -1L const val NO_ID = -1L
private const val KEY_SORT_ORDER = "sort" private const val KEY_SORT_ORDER = "sort"
fun newIntent(context: Context, id: Long = NO_ID): Intent {
return Intent(context, FavouritesCategoryEditActivity::class.java)
.putExtra(EXTRA_ID, id)
}
} }
} }

@ -10,12 +10,12 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.EXTRA_ID
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.NO_ID import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.NO_ID
import org.koitharu.kotatsu.list.domain.ListSortOrder import org.koitharu.kotatsu.list.domain.ListSortOrder
import javax.inject.Inject import javax.inject.Inject
@ -27,7 +27,7 @@ class FavouritesCategoryEditViewModel @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
private val categoryId = savedStateHandle[EXTRA_ID] ?: NO_ID private val categoryId = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID
val onSaved = MutableEventFlow<Unit>() val onSaved = MutableEventFlow<Unit>()
val category = MutableStateFlow<FavouriteCategory?>(null) val category = MutableStateFlow<FavouriteCategory?>(null)

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.favourites.ui.categories.select package org.koitharu.kotatsu.favourites.ui.categories.select
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
@ -13,7 +12,6 @@ import android.widget.Toast
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import coil3.ImageLoader import coil3.ImageLoader
import coil3.request.allowRgb565 import coil3.request.allowRgb565
@ -25,7 +23,7 @@ import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
@ -38,20 +36,16 @@ import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.parsers.model.Manga
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(), class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
OnListItemClickListener<MangaCategoryItem>, DialogInterface.OnClickListener { OnListItemClickListener<MangaCategoryItem>, DialogInterface.OnClickListener {
private val viewModel by viewModels<FavoriteSheetViewModel>() private val viewModel by viewModels<FavoriteDialogViewModel>()
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
@ -84,7 +78,7 @@ class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
} }
override fun onClick(dialog: DialogInterface?, which: Int) { override fun onClick(dialog: DialogInterface?, which: Int) {
startActivity(Intent(context ?: return, FavouriteCategoriesActivity::class.java)) router.openFavoriteCategories()
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
@ -132,19 +126,4 @@ class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
} }
} }
} }
companion object {
private const val TAG = "FavoriteSheet"
const val KEY_MANGA_LIST = "manga_list"
fun show(fm: FragmentManager, manga: Manga) = show(fm, setOf(manga))
fun show(fm: FragmentManager, manga: Collection<Manga>) = FavoriteDialog().withArgs(1) {
putParcelableArrayList(
KEY_MANGA_LIST,
manga.mapTo(ArrayList(manga.size), ::ParcelableManga),
)
}.showDistinct(fm, TAG)
}
} }

@ -16,6 +16,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.ids import org.koitharu.kotatsu.core.model.ids
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -28,13 +29,13 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class FavoriteSheetViewModel @Inject constructor( class FavoriteDialogViewModel @Inject constructor(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
settings: AppSettings, settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
val manga = savedStateHandle.require<List<ParcelableManga>>(FavoriteDialog.KEY_MANGA_LIST).map { val manga = savedStateHandle.require<List<ParcelableManga>>(AppRouter.KEY_MANGA_LIST).map {
it.manga it.manga
} }

@ -6,12 +6,13 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
class FavouriteTabPopupMenuProvider( class FavouriteTabPopupMenuProvider(
private val context: Context, private val context: Context,
private val router: AppRouter,
private val viewModel: FavouritesContainerViewModel, private val viewModel: FavouritesContainerViewModel,
private val categoryId: Long private val categoryId: Long
) : MenuProvider { ) : MenuProvider {
@ -28,12 +29,8 @@ class FavouriteTabPopupMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_hide -> viewModel.hide(categoryId) R.id.action_hide -> viewModel.hide(categoryId)
R.id.action_edit -> context.startActivity( R.id.action_edit -> router.openFavoriteCategoryEdit(categoryId)
FavouritesCategoryEditActivity.newIntent(context, categoryId),
)
R.id.action_delete -> confirmDelete() R.id.action_delete -> confirmDelete()
else -> return false else -> return false
} }
return true return true

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.favourites.ui.container package org.koitharu.kotatsu.favourites.ui.container
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -16,6 +15,7 @@ import coil3.ImageLoader
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.ActionModeListener
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
@ -29,7 +29,6 @@ import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding
import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -55,13 +54,13 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
TabLayoutMediator( TabLayoutMediator(
binding.tabs, binding.tabs,
binding.pager, binding.pager,
FavouritesTabConfigurationStrategy(pagerAdapter, viewModel), FavouritesTabConfigurationStrategy(pagerAdapter, viewModel, router),
).attach() ).attach()
binding.stubEmpty.setOnInflateListener(this) binding.stubEmpty.setOnInflateListener(this)
actionModeDelegate.addListener(this) actionModeDelegate.addListener(this)
viewModel.categories.observe(viewLifecycleOwner, pagerAdapter) viewModel.categories.observe(viewLifecycleOwner, pagerAdapter)
viewModel.isEmpty.observe(viewLifecycleOwner, ::onEmptyStateChanged) viewModel.isEmpty.observe(viewLifecycleOwner, ::onEmptyStateChanged)
addMenuProvider(FavouritesContainerMenuProvider(binding.root.context)) addMenuProvider(FavouritesContainerMenuProvider(router))
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager)) viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager))
} }
@ -102,9 +101,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_retry -> startActivity( R.id.button_retry -> router.openFavoriteCategories()
Intent(v.context, FavouriteCategoriesActivity::class.java),
)
} }
} }

@ -1,16 +1,14 @@
package org.koitharu.kotatsu.favourites.ui.container package org.koitharu.kotatsu.favourites.ui.container
import android.content.Context
import android.content.Intent
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.core.nav.AppRouter
class FavouritesContainerMenuProvider( class FavouritesContainerMenuProvider(
private val context: Context, private val router: AppRouter,
) : MenuProvider { ) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
@ -20,7 +18,7 @@ class FavouritesContainerMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_manage -> { R.id.action_manage -> {
context.startActivity(Intent(context, FavouriteCategoriesActivity::class.java)) router.openFavoriteCategories()
} }
else -> return false else -> return false

@ -3,17 +3,19 @@ package org.koitharu.kotatsu.favourites.ui.container
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.util.PopupMenuMediator import org.koitharu.kotatsu.core.ui.util.PopupMenuMediator
class FavouritesTabConfigurationStrategy( class FavouritesTabConfigurationStrategy(
private val adapter: FavouritesContainerAdapter, private val adapter: FavouritesContainerAdapter,
private val viewModel: FavouritesContainerViewModel, private val viewModel: FavouritesContainerViewModel,
private val router: AppRouter,
) : TabConfigurationStrategy { ) : TabConfigurationStrategy {
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = adapter.getItem(position) val item = adapter.getItem(position)
tab.text = item.title ?: tab.view.context.getString(R.string.all_favourites) tab.text = item.title ?: tab.view.context.getString(R.string.all_favourites)
tab.tag = item tab.tag = item
PopupMenuMediator(FavouriteTabPopupMenuProvider(tab.view.context, viewModel, item.id)).attach(tab.view) PopupMenuMediator(FavouriteTabPopupMenuProvider(tab.view.context, router, viewModel, item.id)).attach(tab.view)
} }
} }

@ -9,13 +9,13 @@ import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentFilterHeaderBinding import org.koitharu.kotatsu.databinding.FragmentFilterHeaderBinding
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.tags.TagsCatalogSheet
import org.koitharu.kotatsu.parsers.model.ContentRating import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Demographic import org.koitharu.kotatsu.parsers.model.Demographic
@ -54,7 +54,7 @@ class FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsV
when (data) { when (data) {
is MangaTag -> filter.toggleTag(data, !chip.isChecked) is MangaTag -> filter.toggleTag(data, !chip.isChecked)
is String -> Unit is String -> Unit
null -> TagsCatalogSheet.show(parentFragmentManager, isExcludeTag = false) null -> router.showTagsCatalogSheet(excludeMode = false)
} }
} }

@ -9,13 +9,12 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.slider.RangeSlider import com.google.android.material.slider.RangeSlider
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.titleResId import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.model.titleRes import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
@ -25,11 +24,9 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.parentView import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.setValueRounded import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.setValuesRounded import org.koitharu.kotatsu.core.util.ext.setValuesRounded
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.databinding.SheetFilterBinding import org.koitharu.kotatsu.databinding.SheetFilterBinding
import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.model.FilterProperty import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.filter.ui.tags.TagsCatalogSheet
import org.koitharu.kotatsu.parsers.model.ContentRating import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Demographic import org.koitharu.kotatsu.parsers.model.Demographic
@ -88,10 +85,10 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
binding.sliderYear.addOnChangeListener(this::onSliderValueChange) binding.sliderYear.addOnChangeListener(this::onSliderValueChange)
binding.sliderYearsRange.addOnChangeListener(this::onRangeSliderValueChange) binding.sliderYearsRange.addOnChangeListener(this::onRangeSliderValueChange)
binding.layoutGenres.setOnMoreButtonClickListener { binding.layoutGenres.setOnMoreButtonClickListener {
TagsCatalogSheet.show(getChildFragmentManager(), isExcludeTag = false) router.showTagsCatalogSheet(excludeMode = false)
} }
binding.layoutGenresExclude.setOnMoreButtonClickListener { binding.layoutGenresExclude.setOnMoreButtonClickListener {
TagsCatalogSheet.show(getChildFragmentManager(), isExcludeTag = true) router.showTagsCatalogSheet(excludeMode = true)
} }
} }
@ -153,7 +150,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
is ContentType -> filter.toggleContentType(data, !chip.isChecked) is ContentType -> filter.toggleContentType(data, !chip.isChecked)
is ContentRating -> filter.toggleContentRating(data, !chip.isChecked) is ContentRating -> filter.toggleContentRating(data, !chip.isChecked)
is Demographic -> filter.toggleDemographic(data, !chip.isChecked) is Demographic -> filter.toggleDemographic(data, !chip.isChecked)
null -> TagsCatalogSheet.show(getChildFragmentManager(), chip.parentView?.id == R.id.chips_genresExclude) null -> router.showTagsCatalogSheet(excludeMode = chip.parentView?.id == R.id.chips_genresExclude)
} }
} }
@ -351,13 +348,4 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
} }
private fun requireFilter() = (requireActivity() as FilterCoordinator.Owner).filterCoordinator private fun requireFilter() = (requireActivity() as FilterCoordinator.Owner).filterCoordinator
companion object {
private const val TAG = "FilterSheet"
fun show(fm: FragmentManager) = FilterSheetFragment().showDistinct(fm, TAG)
fun isSupported(fragment: Fragment) = fragment.activity is FilterCoordinator.Owner
}
} }

@ -9,17 +9,15 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.withCreationCallback import dagger.hilt.android.lifecycle.withCreationCallback
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetTagsBinding import org.koitharu.kotatsu.databinding.SheetTagsBinding
import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.model.TagCatalogItem import org.koitharu.kotatsu.filter.ui.model.TagCatalogItem
@ -33,7 +31,7 @@ class TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(), OnListItemClickL
defaultViewModelCreationExtras.withCreationCallback<TagsCatalogViewModel.Factory> { factory -> defaultViewModelCreationExtras.withCreationCallback<TagsCatalogViewModel.Factory> { factory ->
factory.create( factory.create(
filter = (requireActivity() as FilterCoordinator.Owner).filterCoordinator, filter = (requireActivity() as FilterCoordinator.Owner).filterCoordinator,
isExcludeTag = requireArguments().getBoolean(ARG_EXCLUDE), isExcludeTag = requireArguments().getBoolean(AppRouter.KEY_EXCLUDE),
) )
} }
}, },
@ -89,14 +87,4 @@ class TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(), OnListItemClickL
override fun onStateChanged(sheet: View, newState: Int) { override fun onStateChanged(sheet: View, newState: Int) {
viewBinding?.recyclerView?.isFastScrollerEnabled = newState == AdaptiveSheetBehavior.STATE_EXPANDED viewBinding?.recyclerView?.isFastScrollerEnabled = newState == AdaptiveSheetBehavior.STATE_EXPANDED
} }
companion object {
private const val TAG = "TagsCatalogSheet"
private const val ARG_EXCLUDE = "exclude"
fun show(fm: FragmentManager, isExcludeTag: Boolean) = TagsCatalogSheet().withArgs(1) {
putBoolean(ARG_EXCLUDE, isExcludeTag)
}.showDistinct(fm, TAG)
}
} }

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.history.ui package org.koitharu.kotatsu.history.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -41,9 +39,4 @@ class HistoryActivity :
right = insets.right, right = insets.right,
) )
} }
companion object {
fun newIntent(context: Context) = Intent(context, HistoryActivity::class.java)
}
} }

@ -8,6 +8,7 @@ import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
@ -27,7 +28,7 @@ class HistoryListFragment : MangaListFragment() {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
RecyclerScrollKeeper(binding.recyclerView).attach() RecyclerScrollKeeper(binding.recyclerView).attach()
addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel)) addMenuProvider(HistoryListMenuProvider(binding.root.context, router, viewModel))
viewModel.isStatsEnabled.observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) viewModel.isStatsEnabled.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
} }

@ -1,15 +1,14 @@
package org.koitharu.kotatsu.history.ui package org.koitharu.kotatsu.history.ui
import android.content.Context import android.content.Context
import android.content.Intent
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
import org.koitharu.kotatsu.stats.ui.StatsActivity
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.ZoneId import java.time.ZoneId
@ -17,6 +16,7 @@ import java.time.temporal.ChronoUnit
class HistoryListMenuProvider( class HistoryListMenuProvider(
private val context: Context, private val context: Context,
private val router: AppRouter,
private val viewModel: HistoryListViewModel, private val viewModel: HistoryListViewModel,
) : MenuProvider { ) : MenuProvider {
@ -37,7 +37,7 @@ class HistoryListMenuProvider(
} }
R.id.action_stats -> { R.id.action_stats -> {
context.startActivity(Intent(context, StatsActivity::class.java)) router.openStatistic()
true true
} }

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.image.ui package org.koitharu.kotatsu.image.ui
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -29,6 +27,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.PopupMenuMediator import org.koitharu.kotatsu.core.ui.util.PopupMenuMediator
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
@ -41,7 +40,6 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivityImageBinding import org.koitharu.kotatsu.databinding.ActivityImageBinding
import org.koitharu.kotatsu.databinding.ItemErrorStateBinding import org.koitharu.kotatsu.databinding.ItemErrorStateBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -123,7 +121,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
.memoryCachePolicy(CachePolicy.DISABLED) .memoryCachePolicy(CachePolicy.DISABLED)
.lifecycle(this) .lifecycle(this)
.listener(this) .listener(this)
.mangaSourceExtra(MangaSource(intent.getStringExtra(EXTRA_SOURCE))) .mangaSourceExtra(MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)))
.target(SsivTarget(viewBinding.ssiv)) .target(SsivTarget(viewBinding.ssiv))
.enqueueWith(coil) .enqueueWith(coil)
} }
@ -142,7 +140,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
button.setImageDrawable( button.setImageDrawable(
CircularProgressDrawable(this).also { CircularProgressDrawable(this).also {
it.setStyle(CircularProgressDrawable.LARGE) it.setStyle(CircularProgressDrawable.LARGE)
it.setColorSchemeColors(getThemeColor(com.google.android.material.R.attr.colorControlNormal)) it.setColorSchemeColors(getThemeColor(materialR.attr.colorControlNormal))
it.start() it.start()
}, },
) )
@ -175,15 +173,4 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
} }
} }
} }
companion object {
const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, url: String, source: MangaSource?): Intent {
return Intent(context, ImageActivity::class.java)
.setData(Uri.parse(url))
.putExtra(EXTRA_SOURCE, source?.name)
}
}
} }

@ -13,7 +13,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
@ -35,9 +35,9 @@ class ImageViewModel @Inject constructor(
launchLoadingJob(Dispatchers.Default) { launchLoadingJob(Dispatchers.Default) {
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
.memoryCachePolicy(CachePolicy.READ_ONLY) .memoryCachePolicy(CachePolicy.READ_ONLY)
.data(savedStateHandle.require<Uri>(BaseActivity.EXTRA_DATA)) .data(savedStateHandle.require<Uri>(AppRouter.KEY_DATA))
.memoryCachePolicy(CachePolicy.DISABLED) .memoryCachePolicy(CachePolicy.DISABLED)
.mangaSourceExtra(MangaSource(savedStateHandle[ImageActivity.EXTRA_SOURCE])) .mangaSourceExtra(MangaSource(savedStateHandle[AppRouter.KEY_SOURCE]))
.build() .build()
val bitmap = coil.execute(request).getDrawableOrThrow().toBitmap() val bitmap = coil.execute(request).getDrawableOrThrow().toBitmap()
runInterruptible(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {

@ -24,6 +24,7 @@ import org.koitharu.kotatsu.alternatives.ui.AutoFixService
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
@ -44,9 +45,6 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteDialog
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.QuickFilterListener import org.koitharu.kotatsu.list.domain.QuickFilterListener
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
@ -60,9 +58,7 @@ import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver
import org.koitharu.kotatsu.main.ui.MainActivity import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject import javax.inject.Inject
@ -125,7 +121,6 @@ abstract class MangaListFragment :
isEnabled = isSwipeRefreshEnabled isEnabled = isSwipeRefreshEnabled
} }
addMenuProvider(MangaListMenuProvider(this)) addMenuProvider(MangaListMenuProvider(this))
DownloadDialogFragment.registerCallback(this, binding.recyclerView)
viewModel.listMode.observe(viewLifecycleOwner, ::onListModeChanged) viewModel.listMode.observe(viewLifecycleOwner, ::onListModeChanged)
viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged)
@ -147,7 +142,7 @@ abstract class MangaListFragment :
override fun onItemClick(item: Manga, view: View) { override fun onItemClick(item: Manga, view: View) {
if (selectionController?.onItemClick(item.id) != true) { if (selectionController?.onItemClick(item.id) != true) {
if ((activity as? MangaListActivity)?.showPreview(item) != true) { if ((activity as? MangaListActivity)?.showPreview(item) != true) {
startActivity(DetailsActivity.newIntent(context ?: return, item)) router.openDetails(item)
} }
} }
} }
@ -162,16 +157,14 @@ abstract class MangaListFragment :
override fun onReadClick(manga: Manga, view: View) { override fun onReadClick(manga: Manga, view: View) {
if (selectionController?.onItemClick(manga.id) != true) { if (selectionController?.onItemClick(manga.id) != true) {
val intent = IntentBuilder(view.context).manga(manga).build() router.openReader(manga)
startActivity(intent)
} }
} }
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) { override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (selectionController?.onItemClick(manga.id) != true) { if (selectionController?.onItemClick(manga.id) != true) {
// TODO dialog // TODO dialog
val intent = MangaListActivity.newIntent(view.context, tag.source, MangaListFilter(tags = setOf(tag))) router.openList(tag)
startActivity(intent)
} }
} }
@ -317,13 +310,13 @@ abstract class MangaListFragment :
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavoriteDialog.show(getChildFragmentManager(), selectedItems) router.showFavoriteDialog(selectedItems)
mode?.finish() mode?.finish()
true true
} }
R.id.action_save -> { R.id.action_save -> {
DownloadDialogFragment.show(childFragmentManager, selectedItems) router.showDownloadDialog(selectedItems, viewBinding?.recyclerView)
mode?.finish() mode?.finish()
true true
} }

@ -6,9 +6,9 @@ import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.list.ui.config.ListConfigBottomSheet
import org.koitharu.kotatsu.list.ui.config.ListConfigSection import org.koitharu.kotatsu.list.ui.config.ListConfigSection
import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesFragment import org.koitharu.kotatsu.tracker.ui.updates.UpdatesFragment
@ -30,7 +30,7 @@ class MangaListMenuProvider(
is UpdatesFragment -> ListConfigSection.Updated is UpdatesFragment -> ListConfigSection.Updated
else -> ListConfigSection.General else -> ListConfigSection.General
} }
ListConfigBottomSheet.show(fragment.childFragmentManager, section) fragment.router.showListConfigSheet(section)
true true
} }

@ -8,7 +8,6 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.CompoundButton import android.widget.CompoundButton
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
@ -17,8 +16,6 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.setValueRounded import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
import org.koitharu.kotatsu.databinding.SheetListModeBinding import org.koitharu.kotatsu.databinding.SheetListModeBinding
@ -113,14 +110,4 @@ class ListConfigBottomSheet :
} }
override fun onNothingSelected(parent: AdapterView<*>?) = Unit override fun onNothingSelected(parent: AdapterView<*>?) = Unit
companion object {
private const val TAG = "ListModeSelectDialog"
const val ARG_SECTION = "section"
fun show(fm: FragmentManager, section: ListConfigSection) = ListConfigBottomSheet().withArgs(1) {
putParcelable(ARG_SECTION, section)
}.showDistinct(fm, TAG)
}
} }

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.list.ui.config
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -21,7 +22,7 @@ class ListConfigViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() { ) : BaseViewModel() {
val section = savedStateHandle.require<ListConfigSection>(ListConfigBottomSheet.ARG_SECTION) val section = savedStateHandle.require<ListConfigSection>(AppRouter.KEY_LIST_SECTION)
var listMode: ListMode var listMode: ListMode
get() = when (section) { get() = when (section) {

@ -21,6 +21,7 @@ import coil3.util.CoilUtils
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
@ -31,16 +32,11 @@ import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.FragmentPreviewBinding import org.koitharu.kotatsu.databinding.FragmentPreviewBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject import javax.inject.Inject
@ -77,33 +73,18 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
val manga = viewModel.manga.value val manga = viewModel.manga.value
when (v.id) { when (v.id) {
R.id.button_close -> closeSelf() R.id.button_close -> closeSelf()
R.id.button_open -> startActivity( R.id.button_open -> router.openDetails(manga)
DetailsActivity.newIntent(v.context, manga), R.id.button_read -> router.openReader(manga)
)
R.id.button_read -> {
startActivity(
ReaderActivity.IntentBuilder(v.context)
.manga(manga)
.build(),
)
}
R.id.textView_author -> startActivity( R.id.textView_author -> router.openSearch(
MangaListActivity.newIntent( source = manga.source,
context = v.context, query = manga.author ?: return,
source = manga.source,
filter = MangaListFilter(query = manga.author),
),
) )
R.id.imageView_cover -> startActivity( R.id.imageView_cover -> router.openImage(
ImageActivity.newIntent( url = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
v.context, source = manga.source,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }, anchor = v,
manga.source,
),
scaleUpActivityOptionsOf(v),
) )
} }
} }
@ -114,7 +95,7 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
val tag = data as? MangaTag ?: return val tag = data as? MangaTag ?: return
val filter = (activity as? FilterCoordinator.Owner)?.filterCoordinator val filter = (activity as? FilterCoordinator.Owner)?.filterCoordinator
if (filter == null) { if (filter == null) {
startActivity(MangaListActivity.newIntent(chip.context, tag.source, MangaListFilter(tags = setOf(tag)))) router.openList(tag)
} else { } else {
filter.toggleTag(tag, true) filter.toggleTag(tag, true)
closeSelf() closeSelf()

@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.core.util.ext.require
@ -42,7 +42,7 @@ class PreviewViewModel @Inject constructor(
) : BaseViewModel() { ) : BaseViewModel() {
val manga = MutableStateFlow( val manga = MutableStateFlow(
savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga, savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga,
) )
val footer = combine( val footer = combine(

@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -74,11 +73,4 @@ class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.On
Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show() Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show()
dismiss() dismiss()
} }
companion object {
private const val TAG = "ImportDialogFragment"
fun show(fm: FragmentManager) = ImportDialogFragment().show(fm, TAG)
}
} }

@ -18,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver import org.koitharu.kotatsu.core.ErrorReporterReceiver
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.CoroutineIntentService import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
@ -25,7 +26,6 @@ import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.toUriOrNull import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.local.data.importer.SingleMangaImporter import org.koitharu.kotatsu.local.data.importer.SingleMangaImporter
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@ -113,7 +113,7 @@ class ImportService : CoroutineIntentService() {
).toBitmapOrNull(), ).toBitmapOrNull(),
) )
notification.setSubText(manga.title) notification.setSubText(manga.title)
val intent = DetailsActivity.newIntent(applicationContext, manga) val intent = AppRouter.detailsIntent(applicationContext, manga)
notification.setContentIntent( notification.setContentIntent(
PendingIntentCompat.getActivity( PendingIntentCompat.getActivity(
applicationContext, applicationContext,

@ -16,6 +16,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
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.nav.router
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.ui.widgets.TipView
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
@ -25,12 +26,10 @@ import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.remotelist.ui.MangaSearchMenuProvider import org.koitharu.kotatsu.remotelist.ui.MangaSearchMenuProvider
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.settings.storage.RequestStorageManagerPermissionContract import org.koitharu.kotatsu.settings.storage.RequestStorageManagerPermissionContract
import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity
class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner { class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
@ -68,11 +67,11 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
} }
override fun onEmptyActionClick() { override fun onEmptyActionClick() {
ImportDialogFragment.show(getChildFragmentManager()) router.showImportDialog()
} }
override fun onFilterClick(view: View?) { override fun onFilterClick(view: View?) {
FilterSheetFragment.show(getChildFragmentManager()) router.showFilterSheet()
} }
override fun onPrimaryButtonClick(tipView: TipView) { override fun onPrimaryButtonClick(tipView: TipView) {
@ -82,7 +81,7 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
} }
override fun onSecondaryButtonClick(tipView: TipView) { override fun onSecondaryButtonClick(tipView: TipView) {
startActivity(MangaDirectoriesActivity.newIntent(tipView.context)) router.openDirectoriesSettings()
} }
override fun onScrolledToEnd() = viewModel.loadNextPage() override fun onScrolledToEnd() = viewModel.loadNextPage()

@ -1,14 +1,12 @@
package org.koitharu.kotatsu.local.ui package org.koitharu.kotatsu.local.ui
import android.content.Intent
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity
class LocalListMenuProvider( class LocalListMenuProvider(
private val fragment: Fragment, private val fragment: Fragment,
@ -21,7 +19,7 @@ class LocalListMenuProvider(
override fun onPrepareMenu(menu: Menu) { override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu) super.onPrepareMenu(menu)
menu.findItem(R.id.action_filter)?.isVisible = FilterSheetFragment.isSupported(fragment) menu.findItem(R.id.action_filter)?.isVisible = fragment.router.isFilterSupported()
} }
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
@ -32,14 +30,12 @@ class LocalListMenuProvider(
} }
R.id.action_directories -> { R.id.action_directories -> {
fragment.context?.run { fragment.router.openDirectoriesSettings()
startActivity(Intent(this, MangaDirectoriesActivity::class.java))
}
true true
} }
R.id.action_filter -> { R.id.action_filter -> {
FilterSheetFragment.show(fragment.childFragmentManager) fragment.router.showFilterSheet()
true true
} }

@ -7,13 +7,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.widget.TextViewCompat import androidx.core.widget.TextViewCompat
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView import org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
import org.koitharu.kotatsu.core.util.FileSize import org.koitharu.kotatsu.core.util.FileSize
@ -21,10 +19,7 @@ import org.koitharu.kotatsu.core.util.KotatsuColors
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setProgressIcon import org.koitharu.kotatsu.core.util.ext.setProgressIcon
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.DialogLocalInfoBinding import org.koitharu.kotatsu.databinding.DialogLocalInfoBinding
import org.koitharu.kotatsu.parsers.model.Manga
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@AndroidEntryPoint @AndroidEntryPoint
@ -108,16 +103,4 @@ class LocalInfoDialog : AlertDialogFragment<DialogLocalInfoBinding>(), View.OnCl
) )
view.animateSegments(listOf(segment)) view.animateSegments(listOf(segment))
} }
companion object {
const val ARG_MANGA = "manga"
private const val TAG = "LocalInfoDialog"
fun show(fm: FragmentManager, manga: Manga) {
LocalInfoDialog().withArgs(1) {
putParcelable(ARG_MANGA, ParcelableManga(manga))
}.showDistinct(fm, TAG)
}
}
} }

@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
@ -25,7 +26,7 @@ class LocalInfoViewModel @Inject constructor(
private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase, private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,
) : BaseViewModel() { ) : BaseViewModel() {
private val manga = savedStateHandle.require<ParcelableManga>(LocalInfoDialog.ARG_MANGA).manga private val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
val isCleaningUp = MutableStateFlow(false) val isCleaningUp = MutableStateFlow(false)
val onCleanedUp = MutableEventFlow<Pair<Int, Long>>() val onCleanedUp = MutableEventFlow<Pair<Int, Long>>()

@ -40,6 +40,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.NavItem import org.koitharu.kotatsu.core.prefs.NavItem
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
@ -49,29 +50,20 @@ import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper
import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.service.MangaPrefetchService import org.koitharu.kotatsu.details.service.MangaPrefetchService
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.local.ui.LocalIndexUpdateService import org.koitharu.kotatsu.local.ui.LocalIndexUpdateService
import org.koitharu.kotatsu.local.ui.LocalStorageCleanupWorker import org.koitharu.kotatsu.local.ui.LocalStorageCleanupWorker
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.main.ui.welcome.WelcomeSheet
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
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.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.multi.SearchActivity
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.about.AppUpdateActivity
import org.koitharu.kotatsu.settings.backup.PeriodicalBackupService import org.koitharu.kotatsu.settings.backup.PeriodicalBackupService
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -137,9 +129,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
viewModel.feedCounter.observe(this, ::onFeedCounterChanged) viewModel.feedCounter.observe(this, ::onFeedCounterChanged)
viewModel.appUpdate.observe(this, MenuInvalidator(this)) viewModel.appUpdate.observe(this, MenuInvalidator(this))
viewModel.onFirstStart.observeEvent(this) { viewModel.onFirstStart.observeEvent(this) { router.showWelcomeSheet() }
WelcomeSheet.show(supportFragmentManager)
}
viewModel.isBottomNavPinned.observe(this, ::setNavbarPinned) viewModel.isBottomNavPinned.observe(this, ::setNavbarPinned)
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged) searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
viewBinding.bottomNav?.addOnLayoutChangeListener(this) viewBinding.bottomNav?.addOnLayoutChangeListener(this)
@ -188,7 +178,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
} }
R.id.action_settings -> { R.id.action_settings -> {
startActivity(SettingsActivity.newIntent(this)) router.openSettings()
true true
} }
@ -198,7 +188,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
} }
R.id.action_app_update -> { R.id.action_app_update -> {
startActivity(Intent(this, AppUpdateActivity::class.java)) router.openAppUpdate()
true true
} }
@ -241,7 +231,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
if (fragment == null) { if (fragment == null) {
supportFragmentManager.commit { supportFragmentManager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
add(R.id.container, SearchSuggestionFragment.newInstance(), TAG_SEARCH) add(R.id.container, SearchSuggestionFragment(), TAG_SEARCH)
navigationDelegate.primaryFragment?.let { navigationDelegate.primaryFragment?.let {
setMaxLifecycle(it, Lifecycle.State.STARTED) setMaxLifecycle(it, Lifecycle.State.STARTED)
} }
@ -253,13 +243,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
} }
override fun onMangaClick(manga: Manga) { override fun onMangaClick(manga: Manga) {
startActivity(DetailsActivity.newIntent(this, manga)) router.openDetails(manga)
} }
override fun onQueryClick(query: String, submit: Boolean) { override fun onQueryClick(query: String, submit: Boolean) {
viewBinding.searchView.query = query viewBinding.searchView.query = query
if (submit && query.isNotEmpty()) { if (submit && query.isNotEmpty()) {
startActivity(SearchActivity.newIntent(this, query)) router.openSearch(query)
searchSuggestionViewModel.saveQuery(query) searchSuggestionViewModel.saveQuery(query)
viewBinding.searchView.post { viewBinding.searchView.post {
closeSearchCallback.handleOnBackPressed() closeSearchCallback.handleOnBackPressed()
@ -268,7 +258,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
} }
override fun onTagClick(tag: MangaTag) { override fun onTagClick(tag: MangaTag) {
startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag)))) router.openList(tag)
} }
override fun onQueryChanged(query: String) { override fun onQueryChanged(query: String) {
@ -280,8 +270,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
} }
override fun onSourceClick(source: MangaSource) { override fun onSourceClick(source: MangaSource) {
val intent = MangaListActivity.newIntent(this, source, null) router.openList(source, null)
startActivity(intent)
} }
override fun onSupportActionModeStarted(mode: ActionMode) { override fun onSupportActionModeStarted(mode: ActionMode) {
@ -302,10 +291,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
private fun onOpenReader(manga: Manga) { private fun onOpenReader(manga: Manga) {
val fab = viewBinding.fab ?: viewBinding.navRail?.headerView val fab = viewBinding.fab ?: viewBinding.navRail?.headerView
val options = fab?.let { router.openReader(manga, fab)
scaleUpActivityOptionsOf(it)
}
startActivity(IntentBuilder(this).manga(manga).build(), options)
} }
private fun onFeedCounterChanged(counter: Int) { private fun onFeedCounterChanged(counter: Int) {

@ -9,23 +9,21 @@ import android.view.ViewGroup
import androidx.activity.result.ActivityResultCallback import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.titleResId import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.getDisplayName import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.tryLaunch import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.databinding.SheetWelcomeBinding import org.koitharu.kotatsu.databinding.SheetWelcomeBinding
import org.koitharu.kotatsu.filter.ui.model.FilterProperty import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
import java.util.Locale import java.util.Locale
@AndroidEntryPoint @AndroidEntryPoint
@ -82,7 +80,7 @@ class WelcomeSheet : BaseAdaptiveSheet<SheetWelcomeBinding>(), ChipsView.OnChipC
override fun onActivityResult(result: Uri?) { override fun onActivityResult(result: Uri?) {
if (result != null) { if (result != null) {
RestoreDialogFragment.show(parentFragmentManager, result) router.showBackupRestoreDialog(result)
} }
} }
@ -111,17 +109,4 @@ class WelcomeSheet : BaseAdaptiveSheet<SheetWelcomeBinding>(), ChipsView.OnChipC
}, },
) )
} }
companion object {
private const val TAG = "WelcomeSheet"
fun show(fm: FragmentManager) = WelcomeSheet().showDistinct(fm, TAG)
fun dismiss(fm: FragmentManager): Boolean {
val sheet = fm.findFragmentByTag(TAG) as? WelcomeSheet ?: return false
sheet.dismissAllowingStateLoss()
return true
}
}
} }

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.reader.ui package org.koitharu.kotatsu.reader.ui
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.transition.Fade import android.transition.Fade
@ -29,12 +28,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity
@ -50,9 +47,7 @@ import org.koitharu.kotatsu.core.util.ext.postDelayed
import org.koitharu.kotatsu.core.util.ext.setValueRounded import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.zipWithPrevious import org.koitharu.kotatsu.core.util.ext.zipWithPrevious
import org.koitharu.kotatsu.databinding.ActivityReaderBinding import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.details.ui.pager.pages.PagesSavedObserver import org.koitharu.kotatsu.details.ui.pager.pages.PagesSavedObserver
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.data.TapGridSettings import org.koitharu.kotatsu.reader.data.TapGridSettings
import org.koitharu.kotatsu.reader.domain.TapGridArea import org.koitharu.kotatsu.reader.domain.TapGridArea
@ -169,7 +164,7 @@ class ReaderActivity :
override fun getParentActivityIntent(): Intent? { override fun getParentActivityIntent(): Intent? {
val manga = viewModel.getMangaOrNull() ?: return null val manga = viewModel.getMangaOrNull() ?: return null
return DetailsActivity.newIntent(this, manga) return AppRouter.detailsIntent(this, manga)
} }
override fun onUserInteraction() { override fun onUserInteraction() {
@ -358,7 +353,7 @@ class ReaderActivity :
override fun openMenu() { override fun openMenu() {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return val currentMode = readerManager.currentMode ?: return
ReaderConfigSheet.show(supportFragmentManager, currentMode) router.showReaderConfigSheet(currentMode)
} }
override fun scrollBy(delta: Int, smooth: Boolean): Boolean { override fun scrollBy(delta: Int, smooth: Boolean): Boolean {
@ -413,50 +408,8 @@ class ReaderActivity :
viewModel.uiState.value?.isSliderAvailable() == true viewModel.uiState.value?.isSliderAvailable() == true
} }
class IntentBuilder(context: Context) {
private val intent = Intent(context, ReaderActivity::class.java)
.setAction(ACTION_MANGA_READ)
fun manga(manga: Manga) = apply {
intent.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
fun mangaId(mangaId: Long) = apply {
intent.putExtra(MangaIntent.KEY_ID, mangaId)
}
fun incognito(incognito: Boolean) = apply {
intent.putExtra(EXTRA_INCOGNITO, incognito)
}
fun branch(branch: String?) = apply {
intent.putExtra(EXTRA_BRANCH, branch)
}
fun state(state: ReaderState?) = apply {
intent.putExtra(EXTRA_STATE, state)
}
fun bookmark(bookmark: Bookmark) = manga(
bookmark.manga,
).state(
ReaderState(
chapterId = bookmark.chapterId,
page = bookmark.page,
scroll = bookmark.scroll,
),
)
fun build() = intent
}
companion object { companion object {
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"
const val EXTRA_STATE = "state"
const val EXTRA_BRANCH = "branch"
const val EXTRA_INCOGNITO = "incognito"
private const val TOAST_DURATION = 1500L private const val TOAST_DURATION = 1500L
} }
} }

@ -6,8 +6,7 @@ import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
class ReaderMenuProvider( class ReaderMenuProvider(
private val activity: FragmentActivity, private val activity: FragmentActivity,
@ -42,14 +41,14 @@ class ReaderMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_pages_thumbs -> { R.id.action_pages_thumbs -> {
ChaptersPagesSheet.show(activity.supportFragmentManager) activity.router.showChapterPagesSheet()
true true
} }
R.id.action_options -> { R.id.action_options -> {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return false val currentMode = readerManager.currentMode ?: return false
ReaderConfigSheet.show(activity.supportFragmentManager, currentMode) activity.router.showReaderConfigSheet(currentMode)
true true
} }

@ -31,9 +31,10 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.nav.MangaIntent
import org.koitharu.kotatsu.core.nav.ReaderIntent
import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
@ -104,8 +105,8 @@ class ReaderViewModel @Inject constructor(
private var stateChangeJob: Job? = null private var stateChangeJob: Job? = null
init { init {
selectedBranch.value = savedStateHandle.get<String>(ReaderActivity.EXTRA_BRANCH) selectedBranch.value = savedStateHandle.get<String>(ReaderIntent.EXTRA_BRANCH)
readingState.value = savedStateHandle[ReaderActivity.EXTRA_STATE] readingState.value = savedStateHandle[ReaderIntent.EXTRA_STATE]
mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, false) } mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, false) }
} }
@ -114,7 +115,7 @@ class ReaderViewModel @Inject constructor(
val onShowToast = MutableEventFlow<Int>() val onShowToast = MutableEventFlow<Int>()
val uiState = MutableStateFlow<ReaderUiState?>(null) val uiState = MutableStateFlow<ReaderUiState?>(null)
val incognitoMode = if (savedStateHandle.get<Boolean>(ReaderActivity.EXTRA_INCOGNITO) == true) { val incognitoMode = if (savedStateHandle.get<Boolean>(ReaderIntent.EXTRA_INCOGNITO) == true) {
MutableStateFlow(true) MutableStateFlow(true)
} else { } else {
interactor.observeIncognitoMode(manga) interactor.observeIncognitoMode(manga)
@ -240,7 +241,7 @@ class ReaderViewModel @Inject constructor(
fun saveCurrentState(state: ReaderState? = null) { fun saveCurrentState(state: ReaderState? = null) {
if (state != null) { if (state != null) {
readingState.value = state readingState.value = state
savedStateHandle[ReaderActivity.EXTRA_STATE] = state savedStateHandle[ReaderIntent.EXTRA_STATE] = state
} }
if (incognitoMode.value) { if (incognitoMode.value) {
return return

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.reader.ui.colorfilter package org.koitharu.kotatsu.reader.ui.colorfilter
import android.content.Context
import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
@ -23,8 +21,6 @@ import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.decodeRegion import org.koitharu.kotatsu.core.util.ext.decodeRegion
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
@ -35,7 +31,6 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setChecked import org.koitharu.kotatsu.core.util.ext.setChecked
import org.koitharu.kotatsu.core.util.ext.setValueRounded import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
@ -131,8 +126,8 @@ class ColorFilterConfigActivity :
private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) { private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) {
viewBinding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f) viewBinding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f)
viewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f) viewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f)
viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted ?: false, false) viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted == true, false)
viewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale ?: false, false) viewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale == true, false)
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter() viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
} }
@ -168,15 +163,4 @@ class ColorFilterConfigActivity :
return pattern.format(percent) return pattern.format(percent)
} }
} }
companion object {
const val EXTRA_PAGES = "pages"
const val EXTRA_MANGA = "manga_id"
fun newIntent(context: Context, manga: Manga, page: MangaPage) =
Intent(context, ColorFilterConfigActivity::class.java)
.putExtra(EXTRA_MANGA, ParcelableManga(manga))
.putExtra(EXTRA_PAGES, ParcelableMangaPage(page))
}
} }

@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -13,7 +14,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity.Companion.EXTRA_MANGA
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -23,12 +23,12 @@ class ColorFilterConfigViewModel @Inject constructor(
private val mangaDataRepository: MangaDataRepository, private val mangaDataRepository: MangaDataRepository,
) : BaseViewModel() { ) : BaseViewModel() {
private val manga = savedStateHandle.require<ParcelableManga>(EXTRA_MANGA).manga private val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
private var initialColorFilter: ReaderColorFilter? = null private var initialColorFilter: ReaderColorFilter? = null
val colorFilter = MutableStateFlow<ReaderColorFilter?>(null) val colorFilter = MutableStateFlow<ReaderColorFilter?>(null)
val onDismiss = MutableEventFlow<Unit>() val onDismiss = MutableEventFlow<Unit>()
val preview = savedStateHandle.require<ParcelableMangaPage>(ColorFilterConfigActivity.EXTRA_PAGES).page val preview = savedStateHandle.require<ParcelableMangaPage>(AppRouter.KEY_PAGES).page
val isChanged: Boolean val isChanged: Boolean
get() = colorFilter.value != initialColorFilter get() = colorFilter.value != initialColorFilter

@ -7,7 +7,6 @@ import android.view.ViewGroup
import android.widget.CompoundButton import android.widget.CompoundButton
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.button.MaterialButtonToggleGroup
@ -19,6 +18,8 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
@ -26,15 +27,11 @@ import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderViewModel import org.koitharu.kotatsu.reader.ui.ReaderViewModel
import org.koitharu.kotatsu.reader.ui.ScreenOrientationHelper import org.koitharu.kotatsu.reader.ui.ScreenOrientationHelper
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
import org.koitharu.kotatsu.settings.SettingsActivity
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -64,7 +61,7 @@ class ReaderConfigSheet :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mode = arguments?.getInt(ARG_MODE) mode = arguments?.getInt(AppRouter.KEY_READER_MODE)
?.let { ReaderMode.valueOf(it) } ?.let { ReaderMode.valueOf(it) }
?: ReaderMode.STANDARD ?: ReaderMode.STANDARD
imageServerDelegate = ImageServerDelegate( imageServerDelegate = ImageServerDelegate(
@ -129,7 +126,7 @@ class ReaderConfigSheet :
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_settings -> { R.id.button_settings -> {
startActivity(SettingsActivity.newReaderSettingsIntent(v.context)) router.openReaderSettings()
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }
@ -145,7 +142,7 @@ class ReaderConfigSheet :
R.id.button_color_filter -> { R.id.button_color_filter -> {
val page = viewModel.getCurrentPage() ?: return val page = viewModel.getCurrentPage() ?: return
val manga = viewModel.getMangaOrNull() ?: return val manga = viewModel.getMangaOrNull() ?: return
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page)) router.openColorFilterConfig(manga, page)
} }
R.id.button_image_server -> viewLifecycleScope.launch { R.id.button_image_server -> viewLifecycleScope.launch {
@ -243,14 +240,4 @@ class ReaderConfigSheet :
fun onSavePageClick() fun onSavePageClick()
} }
companion object {
private const val TAG = "ReaderConfigBottomSheet"
private const val ARG_MODE = "mode"
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigSheet().withArgs(1) {
putInt(ARG_MODE, mode.id)
}.showDistinct(fm, TAG)
}
} }

@ -80,7 +80,7 @@ class PageHolderDelegate(
fun showErrorDetails(url: String?) { fun showErrorDetails(url: String?) {
val e = error ?: return val e = error ?: return
exceptionResolver.showDetails(e, url) exceptionResolver.showErrorDetails(e, url)
} }
fun onAttachedToWindow() { fun onAttachedToWindow() {

@ -12,8 +12,8 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
@ -22,12 +22,9 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.SettingsActivity
@AndroidEntryPoint @AndroidEntryPoint
class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner { class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
@ -42,9 +39,7 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
addMenuProvider(RemoteListMenuProvider()) addMenuProvider(RemoteListMenuProvider())
addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel)) addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel))
viewModel.isRandomLoading.observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) viewModel.isRandomLoading.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
viewModel.onOpenManga.observeEvent(viewLifecycleOwner) { viewModel.onOpenManga.observeEvent(viewLifecycleOwner) { router.openDetails(it) }
startActivity(DetailsActivity.newIntent(binding.root.context, it))
}
filterCoordinator.observe().distinctUntilChangedBy { it.listFilter.isEmpty() } filterCoordinator.observe().distinctUntilChangedBy { it.listFilter.isEmpty() }
.drop(1) .drop(1)
.observe(viewLifecycleOwner) { .observe(viewLifecycleOwner) {
@ -66,7 +61,7 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
} }
override fun onFilterClick(view: View?) { override fun onFilterClick(view: View?) {
FilterSheetFragment.show(getChildFragmentManager()) router.showFilterSheet()
} }
override fun onEmptyActionClick() { override fun onEmptyActionClick() {
@ -86,13 +81,10 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
Snackbar.make(requireViewBinding().recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT) Snackbar.make(requireViewBinding().recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
.show() .show()
} else { } else {
startActivity( router.openBrowser(
BrowserActivity.newIntent( url = url,
requireContext(), source = viewModel.source,
url, title = viewModel.source.getTitle(requireContext()),
viewModel.source,
viewModel.source.getTitle(requireContext()),
),
) )
} }
} }
@ -105,7 +97,7 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_source_settings -> { R.id.action_source_settings -> {
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), viewModel.source)) router.openSourceSettings(viewModel.source)
true true
} }

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.scrobbling.common.ui.config package org.koitharu.kotatsu.scrobbling.common.ui.config
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
@ -15,6 +14,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
@ -24,9 +24,7 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.showOrHide import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.databinding.ActivityScrobblerConfigBinding import org.koitharu.kotatsu.databinding.ActivityScrobblerConfigBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.config.adapter.ScrobblingMangaAdapter import org.koitharu.kotatsu.scrobbling.common.ui.config.adapter.ScrobblingMangaAdapter
@ -84,9 +82,7 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
} }
override fun onItemClick(item: ScrobblingInfo, view: View) { override fun onItemClick(item: ScrobblingInfo, view: View) {
startActivity( router.openDetails(item.mangaId)
DetailsActivity.newIntent(this, item.mangaId),
)
} }
override fun onClick(v: View) { override fun onClick(v: View) {
@ -133,16 +129,9 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
} }
companion object { companion object {
const val EXTRA_SERVICE_ID = "service"
const val HOST_SHIKIMORI_AUTH = "shikimori-auth" const val HOST_SHIKIMORI_AUTH = "shikimori-auth"
const val HOST_ANILIST_AUTH = "anilist-auth" const val HOST_ANILIST_AUTH = "anilist-auth"
const val HOST_MAL_AUTH = "mal-auth" const val HOST_MAL_AUTH = "mal-auth"
const val HOST_KITSU_AUTH = "kitsu-auth" const val HOST_KITSU_AUTH = "kitsu-auth"
fun newIntent(context: Context, service: ScrobblerService) =
Intent(context, ScrobblerConfigActivity::class.java)
.putExtra(EXTRA_SERVICE_ID, service.id)
} }
} }

@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
@ -100,11 +100,11 @@ class ScrobblerConfigViewModel @Inject constructor(
private fun getScrobblerService( private fun getScrobblerService(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
): ScrobblerService { ): ScrobblerService {
val serviceId = savedStateHandle.get<Int>(ScrobblerConfigActivity.EXTRA_SERVICE_ID) ?: 0 val serviceId = savedStateHandle.get<Int>(AppRouter.KEY_ID) ?: 0
if (serviceId != 0) { if (serviceId != 0) {
return ScrobblerService.entries.first { it.id == serviceId } return ScrobblerService.entries.first { it.id == serviceId }
} }
val uri = savedStateHandle.require<Uri>(BaseActivity.EXTRA_DATA) val uri = savedStateHandle.require<Uri>(AppRouter.KEY_DATA)
return when (uri.host) { return when (uri.host) {
ScrobblerConfigActivity.HOST_SHIKIMORI_AUTH -> ScrobblerService.SHIKIMORI ScrobblerConfigActivity.HOST_SHIKIMORI_AUTH -> ScrobblerService.SHIKIMORI
ScrobblerConfigActivity.HOST_ANILIST_AUTH -> ScrobblerService.ANILIST ScrobblerConfigActivity.HOST_ANILIST_AUTH -> ScrobblerService.ANILIST

@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView.NO_ID import androidx.recyclerview.widget.RecyclerView.NO_ID
@ -17,8 +16,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
@ -31,15 +29,12 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setProgressIcon import org.koitharu.kotatsu.core.util.ext.setProgressIcon
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerMangaSelectionDecoration import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerMangaSelectionDecoration
import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerSelectorAdapter import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerSelectorAdapter
import javax.inject.Inject import javax.inject.Inject
@ -229,7 +224,7 @@ class ScrobblingSelectorSheet :
private fun initTabs() { private fun initTabs() {
val entries = viewModel.availableScrobblers val entries = viewModel.availableScrobblers
val tabs = requireViewBinding().tabs val tabs = requireViewBinding().tabs
val selectedId = arguments?.getInt(ARG_SCROBBLER, -1) ?: -1 val selectedId = arguments?.getInt(AppRouter.KEY_ID, -1) ?: -1
tabs.removeAllTabs() tabs.removeAllTabs()
tabs.clearOnTabSelectedListeners() tabs.clearOnTabSelectedListeners()
tabs.addOnTabSelectedListener(this) tabs.addOnTabSelectedListener(this)
@ -244,18 +239,4 @@ class ScrobblingSelectorSheet :
} }
} }
} }
companion object {
private const val TAG = "ScrobblingSelectorBottomSheet"
private const val ARG_SCROBBLER = "scrobbler"
fun show(fm: FragmentManager, manga: Manga, scrobblerService: ScrobblerService?) =
ScrobblingSelectorSheet().withArgs(2) {
putParcelable(MangaIntent.KEY_MANGA, ParcelableManga(manga))
if (scrobblerService != null) {
putInt(ARG_SCROBBLER, scrobblerService.id)
}
}.show(fm, TAG)
}
} }

@ -15,7 +15,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
@ -41,7 +41,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
) : BaseViewModel() { ) : BaseViewModel() {
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
val availableScrobblers = scrobblers.filter { it.isEnabled } val availableScrobblers = scrobblers.filter { it.isEnabled }

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.scrobbling.kitsu.ui package org.koitharu.kotatsu.scrobbling.kitsu.ui
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
@ -55,6 +56,7 @@ class KitsuAuthActivity : BaseActivity<ActivityKitsuAuthBinding>(), View.OnClick
&& password.length >= 3 && password.length >= 3
} }
@SuppressLint("UnsafeImplicitIntentLaunch")
private fun continueAuth() { private fun continueAuth() {
val email = viewBinding.editEmail.text?.toString()?.trim().orEmpty() val email = viewBinding.editEmail.text?.toString()?.trim().orEmpty()
val password = viewBinding.editPassword.text?.toString()?.trim().orEmpty() val password = viewBinding.editPassword.text?.toString()?.trim().orEmpty()

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.search.ui package org.koitharu.kotatsu.search.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
@ -19,7 +17,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.BuildConfig
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.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
@ -28,7 +25,8 @@ import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.model.isNsfw
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.parser.MangaIntent import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.model.titleRes import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.util.ViewBadge import org.koitharu.kotatsu.core.util.ViewBadge
@ -46,7 +44,6 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.isNullOrEmpty
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@ -69,8 +66,8 @@ class MangaListActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityMangaListBinding.inflate(layoutInflater)) setContentView(ActivityMangaListBinding.inflate(layoutInflater))
val filter = intent.getParcelableExtraCompat<ParcelableMangaListFilter>(EXTRA_FILTER)?.filter val filter = intent.getParcelableExtraCompat<ParcelableMangaListFilter>(AppRouter.KEY_FILTER)?.filter
source = MangaSource(intent.getStringExtra(EXTRA_SOURCE)) source = MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (viewBinding.containerFilterHeader != null) { if (viewBinding.containerFilterHeader != null) {
viewBinding.appbar.addOnOffsetChangedListener(this) viewBinding.appbar.addOnOffsetChangedListener(this)
@ -104,13 +101,13 @@ class MangaListActivity :
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_order -> FilterSheetFragment.show(supportFragmentManager) R.id.button_order -> router.showFilterSheet()
} }
} }
fun showPreview(manga: Manga): Boolean = setSideFragment( fun showPreview(manga: Manga): Boolean = setSideFragment(
PreviewFragment::class.java, PreviewFragment::class.java,
bundleOf(MangaIntent.KEY_MANGA to ParcelableManga(manga)), bundleOf(AppRouter.KEY_MANGA to ParcelableManga(manga)),
) )
fun hidePreview() = setSideFragment(FilterSheetFragment::class.java, null) fun hidePreview() = setSideFragment(FilterSheetFragment::class.java, null)
@ -193,21 +190,4 @@ class MangaListActivity :
filterOwner.filterCoordinator.set(filter) filterOwner.filterCoordinator.set(filter)
} }
} }
companion object {
private const val EXTRA_FILTER = "filter"
private const val EXTRA_SOURCE = "source"
private const val ACTION_MANGA_EXPLORE = "${BuildConfig.APPLICATION_ID}.action.EXPLORE_MANGA"
fun newIntent(context: Context, source: MangaSource, filter: MangaListFilter?): Intent =
Intent(context, MangaListActivity::class.java)
.setAction(ACTION_MANGA_EXPLORE)
.putExtra(EXTRA_SOURCE, source.name)
.apply {
if (!filter.isNullOrEmpty()) {
putExtra(EXTRA_FILTER, ParcelableMangaListFilter(filter))
}
}
}
} }

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.search.ui.multi package org.koitharu.kotatsu.search.ui.multi
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -15,6 +13,7 @@ import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
@ -25,9 +24,6 @@ import org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivitySearchBinding import org.koitharu.kotatsu.databinding.ActivitySearchBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteDialog
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
@ -35,10 +31,7 @@ import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.multi.adapter.SearchAdapter import org.koitharu.kotatsu.search.ui.multi.adapter.SearchAdapter
import javax.inject.Inject import javax.inject.Inject
@ -62,14 +55,8 @@ class SearchActivity :
setContentView(ActivitySearchBinding.inflate(layoutInflater)) setContentView(ActivitySearchBinding.inflate(layoutInflater))
title = viewModel.query title = viewModel.query
val itemCLickListener = OnListItemClickListener<SearchResultsListModel> { item, view -> val itemClickListener = OnListItemClickListener<SearchResultsListModel> { item, view ->
startActivity( router.openSearch(item.source, viewModel.query)
MangaListActivity.newIntent(
view.context,
item.source,
MangaListFilter(query = viewModel.query),
),
)
} }
val sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = true) val sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = true)
val selectionDecoration = MangaSelectionDecoration(this) val selectionDecoration = MangaSelectionDecoration(this)
@ -83,7 +70,7 @@ class SearchActivity :
lifecycleOwner = this, lifecycleOwner = this,
coil = coil, coil = coil,
listener = this, listener = this,
itemClickListener = itemCLickListener, itemClickListener = itemClickListener,
sizeResolver = sizeResolver, sizeResolver = sizeResolver,
selectionDecoration = selectionDecoration, selectionDecoration = selectionDecoration,
) )
@ -98,8 +85,6 @@ class SearchActivity :
viewModel.list.observe(this, adapter) viewModel.list.observe(this, adapter)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
DownloadDialogFragment.registerCallback(this, viewBinding.recyclerView)
} }
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
@ -114,8 +99,7 @@ class SearchActivity :
override fun onItemClick(item: Manga, view: View) { override fun onItemClick(item: Manga, view: View) {
if (!selectionController.onItemClick(item.id)) { if (!selectionController.onItemClick(item.id)) {
val intent = DetailsActivity.newIntent(this, item) router.openDetails(item)
startActivity(intent)
} }
} }
@ -129,15 +113,13 @@ class SearchActivity :
override fun onReadClick(manga: Manga, view: View) { override fun onReadClick(manga: Manga, view: View) {
if (!selectionController.onItemClick(manga.id)) { if (!selectionController.onItemClick(manga.id)) {
val intent = IntentBuilder(this).manga(manga).build() router.openReader(manga)
startActivity(intent)
} }
} }
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) { override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (!selectionController.onItemClick(manga.id)) { if (!selectionController.onItemClick(manga.id)) {
val intent = MangaListActivity.newIntent(this, manga.source, MangaListFilter(tags = setOf(tag))) router.openList(tag)
startActivity(intent)
} }
} }
@ -179,13 +161,13 @@ class SearchActivity :
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavoriteDialog.show(supportFragmentManager, collectSelectedItems()) router.showFavoriteDialog(collectSelectedItems())
mode?.finish() mode?.finish()
true true
} }
R.id.action_save -> { R.id.action_save -> {
DownloadDialogFragment.show(supportFragmentManager, collectSelectedItems()) router.showDownloadDialog(collectSelectedItems(), viewBinding.recyclerView)
mode?.finish() mode?.finish()
true true
} }
@ -197,13 +179,4 @@ class SearchActivity :
private fun collectSelectedItems(): Set<Manga> { private fun collectSelectedItems(): Set<Manga> {
return viewModel.getItems(selectionController.peekCheckedIds()) return viewModel.getItems(selectionController.peekCheckedIds())
} }
companion object {
const val EXTRA_QUERY = "query"
fun newIntent(context: Context, query: String) =
Intent(context, SearchActivity::class.java)
.putExtra(EXTRA_QUERY, query)
}
} }

@ -25,6 +25,7 @@ import kotlinx.coroutines.sync.withPermit
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.UnknownMangaSource import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -58,7 +59,7 @@ class SearchViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() { ) : BaseViewModel() {
val query = savedStateHandle.get<String>(SearchActivity.EXTRA_QUERY).orEmpty() val query = savedStateHandle.get<String>(AppRouter.KEY_QUERY).orEmpty()
private val retryCounter = MutableStateFlow(0) private val retryCounter = MutableStateFlow(0)
private val listData = retryCounter.flatMapLatest { private val listData = retryCounter.flatMapLatest {

@ -71,16 +71,4 @@ class SearchSuggestionFragment :
super.onResume() super.onResume()
viewModel.onResume() viewModel.onResume()
} }
companion object {
@Deprecated(
"",
ReplaceWith(
"SearchSuggestionFragment()",
"org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment",
),
)
fun newInstance() = SearchSuggestionFragment()
}
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save