Small fixes

master
Koitharu 2 years ago
parent 9425d29596
commit 90f0846fb4
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

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

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

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

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

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

@ -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) {

@ -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)
// FIXME: Deflater has been closed
}
@WorkerThread @Blocking
fun put(name: String, file: File): Boolean { fun put(name: String, file: File): Boolean = withOutput { output ->
return output.appendFile(file, name) output.appendFile(file, name)
} }
@WorkerThread @Blocking
fun put(name: String, content: String): Boolean { fun put(name: String, content: String): Boolean = withOutput { output ->
return output.appendText(content, name) output.appendText(content, name)
} }
@WorkerThread @Blocking
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,25 +40,29 @@ 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)
output.closeEntry()
}
true true
} else { } else {
false false
} }
} }
@WorkerThread @Blocking
fun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean { fun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean {
return if (entryNames.add(entry.name)) { return if (entryNames.add(entry.name)) {
val zipEntry = ZipEntry(entry.name) val zipEntry = ZipEntry(entry.name)
output.putNextEntry(zipEntry) withOutput { output ->
try { output.putNextEntry(zipEntry)
other.getInputStream(entry).use { input -> try {
input.copyTo(output) other.getInputStream(entry).use { input ->
input.copyTo(output)
}
} finally {
output.closeEntry()
} }
} finally {
output.closeEntry()
} }
true true
} else { } else {
@ -68,15 +70,15 @@ class ZipOutput(
} }
} }
fun finish() { @Blocking
fun finish() = withOutput { output ->
output.finish() output.finish()
output.flush()
} }
@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
}
} }

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

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

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

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

Loading…
Cancel
Save