Compare commits

...

17 Commits

Author SHA1 Message Date
Koitharu b57069c55f
Merge remote-tracking branch 'weblate/devel' into devel 2 years ago
Koitharu 5b1a4d3ff5
Update dependencies 2 years ago
Koitharu 2b26f944d0
Fix background color in webttoon mode #832 2 years ago
Koitharu a15197f69d
Update suggestions after config changes #831 2 years ago
Koitharu 41f64b2e36
Handle NoDataReceivedException 2 years ago
Koitharu bec032c7dc
Fix TransactionTooLargeException when using WebView 2 years ago
maryush 061eaa2a56
Translated using Weblate (Polish)
Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: maryush <maryush@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
2 years ago
Anton Prevrhal bc6e29b562
Translated using Weblate (German)
Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: Anton Prevrhal <anton.prevrhal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2 years ago
jonathan | ヨナタン d8c1dcef29
Translated using Weblate (German)
Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: jonathan | ヨナタン <jonathan.evertz@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2 years ago
Anon ca281afba1
Translated using Weblate (Serbian)
Currently translated at 99.8% (635 of 636 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2 years ago
ReksaTresna cde07a60d7
Translated using Weblate (Indonesian)
Currently translated at 95.1% (605 of 636 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: ReksaTresna <ilham151096@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2 years ago
Infy's Tagalog Translations e31af0f43f
Translated using Weblate (Filipino)
Currently translated at 100.0% (638 of 638 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2 years ago
Scrambled777 15dd0f38e7
Translated using Weblate (Hindi)
Currently translated at 100.0% (638 of 638 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
2 years ago
gekka d93647e889
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2 years ago
Oğuz Ersen 509d9a2fba
Translated using Weblate (Turkish)
Currently translated at 100.0% (638 of 638 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2 years ago
gallegonovato 879d05f1a6
Translated using Weblate (Spanish)
Currently translated at 100.0% (638 of 638 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2 years ago
Макар Разин ecf6bbfb66
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (636 of 636 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (636 of 636 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2 years ago

@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 631 versionCode = 632
versionName = '6.8.1' versionName = '6.8.2'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp { ksp {
@ -82,7 +82,7 @@ afterEvaluate {
} }
dependencies { dependencies {
//noinspection GradleDependency //noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:9821e93d25') { implementation('com.github.KotatsuApp:kotatsu-parsers:44ea9fe709') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
@ -127,8 +127,8 @@ dependencies {
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
implementation 'com.google.dagger:hilt-android:2.51' implementation 'com.google.dagger:hilt-android:2.51.1'
kapt 'com.google.dagger:hilt-compiler:2.51' kapt 'com.google.dagger:hilt-compiler:2.51.1'
implementation 'androidx.hilt:hilt-work:1.2.0' implementation 'androidx.hilt:hilt-work:1.2.0'
kapt 'androidx.hilt:hilt-compiler:1.2.0' kapt 'androidx.hilt:hilt-compiler:1.2.0'
@ -161,6 +161,6 @@ dependencies {
androidTestImplementation 'androidx.room:room-testing:2.6.1' androidTestImplementation 'androidx.room:room-testing:2.6.1'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1' androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51' androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.51' kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.51.1'
} }

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.browser package org.koitharu.kotatsu.browser
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -13,17 +12,25 @@ import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.core.util.ext.toUriOrNull import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@SuppressLint("SetJavaScriptEnabled")
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback { class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
private lateinit var onBackPressedCallback: WebViewBackPressedCallback private lateinit var onBackPressedCallback: WebViewBackPressedCallback
@Inject
lateinit var mangaRepositoryFactory: MangaRepository.Factory
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) { if (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {
@ -33,7 +40,11 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
setDisplayHomeAsUpEnabled(true) setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material) setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
} }
viewBinding.webView.configureForParser(null) val userAgent = intent?.getSerializableExtraCompat<MangaSource>(EXTRA_SOURCE)?.let { source ->
val repository = mangaRepositoryFactory.create(source) as? RemoteMangaRepository
repository?.headers?.get(CommonHeaders.USER_AGENT)
}
viewBinding.webView.configureForParser(userAgent)
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true) CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)
viewBinding.webView.webViewClient = BrowserClient(this) viewBinding.webView.webViewClient = BrowserClient(this)
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar) viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
@ -54,16 +65,6 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
} }
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
viewBinding.webView.saveState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
viewBinding.webView.restoreState(savedInstanceState)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_browser, menu) menuInflater.inflate(R.menu.opt_browser, menu)
@ -136,11 +137,13 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
companion object { companion object {
private const val EXTRA_TITLE = "title" private const val EXTRA_TITLE = "title"
private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, url: String, title: String?): Intent { fun newIntent(context: Context, url: String, source: MangaSource?, title: String?): Intent {
return Intent(context, BrowserActivity::class.java) return Intent(context, BrowserActivity::class.java)
.setData(Uri.parse(url)) .setData(Uri.parse(url))
.putExtra(EXTRA_TITLE, title) .putExtra(EXTRA_TITLE, title)
.putExtra(EXTRA_SOURCE, source)
} }
} }
} }

@ -20,7 +20,7 @@ class CaptchaNotifier(
) : EventListener { ) : EventListener {
fun notify(exception: CloudFlareProtectedException) { fun notify(exception: CloudFlareProtectedException) {
if (!context.checkNotificationPermission()) { if (!context.checkNotificationPermission(CHANNEL_ID)) {
return return
} }
val manager = NotificationManagerCompat.from(context) val manager = NotificationManagerCompat.from(context)

@ -81,16 +81,6 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
super.onDestroy() super.onDestroy()
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
viewBinding.webView.saveState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
viewBinding.webView.restoreState(savedInstanceState)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.opt_captcha, menu) menuInflater.inflate(R.menu.opt_captcha, menu)
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)

@ -0,0 +1,7 @@
package org.koitharu.kotatsu.core.exceptions
import okio.IOException
class NoDataReceivedException(
private val url: String,
) : IOException("No data has been received from $url")

@ -82,7 +82,7 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
private fun openInBrowser(url: String) { private fun openInBrowser(url: String) {
val context = activity ?: fragment?.activity ?: return val context = activity ?: fragment?.activity ?: return
context.startActivity(BrowserActivity.newIntent(context, url, null)) context.startActivity(BrowserActivity.newIntent(context, url, null, null))
} }
private fun openAlternatives(manga: Manga) { private fun openAlternatives(manga: Manga) {

@ -14,7 +14,7 @@ import android.content.ContextWrapper
import android.content.OperationApplicationException import android.content.OperationApplicationException
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SyncResult import android.content.SyncResult
import android.content.pm.PackageManager import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.database.SQLException import android.database.SQLException
import android.graphics.Bitmap import android.graphics.Bitmap
@ -216,10 +216,19 @@ fun Context.findActivity(): Activity? = when (this) {
else -> null else -> null
} }
fun Context.checkNotificationPermission(): Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { fun Context.checkNotificationPermission(channelId: String?): Boolean {
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED val hasPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
} else { ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PERMISSION_GRANTED
NotificationManagerCompat.from(this).areNotificationsEnabled() } else {
NotificationManagerCompat.from(this).areNotificationsEnabled()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPermission && channelId != null) {
val channel = NotificationManagerCompat.from(this).getNotificationChannel(channelId)
if (channel != null && channel.importance == NotificationManagerCompat.IMPORTANCE_NONE) {
return false
}
}
return hasPermission
} }
@WorkerThread @WorkerThread

@ -8,12 +8,12 @@ import coil.network.HttpException
import okio.FileNotFoundException import okio.FileNotFoundException
import okio.IOException import okio.IOException
import org.acra.ktx.sendWithAcra import org.acra.ktx.sendWithAcra
import org.json.JSONException
import org.jsoup.HttpStatusException import org.jsoup.HttpStatusException
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CaughtException import org.koitharu.kotatsu.core.exceptions.CaughtException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
import org.koitharu.kotatsu.core.exceptions.SyncApiException import org.koitharu.kotatsu.core.exceptions.SyncApiException
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
@ -55,6 +55,8 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is SocketTimeoutException, is SocketTimeoutException,
-> resources.getString(R.string.network_error) -> resources.getString(R.string.network_error)
is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)
is WrongPasswordException -> resources.getString(R.string.wrong_password) is WrongPasswordException -> resources.getString(R.string.wrong_password)
is NotFoundException -> resources.getString(R.string.not_found_404) is NotFoundException -> resources.getString(R.string.not_found_404)
is UnsupportedSourceException -> resources.getString(R.string.unsupported_source) is UnsupportedSourceException -> resources.getString(R.string.unsupported_source)
@ -115,6 +117,7 @@ private val reportableExceptions = arraySetOf<Class<*>>(
IllegalArgumentException::class.java, IllegalArgumentException::class.java,
ConcurrentModificationException::class.java, ConcurrentModificationException::class.java,
UnsupportedOperationException::class.java, UnsupportedOperationException::class.java,
NoDataReceivedException::class.java,
) )
fun Throwable.isWebViewUnavailable(): Boolean { fun Throwable.isWebViewUnavailable(): Boolean {

@ -189,7 +189,7 @@ class DetailsFragment :
isVisible = false isVisible = false
} }
} }
if (manga.source == MangaSource.LOCAL) { if (manga.source == MangaSource.LOCAL || manga.source == MangaSource.DUMMY) {
infoLayout.textViewSource.isVisible = false infoLayout.textViewSource.isVisible = false
} else { } else {
infoLayout.textViewSource.text = manga.source.title infoLayout.textViewSource.text = manga.source.title
@ -223,7 +223,7 @@ class DetailsFragment :
} }
binding.approximateReadTime.text = time.format(resources) binding.approximateReadTime.text = time.format(resources)
binding.approximateReadTimeTitle.setText( binding.approximateReadTimeTitle.setText(
if (time.isContinue) R.string.approximate_remaining_time else R.string.approximate_reading_time if (time.isContinue) R.string.approximate_remaining_time else R.string.approximate_reading_time,
) )
binding.approximateReadTimeLayout.isVisible = true binding.approximateReadTimeLayout.isVisible = true
} }

