Compare commits

...

13 Commits

Author SHA1 Message Date
Koitharu
2ce5cb524f Increase version 2021-11-18 18:43:30 +02:00
Koitharu
4cbc6392fb Update AdapterDelegates library 2021-11-17 19:23:08 +02:00
Koitharu
049f9fa625 Fix cover image in lists 2021-11-17 19:08:13 +02:00
Koitharu
c853fae820 Fix browser activity insets 2021-11-12 20:46:25 +02:00
Koitharu
dd1d84a4fe Remanga authorization support #73 2021-11-12 20:36:01 +02:00
Koitharu
1569aa5dd5 Cleanup data classes 2021-11-12 20:13:49 +02:00
Koitharu
51cd88eded Update dependencies 2021-11-12 20:13:48 +02:00
Aliaksiej Razumaŭ
bf386deef0 Translated using Weblate (Belarusian)
Currently translated at 100.0% (244 of 244 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
2021-11-12 20:13:26 +02:00
J. Lavoie
5c80cdee81 Translated using Weblate (Spanish)
Currently translated at 98.7% (241 of 244 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
2021-11-12 20:13:26 +02:00
J. Lavoie
b29fbb37cd Translated using Weblate (Finnish)
Currently translated at 100.0% (244 of 244 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
2021-11-12 20:13:26 +02:00
J. Lavoie
589831beef Translated using Weblate (French)
Currently translated at 100.0% (244 of 244 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
2021-11-12 20:13:26 +02:00
J. Lavoie
0f5d153543 Translated using Weblate (Italian)
Currently translated at 100.0% (244 of 244 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
2021-11-12 20:13:26 +02:00
J. Lavoie
ab1c99d132 Translated using Weblate (German)
Currently translated at 100.0% (244 of 244 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
2021-11-12 20:13:26 +02:00
77 changed files with 265 additions and 190 deletions

1
.idea/gradle.xml generated
View File

@@ -14,7 +14,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -13,8 +13,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 31
versionCode 370
versionName '2.0-b2'
versionCode 372
versionName '2.0'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -59,8 +59,6 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += [
'-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
'-Xopt-in=kotlinx.coroutines.FlowPreview',
'-Xopt-in=org.koin.core.component.KoinApiExtension'
]
}
}
@@ -69,14 +67,14 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.activity:activity-ktx:1.3.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-service:2.3.1'
implementation 'androidx.lifecycle:lifecycle-process:2.3.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-service:2.4.0'
implementation 'androidx.lifecycle:lifecycle-process:2.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
@@ -85,7 +83,7 @@ dependencies {
implementation 'androidx.work:work-runtime-ktx:2.7.0'
implementation 'com.google.android.material:material:1.4.0'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.3.1'
kapt 'androidx.lifecycle:lifecycle-compiler:2.4.0'
implementation 'androidx.room:room-runtime:2.3.0'
implementation 'androidx.room:room-ktx:2.3.0'
@@ -95,10 +93,10 @@ dependencies {
implementation 'com.squareup.okio:okio:2.10.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.1'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.1'
implementation 'io.insert-koin:koin-android:3.1.2'
implementation 'io.insert-koin:koin-android:3.1.3'
implementation 'io.coil-kt:coil-base:1.4.0'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.github.solkin:disk-lru-cache:1.3'
@@ -109,7 +107,7 @@ dependencies {
testImplementation 'com.google.truth:truth:1.1.3'
testImplementation 'org.json:json:20210307'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
testImplementation 'io.insert-koin:koin-test-junit4:3.1.2'
testImplementation 'io.insert-koin:koin-test-junit4:3.1.3'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'

View File

@@ -5,9 +5,7 @@
public static void checkReturnedValueIsNotNull(...);
public static void checkFieldIsNotNull(...);
public static void checkParameterIsNotNull(...);
public static void checkNotNullParameter(...);
}
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
-keepclassmembers public class * extends org.koitharu.kotatsu.core.parser.MangaRepository {
public <init>(...);
}
-dontwarn okhttp3.internal.platform.ConscryptPlatform

View File

@@ -5,7 +5,7 @@ import android.net.Uri
import android.os.Bundle
import org.koitharu.kotatsu.core.model.Manga
data class MangaIntent(
class MangaIntent(
val manga: Manga?,
val mangaId: Long,
val uri: Uri?

View File

@@ -1,26 +0,0 @@
package org.koitharu.kotatsu.base.ui.list
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import java.util.*
@Deprecated("")
class AdapterUpdater<T>(oldList: List<T>, newList: List<T>, getId: (T) -> Long) {
private val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
getId(oldList[oldItemPosition]) == getId(newList[newItemPosition])
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
Objects.equals(oldList[oldItemPosition], newList[newItemPosition])
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
})
operator fun invoke(adapter: RecyclerView.Adapter<*>) {
diff.dispatchUpdatesTo(adapter)
}
}

View File

@@ -96,11 +96,32 @@ class ChipsView @JvmOverloads constructor(
}
}
data class ChipModel(
class ChipModel(
@DrawableRes val icon: Int,
val title: CharSequence,
val data: Any? = null
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ChipModel
if (icon != other.icon) return false
if (title != other.title) return false
if (data != other.data) return false
return true
}
override fun hashCode(): Int {
var result = icon
result = 31 * result + title.hashCode()
result = 31 * result + data.hashCode()
return result
}
}
fun interface OnChipClickListener {

View File

@@ -6,7 +6,7 @@ import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.withStyledAttributes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.resolveAdjustedSize
import kotlin.math.roundToInt
class CoverImageView @JvmOverloads constructor(
@@ -17,47 +17,22 @@ class CoverImageView @JvmOverloads constructor(
init {
context.withStyledAttributes(attrs, R.styleable.CoverImageView, defStyleAttr) {
orientation = getInt(R.styleable.CoverImageView_android_orientation, HORIZONTAL)
orientation = getInt(R.styleable.CoverImageView_android_orientation, orientation)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val w: Int
val h: Int
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth: Int
val desiredHeight: Int
if (orientation == VERTICAL) {
val desiredHeight = (drawable?.intrinsicHeight?.coerceAtLeast(0) ?: 0) +
paddingTop + paddingBottom
h = resolveAdjustedSize(
desiredHeight.coerceAtLeast(suggestedMinimumHeight),
maxHeight,
heightMeasureSpec
)
val desiredWidth =
(h * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).toInt() + paddingLeft + paddingRight
w = resolveAdjustedSize(
desiredWidth.coerceAtLeast(suggestedMinimumWidth),
maxWidth,
widthMeasureSpec
)
desiredHeight = measuredHeight
desiredWidth = (desiredHeight * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).roundToInt()
} else {
val desiredWidth = (drawable?.intrinsicWidth?.coerceAtLeast(0) ?: 0) +
paddingLeft + paddingRight
w = resolveAdjustedSize(
desiredWidth.coerceAtLeast(suggestedMinimumWidth),
maxWidth,
widthMeasureSpec
)
val desiredHeight =
(w * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).toInt() + paddingTop + paddingBottom
h = resolveAdjustedSize(
desiredHeight.coerceAtLeast(suggestedMinimumHeight),
maxHeight,
heightMeasureSpec
)
desiredWidth = measuredWidth
desiredHeight = (desiredWidth * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).roundToInt()
}
val widthSize = resolveSizeAndState(w, widthMeasureSpec, 0)
val heightSize = resolveSizeAndState(h, heightMeasureSpec, 0)
setMeasuredDimension(widthSize, heightSize)
setMeasuredDimension(desiredWidth, desiredHeight)
}
companion object {

View File

@@ -92,8 +92,16 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.appbar.updatePadding(top = insets.top)
binding.webView.updatePadding(bottom = insets.bottom)
binding.appbar.updatePadding(
top = insets.top,
left = insets.left,
right = insets.right,
)
binding.root.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom,
)
}
companion object {

View File

@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.backup
import org.json.JSONArray
data class BackupEntry(
class BackupEntry(
val name: String,
val data: JSONArray
) {

View File

@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.core.model.MangaState
import org.koitharu.kotatsu.core.model.MangaTag
@Entity(tableName = "manga")
data class MangaEntity(
class MangaEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val id: Long,
@ColumnInfo(name = "title") val title: String,

View File

@@ -14,7 +14,7 @@ import androidx.room.PrimaryKey
onDelete = ForeignKey.CASCADE
)]
)
data class MangaPrefsEntity(
class MangaPrefsEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val mangaId: Long,
@ColumnInfo(name = "mode") val mode: Int

View File

@@ -20,7 +20,7 @@ import androidx.room.ForeignKey
)
]
)
data class MangaTagsEntity(
class MangaTagsEntity(
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
@ColumnInfo(name = "tag_id", index = true) val tagId: Long
)

View File

@@ -5,7 +5,7 @@ import androidx.room.Junction
import androidx.room.Relation
import org.koitharu.kotatsu.utils.ext.mapToSet
data class MangaWithTags(
class MangaWithTags(
@Embedded val manga: MangaEntity,
@Relation(
parentColumn = "manga_id",

View File

@@ -16,7 +16,7 @@ import androidx.room.PrimaryKey
)
]
)
data class SuggestionEntity(
class SuggestionEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
@ColumnInfo(name = "relevance") val relevance: Float,

View File

@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.utils.ext.longHashCode
@Entity(tableName = "tags")
data class TagEntity(
class TagEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "tag_id") val id: Long,
@ColumnInfo(name = "title") val title: String,

View File

@@ -15,7 +15,7 @@ import androidx.room.PrimaryKey
)
]
)
data class TrackEntity(
class TrackEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val mangaId: Long,
@ColumnInfo(name = "chapters_total") val totalChapters: Int,

View File

@@ -15,7 +15,7 @@ import androidx.room.PrimaryKey
)
]
)
data class TrackLogEntity(
class TrackLogEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") val id: Long = 0L,
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,

View File

@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.model.TrackingLogItem
import org.koitharu.kotatsu.utils.ext.mapToSet
import java.util.*
data class TrackLogWithManga(
class TrackLogWithManga(
@Embedded val trackLog: TrackLogEntity,
@Relation(
parentColumn = "manga_id",

View File

@@ -2,12 +2,12 @@ package org.koitharu.kotatsu.core.github
import java.util.*
data class VersionId(
class VersionId(
val major: Int,
val minor: Int,
val build: Int,
val variantType: String,
val variantNumber: Int
val variantNumber: Int,
) : Comparable<VersionId> {
override fun compareTo(other: VersionId): Int {
@@ -30,6 +30,30 @@ data class VersionId(
return variantNumber.compareTo(other.variantNumber)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as VersionId
if (major != other.major) return false
if (minor != other.minor) return false
if (build != other.build) return false
if (variantType != other.variantType) return false
if (variantNumber != other.variantNumber) return false
return true
}
override fun hashCode(): Int {
var result = major
result = 31 * result + minor
result = 31 * result + build
result = 31 * result + variantType.hashCode()
result = 31 * result + variantNumber
return result
}
companion object {
private fun variantWeight(variantType: String) =

View File

@@ -9,8 +9,13 @@ data class MangaChapter(
val name: String,
val number: Int,
val url: String,
val scanlator: String? = null,
val scanlator: String?,
val uploadDate: Long,
val branch: String? = null,
val source: MangaSource
) : Parcelable
val branch: String?,
val source: MangaSource,
) : Parcelable, Comparable<MangaChapter> {
override fun compareTo(other: MangaChapter): Int {
return number.compareTo(other.number)
}
}

View File

@@ -10,5 +10,5 @@ data class MangaHistory(
val updatedAt: Date,
val chapterId: Long,
val page: Int,
val scroll: Int
val scroll: Int,
) : Parcelable

View File

@@ -8,6 +8,6 @@ data class MangaPage(
val id: Long,
val url: String,
val referer: String,
val preview: String? = null,
val source: MangaSource
val preview: String?,
val source: MangaSource,
) : Parcelable

View File

@@ -14,7 +14,7 @@ import org.koitharu.kotatsu.local.domain.LocalMangaRepository
enum class MangaSource(
val title: String,
val locale: String?,
val cls: Class<out MangaRepository>
val cls: Class<out MangaRepository>,
) : Parcelable {
LOCAL("Local", null, LocalMangaRepository::class.java),
READMANGA_RU("ReadManga", "ru", ReadmangaRepository::class.java),

View File

@@ -7,5 +7,5 @@ import kotlinx.parcelize.Parcelize
data class MangaTag(
val title: String,
val key: String,
val source: MangaSource
val source: MangaSource,
) : Parcelable

View File

@@ -87,8 +87,10 @@ class AnibelRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
name = "Глава " + a.selectFirst("a")?.text().orEmpty(),
number = i + 1,
url = href,
scanlator = null,
branch = null,
uploadDate = 0L,
source = source
source = source,
)
}
)
@@ -115,8 +117,9 @@ class AnibelRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
MangaPage(
id = generateUid(url),
url = url,
preview = null,
referer = fullUrl,
source = source
source = source,
)
}
}

View File

@@ -88,8 +88,10 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
name = tr.selectFirst("a")?.text().orEmpty(),
number = i + 1,
url = href,
scanlator = null,
branch = null,
uploadDate = dateFormat.tryParse(tr.selectFirst("div.date")?.text()),
source = source
source = source,
)
}
)
@@ -117,8 +119,9 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
MangaPage(
id = generateUid(url),
url = url,
preview = null,
referer = fullUrl,
source = source
source = source,
)
}
}

View File

@@ -101,7 +101,9 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
url = "$baseChapterUrl$chid",
uploadDate = it.getLong("date") * 1000,
name = if (title.isEmpty()) volChap else "$volChap: $title",
number = totalChapters - i
number = totalChapters - i,
scanlator = null,
branch = null,
)
}.reversed()
)
@@ -116,8 +118,9 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
MangaPage(
id = generateUid(jo.getLong("id")),
referer = fullUrl,
preview = null,
source = chapter.source,
url = jo.getString("img")
url = jo.getString("img"),
)
}
}

