Refactor navigation

This commit is contained in:
Koitharu
2024-12-18 08:13:27 +02:00
parent a5199e2f06
commit f675c606a2
136 changed files with 1223 additions and 1355 deletions

View File

@@ -1,7 +1,5 @@
package org.koitharu.kotatsu.alternatives.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
@@ -13,8 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseListAdapter
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.observeEvent
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.TypedListSpacingDecoration
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.model.ListModel
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
@AndroidEntryPoint
@@ -65,7 +59,7 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
viewModel.content.observe(this, listAdapter)
viewModel.onMigrated.observeEvent(this) {
Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show()
startActivity(DetailsActivity.newIntent(this, it))
router.openDetails(it)
finishAfterTransition()
}
}
@@ -82,16 +76,9 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
override fun onItemClick(item: MangaAlternativeModel, view: View) {
when (view.id) {
R.id.chip_source -> startActivity(
MangaListActivity.newIntent(
this,
item.manga.source,
MangaListFilter(query = viewModel.manga.title),
),
)
R.id.chip_source -> router.openSearch(item.manga.source, viewModel.manga.title)
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()
}
companion object {
fun newIntent(context: Context, manga: Manga) = Intent(context, AlternativesActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga))
}
}

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,8 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
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.ui.BaseFragment
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.observeEvent
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.adapter.ListHeaderClickListener
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.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import javax.inject.Inject
@AndroidEntryPoint
@@ -115,26 +115,26 @@ class AllBookmarksFragment :
override fun onItemClick(item: Bookmark, view: View) {
if (selectionController?.onItemClick(item.pageId) != true) {
val intent = ReaderActivity.IntentBuilder(view.context)
val intent = ReaderIntent.Builder(view.context)
.bookmark(item)
.incognito(true)
.build()
startActivity(intent)
router.openReader(intent)
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
}
override fun onListHeaderClick(item: ListHeader, view: View) {
val manga = item.payload as? Manga ?: return
startActivity(DetailsActivity.newIntent(view.context, manga))
router.openDetails(manga)
}
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 {
return selectionController?.onItemContextClick(view, item.pageId) ?: false
return selectionController?.onItemContextClick(view, item.pageId) == true
}
override fun onRetryClick(error: Throwable) = Unit
@@ -208,16 +208,4 @@ class AllBookmarksFragment :
invalidateSpanIndexCache()
}
}
companion object {
@Deprecated(
"",
ReplaceWith(
"BookmarksFragment()",
"org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment",
),
)
fun newInstance() = AllBookmarksFragment()
}
}

View File

@@ -1,9 +1,5 @@
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.view.Menu
import android.view.MenuItem
@@ -11,17 +7,18 @@ import android.webkit.CookieManager
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
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.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.ui.BaseActivity
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.parsers.model.MangaSource
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -42,7 +39,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
setDisplayHomeAsUpEnabled(true)
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 userAgent = repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)
viewBinding.webView.configureForParser(userAgent)
@@ -59,7 +56,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
finishAfterTransition()
} else {
onTitleChanged(
intent?.getStringExtra(EXTRA_TITLE) ?: getString(R.string.loading_),
intent?.getStringExtra(AppRouter.KEY_TITLE) ?: getString(R.string.loading_),
url,
)
viewBinding.webView.loadUrl(url)
@@ -80,14 +77,8 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
}
R.id.action_browser -> {
val url = viewBinding.webView.url?.toUriOrNull()
if (url != null) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = url
try {
startActivity(Intent.createChooser(intent, item.title))
} catch (_: ActivityNotFoundException) {
}
if (!router.openExternalBrowser(viewBinding.webView.url.orEmpty(), item.title)) {
Snackbar.make(viewBinding.webView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
true
}
@@ -136,17 +127,4 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
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)
}
}
}

View File

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

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.browser.cloudflare
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -9,7 +8,6 @@ import android.view.MenuItem
import android.webkit.CookieManager
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.graphics.Insets
import androidx.core.net.toUri
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
@@ -19,19 +17,17 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.yield
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
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.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -62,7 +58,7 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
return
}
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
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also {
onBackPressedDispatcher.addCallback(it)
@@ -140,7 +136,7 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
override fun onCheckPassed() {
pendingResult = RESULT_OK
val source = intent?.getStringExtra(ARG_SOURCE)
val source = intent?.getStringExtra(AppRouter.KEY_SOURCE)
if (source != null) {
CaptchaNotifier(this).dismiss(MangaSource(source))
}
@@ -182,38 +178,16 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
class Contract : ActivityResultContract<CloudFlareProtectedException, Boolean>() {
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 {
return resultCode == Activity.RESULT_OK
return resultCode == RESULT_OK
}
}
companion object {
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)
}
}
}
}

View File

