Compare commits
9 Commits
v4.4-beta1
...
v4.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c09b0150ac | ||
|
|
d7c31f3b3b | ||
|
|
362629bb9a | ||
|
|
4ec4421f69 | ||
|
|
029815e0d7 | ||
|
|
019b41a9f9 | ||
|
|
a56e977058 | ||
|
|
f436a49e5f | ||
|
|
652351f79a |
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 515
|
||||
versionName '4.4-beta1'
|
||||
versionCode 516
|
||||
versionName '4.4'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -87,7 +87,7 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:05d705ac03') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:cf345d2d0c') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
android:allowBackup="true"
|
||||
android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:fullBackupOnly="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -39,6 +40,8 @@ abstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {
|
||||
|
||||
open fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder = builder
|
||||
|
||||
open fun onDialogCreated(dialog: AlertDialog) = Unit
|
||||
|
||||
protected fun bindingOrNull(): B? = viewBinding
|
||||
|
||||
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
|
||||
|
||||
@@ -82,6 +82,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
|
||||
@Suppress("DEPRECATION")
|
||||
onBackPressed()
|
||||
true
|
||||
} else super.onOptionsItemSelected(item)
|
||||
@@ -130,7 +131,8 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
|
||||
@Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith")
|
||||
@Deprecated("Should not be used")
|
||||
override fun onBackPressed() {
|
||||
if ( // https://issuetracker.google.com/issues/139738913
|
||||
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q &&
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
@@ -30,6 +31,9 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
val isExpanded: Boolean
|
||||
get() = behavior?.state == BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
val onBackPressedDispatcher: OnBackPressedDispatcher
|
||||
get() = (requireDialog() as AppBottomSheetDialog).onBackPressedDispatcher
|
||||
|
||||
final override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.MenuItem.OnActionExpandListener
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
|
||||
class CollapseActionViewCallback(
|
||||
private val menuItem: MenuItem
|
||||
) : OnBackPressedCallback(menuItem.isActionViewExpanded), OnActionExpandListener {
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
menuItem.collapseActionView()
|
||||
}
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
isEnabled = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
isEnabled = false
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import com.google.android.material.R as materialR
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
|
||||
|
||||
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityBrowserBinding.inflate(layoutInflater))
|
||||
@@ -33,6 +35,8 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
||||
}
|
||||
binding.webView.webViewClient = BrowserClient(this)
|
||||
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
|
||||
onBackPressedCallback = WebViewBackPressedCallback(binding.webView)
|
||||
onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
if (savedInstanceState != null) {
|
||||
return
|
||||
}
|
||||
@@ -84,14 +88,6 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (binding.webView.canGoBack()) {
|
||||
binding.webView.goBack()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
binding.webView.onPause()
|
||||
super.onPause()
|
||||
@@ -116,6 +112,10 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
||||
supportActionBar?.subtitle = subtitle
|
||||
}
|
||||
|
||||
override fun onHistoryChanged() {
|
||||
onBackPressedCallback.onHistoryChanged()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.appbar.updatePadding(
|
||||
top = insets.top,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.koitharu.kotatsu.browser
|
||||
|
||||
interface BrowserCallback {
|
||||
interface BrowserCallback : OnHistoryChangedListener {
|
||||
|
||||
fun onLoadingStateChanged(isLoading: Boolean)
|
||||
|
||||
fun onTitleChanged(title: CharSequence, subtitle: CharSequence?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.graphics.Bitmap
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
|
||||
class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
|
||||
open class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
|
||||
|
||||
override fun onPageFinished(webView: WebView, url: String) {
|
||||
super.onPageFinished(webView, url)
|
||||
@@ -20,4 +20,9 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
|
||||
super.onPageCommitVisible(view, url)
|
||||
callback.onTitleChanged(view.title.orEmpty(), url)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
|
||||
super.doUpdateVisitedHistory(view, url, isReload)
|
||||
callback.onHistoryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.koitharu.kotatsu.browser
|
||||
|
||||
fun interface OnHistoryChangedListener {
|
||||
|
||||
fun onHistoryChanged()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.koitharu.kotatsu.browser
|
||||
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
|
||||
class WebViewBackPressedCallback(
|
||||
private val webView: WebView,
|
||||
) : OnBackPressedCallback(false), OnHistoryChangedListener {
|
||||
|
||||
init {
|
||||
onHistoryChanged()
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
webView.goBack()
|
||||
}
|
||||
|
||||
override fun onHistoryChanged() {
|
||||
isEnabled = webView.canGoBack()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
interface CloudFlareCallback {
|
||||
import org.koitharu.kotatsu.browser.BrowserCallback
|
||||
|
||||
interface CloudFlareCallback : BrowserCallback {
|
||||
|
||||
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
|
||||
|
||||
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) = Unit
|
||||
|
||||
fun onPageLoaded()
|
||||
|
||||
fun onCheckPassed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.koitharu.kotatsu.browser.BrowserClient
|
||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||
|
||||
private const val CF_CLEARANCE = "cf_clearance"
|
||||
@@ -12,22 +12,22 @@ class CloudFlareClient(
|
||||
private val cookieJar: MutableCookieJar,
|
||||
private val callback: CloudFlareCallback,
|
||||
private val targetUrl: String,
|
||||
) : WebViewClient() {
|
||||
) : BrowserClient(callback) {
|
||||
|
||||
private val oldClearance = getClearance()
|
||||
|
||||
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
checkClearance()
|
||||
}
|
||||
|
||||
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
||||
override fun onPageCommitVisible(view: WebView, url: String?) {
|
||||
super.onPageCommitVisible(view, url)
|
||||
callback.onPageLoaded()
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
override fun onPageFinished(webView: WebView, url: String) {
|
||||
super.onPageFinished(webView, url)
|
||||
callback.onPageLoaded()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebSettings
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import okhttp3.Headers
|
||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
|
||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||
@@ -31,6 +33,8 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
||||
@Inject
|
||||
lateinit var cookieJar: MutableCookieJar
|
||||
|
||||
private var onBackPressedCallback: WebViewBackPressedCallback? = null
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -58,6 +62,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
||||
override fun onDestroyView() {
|
||||
binding.webView.stopLoading()
|
||||
binding.webView.destroy()
|
||||
onBackPressedCallback = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@@ -65,6 +70,13 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
||||
return super.onBuildDialog(builder).setNegativeButton(android.R.string.cancel, null)
|
||||
}
|
||||
|
||||
override fun onDialogCreated(dialog: AlertDialog) {
|
||||
super.onDialogCreated(dialog)
|
||||
onBackPressedCallback = WebViewBackPressedCallback(binding.webView).also {
|
||||
dialog.onBackPressedDispatcher.addCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.webView.onResume()
|
||||
@@ -89,6 +101,10 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
override fun onHistoryChanged() {
|
||||
onBackPressedCallback?.onHistoryChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG = "CloudFlareDialog"
|
||||
|
||||
@@ -96,7 +96,7 @@ class ChaptersFragment :
|
||||
return when (item.itemId) {
|
||||
R.id.action_save -> {
|
||||
DownloadService.start(
|
||||
context ?: return false,
|
||||
binding.recyclerViewChapters,
|
||||
viewModel.getRemoteManga() ?: viewModel.manga.value ?: return false,
|
||||
selectionController?.snapshot(),
|
||||
)
|
||||
|
||||
@@ -277,7 +277,7 @@ class DetailsActivity :
|
||||
)
|
||||
}
|
||||
setNeutralButton(R.string.download) { _, _ ->
|
||||
DownloadService.start(this@DetailsActivity, remoteManga, setOf(chapterId))
|
||||
DownloadService.start(binding.appbar, remoteManga, setOf(chapterId))
|
||||
}
|
||||
setCancelable(true)
|
||||
}.show()
|
||||
|
||||
@@ -86,7 +86,7 @@ class DetailsMenuProvider(
|
||||
if (chaptersCount > 5 || branches.size > 1) {
|
||||
showSaveConfirmation(it, chaptersCount, branches)
|
||||
} else {
|
||||
DownloadService.start(activity, it)
|
||||
DownloadService.start(snackbarHost, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ class DetailsMenuProvider(
|
||||
val chaptersIds = manga.chapters?.mapNotNullToSet { c ->
|
||||
if (c.branch in selectedBranches) c.id else null
|
||||
}
|
||||
DownloadService.start(activity, manga, chaptersIds)
|
||||
DownloadService.start(snackbarHost, manga, chaptersIds)
|
||||
}
|
||||
} else {
|
||||
dialogBuilder.setMessage(
|
||||
@@ -149,7 +149,7 @@ class DetailsMenuProvider(
|
||||
activity.resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount),
|
||||
),
|
||||
).setPositiveButton(R.string.save) { _, _ ->
|
||||
DownloadService.start(activity, manga)
|
||||
DownloadService.start(snackbarHost, manga)
|
||||
}
|
||||
}
|
||||
dialogBuilder.show()
|
||||
|
||||
@@ -9,6 +9,13 @@ sealed interface DownloadState {
|
||||
val manga: Manga
|
||||
val cover: Drawable?
|
||||
|
||||
override fun equals(other: Any?): Boolean
|
||||
|
||||
override fun hashCode(): Int
|
||||
|
||||
val isTerminal: Boolean
|
||||
get() = this is Done || this is Cancelled || (this is Error && !canRetry)
|
||||
|
||||
class Queued(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
package org.koitharu.kotatsu.download.ui
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import coil.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
@@ -29,6 +21,8 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private lateinit var serviceConnection: DownloadsConnection
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityDownloadsBinding.inflate(layoutInflater))
|
||||
@@ -38,9 +32,12 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.adapter = adapter
|
||||
val connection = DownloadServiceConnection(adapter)
|
||||
bindService(Intent(this, DownloadService::class.java), connection, 0)
|
||||
lifecycle.addObserver(connection)
|
||||
serviceConnection = DownloadsConnection(this, this)
|
||||
serviceConnection.items.observe(this) { items ->
|
||||
adapter.items = items
|
||||
binding.textViewHolder.isVisible = items.isNullOrEmpty()
|
||||
}
|
||||
serviceConnection.bind()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
@@ -55,46 +52,6 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
)
|
||||
}
|
||||
|
||||
private inner class DownloadServiceConnection(
|
||||
private val adapter: DownloadsAdapter,
|
||||
) : ServiceConnection, DefaultLifecycleObserver {
|
||||
|
||||
private var collectJob: Job? = null
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
collectJob?.cancel()
|
||||
val binder = (service as? DownloadService.DownloadBinder)
|
||||
collectJob = if (binder == null) {
|
||||
null
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
binder.downloads.collect {
|
||||
setItems(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
collectJob?.cancel()
|
||||
collectJob = null
|
||||
setItems(null)
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
collectJob?.cancel()
|
||||
collectJob = null
|
||||
owner.lifecycle.removeObserver(this)
|
||||
unbindService(this)
|
||||
}
|
||||
|
||||
private fun setItems(items: Collection<DownloadItem>?) {
|
||||
adapter.items = items?.toList().orEmpty()
|
||||
binding.textViewHolder.isVisible = items.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, DownloadsActivity::class.java)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.koitharu.kotatsu.download.ui
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
|
||||
|
||||
class DownloadsConnection(
|
||||
private val context: Context,
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
) : ServiceConnection {
|
||||
|
||||
private var bindingObserver: BindingLifecycleObserver? = null
|
||||
private var collectJob: Job? = null
|
||||
private val itemsFlow = MutableStateFlow<List<PausingProgressJob<DownloadState>>>(emptyList())
|
||||
|
||||
val items
|
||||
get() = itemsFlow.asFlowLiveData()
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
collectJob?.cancel()
|
||||
val binder = (service as? DownloadService.DownloadBinder)
|
||||
collectJob = if (binder == null) {
|
||||
null
|
||||
} else {
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
binder.downloads.collect {
|
||||
itemsFlow.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
collectJob?.cancel()
|
||||
collectJob = null
|
||||
itemsFlow.value = itemsFlow.value.filter { it.progressValue.isTerminal }
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
if (bindingObserver != null) {
|
||||
return
|
||||
}
|
||||
bindingObserver = BindingLifecycleObserver().also {
|
||||
lifecycleOwner.lifecycle.addObserver(it)
|
||||
}
|
||||
context.bindService(Intent(context, DownloadService::class.java), this, 0)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
bindingObserver?.let {
|
||||
lifecycleOwner.lifecycle.removeObserver(it)
|
||||
}
|
||||
bindingObserver = null
|
||||
context.unbindService(this)
|
||||
}
|
||||
|
||||
private inner class BindingLifecycleObserver : DefaultLifecycleObserver {
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
unbind()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import android.content.IntentFilter
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.widget.Toast
|
||||
import android.view.View
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -15,6 +15,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -29,6 +30,7 @@ import org.koitharu.kotatsu.base.ui.BaseService
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.download.domain.DownloadManager
|
||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||
import org.koitharu.kotatsu.download.ui.DownloadsActivity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||
import org.koitharu.kotatsu.utils.ext.throttle
|
||||
@@ -66,7 +68,7 @@ class DownloadService : BaseService() {
|
||||
val intentFilter = IntentFilter()
|
||||
intentFilter.addAction(ACTION_DOWNLOAD_CANCEL)
|
||||
intentFilter.addAction(ACTION_DOWNLOAD_RESUME)
|
||||
registerReceiver(controlReceiver, intentFilter)
|
||||
ContextCompat.registerReceiver(this, controlReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
@@ -155,9 +157,6 @@ class DownloadService : BaseService() {
|
||||
!state.isTerminal
|
||||
}
|
||||
|
||||
private val DownloadState.isTerminal: Boolean
|
||||
get() = this is DownloadState.Done || this is DownloadState.Cancelled || (this is DownloadState.Error && !canRetry)
|
||||
|
||||
@MainThread
|
||||
private fun stopSelfIfIdle() {
|
||||
if (jobs.any { (_, job) -> job.isActive }) {
|
||||
@@ -221,37 +220,38 @@ class DownloadService : BaseService() {
|
||||
private const val EXTRA_CHAPTERS_IDS = "chapters_ids"
|
||||
private const val EXTRA_CANCEL_ID = "cancel_id"
|
||||
|
||||
fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>? = null) {
|
||||
fun start(view: View, manga: Manga, chaptersIds: Collection<Long>? = null) {
|
||||
if (chaptersIds?.isEmpty() == true) {
|
||||
return
|
||||
}
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
val intent = Intent(view.context, DownloadService::class.java)
|
||||
intent.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false))
|
||||
if (chaptersIds != null) {
|
||||
intent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray())
|
||||
}
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
Toast.makeText(context, R.string.manga_downloading_, Toast.LENGTH_SHORT).show()
|
||||
ContextCompat.startForegroundService(view.context, intent)
|
||||
showStartedSnackbar(view)
|
||||
}
|
||||
|
||||
fun start(context: Context, manga: Collection<Manga>) {
|
||||
fun start(view: View, manga: Collection<Manga>) {
|
||||
if (manga.isEmpty()) {
|
||||
return
|
||||
}
|
||||
for (item in manga) {
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
val intent = Intent(view.context, DownloadService::class.java)
|
||||
intent.putExtra(EXTRA_MANGA, ParcelableManga(item, withChapters = false))
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
ContextCompat.startForegroundService(view.context, intent)
|
||||
}
|
||||
showStartedSnackbar(view)
|
||||
}
|
||||
|
||||
fun confirmAndStart(context: Context, items: Set<Manga>) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
fun confirmAndStart(view: View, items: Set<Manga>) {
|
||||
MaterialAlertDialogBuilder(view.context)
|
||||
.setTitle(R.string.save_manga)
|
||||
.setMessage(R.string.batch_manga_save_confirm)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.save) { _, _ ->
|
||||
start(context, items)
|
||||
start(view, items)
|
||||
}.show()
|
||||
}
|
||||
|
||||
@@ -267,5 +267,12 @@ class DownloadService : BaseService() {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun showStartedSnackbar(view: View) {
|
||||
Snackbar.make(view, R.string.download_started, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.details) {
|
||||
it.context.startActivity(DownloadsActivity.newIntent(it.context))
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ abstract class MangaListFragment :
|
||||
}
|
||||
|
||||
R.id.action_save -> {
|
||||
DownloadService.confirmAndStart(requireContext(), selectedItems)
|
||||
DownloadService.confirmAndStart(binding.recyclerView, selectedItems)
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
package org.koitharu.kotatsu.list.ui.filter
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.util.CollapseActionViewCallback
|
||||
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
|
||||
import org.koitharu.kotatsu.utils.ext.isScrolledToTop
|
||||
import org.koitharu.kotatsu.utils.ext.parentFragmentViewModels
|
||||
|
||||
class FilterBottomSheet :
|
||||
BaseBottomSheet<SheetFilterBinding>(),
|
||||
MenuItem.OnActionExpandListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
DialogInterface.OnKeyListener,
|
||||
AsyncListDiffer.ListListener<FilterItem> {
|
||||
|
||||
private val viewModel by parentFragmentViewModels<RemoteListViewModel>()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).also {
|
||||
it.setOnKeyListener(this)
|
||||
}
|
||||
}
|
||||
private var collapsibleActionViewCallback: CollapseActionViewCallback? = null
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
|
||||
return SheetFilterBinding.inflate(inflater, container, false)
|
||||
@@ -42,8 +37,14 @@ class FilterBottomSheet :
|
||||
initOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
collapsibleActionViewCallback = null
|
||||
}
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
setExpanded(isExpanded = true, isLocked = true)
|
||||
collapsibleActionViewCallback?.onMenuItemActionExpand(item)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ class FilterBottomSheet :
|
||||
val searchView = (item.actionView as? SearchView) ?: return false
|
||||
searchView.setQuery("", false)
|
||||
searchView.post { setExpanded(isExpanded = false, isLocked = false) }
|
||||
collapsibleActionViewCallback?.onMenuItemActionCollapse(item)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -61,19 +63,6 @@ class FilterBottomSheet :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
val menuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search) ?: return false
|
||||
if (menuItem.isActionViewExpanded) {
|
||||
if (event?.action == KeyEvent.ACTION_UP) {
|
||||
menuItem.collapseActionView()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCurrentListChanged(previousList: MutableList<FilterItem>, currentList: MutableList<FilterItem>) {
|
||||
if (currentList.size > previousList.size && view != null) {
|
||||
(binding.recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0)
|
||||
@@ -81,13 +70,16 @@ class FilterBottomSheet :
|
||||
}
|
||||
|
||||
private fun initOptionsMenu() {
|
||||
binding.headerBar.toolbar.inflateMenu(R.menu.opt_filter)
|
||||
val searchMenuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search)
|
||||
binding.headerBar.inflateMenu(R.menu.opt_filter)
|
||||
val searchMenuItem = binding.headerBar.menu.findItem(R.id.action_search)
|
||||
searchMenuItem.setOnActionExpandListener(this)
|
||||
val searchView = searchMenuItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(this)
|
||||
searchView.setIconifiedByDefault(false)
|
||||
searchView.queryHint = searchMenuItem.title
|
||||
collapsibleActionViewCallback = CollapseActionViewCallback(searchMenuItem).also {
|
||||
onBackPressedDispatcher.addCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.os.Bundle
|
||||
import android.util.SparseIntArray
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.view.ActionMode
|
||||
@@ -84,6 +85,7 @@ class MainActivity :
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
|
||||
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
|
||||
private val closeSearchCallback = CloseSearchCallback()
|
||||
private lateinit var navigationDelegate: MainNavigationDelegate
|
||||
|
||||
override val appBar: AppBarLayout
|
||||
@@ -121,8 +123,9 @@ class MainActivity :
|
||||
navigationDelegate.addOnFragmentChangedListener(this)
|
||||
navigationDelegate.onCreate(savedInstanceState)
|
||||
|
||||
onBackPressedDispatcher.addCallback(navigationDelegate)
|
||||
onBackPressedDispatcher.addCallback(ExitCallback(this, binding.container))
|
||||
onBackPressedDispatcher.addCallback(navigationDelegate)
|
||||
onBackPressedDispatcher.addCallback(closeSearchCallback)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
onFirstStart()
|
||||
@@ -142,21 +145,6 @@ class MainActivity :
|
||||
adjustSearchUI(isSearchOpened(), animate = false)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
|
||||
binding.searchView.clearFocus()
|
||||
when {
|
||||
fragment != null -> supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
remove(fragment)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
runOnCommit { onSearchClosed() }
|
||||
}
|
||||
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
|
||||
adjustFabVisibility(topFragment = fragment)
|
||||
if (fromUser) {
|
||||
@@ -300,11 +288,13 @@ class MainActivity :
|
||||
|
||||
private fun onSearchOpened() {
|
||||
adjustSearchUI(isOpened = true, animate = true)
|
||||
closeSearchCallback.isEnabled = true
|
||||
}
|
||||
|
||||
private fun onSearchClosed() {
|
||||
binding.searchView.hideKeyboard()
|
||||
adjustSearchUI(isOpened = false, animate = true)
|
||||
closeSearchCallback.isEnabled = false
|
||||
}
|
||||
|
||||
private fun showNav(visible: Boolean) {
|
||||
@@ -406,4 +396,23 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class CloseSearchCallback : OnBackPressedCallback(false) {
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
|
||||
binding.searchView.clearFocus()
|
||||
if (fragment == null) {
|
||||
// this should not happen but who knows
|
||||
isEnabled = false
|
||||
return
|
||||
}
|
||||
supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
remove(fragment)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
runOnCommit { onSearchClosed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.koitharu.kotatsu.scrobbling.common.ui.selector
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -20,6 +17,7 @@ import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.base.ui.util.CollapseActionViewCallback
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||
@@ -43,7 +41,6 @@ class ScrobblingSelectorBottomSheet :
|
||||
View.OnClickListener,
|
||||
MenuItem.OnActionExpandListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
DialogInterface.OnKeyListener,
|
||||
TabLayout.OnTabSelectedListener,
|
||||
ListStateHolderListener {
|
||||
|
||||
@@ -53,6 +50,8 @@ class ScrobblingSelectorBottomSheet :
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private var collapsibleActionViewCallback: CollapseActionViewCallback? = null
|
||||
|
||||
private val viewModel by assistedViewModels {
|
||||
viewModelFactory.create(
|
||||
requireArguments().requireParcelable<ParcelableManga>(MangaIntent.KEY_MANGA).manga,
|
||||
@@ -63,12 +62,6 @@ class ScrobblingSelectorBottomSheet :
|
||||
return SheetScrobblingSelectorBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).also {
|
||||
it.setOnKeyListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this, this)
|
||||
@@ -102,6 +95,11 @@ class ScrobblingSelectorBottomSheet :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
collapsibleActionViewCallback = null
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_done -> viewModel.onDoneClick()
|
||||
@@ -126,6 +124,7 @@ class ScrobblingSelectorBottomSheet :
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
setExpanded(isExpanded = true, isLocked = true)
|
||||
collapsibleActionViewCallback?.onMenuItemActionExpand(item)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -133,6 +132,7 @@ class ScrobblingSelectorBottomSheet :
|
||||
val searchView = (item.actionView as? SearchView) ?: return false
|
||||
searchView.setQuery("", false)
|
||||
searchView.post { setExpanded(isExpanded = false, isLocked = false) }
|
||||
collapsibleActionViewCallback?.onMenuItemActionCollapse(item)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -147,19 +147,6 @@ class ScrobblingSelectorBottomSheet :
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean = false
|
||||
|
||||
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
val menuItem = binding.headerBar.menu.findItem(R.id.action_search) ?: return false
|
||||
if (menuItem.isActionViewExpanded) {
|
||||
if (event?.action == KeyEvent.ACTION_UP) {
|
||||
menuItem.collapseActionView()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
viewModel.setScrobblerIndex(tab.position)
|
||||
}
|
||||
@@ -193,6 +180,9 @@ class ScrobblingSelectorBottomSheet :
|
||||
searchView.setOnQueryTextListener(this)
|
||||
searchView.setIconifiedByDefault(false)
|
||||
searchView.queryHint = searchMenuItem.title
|
||||
collapsibleActionViewCallback = CollapseActionViewCallback(searchMenuItem).also {
|
||||
onBackPressedDispatcher.addCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initTabs() {
|
||||
|
||||
@@ -164,7 +164,7 @@ class MultiSearchActivity :
|
||||
}
|
||||
|
||||
R.id.action_save -> {
|
||||
DownloadService.confirmAndStart(this, collectSelectedItems())
|
||||
DownloadService.confirmAndStart(binding.recyclerView, collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.browser.BrowserCallback
|
||||
import org.koitharu.kotatsu.browser.BrowserClient
|
||||
import org.koitharu.kotatsu.browser.ProgressChromeClient
|
||||
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
@@ -34,6 +35,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
||||
@Inject
|
||||
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
||||
|
||||
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
|
||||
private lateinit var authProvider: MangaParserAuthProvider
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -66,6 +68,8 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
||||
}
|
||||
binding.webView.webViewClient = BrowserClient(this)
|
||||
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
|
||||
onBackPressedCallback = WebViewBackPressedCallback(binding.webView)
|
||||
onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
if (savedInstanceState != null) {
|
||||
return
|
||||
}
|
||||
@@ -103,14 +107,6 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (binding.webView.canGoBack()) {
|
||||
binding.webView.goBack()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
binding.webView.onPause()
|
||||
super.onPause()
|
||||
@@ -135,6 +131,10 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
||||
supportActionBar?.subtitle = subtitle
|
||||
}
|
||||
|
||||
override fun onHistoryChanged() {
|
||||
onBackPressedCallback.onHistoryChanged()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.appbar.updatePadding(top = insets.top)
|
||||
binding.webView.updatePadding(bottom = insets.bottom)
|
||||
|
||||
@@ -68,7 +68,7 @@ class ShelfSelectionCallback(
|
||||
}
|
||||
|
||||
R.id.action_save -> {
|
||||
DownloadService.confirmAndStart(context, collectSelectedItems(controller))
|
||||
DownloadService.confirmAndStart(recyclerView, collectSelectedItems(controller))
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
@@ -125,7 +125,7 @@ class ShelfSelectionCallback(
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
}
|
||||
MaterialAlertDialogBuilder(context ?: return)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_manga)
|
||||
.setMessage(context.getString(R.string.text_delete_local_manga_batch))
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isGone
|
||||
@@ -28,6 +29,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
||||
|
||||
private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null
|
||||
private var resultBundle: Bundle? = null
|
||||
private val pageBackCallback = PageBackCallback()
|
||||
|
||||
private val viewModel by viewModels<SyncAuthViewModel>()
|
||||
|
||||
@@ -44,9 +46,13 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
||||
binding.editEmail.addTextChangedListener(EmailTextWatcher(binding.buttonNext))
|
||||
binding.editPassword.addTextChangedListener(PasswordTextWatcher(binding.buttonDone))
|
||||
|
||||
onBackPressedDispatcher.addCallback(pageBackCallback)
|
||||
|
||||
viewModel.onTokenObtained.observe(this, ::onTokenReceived)
|
||||
viewModel.onError.observe(this, ::onError)
|
||||
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
|
||||
|
||||
pageBackCallback.update()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
@@ -59,27 +65,23 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
override fun onBackPressed() {
|
||||
if (binding.switcher.isVisible && binding.switcher.displayedChild > 0) {
|
||||
binding.switcher.showPrevious()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_cancel -> {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
R.id.button_next -> {
|
||||
binding.switcher.showNext()
|
||||
pageBackCallback.update()
|
||||
}
|
||||
|
||||
R.id.button_back -> {
|
||||
binding.switcher.showPrevious()
|
||||
pageBackCallback.update()
|
||||
}
|
||||
|
||||
R.id.button_done -> {
|
||||
viewModel.obtainToken(
|
||||
email = binding.editEmail.text.toString(),
|
||||
@@ -105,6 +107,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
||||
TransitionManager.beginDelayedTransition(binding.root, Fade())
|
||||
binding.switcher.isGone = isLoading
|
||||
binding.layoutProgress.isVisible = isLoading
|
||||
pageBackCallback.update()
|
||||
}
|
||||
|
||||
private fun onError(error: Throwable) {
|
||||
@@ -161,4 +164,16 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
|
||||
button.isEnabled = text != null && text.length >= 4
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PageBackCallback : OnBackPressedCallback(false) {
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
binding.switcher.showPrevious()
|
||||
update()
|
||||
}
|
||||
|
||||
fun update() {
|
||||
isEnabled = binding.switcher.isVisible && binding.switcher.displayedChild > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsingToolbarLayout"
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
app:toolbarId="@id/toolbar">
|
||||
|
||||
@@ -39,7 +38,8 @@
|
||||
android:paddingHorizontal="@dimen/list_spacing"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
tools:listitem="@layout/item_download" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_holder"
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="@dimen/manga_list_details_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
@@ -31,8 +30,8 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
@@ -47,11 +46,11 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/imageView_cover"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_title"
|
||||
tools:progress="25"/>
|
||||
tools:progress="25" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_status"
|
||||
@@ -59,7 +58,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
@@ -72,7 +71,7 @@
|
||||
android:id="@+id/textView_percent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/textView_status"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -84,7 +83,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="4"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
@@ -98,6 +97,8 @@
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/try_again"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@@ -111,6 +112,8 @@
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@android:string/cancel"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -83,4 +83,7 @@
|
||||
<string name="network_consumption_warning">هذا يمكن أن يؤدي إلى ارتفاع استهلاك حركة المرور.</string>
|
||||
<string name="dont_ask_again">لا تسأل مرة أخرى</string>
|
||||
<string name="warning">تحذير</string>
|
||||
<string name="cancelling_">إلغاء…</string>
|
||||
<string name="error">خطاء</string>
|
||||
<string name="clear_search_history">مسح تاريخ البحث</string>
|
||||
</resources>
|
||||
@@ -419,4 +419,7 @@
|
||||
<string name="theme_name_kanade">Canadé</string>
|
||||
<string name="scrobbling_empty_hint">Para realizar un seguimiento del progreso de la lectura, seleccione Menú → Seguimiento en la pantalla de detalles del manga.</string>
|
||||
<string name="services">Servicios</string>
|
||||
<string name="allow_unstable_updates_summary">Actualizaciones propuestas para las versiones beta de la aplicación</string>
|
||||
<string name="allow_unstable_updates">Permitir actualizaciones inestables</string>
|
||||
<string name="download_started">Descarga iniciada</string>
|
||||
</resources>
|
||||
@@ -1,2 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="details">विवरण</string>
|
||||
<string name="chapters">अध्याय</string>
|
||||
<string name="nothing_found">कुछ भी नहीं मिला</string>
|
||||
<string name="history_is_empty">अभी तक कोई इतिहास नहीं है</string>
|
||||
<string name="read">पढ़ना</string>
|
||||
<string name="add_to_favourites">इसे पसंद करें</string>
|
||||
<string name="add">जोड़ना</string>
|
||||
<string name="save">बचाना</string>
|
||||
<string name="newest">नवीनतम</string>
|
||||
<string name="light">रोशनी</string>
|
||||
<string name="dark">अँधेरा</string>
|
||||
<string name="close">बंद करना</string>
|
||||
<string name="try_again">पुनः प्रयास करें</string>
|
||||
<string name="you_have_not_favourites_yet">अभी तक कोई पसंदीदा नहीं है</string>
|
||||
<string name="remove">निकालना</string>
|
||||
<string name="by_name">नाम</string>
|
||||
<string name="popular">लोकप्रिय</string>
|
||||
</resources>
|
||||
@@ -7,7 +7,7 @@
|
||||
<string name="history">История</string>
|
||||
<string name="error_occurred">Произошла ошибка</string>
|
||||
<string name="network_error">Ошибка сети</string>
|
||||
<string name="details">Подробный</string>
|
||||
<string name="details">Подробности</string>
|
||||
<string name="chapters">Главы</string>
|
||||
<string name="list">Список</string>
|
||||
<string name="detailed_list">Подробный список</string>
|
||||
@@ -419,4 +419,7 @@
|
||||
<string name="theme_name_kanade">Канадэ</string>
|
||||
<string name="scrobbling_empty_hint">Чтобы отслеживать прогресс чтения, выберите «Меню» → «Отслеживать» на экране сведений о манге.</string>
|
||||
<string name="nothing_here">Здесь ничего нет</string>
|
||||
<string name="download_started">Загрузка началась</string>
|
||||
<string name="allow_unstable_updates">Разрешить нестабильные обновления</string>
|
||||
<string name="allow_unstable_updates_summary">Предлагать обновления до бета-версий приложения</string>
|
||||
</resources>
|
||||
@@ -418,4 +418,7 @@
|
||||
<string name="theme_name_kanade">Kanade</string>
|
||||
<string name="nothing_here">这里什么也没有</string>
|
||||
<string name="scrobbling_empty_hint">要跟踪阅读进度,在漫画详情屏幕上选中“菜单→ 跟踪。</string>
|
||||
<string name="allow_unstable_updates">允许不稳定更新</string>
|
||||
<string name="allow_unstable_updates_summary">提示更新到测试版</string>
|
||||
<string name="download_started">已开始下载</string>
|
||||
</resources>
|
||||
@@ -423,4 +423,5 @@
|
||||
<string name="services">Services</string>
|
||||
<string name="allow_unstable_updates">Allow unstable updates</string>
|
||||
<string name="allow_unstable_updates_summary">Propose updates to beta versions of the app</string>
|
||||
<string name="download_started">Download started</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user