View File

@@ -143,6 +143,8 @@ class ExHentaiRepository(
url = url,
uploadDate = 0L,
source = source,
scanlator = null,
branch = null,
)
}
chapters

View File

@@ -142,7 +142,8 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
url = href,
uploadDate = dateFormat.tryParse(tr.selectFirst("td.d-none")?.text()),
scanlator = translators,
source = source
source = source,
branch = null,
)
}
)
@@ -167,8 +168,9 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
MangaPage(
id = generateUid(url),
url = url,
preview = null,
referer = chapter.url,
source = source
source = source,
)
}
}

View File

@@ -50,7 +50,9 @@ class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(load
source = source,
number = 1,
uploadDate = 0L,
name = manga.title
name = manga.title,
scanlator = null,
branch = null,
)
)
)

View File

@@ -118,7 +118,8 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
item.getString("chapter_created_at").substringBefore(" ")
),
scanlator = scanlator,
name = if (nameChapter.isNullOrBlank()) fullNameChapter else "$fullNameChapter - $nameChapter"
branch = null,
name = if (nameChapter.isNullOrBlank()) fullNameChapter else "$fullNameChapter - $nameChapter",
)
)
}
@@ -178,8 +179,9 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
MangaPage(
id = generateUid(pageUrl),
url = pageUrl,
preview = null,
referer = fullUrl,
source = source
source = source,
)
}
}