@ -89,7 +89,7 @@ class DetailsMenuProvider(
R.id.action_browser -> { R.id.action_browser -> {
viewModel.manga.value?.let { viewModel.manga.value?.let {
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.title)) activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.source, it.title))
} }
} }

@ -12,6 +12,7 @@ import okio.Source
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import okio.use import okio.use
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
import org.koitharu.kotatsu.core.util.FileSize import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.compressToPNG import org.koitharu.kotatsu.core.util.ext.compressToPNG
import org.koitharu.kotatsu.core.util.ext.longHashCode import org.koitharu.kotatsu.core.util.ext.longHashCode
@ -62,7 +63,9 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
val bytes = file.sink(append = false).buffer().use { val bytes = file.sink(append = false).buffer().use {
it.writeAllCancellable(source) it.writeAllCancellable(source)
} }
check(bytes != 0L) { "No data has been written" } if (bytes == 0L) {
throw NoDataReceivedException(url)
}
lruCache.get().put(url, file) lruCache.get().put(url, file)
} finally { } finally {
file.delete() file.delete()

@ -50,7 +50,7 @@ class ImportWorker @AssistedInject constructor(
val result = runCatchingCancellable { val result = runCatchingCancellable {
importer.import(uri).manga importer.import(uri).manga
} }
if (applicationContext.checkNotificationPermission()) { if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
val notification = buildNotification(result) val notification = buildNotification(result)
notificationManager.notify(uri.hashCode(), notification) notificationManager.notify(uri.hashCode(), notification)
} }

@ -59,6 +59,9 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
viewModel.defaultWebtoonZoomOut.take(1).observe(viewLifecycleOwner) { viewModel.defaultWebtoonZoomOut.take(1).observe(viewLifecycleOwner) {
binding.frame.zoom = 1f - it binding.frame.zoom = 1f - it
} }
viewModel.readerSettings.observe(viewLifecycleOwner) {
it.applyBackground(binding.root)
}
} }
override fun onDestroyView() { override fun onDestroyView() {

@ -4,6 +4,7 @@ import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -15,8 +16,7 @@ import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class SuggestionsSettingsFragment : class SuggestionsSettingsFragment : BasePreferenceFragment(R.string.suggestions),
BasePreferenceFragment(R.string.suggestions),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
@Inject @Inject
@ -48,16 +48,17 @@ class SuggestionsSettingsFragment :
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == AppSettings.KEY_SUGGESTIONS && settings.isSuggestionsEnabled) { if (settings.isSuggestionsEnabled && (key == AppSettings.KEY_SUGGESTIONS
onSuggestionsEnabled() || key == AppSettings.KEY_SUGGESTIONS_EXCLUDE_TAGS
|| key == AppSettings.KEY_SUGGESTIONS_EXCLUDE_NSFW)
) {
updateSuggestions()
} }
} }
private fun onSuggestionsEnabled() { private fun updateSuggestions() {
lifecycleScope.launch { lifecycleScope.launch(Dispatchers.Default) {
if (repository.isEmpty()) { suggestionsScheduler.startNow()
suggestionsScheduler.startNow()
}
} }
} }
} }

