Merge branch 'devel' into feature/nextgen
This commit is contained in:
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -2,4 +2,4 @@ blank_issues_enabled: false
|
|||||||
contact_links:
|
contact_links:
|
||||||
- name: ⚠️ Source issue
|
- name: ⚠️ Source issue
|
||||||
url: https://github.com/KotatsuApp/kotatsu-parsers/issues/new
|
url: https://github.com/KotatsuApp/kotatsu-parsers/issues/new
|
||||||
about: Issues and requests for sources should be opened in the kotatsu-parsers repository instead
|
about: If you have troubles with a manga parser or want to propose new manga source, please open an issue in the kotatsu-parsers repository instead
|
||||||
64
.github/ISSUE_TEMPLATE/report_bug.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/report_bug.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
name: 🐞 Bug report
|
||||||
|
description: Report a bug in Kotatsu
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Brief summary
|
||||||
|
description: Please describe, what went wrong
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce-steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Please provide a way to reproduce this issue. Screenshots or videos can be very helpful
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
3. Issue here
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: kotatsu-version
|
||||||
|
attributes:
|
||||||
|
label: Kotatsu version
|
||||||
|
description: You can find your Kotatsu version in **Settings → About**.
|
||||||
|
placeholder: |
|
||||||
|
Example: "3.3"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: android-version
|
||||||
|
attributes:
|
||||||
|
label: Android version
|
||||||
|
description: You can find this somewhere in your Android settings.
|
||||||
|
placeholder: |
|
||||||
|
Example: "12.0"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: device
|
||||||
|
attributes:
|
||||||
|
label: Device
|
||||||
|
description: List your device and model.
|
||||||
|
placeholder: |
|
||||||
|
Example: "LG Nexus 5X"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||||
|
required: true
|
||||||
91
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
91
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@@ -1,91 +0,0 @@
|
|||||||
name: 🐞 Issue report
|
|
||||||
description: Report an issue in Kotatsu
|
|
||||||
labels: [bug]
|
|
||||||
body:
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: reproduce-steps
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: Provide an example of the issue.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
1. First step
|
|
||||||
2. Second step
|
|
||||||
3. Issue here
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: expected-behavior
|
|
||||||
attributes:
|
|
||||||
label: Expected behavior
|
|
||||||
description: Explain what you should expect to happen.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"This should happen..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: actual-behavior
|
|
||||||
attributes:
|
|
||||||
label: Actual behavior
|
|
||||||
description: Explain what actually happens.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"This happened instead..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: kotatsu-version
|
|
||||||
attributes:
|
|
||||||
label: Kotatsu version
|
|
||||||
description: You can find your Kotatsu version in **Settings → About**.
|
|
||||||
placeholder: |
|
|
||||||
Example: "3.3"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: android-version
|
|
||||||
attributes:
|
|
||||||
label: Android version
|
|
||||||
description: You can find this somewhere in your Android settings.
|
|
||||||
placeholder: |
|
|
||||||
Example: "Android 12"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: device
|
|
||||||
attributes:
|
|
||||||
label: Device
|
|
||||||
description: List your device and model.
|
|
||||||
placeholder: |
|
|
||||||
Example: "LG Nexus 5X"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: other-details
|
|
||||||
attributes:
|
|
||||||
label: Other details
|
|
||||||
placeholder: |
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new).
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
|
||||||
25
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
25
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: ⭐ Feature request
|
name: ⭐ Feature request
|
||||||
description: Suggest a feature to improve Kotatsu
|
description: Suggest a new idea how to improve Kotatsu
|
||||||
labels: [feature request]
|
labels: [feature request]
|
||||||
body:
|
body:
|
||||||
|
|
||||||
@@ -14,23 +14,6 @@ body:
|
|||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: other-details
|
|
||||||
attributes:
|
|
||||||
label: Other details
|
|
||||||
placeholder: |
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: kotatsu-version
|
|
||||||
attributes:
|
|
||||||
label: Kotatsu version
|
|
||||||
description: You can find your Kotatsu version in **Settings → About**.
|
|
||||||
placeholder: |
|
|
||||||
Example: "3.3"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: acknowledgements
|
id: acknowledgements
|
||||||
attributes:
|
attributes:
|
||||||
@@ -38,10 +21,4 @@ body:
|
|||||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new).
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
required: true
|
||||||
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 32
|
targetSdkVersion 32
|
||||||
versionCode 414
|
versionCode 415
|
||||||
versionName '3.4.2'
|
versionName '3.4.3'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class CloudFlareInterceptor : Interceptor {
|
|||||||
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) {
|
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) {
|
||||||
if (response.header(HEADER_SERVER)?.startsWith(SERVER_CLOUDFLARE) == true) {
|
if (response.header(HEADER_SERVER)?.startsWith(SERVER_CLOUDFLARE) == true) {
|
||||||
response.closeQuietly()
|
response.closeQuietly()
|
||||||
throw CloudFlareProtectedException(chain.request().url.toString())
|
throw CloudFlareProtectedException(response.request.url.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.acra.ktx.sendWithAcra
|
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
@@ -47,10 +46,7 @@ import org.koitharu.kotatsu.settings.SettingsActivity
|
|||||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||||
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
|
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
|
||||||
import org.koitharu.kotatsu.utils.ShareHelper
|
import org.koitharu.kotatsu.utils.ShareHelper
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
import org.koitharu.kotatsu.utils.ext.hasGlobalPoint
|
|
||||||
import org.koitharu.kotatsu.utils.ext.observeWithPrevious
|
|
||||||
import org.koitharu.kotatsu.utils.ext.postDelayed
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class ReaderActivity :
|
class ReaderActivity :
|
||||||
@@ -375,7 +371,7 @@ class ReaderActivity :
|
|||||||
if (ExceptionResolver.canResolve(exception)) {
|
if (ExceptionResolver.canResolve(exception)) {
|
||||||
tryResolve(exception)
|
tryResolve(exception)
|
||||||
} else {
|
} else {
|
||||||
exception.sendWithAcra()
|
exception.report("ReaderActivity::onError")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onCancel(dialog)
|
onCancel(dialog)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.koitharu.kotatsu.settings
|
||||||
|
|
||||||
|
import okhttp3.internal.toCanonicalHost
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.utils.EditTextValidator
|
||||||
|
|
||||||
|
class DomainValidator : EditTextValidator() {
|
||||||
|
|
||||||
|
override fun validate(text: String): ValidationResult {
|
||||||
|
if (text.isBlank()) {
|
||||||
|
return ValidationResult.Success
|
||||||
|
}
|
||||||
|
val host = text.trim().toCanonicalHost()
|
||||||
|
return if (host == null) {
|
||||||
|
ValidationResult.Failed(context.getString(R.string.invalid_domain_message))
|
||||||
|
} else {
|
||||||
|
ValidationResult.Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
|
|||||||
EditTextBindListener(
|
EditTextBindListener(
|
||||||
inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,
|
inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,
|
||||||
hint = key.defaultValue,
|
hint = key.defaultValue,
|
||||||
|
validator = DomainValidator(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
setTitle(R.string.domain)
|
setTitle(R.string.domain)
|
||||||
|
|||||||
@@ -2,14 +2,17 @@ package org.koitharu.kotatsu.settings.utils
|
|||||||
|
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
|
import org.koitharu.kotatsu.utils.EditTextValidator
|
||||||
|
|
||||||
class EditTextBindListener(
|
class EditTextBindListener(
|
||||||
private val inputType: Int,
|
private val inputType: Int,
|
||||||
private val hint: String
|
private val hint: String,
|
||||||
|
private val validator: EditTextValidator?,
|
||||||
) : EditTextPreference.OnBindEditTextListener {
|
) : EditTextPreference.OnBindEditTextListener {
|
||||||
|
|
||||||
override fun onBindEditText(editText: EditText) {
|
override fun onBindEditText(editText: EditText) {
|
||||||
editText.inputType = inputType
|
editText.inputType = inputType
|
||||||
editText.hint = hint
|
editText.hint = hint
|
||||||
|
validator?.attachToEditText(editText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.utils
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellableContinuation
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@@ -32,11 +34,13 @@ class CompositeMutex<T : Any> : Set<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun lock(element: T) {
|
suspend fun lock(element: T) {
|
||||||
waitForRemoval(element)
|
while (currentCoroutineContext().isActive) {
|
||||||
mutex.withLock {
|
waitForRemoval(element)
|
||||||
val lastValue = data.put(element, LinkedList<CancellableContinuation<Unit>>())
|
mutex.withLock {
|
||||||
check(lastValue == null) {
|
if (data[element] == null) {
|
||||||
"CompositeMutex is double-locked for $element"
|
data[element] = LinkedList<CancellableContinuation<Unit>>()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
abstract class EditTextValidator : TextWatcher {
|
||||||
|
|
||||||
|
private var editTextRef: WeakReference<EditText>? = null
|
||||||
|
|
||||||
|
protected val context: Context
|
||||||
|
get() = checkNotNull(editTextRef?.get()?.context) {
|
||||||
|
"EditTextValidator is not attached to EditText"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
val editText = editTextRef?.get() ?: return
|
||||||
|
val newText = s?.toString().orEmpty()
|
||||||
|
val result = runCatching {
|
||||||
|
validate(newText)
|
||||||
|
}.getOrElse { e ->
|
||||||
|
ValidationResult.Failed(e.getDisplayMessage(editText.resources))
|
||||||
|
}
|
||||||
|
editText.error = when (result) {
|
||||||
|
is ValidationResult.Failed -> result.message
|
||||||
|
ValidationResult.Success -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attachToEditText(editText: EditText) {
|
||||||
|
editTextRef = WeakReference(editText)
|
||||||
|
editText.removeTextChangedListener(this)
|
||||||
|
editText.addTextChangedListener(this)
|
||||||
|
afterTextChanged(editText.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun validate(text: String): ValidationResult
|
||||||
|
|
||||||
|
sealed class ValidationResult {
|
||||||
|
|
||||||
|
object Success : ValidationResult()
|
||||||
|
|
||||||
|
class Failed(val message: CharSequence) : ValidationResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -302,4 +302,19 @@
|
|||||||
<string name="appwidget_shelf_description">Mangas de tus favoritos</string>
|
<string name="appwidget_shelf_description">Mangas de tus favoritos</string>
|
||||||
<string name="appwidget_recent_description">Sus mangas recientemente leídos</string>
|
<string name="appwidget_recent_description">Sus mangas recientemente leídos</string>
|
||||||
<string name="logout">Cerrar sesión</string>
|
<string name="logout">Cerrar sesión</string>
|
||||||
|
<string name="status_planned">Planeado</string>
|
||||||
|
<string name="status_reading">Lectura</string>
|
||||||
|
<string name="status_dropped">Abandonado</string>
|
||||||
|
<string name="status_on_hold">En espera</string>
|
||||||
|
<string name="report">Informar</string>
|
||||||
|
<string name="tracking">Seguimiento</string>
|
||||||
|
<string name="disable_battery_optimization_summary">Ayuda con las comprobaciones de las actualizaciones en segundo plano</string>
|
||||||
|
<string name="status_re_reading">Relectura</string>
|
||||||
|
<string name="status_completed">Completado</string>
|
||||||
|
<string name="show_reading_indicators">Mostrar indicadores de progreso en la lectura</string>
|
||||||
|
<string name="data_deletion">Eliminación de datos</string>
|
||||||
|
<string name="show_reading_indicators_summary">Mostrar porcentaje de lectura en el historial y en los favoritos</string>
|
||||||
|
<string name="exclude_nsfw_from_history_summary">El manga marcado como NSFW nunca se añadirá al historial y no se guardará tu progreso</string>
|
||||||
|
<string name="clear_cookies_summary">Puede ayudar en caso de algunos problemas. Todas las autorizaciones serán invalidadas</string>
|
||||||
|
<string name="show_all">Mostrar todo</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<string name="clear_cookies">清除cookies</string>
|
<string name="clear_cookies">清除cookies</string>
|
||||||
<string name="chapters_checking_progress">检查新的章节: %1$d/%2$d</string>
|
<string name="chapters_checking_progress">检查新的章节: %1$d/%2$d</string>
|
||||||
<string name="error_empty_name">你必须输入一个名称</string>
|
<string name="error_empty_name">你必须输入一个名称</string>
|
||||||
<string name="new_sources_text">有了新的漫画来源</string>
|
<string name="new_sources_text">新的漫画来源</string>
|
||||||
<string name="suggestions_summary">根据你的喜好推荐漫画</string>
|
<string name="suggestions_summary">根据你的喜好推荐漫画</string>
|
||||||
<string name="suggestions_info">所有的数据都在这个设备上进行本地分析. 您的个人数据不会被转移到任何服务机构</string>
|
<string name="suggestions_info">所有的数据都在这个设备上进行本地分析. 您的个人数据不会被转移到任何服务机构</string>
|
||||||
<string name="never">从不</string>
|
<string name="never">从不</string>
|
||||||
@@ -284,7 +284,7 @@
|
|||||||
<string name="bookmark_remove">删除书签</string>
|
<string name="bookmark_remove">删除书签</string>
|
||||||
<string name="bookmarks">书签</string>
|
<string name="bookmarks">书签</string>
|
||||||
<string name="bookmark_removed">删除书签</string>
|
<string name="bookmark_removed">删除书签</string>
|
||||||
<string name="bookmark_added">加入书签</string>
|
<string name="bookmark_added">添加书签</string>
|
||||||
<string name="undo">撤销</string>
|
<string name="undo">撤销</string>
|
||||||
<string name="removed_from_history">从历史中删除</string>
|
<string name="removed_from_history">从历史中删除</string>
|
||||||
<string name="dns_over_https">DNS over HTTPS</string>
|
<string name="dns_over_https">DNS over HTTPS</string>
|
||||||
@@ -295,4 +295,22 @@
|
|||||||
<string name="disable_battery_optimization_summary">帮助进行背景更新检查</string>
|
<string name="disable_battery_optimization_summary">帮助进行背景更新检查</string>
|
||||||
<string name="crash_text">出了点问题. 请向开发人员提交一份错误报告以帮助我们修复它.</string>
|
<string name="crash_text">出了点问题. 请向开发人员提交一份错误报告以帮助我们修复它.</string>
|
||||||
<string name="send">发送</string>
|
<string name="send">发送</string>
|
||||||
|
<string name="disable_all">全部禁用</string>
|
||||||
|
<string name="status_planned">计划</string>
|
||||||
|
<string name="status_on_hold">暂停</string>
|
||||||
|
<string name="report">报告</string>
|
||||||
|
<string name="tracking">追踪</string>
|
||||||
|
<string name="logout">注销</string>
|
||||||
|
<string name="status_reading">阅读</string>
|
||||||
|
<string name="status_re_reading">重读</string>
|
||||||
|
<string name="status_completed">完成</string>
|
||||||
|
<string name="use_fingerprint">使用指纹</string>
|
||||||
|
<string name="appwidget_shelf_description">你喜欢的漫画</string>
|
||||||
|
<string name="appwidget_recent_description">您最近阅读的漫画</string>
|
||||||
|
<string name="show_reading_indicators_summary">在历史和收藏夹中显示阅读百分比</string>
|
||||||
|
<string name="show_reading_indicators">显示阅读进度指标</string>
|
||||||
|
<string name="data_deletion">数据删除</string>
|
||||||
|
<string name="exclude_nsfw_from_history_summary">标记为NSFW的漫画将永远不会被添加到历史中你的进度也不会被保存</string>
|
||||||
|
<string name="clear_cookies_summary">可以在出现一些问题时提供帮助. 所有授权将被视为无效</string>
|
||||||
|
<string name="show_all">显示全部</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -320,6 +320,7 @@
|
|||||||
<string name="exclude_nsfw_from_history_summary">Manga marked as NSFW will never added to the history and your progress will not be saved</string>
|
<string name="exclude_nsfw_from_history_summary">Manga marked as NSFW will never added to the history and your progress will not be saved</string>
|
||||||
<string name="clear_cookies_summary">Can help in case of some issues. All authorizations will be invalidated</string>
|
<string name="clear_cookies_summary">Can help in case of some issues. All authorizations will be invalidated</string>
|
||||||
<string name="show_all">Show all</string>
|
<string name="show_all">Show all</string>
|
||||||
|
<string name="invalid_domain_message">Invalid domain</string>
|
||||||
<string name="clear_all_history">Clear all history</string>
|
<string name="clear_all_history">Clear all history</string>
|
||||||
<string name="last_2_hours">Last 2 hours</string>
|
<string name="last_2_hours">Last 2 hours</string>
|
||||||
<string name="history_cleared">History cleared</string>
|
<string name="history_cleared">History cleared</string>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CompositeMutexTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSingleLock() = runTest {
|
||||||
|
val mutex = CompositeMutex<Int>()
|
||||||
|
mutex.lock(1)
|
||||||
|
mutex.lock(2)
|
||||||
|
mutex.unlock(1)
|
||||||
|
assert(mutex.size == 1)
|
||||||
|
mutex.unlock(2)
|
||||||
|
assert(mutex.isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDoubleLock() = runTest {
|
||||||
|
val mutex = CompositeMutex<Int>()
|
||||||
|
repeat(2) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
mutex.lock(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield()
|
||||||
|
mutex.unlock(1)
|
||||||
|
val tryLock = withTimeoutOrNull(1000) {
|
||||||
|
mutex.lock(1)
|
||||||
|
}
|
||||||
|
assertNull(tryLock)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user