View File

@@ -101,8 +101,10 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
name = a.select("label").text(),
number = i + 1,
url = href,
scanlator = null,
branch = null,
uploadDate = dateFormat.tryParse(li.selectFirst("small:last-of-type")?.text()),
source = MangaSource.MANGAOWL
source = MangaSource.MANGAOWL,
)
}
)
@@ -117,8 +119,9 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
MangaPage(
id = generateUid(url),
url = url,
preview = null,
referer = fullUrl,
source = MangaSource.MANGAOWL
source = MangaSource.MANGAOWL,
)
}
}

View File

@@ -124,7 +124,9 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
dateFormat,
li.selectFirst("span.time")?.text()
),
name = name.ifEmpty { "${manga.title} - ${i + 1}" }
name = name.ifEmpty { "${manga.title} - ${i + 1}" },
scanlator = null,
branch = null,
)
}
)
@@ -143,8 +145,9 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
MangaPage(
id = generateUid(href),
url = href,
preview = null,
referer = fullUrl,
source = MangaSource.MANGATOWN
source = MangaSource.MANGATOWN,
)
} ?: parseFailed("Pages list not found")
}

View File

@@ -146,7 +146,9 @@ class MangareadRepository(
dateFormat,
doc2.selectFirst("span.chapter-release-date i")?.text()
),
source = MangaSource.MANGAREAD
source = MangaSource.MANGAREAD,
scanlator = null,
branch = null,
)
}
)
@@ -164,8 +166,9 @@ class MangareadRepository(
MangaPage(
id = generateUid(url),
url = url,
preview = null,
referer = fullUrl,
source = MangaSource.MANGAREAD
source = MangaSource.MANGAREAD,
)
}
}

