Small fixes

This commit is contained in:
Koitharu
2024-10-27 16:28:11 +02:00
parent 9425d29596
commit 90f0846fb4
15 changed files with 103 additions and 51 deletions

View File

@@ -42,7 +42,7 @@ class StrictModeNotifier(
override fun onViolation(violation: FragmentViolation) = showNotification(violation) override fun onViolation(violation: FragmentViolation) = showNotification(violation)
private fun showNotification(violation: Throwable) = Notification.Builder(context, CHANNEL_ID) private fun showNotification(violation: Throwable) = Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_notify_error) .setSmallIcon(R.drawable.ic_bug)
.setContentTitle(context.getString(R.string.strict_mode)) .setContentTitle(context.getString(R.string.strict_mode))
.setContentText(violation.message) .setContentText(violation.message)
.setStyle( .setStyle(

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.98150784"
android:scaleY="0.98150784"
android:translateX="0.22190611"
android:translateY="-0.2688478">
<path
android:fillColor="@android:color/white"
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

View File

@@ -29,7 +29,7 @@ class CaptchaNotifier(
return return
} }
val manager = NotificationManagerCompat.from(context) val manager = NotificationManagerCompat.from(context)
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT) val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
.setName(context.getString(R.string.captcha_required)) .setName(context.getString(R.string.captcha_required))
.setShowBadge(true) .setShowBadge(true)
.setVibrationEnabled(false) .setVibrationEnabled(false)
@@ -42,8 +42,8 @@ class CaptchaNotifier(
.setData(exception.url.toUri()) .setData(exception.url.toUri())
val notification = NotificationCompat.Builder(context, CHANNEL_ID) val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(channel.name) .setContentTitle(channel.name)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_LOW)
.setDefaults(NotificationCompat.DEFAULT_SOUND) .setDefaults(0)
.setSmallIcon(android.R.drawable.stat_notify_error) .setSmallIcon(android.R.drawable.stat_notify_error)
.setGroup(GROUP_CAPTCHA) .setGroup(GROUP_CAPTCHA)
.setAutoCancel(true) .setAutoCancel(true)

View File

@@ -42,7 +42,7 @@ class ExternalMangaRepository(
override var defaultSortOrder: SortOrder override var defaultSortOrder: SortOrder
get() = capabilities?.availableSortOrders?.firstOrNull() ?: SortOrder.ALPHABETICAL get() = capabilities?.availableSortOrders?.firstOrNull() ?: SortOrder.ALPHABETICAL
set(value) = Unit set(_) = Unit
override suspend fun getFilterOptions(): MangaListFilterOptions = filterOptions.get() override suspend fun getFilterOptions(): MangaListFilterOptions = filterOptions.get()

View File

@@ -8,6 +8,7 @@ import androidx.core.net.toUri
import org.jetbrains.annotations.Blocking import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.ContentRating import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Demographic import org.koitharu.kotatsu.parsers.model.Demographic

View File

@@ -42,7 +42,10 @@ import java.net.UnknownHostException
private const val MSG_NO_SPACE_LEFT = "No space left on device" private const val MSG_NO_SPACE_LEFT = "No space left on device"
private const val IMAGE_FORMAT_NOT_SUPPORTED = "Image format not supported" private const val IMAGE_FORMAT_NOT_SUPPORTED = "Image format not supported"
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) { fun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessageOrNull(resources)
?: resources.getString(R.string.error_occurred)
private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) {
is ScrobblerAuthRequiredException -> resources.getString( is ScrobblerAuthRequiredException -> resources.getString(
R.string.scrobbler_auth_required, R.string.scrobbler_auth_required,
resources.getString(scrobbler.titleResId), resources.getString(scrobbler.titleResId),
@@ -88,7 +91,11 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
) )
is NoDataReceivedException -> resources.getString(R.string.error_no_data_received) is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)
is IncompatiblePluginException -> resources.getString(R.string.plugin_incompatible) is IncompatiblePluginException -> {
cause?.getDisplayMessageOrNull(resources)?.let {
resources.getString(R.string.plugin_incompatible_with_cause, it)
} ?: resources.getString(R.string.plugin_incompatible)
}
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)
@@ -97,9 +104,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is HttpStatusException -> getHttpDisplayMessage(statusCode, resources) is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)
else -> getDisplayMessage(message, resources) ?: message else -> getDisplayMessage(message, resources) ?: message
}.ifNullOrEmpty { }.takeUnless { it.isNullOrBlank() }
resources.getString(R.string.error_occurred)
}
@DrawableRes @DrawableRes
fun Throwable.getDisplayIcon() = when (this) { fun Throwable.getDisplayIcon() = when (this) {

View File

@@ -2,11 +2,13 @@ package org.koitharu.kotatsu.core.zip
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.collection.ArraySet import androidx.collection.ArraySet
import okhttp3.internal.closeQuietly
import okio.Closeable import okio.Closeable
import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.core.util.ext.withChildren import org.koitharu.kotatsu.core.util.ext.withChildren
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.util.concurrent.atomic.AtomicBoolean import java.io.FileOutputStream
import java.util.zip.Deflater import java.util.zip.Deflater
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipFile
@@ -14,27 +16,23 @@ import java.util.zip.ZipOutputStream
class ZipOutput( class ZipOutput(
val file: File, val file: File,
compressionLevel: Int = Deflater.DEFAULT_COMPRESSION, private val compressionLevel: Int = Deflater.DEFAULT_COMPRESSION,
) : Closeable { ) : Closeable {
private val entryNames = ArraySet<String>() private val entryNames = ArraySet<String>()
private val isClosed = AtomicBoolean(false) private var cachedOutput: ZipOutputStream? = null
private val output = ZipOutputStream(file.outputStream()).apply {
setLevel(compressionLevel) @Blocking
// FIXME: Deflater has been closed fun put(name: String, file: File): Boolean = withOutput { output ->
output.appendFile(file, name)
} }
@WorkerThread @Blocking
fun put(name: String, file: File): Boolean { fun put(name: String, content: String): Boolean = withOutput { output ->
return output.appendFile(file, name) output.appendText(content, name)
} }
@WorkerThread @Blocking
fun put(name: String, content: String): Boolean {
return output.appendText(content, name)
}
@WorkerThread
fun addDirectory(name: String): Boolean { fun addDirectory(name: String): Boolean {
val entry = if (name.endsWith("/")) { val entry = if (name.endsWith("/")) {
ZipEntry(name) ZipEntry(name)
@@ -42,24 +40,8 @@ class ZipOutput(
ZipEntry("$name/") ZipEntry("$name/")
} }
return if (entryNames.add(entry.name)) { return if (entryNames.add(entry.name)) {
output.putNextEntry(entry) withOutput { output ->
output.closeEntry() output.putNextEntry(entry)
true
} else {
false
}
}
@WorkerThread
fun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean {
return if (entryNames.add(entry.name)) {
val zipEntry = ZipEntry(entry.name)
output.putNextEntry(zipEntry)
try {
other.getInputStream(entry).use { input ->
input.copyTo(output)
}
} finally {
output.closeEntry() output.closeEntry()
} }
true true
@@ -68,15 +50,35 @@ class ZipOutput(
} }
} }
fun finish() { @Blocking
output.finish() fun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean {
output.flush() return if (entryNames.add(entry.name)) {
val zipEntry = ZipEntry(entry.name)
withOutput { output ->
output.putNextEntry(zipEntry)
try {
other.getInputStream(entry).use { input ->
input.copyTo(output)
}
} finally {
output.closeEntry()
}
}
true
} else {
false
}
} }
@Blocking
fun finish() = withOutput { output ->
output.finish()
}
@Synchronized
override fun close() { override fun close() {
if (isClosed.compareAndSet(false, true)) { cachedOutput?.close()
output.close() cachedOutput = null
}
} }
@WorkerThread @WorkerThread
@@ -128,4 +130,18 @@ class ZipOutput(
} }
return true return true
} }
@Synchronized
private fun <T> withOutput(block: (ZipOutputStream) -> T): T {
val output = cachedOutput ?: newOutput(append = false)
val res = block(output)
output.flush()
return res
}
private fun newOutput(append: Boolean) = ZipOutputStream(FileOutputStream(file, append)).also {
it.setLevel(compressionLevel)
cachedOutput?.closeQuietly()
cachedOutput = it
}
} }