@@ -8,6 +8,7 @@ import android.net.Uri
import android.os.BadParcelableException
import androidx.core.app.PendingIntentCompat
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.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.report
@@ -15,20 +16,19 @@ import org.koitharu.kotatsu.core.util.ext.report
class ErrorReporterReceiver : BroadcastReceiver() {
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()
}
companion object {
private const val EXTRA_ERROR = "err"
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = try {
val intent = Intent(context, ErrorReporterReceiver::class.java)
intent.setAction(ACTION_REPORT)
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)
} catch (e: BadParcelableException) {
e.printStackTraceDebug()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()
}
}

View File

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

View File

@@ -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
}

View File

@@ -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"
}
}

View File

@@ -23,6 +23,8 @@ import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
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.favicon.faviconUri
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.util.mapNotNullToSet
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.Singleton
@@ -155,9 +155,10 @@ class AppShortcutManager @Inject constructor(
.setIcon(icon)
.setLongLived(true)
.setIntent(
ReaderActivity.IntentBuilder(context)
ReaderIntent.Builder(context)
.mangaId(manga.id)
.build(),
.build()
.intent,
)
.build()
}
@@ -181,7 +182,7 @@ class AppShortcutManager @Inject constructor(
.setLongLabel(title)
.setIcon(icon)
.setLongLived(true)
.setIntent(MangaListActivity.newIntent(context, source, null))
.setIntent(AppRouter.listIntent(context, source, null))
.build()
}
}

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
package org.koitharu.kotatsu.core.ui
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.annotation.CallSuper
@@ -14,10 +12,8 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.preference.get
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings
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)
}
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) {
val pref = findPreference<Preference>(key)
if (pref == null) {

View File

@@ -10,14 +10,14 @@ import androidx.core.text.HtmlCompat
import androidx.core.text.htmlEncode
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.text.parseAsHtml
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
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.report
import org.koitharu.kotatsu.core.util.ext.requireSerializable
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.DialogErrorDetailsBinding
class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
@@ -27,7 +27,7 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = requireArguments()
exception = args.requireSerializable(ARG_ERROR)
exception = args.requireSerializable(AppRouter.KEY_ERROR)
}
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogErrorDetailsBinding {
@@ -41,7 +41,7 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
text = context.getString(
R.string.manga_error_description_pattern,
exception.message?.htmlEncode().orEmpty(),
arguments?.getString(ARG_URL),
arguments?.getString(AppRouter.KEY_URL) ?: exception.getCauseUrl(),
).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY)
}
}
@@ -71,16 +71,4 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
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)
}
}

View File

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

View File

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

View File