View File

@@ -114,6 +114,8 @@ abstract class NineMangaRepository(
url = href,
uploadDate = parseChapterDateByLang(li.selectFirst("span")?.text().orEmpty()),
source = source,
scanlator = null,
branch = null,
)
}
)

View File

@@ -6,16 +6,20 @@ import org.json.JSONObject
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
import java.text.SimpleDateFormat
import java.util.*
class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) {
class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext),
MangaRepositoryAuthProvider {
override val source = MangaSource.REMANGA
override val defaultDomain = "remanga.org"
override val authUrl: String
get() = "https://${getDomain()}/user/login"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
@@ -30,6 +34,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
tags: Set<MangaTag>?,
sortOrder: SortOrder?
): List<Manga> {
copyCookies()
val domain = getDomain()
val urlBuilder = StringBuilder()
.append("https://api.")
@@ -78,6 +83,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
override suspend fun getDetails(manga: Manga): Manga {
copyCookies()
val domain = getDomain()
val slug = manga.url.find(LAST_URL_PATH_REGEX)
?: throw ParseException("Cannot obtain slug from ${manga.url}")
@@ -130,7 +136,8 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
},
uploadDate = dateFormat.tryParse(jo.getString("upload_date")),
scanlator = publishers.optJSONObject(0)?.getStringOrNull("name"),
source = MangaSource.REMANGA
source = MangaSource.REMANGA,
branch = null,
)
}.asReversed()
)
@@ -164,6 +171,17 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
}
override fun isAuthorized(): Boolean {
return loaderContext.cookieJar.getCookies(getDomain()).any {
it.name == "user"
}
}
private fun copyCookies() {
val domain = getDomain()
loaderContext.cookieJar.copyCookies(domain, "api.$domain")
}
private fun getSortKey(order: SortOrder?) = when (order) {
SortOrder.UPDATED -> "-chapter_date"
SortOrder.POPULARITY -> "-rating"
@@ -175,8 +193,9 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
private fun parsePage(jo: JSONObject, referer: String) = MangaPage(
id = generateUid(jo.getLong("id")),
url = jo.getString("link"),
preview = null,
referer = referer,
source = source
source = source,
)
private companion object {

View File

@@ -30,7 +30,9 @@ class YaoiChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loa
number = i + 1,
url = href,
uploadDate = 0L,
source = source
source = source,
scanlator = null,
branch = null,
)
}
)