@ -82,16 +82,6 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
viewBinding.webView.loadUrl(url) viewBinding.webView.loadUrl(url)
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
viewBinding.webView.saveState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
viewBinding.webView.restoreState(savedInstanceState)
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
viewBinding.webView.destroy() viewBinding.webView.destroy()

@ -64,6 +64,8 @@ class SuggestionsViewModel @Inject constructor(
override fun onRetry() = Unit override fun onRetry() = Unit
fun updateSuggestions() { fun updateSuggestions() {
suggestionsScheduler.startNow() launchJob(Dispatchers.Default) {
suggestionsScheduler.startNow()
}
} }
} }

@ -1,11 +1,12 @@
package org.koitharu.kotatsu.suggestions.ui package org.koitharu.kotatsu.suggestions.ui
import android.annotation.SuppressLint import android.Manifest
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.os.Build import android.os.Build
import androidx.annotation.FloatRange import androidx.annotation.FloatRange
import androidx.annotation.RequiresPermission
import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@ -50,6 +51,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.almostEquals import org.koitharu.kotatsu.core.util.ext.almostEquals
import org.koitharu.kotatsu.core.util.ext.asArrayList import org.koitharu.kotatsu.core.util.ext.asArrayList
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.flatten import org.koitharu.kotatsu.core.util.ext.flatten
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
@ -189,7 +191,9 @@ class SuggestionsWorker @AssistedInject constructor(
.sortedBy { it.relevance } .sortedBy { it.relevance }
.take(MAX_RESULTS) .take(MAX_RESULTS)
suggestionRepository.replace(suggestions) suggestionRepository.replace(suggestions)
if (appSettings.isSuggestionsNotificationAvailable && applicationContext.checkNotificationPermission()) { if (appSettings.isSuggestionsNotificationAvailable
&& applicationContext.checkNotificationPermission(MANGA_CHANNEL_ID)
) {
for (i in 0..3) { for (i in 0..3) {
try { try {
val manga = suggestions[Random.nextInt(0, suggestions.size / 3)] val manga = suggestions[Random.nextInt(0, suggestions.size / 3)]
@ -252,7 +256,7 @@ class SuggestionsWorker @AssistedInject constructor(
e.printStackTraceDebug() e.printStackTraceDebug()
}.getOrDefault(emptyList()) }.getOrDefault(emptyList())
@SuppressLint("MissingPermission") @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
private suspend fun showNotification(manga: Manga) { private suspend fun showNotification(manga: Manga) {
val channel = NotificationChannelCompat.Builder(MANGA_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT) val channel = NotificationChannelCompat.Builder(MANGA_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName(applicationContext.getString(R.string.suggestions)) .setName(applicationContext.getString(R.string.suggestions))
@ -393,7 +397,10 @@ class SuggestionsWorker @AssistedInject constructor(
.any { !it.state.isFinished } .any { !it.state.isFinished }
} }
fun startNow() { suspend fun startNow() {
if (workManager.awaitWorkInfosByTag(TAG_ONESHOT).any { !it.state.isFinished }) {
return
}
val constraints = Constraints.Builder() val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()
@ -402,7 +409,7 @@ class SuggestionsWorker @AssistedInject constructor(
.addTag(TAG_ONESHOT) .addTag(TAG_ONESHOT)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build() .build()
workManager.enqueue(request) workManager.enqueue(request).await()
} }
private fun createConstraints() = Constraints.Builder() private fun createConstraints() = Constraints.Builder()

@ -168,7 +168,7 @@ class TrackWorker @AssistedInject constructor(
} }
} }
}.onEachIndexed { index, it -> }.onEachIndexed { index, it ->
if (applicationContext.checkNotificationPermission()) { if (applicationContext.checkNotificationPermission(WORKER_CHANNEL_ID)) {
notificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1)) notificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1))
} }
when (it) { when (it) {
@ -197,7 +197,7 @@ class TrackWorker @AssistedInject constructor(
channelId: String?, channelId: String?,
newChapters: List<MangaChapter>, newChapters: List<MangaChapter>,
) { ) {
if (newChapters.isEmpty() || channelId == null || !applicationContext.checkNotificationPermission()) { if (newChapters.isEmpty() || channelId == null || !applicationContext.checkNotificationPermission(channelId)) {
return return
} }
val id = manga.url.hashCode() val id = manga.url.hashCode()

@ -633,4 +633,6 @@
<string name="unread">Sin leer</string> <string name="unread">Sin leer</string>
<string name="enable_source">Activar fuente</string> <string name="enable_source">Activar fuente</string>
<string name="unsupported_source">Esta fuente del manga no es compatible</string> <string name="unsupported_source">Esta fuente del manga no es compatible</string>
</resources> <string name="show_pages_thumbs">Mostrar miniaturas de las páginas</string>
<string name="show_pages_thumbs_summary">Habilite la pestaña \"Páginas\" en la pantalla de detalles</string>
</resources>

@ -633,4 +633,6 @@
<string name="order_oldest">Pinakaluma</string> <string name="order_oldest">Pinakaluma</string>
<string name="enable_source">Paganahin ang source</string> <string name="enable_source">Paganahin ang source</string>
<string name="unsupported_source">Ang manga source na ito ay hindi suportado</string> <string name="unsupported_source">Ang manga source na ito ay hindi suportado</string>
</resources> <string name="show_pages_thumbs">Ipakita ang mga thumbnail ng pahina</string>
<string name="show_pages_thumbs_summary">Paganahin ang tab na \"Mga Pahina\" sa screen ng mga detalye</string>
</resources>

@ -633,4 +633,6 @@
<string name="unread">अपठित</string> <string name="unread">अपठित</string>
<string name="unsupported_source">यह मंगा स्रोत समर्थित नहीं है</string> <string name="unsupported_source">यह मंगा स्रोत समर्थित नहीं है</string>
<string name="enable_source">स्रोत सक्षम करें</string> <string name="enable_source">स्रोत सक्षम करें</string>
</resources> <string name="show_pages_thumbs_summary">विवरण स्क्रीन पर \"पेज\" टैब सक्षम करें</string>
<string name="show_pages_thumbs">पेज थंबनेल दिखाएँ</string>
</resources>

@ -633,4 +633,6 @@
<string name="unread">Okunmadı</string> <string name="unread">Okunmadı</string>
<string name="unsupported_source">Bu manga kaynağı desteklenmiyor</string> <string name="unsupported_source">Bu manga kaynağı desteklenmiyor</string>
<string name="enable_source">Kaynağı etkinleştir</string> <string name="enable_source">Kaynağı etkinleştir</string>
</resources> <string name="show_pages_thumbs_summary">Ayrıntılar ekranında \"Sayfalar\" sekmesini etkinleştir</string>
<string name="show_pages_thumbs">Sayfa küçük resimlerini göster</string>
</resources>

@ -640,4 +640,5 @@
<string name="unsupported_source">This manga source is not supported</string> <string name="unsupported_source">This manga source is not supported</string>
<string name="show_pages_thumbs">Show pages thumbnails</string> <string name="show_pages_thumbs">Show pages thumbnails</string>
<string name="show_pages_thumbs_summary">Enable the \"Pages\" tab on the details screen</string> <string name="show_pages_thumbs_summary">Enable the \"Pages\" tab on the details screen</string>
<string name="error_no_data_received">No data was received from server</string>
</resources> </resources>

@ -6,7 +6,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.3.1' classpath 'com.android.tools.build:gradle:8.3.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51' classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1'
classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.23-1.0.19' classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.23-1.0.19'
} }
} }

Loading…
Cancel
Save