@@ -2,9 +2,7 @@ package org.koitharu.kotatsu.core.util.ext
import android.os.Bundle
import androidx.core.view.MenuProvider
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
@@ -18,36 +16,10 @@ inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
val Fragment.viewLifecycleScope
inline get() = viewLifecycleOwner.lifecycle.coroutineScope
fun DialogFragment.showAllowStateLoss(manager: FragmentManager, tag: String?) {
if (!manager.isStateSaved) {
show(manager, tag)
}
}
fun Fragment.addMenuProvider(provider: MenuProvider) {
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")
tailrec fun <T> Fragment.findParentCallback(cls: Class<T>): T? {
val parent = parentFragment

View File

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

View File

@@ -14,8 +14,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import okio.IOException
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.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.ext.peek
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug

View File

@@ -1,7 +1,6 @@
package org.koitharu.kotatsu.details.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.transition.TransitionManager
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.UnknownMangaSource
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.nav.ReaderIntent
import org.koitharu.kotatsu.core.nav.router
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.ui.BaseActivity
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.observeEvent
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.textAndVisible
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.ui.model.ChapterListItem
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.ScrollingInfoAdapter
import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
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.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel
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.MangaListFilter
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.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject
import kotlin.math.roundToInt
import com.google.android.material.R as materialR
@@ -164,13 +152,14 @@ class DetailsActivity :
}
TitleExpandListener(viewBinding.textViewTitle).attach()
val appRouter = router
viewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)
viewModel.onError
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) }
.filterNot { appRouter.isChapterPagesSheetShown() }
.observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver))
viewModel.onActionDone
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) }
.filterNot { appRouter.isChapterPagesSheetShown() }
.observeEvent(this, ReversibleActionObserver(viewBinding.scrollView, null))
combine(viewModel.historyInfo, viewModel.isLoading, ::Pair).observe(this) {
onHistoryChanged(it.first, it.second)
@@ -189,10 +178,8 @@ class DetailsActivity :
}
viewModel.chapters.observe(this, PrefetchObserver(this))
viewModel.onDownloadStarted
.filterNot { ChaptersPagesSheet.isShown(supportFragmentManager) }
.filterNot { appRouter.isChapterPagesSheetShown() }
.observeEvent(this, DownloadStartedObserver(viewBinding.scrollView))
DownloadDialogFragment.registerCallback(this, viewBinding.scrollView)
menuProvider = DetailsMenuProvider(
activity = this,
viewModel = viewModel,
@@ -210,54 +197,30 @@ class DetailsActivity :
R.id.textView_author -> {
val manga = viewModel.manga.value ?: return
startActivity(
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
filter = MangaListFilter(query = manga.author),
),
)
router.openSearch(manga.source, manga.author ?: return)
}
R.id.textView_source -> {
val manga = viewModel.manga.value ?: return
startActivity(
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
filter = null,
),
)
router.openList(manga.source, null)
}
R.id.textView_local -> {
val manga = viewModel.manga.value ?: return
LocalInfoDialog.show(supportFragmentManager, manga)
router.showLocalInfoDialog(manga)
}
R.id.chip_favorite -> {
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 -> {
val manga = viewModel.manga.value ?: return
startActivity(
ImageActivity.newIntent(
v.context,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
manga.source,
),
scaleUpActivityOptionsOf(v),
router.openImage(
url = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
source = manga.source,
anchor = v,
)
}
@@ -273,12 +236,12 @@ class DetailsActivity :
R.id.button_scrobbling_more -> {
val manga = viewModel.manga.value ?: return
ScrobblingSelectorSheet.show(supportFragmentManager, manga, null)
router.showScrobblingSelectorSheet(manga, null)
}
R.id.button_related_more -> {
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?) {
val tag = data as? MangaTag ?: return
// TODO dialog
startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag))))
router.openList(tag)
}
override fun onContextClick(v: View): Boolean = onLongClick(v)
@@ -324,9 +287,7 @@ class DetailsActivity :
}
override fun onItemClick(item: Bookmark, view: View) {
startActivity(
ReaderActivity.IntentBuilder(view.context).bookmark(item).incognito(true).build(),
)
router.openReader(ReaderIntent.Builder(view.context).bookmark(item).incognito(true).build())
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
}
@@ -393,7 +354,7 @@ class DetailsActivity :
coil, this,
StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),
) { item, view ->
startActivity(newIntent(view.context, item))
router.openDetails(item)
},
).also { rv.adapter = it }
adapter.items = related
@@ -410,7 +371,7 @@ class DetailsActivity :
if (adapter != null) {
adapter.items = scrobblings
} else {
adapter = ScrollingInfoAdapter(this, coil, supportFragmentManager)
adapter = ScrollingInfoAdapter(this, coil, router)
adapter.items = scrobblings
viewBinding.recyclerViewScrobbling.adapter = adapter
viewBinding.recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration())
@@ -531,8 +492,8 @@ class DetailsActivity :
Snackbar.make(viewBinding.scrollView, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
.show()
} else {
startActivity(
ReaderActivity.IntentBuilder(this)
router.openReader(
ReaderIntent.Builder(this)
.manga(manga)
.branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode)
@@ -604,15 +565,5 @@ class DetailsActivity :
companion object {
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)
}
}
}

View File

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

View File

