Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3121532217 | |||
| 20ac12ca0d | |||
| f0b222140e | |||
| 2a35ca6094 | |||
| 93f9636916 | |||
| 2c24aba558 |
27
README.md
27
README.md
@@ -10,9 +10,9 @@ Kotatsu is a free and open source manga reader for Android.
|
|||||||
alt="Get it on F-Droid"
|
alt="Get it on F-Droid"
|
||||||
height="80">](https://f-droid.org/packages/org.koitharu.kotatsu)
|
height="80">](https://f-droid.org/packages/org.koitharu.kotatsu)
|
||||||
|
|
||||||
Download APK from GitHub Releases:
|
Download APK directly from GitHub:
|
||||||
|
|
||||||
- [Latest release](https://github.com/KotatsuApp/Kotatsu/releases/latest)
|
- **[Latest release](https://github.com/KotatsuApp/Kotatsu/releases/latest)**
|
||||||
|
|
||||||
### Main Features
|
### Main Features
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ Download APK from GitHub Releases:
|
|||||||
* Tablet-optimized material design UI
|
* Tablet-optimized material design UI
|
||||||
* Standard and Webtoon-optimized reader
|
* Standard and Webtoon-optimized reader
|
||||||
* Notifications about new chapters with updates feed
|
* Notifications about new chapters with updates feed
|
||||||
* Available in multiple languages
|
* Shikimori integration (manga tracking)
|
||||||
* Password protect access to the app
|
* Password/fingerprint protect access to the app
|
||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||
@@ -38,23 +38,20 @@ Download APK from GitHub Releases:
|
|||||||
|
|
||||||
### Localization
|
### Localization
|
||||||
|
|
||||||
<a href="https://hosted.weblate.org/engage/kotatsu/">
|
[<img src="https://hosted.weblate.org/widgets/kotatsu/-/287x66-white.png" alt="Translation status">](https://hosted.weblate.org/engage/kotatsu/)
|
||||||
<img src="https://hosted.weblate.org/widgets/kotatsu/-/287x66-white.png" alt="Translation status" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
Kotatsu is localized in a number of different languages, if you would like to help improve these or add new languages,
|
Kotatsu is localized in a number of different languages, if you would like to help improve these or add new languages,
|
||||||
please head over to the Weblate <a href="https://hosted.weblate.org/engage/kotatsu/">project page</a>
|
please head over to the [Weblate project page](https://hosted.weblate.org/engage/kotatsu/)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
Kotatsu is Free Software: You can use, study share and improve it at your
|
You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications
|
||||||
will. Specifically you can redistribute and/or modify it under the terms of the
|
to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build &
|
||||||
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as
|
install instructions.
|
||||||
published by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
### Disclaimer
|
### DMCA disclaimer
|
||||||
|
|
||||||
The developers of this application does not have any affiliation with the content providers available.
|
The developers of this application does not have any affiliation with the content available in the app.
|
||||||
|
It is collecting from the sources freely available through any web browser.
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 32
|
targetSdkVersion 32
|
||||||
versionCode 412
|
versionCode 413
|
||||||
versionName '3.4'
|
versionName '3.4.1'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||||
implementation('com.github.nv95:kotatsu-parsers:da3b0ae0cf') {
|
implementation('com.github.nv95:kotatsu-parsers:8c26f3c790') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
class WebtoonLayoutManager : LinearLayoutManager {
|
||||||
|
|
||||||
|
private var scrollDirection: Int = 0
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
orientation: Int,
|
||||||
|
reverseLayout: Boolean,
|
||||||
|
) : super(context, orientation, reverseLayout)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet?,
|
||||||
|
defStyleAttr: Int,
|
||||||
|
defStyleRes: Int,
|
||||||
|
) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||||
|
|
||||||
|
override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State): Int {
|
||||||
|
scrollDirection = dy.sign
|
||||||
|
return super.scrollVerticallyBy(dy, recycler, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
|
||||||
|
if (state.hasTargetScrollPosition()) {
|
||||||
|
super.calculateExtraLayoutSpace(state, extraLayoutSpace)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val pageSize = height
|
||||||
|
extraLayoutSpace[0] = if (scrollDirection < 0) pageSize else 0
|
||||||
|
extraLayoutSpace[1] = if (scrollDirection < 0) 0 else pageSize
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.reader.ui.ReaderState
|
|||||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||||
import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged
|
|
||||||
import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
|
import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
|
||||||
import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition
|
import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition
|
||||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||||
@@ -33,7 +32,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
|||||||
with(binding.recyclerView) {
|
with(binding.recyclerView) {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = webtoonAdapter
|
adapter = webtoonAdapter
|
||||||
doOnCurrentItemChanged(::notifyPageChanged)
|
addOnPageScrollListener(PageScrollListener())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,4 +92,12 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
|||||||
override fun switchPageTo(position: Int, smooth: Boolean) {
|
override fun switchPageTo(position: Int, smooth: Boolean) {
|
||||||
binding.recyclerView.firstVisibleItemPosition = position
|
binding.recyclerView.firstVisibleItemPosition = position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() {
|
||||||
|
|
||||||
|
override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) {
|
||||||
|
super.onPageChanged(recyclerView, index)
|
||||||
|
notifyPageChanged(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,25 +2,27 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat.TYPE_TOUCH
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class WebtoonRecyclerView @JvmOverloads constructor(
|
class WebtoonRecyclerView @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
override fun startNestedScroll(axes: Int) = startNestedScroll(axes, ViewCompat.TYPE_TOUCH)
|
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
|
||||||
|
|
||||||
override fun startNestedScroll(axes: Int, type: Int): Boolean {
|
override fun startNestedScroll(axes: Int) = startNestedScroll(axes, TYPE_TOUCH)
|
||||||
return true
|
|
||||||
}
|
override fun startNestedScroll(axes: Int, type: Int): Boolean = true
|
||||||
|
|
||||||
override fun dispatchNestedPreScroll(
|
override fun dispatchNestedPreScroll(
|
||||||
dx: Int,
|
dx: Int,
|
||||||
dy: Int,
|
dy: Int,
|
||||||
consumed: IntArray?,
|
consumed: IntArray?,
|
||||||
offsetInWindow: IntArray?
|
offsetInWindow: IntArray?
|
||||||
) = dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH)
|
) = dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH)
|
||||||
|
|
||||||
override fun dispatchNestedPreScroll(
|
override fun dispatchNestedPreScroll(
|
||||||
dx: Int,
|
dx: Int,
|
||||||
@@ -34,6 +36,7 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
consumed[0] = 0
|
consumed[0] = 0
|
||||||
consumed[1] = consumedY
|
consumed[1] = consumedY
|
||||||
}
|
}
|
||||||
|
notifyScrollChanged(dy)
|
||||||
return consumedY != 0 || dy == 0
|
return consumedY != 0 || dy == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,4 +78,39 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addOnPageScrollListener(listener: OnPageScrollListener) {
|
||||||
|
val list = onPageScrollListeners ?: LinkedList<OnPageScrollListener>().also { onPageScrollListeners = it }
|
||||||
|
list.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOnPageScrollListener(listener: OnPageScrollListener) {
|
||||||
|
onPageScrollListeners?.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyScrollChanged(dy: Int) {
|
||||||
|
val listeners = onPageScrollListeners
|
||||||
|
if (listeners.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val centerPosition = findCenterViewPosition()
|
||||||
|
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class OnPageScrollListener {
|
||||||
|
|
||||||
|
private var lastPosition = NO_POSITION
|
||||||
|
|
||||||
|
fun dispatchScroll(recyclerView: WebtoonRecyclerView, dy: Int, centerPosition: Int) {
|
||||||
|
onScroll(recyclerView, dy)
|
||||||
|
if (centerPosition != NO_POSITION && centerPosition != lastPosition) {
|
||||||
|
lastPosition = centerPosition
|
||||||
|
onPageChanged(recyclerView, centerPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onScroll(recyclerView: WebtoonRecyclerView, dy: Int) = Unit
|
||||||
|
|
||||||
|
open fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) = Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.core.net.toUri
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -23,9 +23,6 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
|||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
import java.security.cert.CertificateEncodingException
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -74,7 +71,8 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
|
|||||||
.setTitle(R.string.app_update_available)
|
.setTitle(R.string.app_update_available)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setPositiveButton(R.string.download) { _, _ ->
|
.setPositiveButton(R.string.download) { _, _ ->
|
||||||
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(version.apkUrl)))
|
val intent = Intent(Intent.ACTION_VIEW, version.apkUrl.toUri())
|
||||||
|
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser)))
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.close, null)
|
.setNegativeButton(R.string.close, null)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
@@ -88,42 +86,23 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
|
|||||||
private val PERIOD = TimeUnit.HOURS.toMillis(6)
|
private val PERIOD = TimeUnit.HOURS.toMillis(6)
|
||||||
|
|
||||||
fun isUpdateSupported(context: Context): Boolean {
|
fun isUpdateSupported(context: Context): Boolean {
|
||||||
return getCertificateSHA1Fingerprint(context) == CERT_SHA1
|
return BuildConfig.DEBUG || getCertificateSHA1Fingerprint(context) == CERT_SHA1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@SuppressLint("PackageManagerGetSignatures")
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
private fun getCertificateSHA1Fingerprint(context: Context): String? {
|
private fun getCertificateSHA1Fingerprint(context: Context): String? = runCatching {
|
||||||
val packageInfo = try {
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
||||||
context.packageManager.getPackageInfo(
|
val signatures = requireNotNull(packageInfo?.signatures)
|
||||||
context.packageName,
|
val cert: ByteArray = signatures.first().toByteArray()
|
||||||
PackageManager.GET_SIGNATURES
|
|
||||||
)
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
e.printStackTraceDebug()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val signatures = packageInfo?.signatures
|
|
||||||
val cert: ByteArray = signatures?.firstOrNull()?.toByteArray() ?: return null
|
|
||||||
val input: InputStream = ByteArrayInputStream(cert)
|
val input: InputStream = ByteArrayInputStream(cert)
|
||||||
val c = try {
|
|
||||||
val cf = CertificateFactory.getInstance("X509")
|
val cf = CertificateFactory.getInstance("X509")
|
||||||
cf.generateCertificate(input) as X509Certificate
|
val c = cf.generateCertificate(input) as X509Certificate
|
||||||
} catch (e: CertificateException) {
|
|
||||||
e.printStackTraceDebug()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
val md: MessageDigest = MessageDigest.getInstance("SHA1")
|
val md: MessageDigest = MessageDigest.getInstance("SHA1")
|
||||||
val publicKey: ByteArray = md.digest(c.encoded)
|
val publicKey: ByteArray = md.digest(c.encoded)
|
||||||
publicKey.byte2HexFormatted()
|
return publicKey.byte2HexFormatted()
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
}.onFailure { error ->
|
||||||
e.printStackTraceDebug()
|
error.printStackTraceDebug()
|
||||||
null
|
}.getOrNull()
|
||||||
} catch (e: CertificateEncodingException) {
|
|
||||||
e.printStackTraceDebug()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,22 +91,6 @@ fun View.resetTransformations() {
|
|||||||
rotationY = 0f
|
rotationY = 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun RecyclerView.doOnCurrentItemChanged(crossinline callback: (Int) -> Unit) {
|
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
|
|
||||||
private var lastItem = -1
|
|
||||||
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
val item = recyclerView.findCenterViewPosition()
|
|
||||||
if (item != RecyclerView.NO_POSITION && item != lastItem) {
|
|
||||||
lastItem = item
|
|
||||||
callback(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RecyclerView.findCenterViewPosition(): Int {
|
fun RecyclerView.findCenterViewPosition(): Int {
|
||||||
val centerX = width / 2f
|
val centerX = width / 2f
|
||||||
val centerY = height / 2f
|
val centerY = height / 2f
|
||||||
|
|||||||
@@ -42,9 +42,8 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_titles"
|
android:id="@+id/layout_titles"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/imageView_cover"
|
app:layout_constraintStart_toEndOf="@id/imageView_cover"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
@@ -151,6 +150,14 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier_header"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:barrierMargin="8dp"
|
||||||
|
app:constraint_referenced_ids="layout_titles,imageView_cover" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||||
android:id="@+id/chips_tags"
|
android:id="@+id/chips_tags"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -162,7 +169,7 @@
|
|||||||
app:chipSpacingVertical="6dp"
|
app:chipSpacingVertical="6dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/layout_titles" />
|
app:layout_constraintTop_toBottomOf="@+id/barrier_header" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_bookmarks"
|
android:id="@+id/textView_bookmarks"
|
||||||
@@ -223,7 +230,7 @@
|
|||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/recyclerView_bookmarks"
|
app:layout_constraintTop_toBottomOf="@id/scrobbling_layout"
|
||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
tools:text="@tools:sample/lorem/random[250]" />
|
tools:text="@tools:sample/lorem/random[250]" />
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
app:layoutManager="org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonLayoutManager" />
|
||||||
@@ -302,4 +302,19 @@
|
|||||||
<string name="use_fingerprint">Использовать отпечаток пальца, если доступно</string>
|
<string name="use_fingerprint">Использовать отпечаток пальца, если доступно</string>
|
||||||
<string name="appwidget_shelf_description">Манга из Вашего избранного</string>
|
<string name="appwidget_shelf_description">Манга из Вашего избранного</string>
|
||||||
<string name="appwidget_recent_description">Манга, которую Вы недавно читали</string>
|
<string name="appwidget_recent_description">Манга, которую Вы недавно читали</string>
|
||||||
|
<string name="status_reading">Читаю</string>
|
||||||
|
<string name="status_planned">Запланировано</string>
|
||||||
|
<string name="status_on_hold">Отложено</string>
|
||||||
|
<string name="status_dropped">Заброшено</string>
|
||||||
|
<string name="status_completed">Завершено</string>
|
||||||
|
<string name="show_reading_indicators_summary">Показать процент прочитанного в истории и избранном</string>
|
||||||
|
<string name="exclude_nsfw_from_history_summary">Манга, помеченная как NSFW, никогда не будет добавлена в историю и ваш прогресс чтения не будет сохранен</string>
|
||||||
|
<string name="percent_string_pattern">%1$s%%</string>
|
||||||
|
<string name="report">Отчёт</string>
|
||||||
|
<string name="logout">Выйти</string>
|
||||||
|
<string name="status_re_reading">Перечитываю</string>
|
||||||
|
<string name="show_reading_indicators">Показать индикаторы прогресса чтения</string>
|
||||||
|
<string name="data_deletion">Удаление данных</string>
|
||||||
|
<string name="clear_cookies_summary">Может помочь в случае каких-либо проблем. Все авторизации будут аннулированы</string>
|
||||||
|
<string name="show_all">Показать все</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
Kotatsu is a free and open source manga reader for Android.<br>
|
Kotatsu is a free and open source manga reader for Android.
|
||||||
|
|
||||||
|
|
||||||
**Main Features:**
|
**Main Features:**
|
||||||
|
|
||||||
- Online manga catalogues
|
- Online manga catalogues
|
||||||
@@ -9,5 +11,5 @@ Kotatsu is a free and open source manga reader for Android.<br>
|
|||||||
- Tablet-optimized material design UI
|
- Tablet-optimized material design UI
|
||||||
- Standard and Webtoon-optimized reader
|
- Standard and Webtoon-optimized reader
|
||||||
- Notifications about new chapters with updates feed
|
- Notifications about new chapters with updates feed
|
||||||
- Available in multiple languages
|
- Shikimori integration (manga tracking)
|
||||||
- Password protect access to the app
|
- Password/fingerprint protect access to the app
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
Kotatsu - приложения для чтения манги с открытым исходным кодом.<br>
|
Kotatsu - приложения для чтения манги с открытым исходным кодом.
|
||||||
|
|
||||||
|
|
||||||
**Основные возможности:**
|
**Основные возможности:**
|
||||||
|
|
||||||
- Онлайн каталоги с мангой
|
- Онлайн каталоги с мангой
|
||||||
@@ -9,3 +11,5 @@ Kotatsu - приложения для чтения манги с открыты
|
|||||||
- Интерфейс также оптимизирован для планшетов
|
- Интерфейс также оптимизирован для планшетов
|
||||||
- Поддержка манхвы (Webtoon)
|
- Поддержка манхвы (Webtoon)
|
||||||
- Уведомления о новых главах и лента обновлений
|
- Уведомления о новых главах и лента обновлений
|
||||||
|
- Интеграция с Shikimori
|
||||||
|
- Защита доступа в приложение паролем/отпечатком пальца
|
||||||
|
|||||||
Reference in New Issue
Block a user