View File

@@ -62,7 +62,7 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(LocalListMenuProvider(binding.root.context, this::onEmptyActionClick)) addMenuProvider(LocalListMenuProvider(binding.root.context, childFragmentManager, this::onEmptyActionClick))
addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel)) addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel))
viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() } viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() }
} }

View File

@@ -5,11 +5,14 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentManager
import org.koitharu.kotatsu.R 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.settings.storage.directories.MangaDirectoriesActivity
class LocalListMenuProvider( class LocalListMenuProvider(
private val context: Context, private val context: Context,
private val fragmentManager: FragmentManager,
private val onImportClick: Function0<Unit>, private val onImportClick: Function0<Unit>,
) : MenuProvider { ) : MenuProvider {
@@ -29,6 +32,11 @@ class LocalListMenuProvider(
true true
} }
R.id.action_filter -> {
FilterSheetFragment.show(fragmentManager)
true
}
else -> false else -> false
} }
} }

View File

@@ -9,9 +9,15 @@
android:title="@string/_import" android:title="@string/_import"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_filter"
android:orderInCategory="30"
android:title="@string/filter"
app:showAsAction="never" />
<item <item
android:id="@+id/action_directories" android:id="@+id/action_directories"
android:orderInCategory="96" android:orderInCategory="80"
android:title="@string/directories" android:title="@string/directories"
app:showAsAction="never" /> app:showAsAction="never" />

View File

@@ -679,6 +679,7 @@
<string name="chapters_left">Chapters left</string> <string name="chapters_left">Chapters left</string>
<string name="external_source">External/plugin</string> <string name="external_source">External/plugin</string>
<string name="plugin_incompatible">Incompatible plugin or internal error. Make sure you are using the latest version of the plugin and Kotatsu</string> <string name="plugin_incompatible">Incompatible plugin or internal error. Make sure you are using the latest version of the plugin and Kotatsu</string>
<string name="plugin_incompatible_with_cause">Plugin error: %s\n Make sure you are using the latest version of the plugin and Kotatsu</string>
<string name="connection_ok">Connection is OK</string> <string name="connection_ok">Connection is OK</string>
<string name="invalid_proxy_configuration">Invalid proxy configuration</string> <string name="invalid_proxy_configuration">Invalid proxy configuration</string>
<string name="show_quick_filters">Show quick filters</string> <string name="show_quick_filters">Show quick filters</string>