@@ -14,16 +14,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
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.isLocal
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.os.AppShortcutManager
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(
private val activity: FragmentActivity,
@@ -49,23 +44,21 @@ class DetailsMenuProvider(
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
val manga = viewModel.getMangaOrNull() ?: return false
when (menuItem.itemId) {
R.id.action_share -> {
viewModel.manga.value?.let {
val shareHelper = ShareHelper(activity)
if (it.isLocal) {
shareHelper.shareCbz(listOf(it.url.toUri().toFile()))
} else {
shareHelper.shareMangaLink(it)
}
val shareHelper = ShareHelper(activity)
if (manga.isLocal) {
shareHelper.shareCbz(listOf(manga.url.toUri().toFile()))
} else {
shareHelper.shareMangaLink(manga)
}
}
R.id.action_delete -> {
val title = viewModel.manga.value?.title.orEmpty()
MaterialAlertDialogBuilder(activity)
.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) { _, _ ->
viewModel.deleteLocal()
}
@@ -74,52 +67,38 @@ class DetailsMenuProvider(
}
R.id.action_save -> {
DownloadDialogFragment.show(activity.supportFragmentManager, listOfNotNull(viewModel.manga.value))
activity.router.showDownloadDialog(manga, snackbarHost)
}
R.id.action_browser -> {
viewModel.manga.value?.let {
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.source, it.title))
}
activity.router.openBrowser(url = manga.publicUrl, source = manga.source, title = manga.title)
}
R.id.action_online -> {
viewModel.remoteManga.value?.let {
activity.startActivity(DetailsActivity.newIntent(activity, it))
}
activity.router.openDetails(manga)
}
R.id.action_related -> {
viewModel.manga.value?.let {
activity.startActivity(SearchActivity.newIntent(activity, it.title))
}
activity.router.openSearch(manga.title)
}
R.id.action_alternatives -> {
viewModel.manga.value?.let {
activity.startActivity(AlternativesActivity.newIntent(activity, it))
}
activity.router.openAlternatives(manga)
}
R.id.action_stats -> {
viewModel.manga.value?.let {
MangaStatsSheet.show(activity.supportFragmentManager, it)
}
activity.router.showStatisticSheet(manga)
}
R.id.action_scrobbling -> {
viewModel.manga.value?.let {
ScrobblingSelectorSheet.show(activity.supportFragmentManager, it, null)
}
activity.router.showScrobblingSelectorSheet(manga, null)
}
R.id.action_shortcut -> {
viewModel.manga.value?.let {
activity.lifecycleScope.launch {
if (!appShortcutManager.requestPinShortcut(it)) {
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
.show()
}
activity.lifecycleScope.launch {
if (!appShortcutManager.requestPinShortcut(manga)) {
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
.show()
}
}
}

View File

@@ -21,7 +21,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
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.ListMode
import org.koitharu.kotatsu.core.ui.util.ReversibleAction

View File

@@ -15,7 +15,6 @@ import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.MenuCompat
import androidx.core.view.get
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialSplitButton
@@ -23,16 +22,16 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R
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.observe
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(
private val splitButton: MaterialSplitButton,
private val viewModel: DetailsViewModel,
private val router: AppRouter,
) : View.OnClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
private val buttonRead = splitButton[0] as MaterialButton
@@ -52,10 +51,12 @@ class ReadButtonDelegate(
when (item.itemId) {
R.id.action_incognito -> openReader(isIncognitoMode = true)
R.id.action_forget -> viewModel.removeFromHistory()
R.id.action_download -> DownloadDialogFragment.show(
fm = (context.findActivity() as? FragmentActivity)?.supportFragmentManager ?: return false,
manga = setOf(viewModel.getMangaOrNull() ?: return false),
)
R.id.action_download -> {
router.showDownloadDialog(
manga = setOf(viewModel.getMangaOrNull() ?: return false),
snackbarHost = splitButton,
)
}
Menu.NONE -> {
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)
.show() // TODO
} else {
context.startActivity(
ReaderActivity.IntentBuilder(context)
router.openReader(
ReaderIntent.Builder(context)
.manga(manga)
.branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode)

View File

@@ -6,10 +6,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
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.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_COLLAPSED
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.recyclerView
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.details.ui.DetailsViewModel
import org.koitharu.kotatsu.details.ui.ReadButtonDelegate
@@ -51,13 +50,13 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
disableFitToContents()
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)
if (!adapter.isPagesTabEnabled) {
defaultTab = (defaultTab - 1).coerceAtLeast(TAB_CHAPTERS)
}
(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.recyclerView?.isNestedScrollingEnabled = false
@@ -145,22 +144,5 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
const val TAB_CHAPTERS = 0
const val TAB_PAGES = 1
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
}
}
}

View File

@@ -18,13 +18,15 @@ import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.BookmarksSelectionDecoration
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
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.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper
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.findParentCallback
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.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback
import javax.inject.Inject
@@ -124,21 +125,21 @@ class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
if (listener != null && listener.onBookmarkSelected(item)) {
dismissParentDialog()
} else {
val intent = IntentBuilder(view.context)
val intent = ReaderIntent.Builder(view.context)
.manga(activityViewModel.getMangaOrNull() ?: return)
.bookmark(item)
.incognito(true)
.build()
startActivity(intent)
router.openReader(intent)
}
}
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 {
return selectionController?.onItemContextClick(view, item.pageId) ?: false
return selectionController?.onItemContextClick(view, item.pageId) == true
}
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {

View File

@@ -15,6 +15,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
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.dialog.CommonAlertDialogs
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.widgets.ChipsView
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.findParentCallback
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.ui.adapter.TypedListSpacingDecoration
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.ReaderState
import javax.inject.Inject
@@ -111,8 +112,8 @@ class ChaptersFragment :
if (listener != null && listener.onChapterSelected(item.chapter)) {
dismissParentDialog()
} else {
startActivity(
IntentBuilder(view.context)
router.openReader(
ReaderIntent.Builder(view.context)
.manga(viewModel.getMangaOrNull() ?: return)
.state(ReaderState(item.chapter.id, 0, 0))
.build(),

View File

@@ -22,6 +22,9 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.R
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.ui.BaseFragment
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.util.PagerNestedScrollHelper
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.findParentCallback
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.model.ListModel
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.ReaderState
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
@@ -151,8 +152,8 @@ class PagesFragment :
if (listener != null && listener.onPageSelected(item.page)) {
dismissParentDialog()
} else {
startActivity(
IntentBuilder(view.context)
router.openReader(
ReaderIntent.Builder(view.context)
.manga(parentViewModel.getMangaOrNull() ?: return)
.state(ReaderState(item.page.chapterId, item.page.index, 0))
.build(),
@@ -161,11 +162,11 @@ class PagesFragment :
}
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 {
return selectionController?.onItemContextClick(view, item.page.id) ?: false
return selectionController?.onItemContextClick(view, item.page.id) == true
}
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {

View File

@@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
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.nav.AppRouter
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.call
@@ -37,7 +37,7 @@ class RelatedListViewModel @Inject constructor(
downloadScheduler: DownloadWorker.Scheduler,
) : 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 mangaList = MutableStateFlow<List<Manga>?>(null)
private val listError = MutableStateFlow<Throwable?>(null)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ import androidx.core.view.updatePadding
import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
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.list.ListSelectionController
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.observeEvent
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.list.ui.adapter.TypedListSpacingDecoration
import javax.inject.Inject
@@ -84,7 +84,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
if (selectionController.onItemClick(item.id.mostSignificantBits)) {
return
}
startActivity(DetailsActivity.newIntent(view.context, item.manga ?: return))
router.openDetails(item.manga ?: return)
}
override fun onItemLongClick(item: DownloadItemModel, view: View): Boolean {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,12 @@
package org.koitharu.kotatsu.favourites.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint
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.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
@@ -21,7 +19,7 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
super.onCreate(savedInstanceState)
setContentView(ActivityContainerBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val categoryTitle = intent.getStringExtra(EXTRA_TITLE)
val categoryTitle = intent.getStringExtra(AppRouter.KEY_TITLE)
if (categoryTitle != null) {
title = categoryTitle
}
@@ -29,7 +27,7 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
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)
}
}
@@ -41,16 +39,4 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
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)
}
}

View File

@@ -46,16 +46,6 @@ class CategoriesSelectionCallback(
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {
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 -> {
viewModel.setIsVisible(controller.snapshot(), true)
mode?.finish()

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
package org.koitharu.kotatsu.favourites.ui.categories.select
import android.content.DialogInterface
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
@@ -13,7 +12,6 @@ import android.widget.Toast
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import coil3.ImageLoader
import coil3.request.allowRgb565
@@ -25,7 +23,7 @@ import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
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.list.OnListItemClickListener
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.observe
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.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.parsers.model.Manga
import javax.inject.Inject
@AndroidEntryPoint
class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
OnListItemClickListener<MangaCategoryItem>, DialogInterface.OnClickListener {
private val viewModel by viewModels<FavoriteSheetViewModel>()
private val viewModel by viewModels<FavoriteDialogViewModel>()
@Inject
lateinit var coil: ImageLoader
@@ -84,7 +78,7 @@ class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
}
override fun onClick(dialog: DialogInterface?, which: Int) {
startActivity(Intent(context ?: return, FavouriteCategoriesActivity::class.java))
router.openFavoriteCategories()
}
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)
}
}

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.ids
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.observeAsFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
@@ -28,13 +29,13 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
import javax.inject.Inject
@HiltViewModel
class FavoriteSheetViewModel @Inject constructor(
class FavoriteDialogViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val favouritesRepository: FavouritesRepository,
settings: AppSettings,
) : 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
}

View File

@@ -6,12 +6,13 @@ import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
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
class FavouriteTabPopupMenuProvider(
private val context: Context,
private val router: AppRouter,
private val viewModel: FavouritesContainerViewModel,
private val categoryId: Long
) : MenuProvider {
@@ -28,12 +29,8 @@ class FavouriteTabPopupMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.action_hide -> viewModel.hide(categoryId)
R.id.action_edit -> context.startActivity(
FavouritesCategoryEditActivity.newIntent(context, categoryId),
)
R.id.action_edit -> router.openFavoriteCategoryEdit(categoryId)
R.id.action_delete -> confirmDelete()
else -> return false
}
return true

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,13 +9,12 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.core.view.isGone
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.slider.RangeSlider
import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.R
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.sheet.BaseAdaptiveSheet
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.setValueRounded
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.filter.ui.FilterCoordinator
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.ContentType
import org.koitharu.kotatsu.parsers.model.Demographic
@@ -88,10 +85,10 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
binding.sliderYear.addOnChangeListener(this::onSliderValueChange)
binding.sliderYearsRange.addOnChangeListener(this::onRangeSliderValueChange)
binding.layoutGenres.setOnMoreButtonClickListener {
TagsCatalogSheet.show(getChildFragmentManager(), isExcludeTag = false)
router.showTagsCatalogSheet(excludeMode = false)
}
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 ContentRating -> filter.toggleContentRating(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
companion object {
private const val TAG = "FilterSheet"
fun show(fm: FragmentManager) = FilterSheetFragment().showDistinct(fm, TAG)
fun isSupported(fragment: Fragment) = fragment.activity is FilterCoordinator.Owner
}
}

View File

@@ -9,17 +9,15 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
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.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
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.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.model.TagCatalogItem
@@ -33,7 +31,7 @@ class TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(), OnListItemClickL
defaultViewModelCreationExtras.withCreationCallback<TagsCatalogViewModel.Factory> { factory ->
factory.create(
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) {
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)
}
}

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
package org.koitharu.kotatsu.image.ui
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
@@ -29,6 +27,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
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.util.PopupMenuMediator
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.databinding.ActivityImageBinding
import org.koitharu.kotatsu.databinding.ItemErrorStateBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -123,7 +121,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
.memoryCachePolicy(CachePolicy.DISABLED)
.lifecycle(this)
.listener(this)
.mangaSourceExtra(MangaSource(intent.getStringExtra(EXTRA_SOURCE)))
.mangaSourceExtra(MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)))
.target(SsivTarget(viewBinding.ssiv))
.enqueueWith(coil)
}
@@ -142,7 +140,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
button.setImageDrawable(
CircularProgressDrawable(this).also {
it.setStyle(CircularProgressDrawable.LARGE)
it.setColorSchemeColors(getThemeColor(com.google.android.material.R.attr.colorControlNormal))
it.setColorSchemeColors(getThemeColor(materialR.attr.colorControlNormal))
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)
}
}
}