View File

@@ -14,16 +14,35 @@ sealed class DateTimeAgo : ListModel {
}
}
data class MinutesAgo(val minutes: Int) : DateTimeAgo() {
class MinutesAgo(val minutes: Int) : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getQuantityString(R.plurals.minutes_ago, minutes, minutes)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MinutesAgo
return minutes == other.minutes
}
override fun hashCode(): Int = minutes
}
data class HoursAgo(val hours: Int) : DateTimeAgo() {
class HoursAgo(val hours: Int) : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getQuantityString(R.plurals.hours_ago, hours, hours)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HoursAgo
return hours == other.hours
}
override fun hashCode(): Int = hours
}
object Today : DateTimeAgo() {
@@ -38,10 +57,19 @@ sealed class DateTimeAgo : ListModel {
}
}
data class DaysAgo(val days: Int) : DateTimeAgo() {
class DaysAgo(val days: Int) : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getQuantityString(R.plurals.days_ago, days, days)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DaysAgo
return days == other.days
}
override fun hashCode(): Int = days
}
object LongAgo : DateTimeAgo() {

View File

@@ -44,7 +44,7 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
TabLayoutMediator.TabConfigurationStrategy {
private val viewModel by viewModel<DetailsViewModel>(mode = LazyThreadSafetyMode.NONE) {
private val viewModel by viewModel<DetailsViewModel> {
parametersOf(MangaIntent.from(intent))
}

View File

@@ -19,10 +19,6 @@ class ChaptersAdapter(
return items[position].chapter.id
}
fun setItems(newItems: List<ChapterListItem>, callback: Runnable) {
differ.submitList(newItems, callback)
}
private class DiffCallback : DiffUtil.ItemCallback<ChapterListItem>() {
override fun areItemsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean {

View File

@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.model.SortOrder
import java.util.*
@Entity(tableName = "favourite_categories")
data class FavouriteCategoryEntity(
class FavouriteCategoryEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "category_id") val categoryId: Int,
@ColumnInfo(name = "created_at") val createdAt: Long,

View File

@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
)
]
)
data class FavouriteEntity(
class FavouriteEntity(
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
@ColumnInfo(name = "category_id", index = true) val categoryId: Long,
@ColumnInfo(name = "created_at") val createdAt: Long

View File

@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
data class FavouriteManga(
class FavouriteManga(
@Embedded val favourite: FavouriteEntity,
@Relation(
parentColumn = "manga_id",

View File

@@ -30,9 +30,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
override val recycledViewPool = RecyclerView.RecycledViewPool()
private val viewModel by viewModel<FavouritesCategoriesViewModel>(
mode = LazyThreadSafetyMode.NONE
)
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
CategoriesEditDelegate(requireContext(), this)
}

View File

@@ -30,9 +30,7 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<FavouriteCategory>,
View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback {
private val viewModel by viewModel<FavouritesCategoriesViewModel>(
mode = LazyThreadSafetyMode.NONE
)
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private lateinit var adapter: CategoriesAdapter
private lateinit var reorderHelper: ItemTouchHelper

View File

@@ -25,7 +25,7 @@ class FavouriteCategoriesDialog : BaseBottomSheet<DialogFavoriteCategoriesBindin
OnListItemClickListener<MangaCategoryItem>, CategoriesEditDelegate.CategoriesEditCallback,
View.OnClickListener {
private val viewModel by viewModel<MangaCategoriesViewModel>(mode = LazyThreadSafetyMode.NONE) {
private val viewModel by viewModel<MangaCategoriesViewModel> {
parametersOf(requireNotNull(arguments?.getParcelable<Manga>(MangaIntent.KEY_MANGA)))
}
@@ -36,7 +36,7 @@ class FavouriteCategoriesDialog : BaseBottomSheet<DialogFavoriteCategoriesBindin
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
container: ViewGroup?,
) = DialogFavoriteCategoriesBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -12,7 +12,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class FavouritesListFragment : MangaListFragment() {
override val viewModel by viewModel<FavouritesListViewModel>(mode = LazyThreadSafetyMode.NONE) {
override val viewModel by viewModel<FavouritesListViewModel> {
parametersOf(categoryId)
}

View File

@@ -18,14 +18,14 @@ import java.util.*
)
]
)
data class HistoryEntity(
class HistoryEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val mangaId: Long,
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(),
@ColumnInfo(name = "updated_at") val updatedAt: Long,
@ColumnInfo(name = "chapter_id") val chapterId: Long,
@ColumnInfo(name = "page") val page: Int,
@ColumnInfo(name = "scroll") val scroll: Float
@ColumnInfo(name = "scroll") val scroll: Float,
) {
fun toMangaHistory() = MangaHistory(

View File

@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
data class HistoryWithManga(
class HistoryWithManga(
@Embedded val history: HistoryEntity,
@Relation(
parentColumn = "manga_id",

View File

@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.ext.ellipsize
class HistoryListFragment : MangaListFragment() {
override val viewModel by viewModel<HistoryListViewModel>(mode = LazyThreadSafetyMode.NONE)
override val viewModel by viewModel<HistoryListViewModel>()
override val isSwipeRefreshEnabled = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -43,10 +43,6 @@ class MangaListAdapter(
.addDelegate(ITEM_TYPE_FILTER, currentFilterAD(onTagRemoveClick))
}
fun setItems(list: List<ListModel>, commitCallback: Runnable) {
differ.submitList(list, commitCallback)
}
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel) = when {

View File

@@ -69,8 +69,9 @@ class LocalMangaRepository(private val context: Context) : MangaRepository {
MangaPage(
id = entryUri.longHashCode(),
url = entryUri,
preview = null,
referer = chapter.url,
source = MangaSource.LOCAL
source = MangaSource.LOCAL,
)
}
}
@@ -124,7 +125,9 @@ class LocalMangaRepository(private val context: Context) : MangaRepository {
number = i + 1,
source = MangaSource.LOCAL,
uploadDate = 0L,
url = uriBuilder.fragment(s).build().toString()
url = uriBuilder.fragment(s).build().toString(),
scanlator = null,
branch = null,
)
}
)

View File

@@ -19,9 +19,9 @@ import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.utils.ext.ellipsize
class LocalListFragment : MangaListFragment(), ActivityResultCallback<Uri> {
class LocalListFragment : MangaListFragment(), ActivityResultCallback<Uri?> {
override val viewModel by viewModel<LocalListViewModel>(mode = LazyThreadSafetyMode.NONE)
override val viewModel by viewModel<LocalListViewModel>()
private val importCall = registerForActivityResult(
ActivityResultContracts.OpenDocument(),
this

View File

@@ -57,10 +57,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
NavigationView.OnNavigationItemSelectedListener, AppBarOwner,
View.OnClickListener, View.OnFocusChangeListener, SearchSuggestionListener {
private val viewModel by viewModel<MainViewModel>(mode = LazyThreadSafetyMode.NONE)
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>(
mode = LazyThreadSafetyMode.NONE
)
private val viewModel by viewModel<MainViewModel>()
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
private lateinit var navHeaderBinding: NavigationHeaderBinding
private lateinit var drawerToggle: ActionBarDrawerToggle

View File

@@ -19,7 +19,7 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class ProtectActivity : BaseActivity<ActivityProtectBinding>(), TextView.OnEditorActionListener,
TextWatcher, View.OnClickListener {
private val viewModel by viewModel<ProtectViewModel>(mode = LazyThreadSafetyMode.NONE)
private val viewModel by viewModel<ProtectViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -55,7 +55,7 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback,
ActivityResultCallback<Boolean>, ReaderControlDelegate.OnInteractionListener {
private val viewModel by viewModel<ReaderViewModel>(mode = LazyThreadSafetyMode.NONE) {
private val viewModel by viewModel<ReaderViewModel> {
parametersOf(MangaIntent.from(intent), intent?.getParcelableExtra<ReaderState>(EXTRA_STATE))
}

View File

@@ -47,10 +47,6 @@ abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
viewType: Int
): H = onCreateViewHolder(parent, loader, settings, exceptionResolver)
fun setItems(items: List<ReaderPage>, callback: Runnable) {
differ.submitList(items, callback)
}
suspend fun setItems(items: List<ReaderPage>) = suspendCoroutine<Unit> { cont ->
differ.submitList(items) {
cont.resume(Unit)

View File

@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class RemoteListFragment : MangaListFragment() {
override val viewModel by viewModel<RemoteListViewModel>(mode = LazyThreadSafetyMode.NONE) {
override val viewModel by viewModel<RemoteListViewModel> {
parametersOf(source)
}

View File

@@ -18,9 +18,7 @@ import org.koitharu.kotatsu.utils.ext.showKeyboard
class SearchActivity : BaseActivity<ActivitySearchBinding>(), SearchView.OnQueryTextListener {
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>(
mode = LazyThreadSafetyMode.NONE
)
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
private lateinit var source: MangaSource
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class SearchFragment : MangaListFragment() {
override val viewModel by viewModel<SearchViewModel>(mode = LazyThreadSafetyMode.NONE) {
override val viewModel by viewModel<SearchViewModel> {
parametersOf(source, query)
}

View File

@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class GlobalSearchFragment : MangaListFragment() {
override val viewModel by viewModel<GlobalSearchViewModel>(mode = LazyThreadSafetyMode.NONE) {
override val viewModel by viewModel<GlobalSearchViewModel> {
parametersOf(query)
}

View File

@@ -21,7 +21,7 @@ import java.io.FileOutputStream
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
private val viewModel by viewModel<BackupViewModel>(mode = LazyThreadSafetyMode.NONE)
private val viewModel by viewModel<BackupViewModel>()
private var backup: File? = null
private val saveFileContract =

View File

@@ -13,7 +13,7 @@ import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.prefs.AppSettings
class BackupSettingsFragment : BasePreferenceFragment(R.string.backup_restore),
ActivityResultCallback<Uri> {
ActivityResultCallback<Uri?> {
private val backupSelectCall = registerForActivityResult(
ActivityResultContracts.OpenDocument(),

View File

@@ -22,10 +22,10 @@ class RestoreDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
container: ViewGroup?,
) = DialogProgressBinding.inflate(inflater, container, false)
private val viewModel by viewModel<RestoreViewModel>(mode = LazyThreadSafetyMode.NONE) {
private val viewModel by viewModel<RestoreViewModel> {
parametersOf(arguments?.getString(ARG_FILE)?.toUriOrNull())
}

View File

@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class OnboardDialogFragment : AlertDialogFragment<DialogOnboardBinding>(),
OnListItemClickListener<SourceLocale>, DialogInterface.OnClickListener {
private val viewModel by viewModel<OnboardViewModel>(mode = LazyThreadSafetyMode.NONE)
private val viewModel by viewModel<OnboardViewModel>()
private var isWelcome: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding
class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWatcher,
View.OnClickListener, TextView.OnEditorActionListener {
private val viewModel by viewModel<ProtectSetupViewModel>(mode = LazyThreadSafetyMode.NONE)
private val viewModel by viewModel<ProtectSetupViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -26,7 +26,7 @@ import org.koitharu.kotatsu.utils.progress.Progress
class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListener.Callback,
OnListItemClickListener<Manga> {
private val viewModel by viewModel<FeedViewModel>(mode = LazyThreadSafetyMode.NONE)
private val viewModel by viewModel<FeedViewModel>()
private var feedAdapter: FeedAdapter? = null
private var updateStatusSnackbar: Snackbar? = null

View File

@@ -122,7 +122,7 @@ fun ByteArray.byte2HexFormatted(): String {
if (l > 2) {
h = h.substring(l - 2, l)
}
str.append(h.toUpperCase(Locale.ROOT))
str.append(h.uppercase(Locale.ROOT))
if (i < size - 1) {
str.append(':')
}

View File

@@ -27,9 +27,10 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(), OnListItemClickListener<CategoryItem> {
class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<CategoryItem> {
private val viewModel by viewModel<ShelfConfigViewModel>(mode = LazyThreadSafetyMode.NONE)
private val viewModel by viewModel<ShelfConfigViewModel>()
private lateinit var adapter: CategorySelectAdapter
private lateinit var config: AppWidgetConfig

View File

@@ -11,15 +11,15 @@
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_margin="8dp"
app:cardCornerRadius="4dp"
app:cardElevation="4dp">
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
android:id="@+id/imageView_cover"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />

View File

@@ -10,7 +10,7 @@
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_margin="8dp"
app:cardElevation="4dp">

View File

@@ -242,4 +242,6 @@
<string name="genres">Жанры</string>
<string name="state_finished">Завершана</string>
<string name="state_ongoing">Ангоінг</string>
<string name="date_format">Фармат даты</string>
<string name="system_default">Па змаўчанні</string>
</resources>

View File

@@ -242,4 +242,6 @@
<string name="auth_complete">Autorisierung abgeschlossen</string>
<string name="state_finished">Beendet</string>
<string name="state_ongoing">Fortlaufend</string>
<string name="date_format">Datumsformat</string>
<string name="system_default">Standard</string>
</resources>

View File

@@ -239,4 +239,6 @@
<string name="chapter_is_missing_text">Este capítulo no aparece en su dispositivo. Descárguelo o léalo en línea.</string>
<string name="auth_complete">Autorización completa</string>
<string name="about_feedback_4pda">Tema sobre 4PDA</string>
<string name="date_format">Formato de la fecha</string>
<string name="system_default">Por defecto</string>
</resources>

View File

@@ -242,4 +242,6 @@
<string name="enter_password">Syötä salasana</string>
<string name="state_finished">Päättynyt</string>
<string name="state_ongoing">Jatkuva</string>
<string name="date_format">Päivämäärän muoto</string>
<string name="system_default">Oletus</string>
</resources>

View File

@@ -242,4 +242,6 @@
<string name="auth_complete">Autorisation complète</string>
<string name="state_finished">Terminé</string>
<string name="state_ongoing">En cours</string>
<string name="date_format">Format de la date</string>
<string name="system_default">Par défaut</string>
</resources>

View File

@@ -242,4 +242,6 @@
<string name="auth_complete">Autorizzazione completa</string>
<string name="state_finished">Finito</string>
<string name="state_ongoing">In corso</string>
<string name="system_default">Predefinito</string>
<string name="date_format">Formato della data</string>
</resources>