Small fixes
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
15
app/src/debug/res/drawable-anydpi-v24/ic_bug.xml
Normal file
15
app/src/debug/res/drawable-anydpi-v24/ic_bug.xml
Normal 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>
|
||||||
BIN
app/src/debug/res/drawable-hdpi/ic_bug.png
Normal file
BIN
app/src/debug/res/drawable-hdpi/ic_bug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 417 B |
BIN
app/src/debug/res/drawable-mdpi/ic_bug.png
Normal file
BIN
app/src/debug/res/drawable-mdpi/ic_bug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 308 B |
BIN
app/src/debug/res/drawable-xhdpi/ic_bug.png
Normal file
BIN
app/src/debug/res/drawable-xhdpi/ic_bug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 480 B |
BIN
app/src/debug/res/drawable-xxhdpi/ic_bug.png
Normal file
BIN
app/src/debug/res/drawable-xxhdpi/ic_bug.png
Normal file
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)
|
@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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user