View File

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

View File

@@ -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.SnackbarErrorObserver
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.ListMode
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.viewLifecycleScope
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.QuickFilterListener
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.owners.AppBarOwner
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.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity
import javax.inject.Inject
@@ -125,7 +121,6 @@ abstract class MangaListFragment :
isEnabled = isSwipeRefreshEnabled
}
addMenuProvider(MangaListMenuProvider(this))
DownloadDialogFragment.registerCallback(this, binding.recyclerView)
viewModel.listMode.observe(viewLifecycleOwner, ::onListModeChanged)
viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged)
@@ -147,7 +142,7 @@ abstract class MangaListFragment :
override fun onItemClick(item: Manga, view: View) {
if (selectionController?.onItemClick(item.id) != 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) {
if (selectionController?.onItemClick(manga.id) != true) {
val intent = IntentBuilder(view.context).manga(manga).build()
startActivity(intent)
router.openReader(manga)
}
}
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (selectionController?.onItemClick(manga.id) != true) {
// TODO dialog
val intent = MangaListActivity.newIntent(view.context, tag.source, MangaListFilter(tags = setOf(tag)))
startActivity(intent)
router.openList(tag)
}
}
@@ -317,13 +310,13 @@ abstract class MangaListFragment :
}
R.id.action_favourite -> {
FavoriteDialog.show(getChildFragmentManager(), selectedItems)
router.showFavoriteDialog(selectedItems)
mode?.finish()
true
}
R.id.action_save -> {
DownloadDialogFragment.show(childFragmentManager, selectedItems)
router.showDownloadDialog(selectedItems, viewBinding?.recyclerView)
mode?.finish()
true
}

View File

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

View File

@@ -8,7 +8,6 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.CompoundButton
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import com.google.android.material.button.MaterialButtonToggleGroup
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.ui.sheet.BaseAdaptiveSheet
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.databinding.SheetListModeBinding
@@ -113,14 +110,4 @@ class ListConfigBottomSheet :
}
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)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
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.util.ext.checkNotificationPermission
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.toBitmapOrNull
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.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -113,7 +113,7 @@ class ImportService : CoroutineIntentService() {
).toBitmapOrNull(),
)
notification.setSubText(manga.title)
val intent = DetailsActivity.newIntent(applicationContext, manga)
val intent = AppRouter.detailsIntent(applicationContext, manga)
notification.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,

View File

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

View File

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

View File

@@ -7,13 +7,11 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.widget.TextViewCompat
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.combine
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.widgets.SegmentedBarView
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.observeEvent
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.parsers.model.Manga
import com.google.android.material.R as materialR
@AndroidEntryPoint
@@ -108,16 +103,4 @@ class LocalInfoDialog : AlertDialogFragment<DialogLocalInfoBinding>(), View.OnCl
)
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)
}
}
}

View File

@@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
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.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
@@ -25,7 +26,7 @@ class LocalInfoViewModel @Inject constructor(
private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,
) : 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 onCleanedUp = MutableEventFlow<Pair<Int, Long>>()

View File

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

View File

@@ -9,23 +9,21 @@ import android.view.ViewGroup
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isGone
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
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.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.getDisplayName
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.databinding.SheetWelcomeBinding
import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
import java.util.Locale
@AndroidEntryPoint
@@ -82,7 +80,7 @@ class WelcomeSheet : BaseAdaptiveSheet<SheetWelcomeBinding>(), ChipsView.OnChipC
override fun onActivityResult(result: Uri?) {
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
}
}
}

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.reader.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.transition.Fade
@@ -29,12 +28,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig
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.model.parcelable.ParcelableManga
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.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode
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.zipWithPrevious
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.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.data.TapGridSettings
import org.koitharu.kotatsu.reader.domain.TapGridArea
@@ -169,7 +164,7 @@ class ReaderActivity :
override fun getParentActivityIntent(): Intent? {
val manga = viewModel.getMangaOrNull() ?: return null
return DetailsActivity.newIntent(this, manga)
return AppRouter.detailsIntent(this, manga)
}
override fun onUserInteraction() {
@@ -358,7 +353,7 @@ class ReaderActivity :
override fun openMenu() {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return
ReaderConfigSheet.show(supportFragmentManager, currentMode)
router.showReaderConfigSheet(currentMode)
}
override fun scrollBy(delta: Int, smooth: Boolean): Boolean {
@@ -413,50 +408,8 @@ class ReaderActivity :
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 {
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
}
}

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
package org.koitharu.kotatsu.reader.ui.colorfilter
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.os.Bundle
@@ -23,8 +21,6 @@ import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
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.util.ext.decodeRegion
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.setValueRounded
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.util.format
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
@@ -131,8 +126,8 @@ class ColorFilterConfigActivity :
private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) {
viewBinding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f)
viewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f)
viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted ?: false, false)
viewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale ?: false, false)
viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted == true, false)
viewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale == true, false)
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
}
@@ -168,15 +163,4 @@ class ColorFilterConfigActivity :
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))
}
}

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
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.prefs.AppSettings
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.require
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity.Companion.EXTRA_MANGA
import javax.inject.Inject
@HiltViewModel
@@ -23,12 +23,12 @@ class ColorFilterConfigViewModel @Inject constructor(
private val mangaDataRepository: MangaDataRepository,
) : 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
val colorFilter = MutableStateFlow<ReaderColorFilter?>(null)
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
get() = colorFilter.value != initialColorFilter

View File

@@ -7,7 +7,6 @@ import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButtonToggleGroup
@@ -19,6 +18,8 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
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.prefs.AppSettings
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.util.ext.findParentCallback
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.withArgs
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
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
@AndroidEntryPoint
@@ -64,7 +61,7 @@ class ReaderConfigSheet :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mode = arguments?.getInt(ARG_MODE)
mode = arguments?.getInt(AppRouter.KEY_READER_MODE)
?.let { ReaderMode.valueOf(it) }
?: ReaderMode.STANDARD
imageServerDelegate = ImageServerDelegate(
@@ -129,7 +126,7 @@ class ReaderConfigSheet :
override fun onClick(v: View) {
when (v.id) {
R.id.button_settings -> {
startActivity(SettingsActivity.newReaderSettingsIntent(v.context))
router.openReaderSettings()
dismissAllowingStateLoss()
}
@@ -145,7 +142,7 @@ class ReaderConfigSheet :
R.id.button_color_filter -> {
val page = viewModel.getCurrentPage() ?: return
val manga = viewModel.getMangaOrNull() ?: return
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page))
router.openColorFilterConfig(manga, page)
}
R.id.button_image_server -> viewLifecycleScope.launch {
@@ -243,14 +240,4 @@ class ReaderConfigSheet :
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)
}
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.scrobbling.common.ui.config
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
@@ -15,6 +14,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
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.list.OnListItemClickListener
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.showOrHide
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.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
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) {
startActivity(
DetailsActivity.newIntent(this, item.mangaId),
)
router.openDetails(item.mangaId)
}
override fun onClick(v: View) {
@@ -133,16 +129,9 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
}
companion object {
const val EXTRA_SERVICE_ID = "service"
const val HOST_SHIKIMORI_AUTH = "shikimori-auth"
const val HOST_ANILIST_AUTH = "anilist-auth"
const val HOST_MAL_AUTH = "mal-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)
}
}

View File

@@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
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.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
@@ -100,11 +100,11 @@ class ScrobblerConfigViewModel @Inject constructor(
private fun getScrobblerService(
savedStateHandle: SavedStateHandle,
): ScrobblerService {
val serviceId = savedStateHandle.get<Int>(ScrobblerConfigActivity.EXTRA_SERVICE_ID) ?: 0
val serviceId = savedStateHandle.get<Int>(AppRouter.KEY_ID) ?: 0
if (serviceId != 0) {
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) {
ScrobblerConfigActivity.HOST_SHIKIMORI_AUTH -> ScrobblerService.SHIKIMORI
ScrobblerConfigActivity.HOST_ANILIST_AUTH -> ScrobblerService.ANILIST

View File

@@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView.NO_ID
@@ -17,8 +16,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
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.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener
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.setTabsEnabled
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.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
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.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerMangaSelectionDecoration
import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerSelectorAdapter
import javax.inject.Inject
@@ -229,7 +224,7 @@ class ScrobblingSelectorSheet :
private fun initTabs() {
val entries = viewModel.availableScrobblers
val tabs = requireViewBinding().tabs
val selectedId = arguments?.getInt(ARG_SCROBBLER, -1) ?: -1
val selectedId = arguments?.getInt(AppRouter.KEY_ID, -1) ?: -1
tabs.removeAllTabs()
tabs.clearOnTabSelectedListeners()
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)
}
}

View File

@@ -15,7 +15,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
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.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
@@ -41,7 +41,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
private val historyRepository: HistoryRepository,
) : 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 }

View File

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

View File

@@ -1,7 +1,5 @@
package org.koitharu.kotatsu.search.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
@@ -19,7 +17,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.LocalMangaSource
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.parcelable.ParcelableManga
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.model.titleRes
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.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.isNullOrEmpty
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import kotlin.math.absoluteValue
import com.google.android.material.R as materialR
@@ -69,8 +66,8 @@ class MangaListActivity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMangaListBinding.inflate(layoutInflater))
val filter = intent.getParcelableExtraCompat<ParcelableMangaListFilter>(EXTRA_FILTER)?.filter
source = MangaSource(intent.getStringExtra(EXTRA_SOURCE))
val filter = intent.getParcelableExtraCompat<ParcelableMangaListFilter>(AppRouter.KEY_FILTER)?.filter
source = MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (viewBinding.containerFilterHeader != null) {
viewBinding.appbar.addOnOffsetChangedListener(this)
@@ -104,13 +101,13 @@ class MangaListActivity :
override fun onClick(v: View) {
when (v.id) {
R.id.button_order -> FilterSheetFragment.show(supportFragmentManager)
R.id.button_order -> router.showFilterSheet()
}
}
fun showPreview(manga: Manga): Boolean = setSideFragment(
PreviewFragment::class.java,
bundleOf(MangaIntent.KEY_MANGA to ParcelableManga(manga)),
bundleOf(AppRouter.KEY_MANGA to ParcelableManga(manga)),
)
fun hidePreview() = setSideFragment(FilterSheetFragment::class.java, null)
@@ -193,21 +190,4 @@ class MangaListActivity :
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))
}
}
}
}

View File

@@ -1,7 +1,5 @@
package org.koitharu.kotatsu.search.ui.multi
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@@ -15,6 +13,7 @@ import coil3.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
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.ui.BaseActivity
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.observeEvent
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.ui.MangaSelectionDecoration
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.size.DynamicItemSizeResolver
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.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.multi.adapter.SearchAdapter
import javax.inject.Inject
@@ -62,14 +55,8 @@ class SearchActivity :
setContentView(ActivitySearchBinding.inflate(layoutInflater))
title = viewModel.query
val itemCLickListener = OnListItemClickListener<SearchResultsListModel> { item, view ->
startActivity(
MangaListActivity.newIntent(
view.context,
item.source,
MangaListFilter(query = viewModel.query),
),
)
val itemClickListener = OnListItemClickListener<SearchResultsListModel> { item, view ->
router.openSearch(item.source, viewModel.query)
}
val sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = true)
val selectionDecoration = MangaSelectionDecoration(this)
@@ -83,7 +70,7 @@ class SearchActivity :
lifecycleOwner = this,
coil = coil,
listener = this,
itemClickListener = itemCLickListener,
itemClickListener = itemClickListener,
sizeResolver = sizeResolver,
selectionDecoration = selectionDecoration,
)
@@ -98,8 +85,6 @@ class SearchActivity :
viewModel.list.observe(this, adapter)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
DownloadDialogFragment.registerCallback(this, viewBinding.recyclerView)
}
override fun onWindowInsetsChanged(insets: Insets) {
@@ -114,8 +99,7 @@ class SearchActivity :
override fun onItemClick(item: Manga, view: View) {
if (!selectionController.onItemClick(item.id)) {
val intent = DetailsActivity.newIntent(this, item)
startActivity(intent)
router.openDetails(item)
}
}
@@ -129,15 +113,13 @@ class SearchActivity :
override fun onReadClick(manga: Manga, view: View) {
if (!selectionController.onItemClick(manga.id)) {
val intent = IntentBuilder(this).manga(manga).build()
startActivity(intent)
router.openReader(manga)
}
}
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (!selectionController.onItemClick(manga.id)) {
val intent = MangaListActivity.newIntent(this, manga.source, MangaListFilter(tags = setOf(tag)))
startActivity(intent)
router.openList(tag)
}
}
@@ -179,13 +161,13 @@ class SearchActivity :
}
R.id.action_favourite -> {
FavoriteDialog.show(supportFragmentManager, collectSelectedItems())
router.showFavoriteDialog(collectSelectedItems())
mode?.finish()
true
}
R.id.action_save -> {
DownloadDialogFragment.show(supportFragmentManager, collectSelectedItems())
router.showDownloadDialog(collectSelectedItems(), viewBinding.recyclerView)
mode?.finish()
true
}
@@ -197,13 +179,4 @@ class SearchActivity :
private fun collectSelectedItems(): Set<Manga> {
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)
}
}

View File

@@ -25,6 +25,7 @@ import kotlinx.coroutines.sync.withPermit
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.LocalMangaSource
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.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel
@@ -58,7 +59,7 @@ class SearchViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository,
) : 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 listData = retryCounter.flatMapLatest {

View File

@@ -71,16 +71,4 @@ class SearchSuggestionFragment :
super.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