Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0153e90bf0 | ||
|
|
d4f8fe83f5 | ||
|
|
d28b1e4094 | ||
|
|
cd2de0136a | ||
|
|
15e99c03a9 | ||
|
|
b425f3e779 | ||
|
|
c6a51d4d08 | ||
|
|
503bff292c | ||
|
|
0aa78c0d7e | ||
|
|
8e1d02f356 | ||
|
|
1e90d5541b | ||
|
|
04c7ca7291 | ||
|
|
8d52cab6d8 | ||
|
|
efa13df106 | ||
|
|
8bc29ac331 | ||
|
|
7991f9ca97 | ||
|
|
eb1eee1681 | ||
|
|
b3f748c000 | ||
|
|
58a9f7b25a |
114
.github/workflows/auto_release.yml
vendored
Normal file
114
.github/workflows/auto_release.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Build automatic release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
should_build: ${{ steps.check-updates.outputs.has_changes }}
|
||||
steps:
|
||||
- name: Check for updates 🌏
|
||||
id: check-updates
|
||||
run: |
|
||||
last_run=$(curl -s "https://api.github.com/repos/${{ github.repository }}/releases/latest" | jq -r '.created_at')
|
||||
kotatsu_updated=$(curl -s "https://api.github.com/repos/KotatsuApp/Kotatsu/commits?since=$last_run" | jq '. | length')
|
||||
parsers_updated=$(curl -s "https://api.github.com/repos/KotatsuApp/kotatsu-parsers/commits?since=$last_run" | jq '. | length')
|
||||
if [ "$kotatsu_updated" -gt "0" ] || [ "$parsers_updated" -gt "0" ]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: check
|
||||
if: needs.check.outputs.should_build == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
new_tag: ${{ steps.tagger.outputs.new_tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: autobuild
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Setup Android SDK 💻
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Grant permissions 💻
|
||||
run: chmod a+x gradlew
|
||||
|
||||
- name: Generate build number 📆
|
||||
id: tagger
|
||||
run: |
|
||||
echo "new_tag=$(./gradlew -q versionInfo -DbuildNumberIncrement=true)" >> $GITHUB_OUTPUT
|
||||
echo "formatted_date=$(date +'%Y/%m/%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Decode Keystore
|
||||
id: decode_keystore
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: 'keystore/kotatsu.jks'
|
||||
encodedString: ${{ secrets.ANDROID_SIGNING_KEY }}
|
||||
|
||||
- name: Building new APK 💻
|
||||
run: >-
|
||||
./gradlew assembleRelease
|
||||
-DparsersVersionOverride=$(curl -s https://api.github.com/repos/kotatsuapp/kotatsu-parsers/commits/master -H "Accept: application/vnd.github.sha" | cut -c -10)
|
||||
|
||||
- name: Prepare to Upload 🌏
|
||||
run: |
|
||||
mv ${{steps.sign_app.outputs.signedFile}} app/build/outputs/apk/release/release.apk
|
||||
echo "SIGNED_APK=app/build/outputs/apk/release/release.apk" >> $GITHUB_ENV
|
||||
|
||||
- name: Get latest changes 📑
|
||||
id: changelog
|
||||
run: |
|
||||
CHANGELOG=$(cat CHANGELOG.txt)
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create new GH Release + Uploading 🌏
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ steps.tagger.outputs.new_tag }}
|
||||
name: "Build ${{ steps.tagger.outputs.new_tag }}"
|
||||
body: |
|
||||
Automated build generated on ${{ steps.tagger.outputs.formatted_date }}
|
||||
|
||||
${{ steps.changelog.outputs.content }}
|
||||
files: ${{ env.SIGNED_APK }}
|
||||
prerelease: false
|
||||
|
||||
update:
|
||||
needs: build
|
||||
if: needs.check.outputs.should_build == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: autobuild
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Commit 🌏
|
||||
run: |
|
||||
git config --local user.email "autorelease@users.noreply.github.com"
|
||||
git config --local user.name "autorelease"
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
git add README.md
|
||||
git commit -m "Automatic release v${{ needs.build.outputs.new_tag }}"
|
||||
git push origin autobuild
|
||||
else
|
||||
echo "No changes to push!"
|
||||
fi
|
||||
@@ -9,17 +9,24 @@ plugins {
|
||||
id 'dagger.hilt.android.plugin'
|
||||
}
|
||||
|
||||
def Properties versionProps = getVersionProps()
|
||||
|
||||
android {
|
||||
compileSdk = 35
|
||||
buildToolsVersion = '35.0.0'
|
||||
namespace = 'org.koitharu.kotatsu'
|
||||
|
||||
defaultConfig {
|
||||
def code = versionProps['code'].toInteger()
|
||||
def base = versionProps['base'].trim()
|
||||
def build = versionProps['build'].toInteger()
|
||||
def variant = versionProps['variant'].trim()
|
||||
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 697
|
||||
versionName = '7.7.5'
|
||||
versionCode = code * 1000 + build
|
||||
versionName = base + (build == 0 ? '' : '.' + build) + (variant == '' ? '' : '-') + variant
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
@@ -30,6 +37,22 @@ android {
|
||||
generateLocaleConfig true
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
def tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
|
||||
def allFilesFromDir = new File(tmpFilePath).listFiles()
|
||||
|
||||
if (allFilesFromDir != null) {
|
||||
def keystoreFile = allFilesFromDir.first()
|
||||
keystoreFile.renameTo("keystore/kotatsu.jks")
|
||||
}
|
||||
|
||||
storeFile = file("keystore/kotatsu.jks")
|
||||
storePassword System.getenv("SIGNING_STORE_PASSWORD")
|
||||
keyAlias System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = '.debug'
|
||||
@@ -38,6 +61,7 @@ android {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
nightly {
|
||||
initWith release
|
||||
@@ -194,3 +218,22 @@ dependencies {
|
||||
androidTestImplementation libs.hilt.android.testing
|
||||
kaptAndroidTest libs.hilt.android.compiler
|
||||
}
|
||||
|
||||
tasks.register('versionInfo') {
|
||||
def base = versionProps['base'].trim()
|
||||
def build = versionProps['build'].toInteger()
|
||||
def variant = versionProps['variant'].trim()
|
||||
println base + (build == 0 ? '' : '.' + build) + (variant == '' ? '' : '-') + variant
|
||||
}
|
||||
|
||||
def getVersionProps() {
|
||||
def versionPropsFile = file('version.properties')
|
||||
def Properties versionProps = new Properties()
|
||||
versionProps.load(new FileInputStream(versionPropsFile))
|
||||
if (System.getProperty('buildNumberIncrement') == 'true') {
|
||||
def code = versionProps['build'].toInteger() + 1
|
||||
versionProps['build'] = code.toString()
|
||||
versionProps.store(versionPropsFile.newWriter(), null)
|
||||
}
|
||||
return versionProps
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
android:allowBackup="true"
|
||||
android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:enableOnBackInvokedCallback="@bool/is_predictive_back_enabled"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:fullBackupOnly="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -209,6 +209,7 @@
|
||||
<activity android:name="org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.sync.ui.SyncAuthActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/sync" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity"
|
||||
@@ -279,6 +280,10 @@
|
||||
<service
|
||||
android:name="org.koitharu.kotatsu.local.ui.LocalIndexUpdateService"
|
||||
android:label="@string/local_manga_processing" />
|
||||
<service
|
||||
android:name="org.koitharu.kotatsu.local.ui.ImportService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:label="@string/importing_manga" />
|
||||
<service
|
||||
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetService"
|
||||
android:label="@string/manga_shelf"
|
||||
|
||||
@@ -8,13 +8,13 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.almostEquals
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.almostEquals
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.db.entity
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.util.ext.longHashCode
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.longHashCode
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
|
||||
@@ -49,7 +49,7 @@ fun Manga.toEntity() = MangaEntity(
|
||||
publicUrl = publicUrl,
|
||||
source = source.name,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
coverUrl = coverUrl,
|
||||
coverUrl = coverUrl.orEmpty(),
|
||||
altTitle = altTitle,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
|
||||
@@ -14,7 +14,7 @@ data class MangaEntity(
|
||||
@ColumnInfo(name = "url") val url: String,
|
||||
@ColumnInfo(name = "public_url") val publicUrl: String,
|
||||
@ColumnInfo(name = "rating") val rating: Float, // normalized value [0..1] or -1
|
||||
@ColumnInfo(name = "nsfw") val isNsfw: Boolean,
|
||||
@ColumnInfo(name = "nsfw") val isNsfw: Boolean, // TODO change to contentRating
|
||||
@ColumnInfo(name = "cover_url") val coverUrl: String,
|
||||
@ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?,
|
||||
@ColumnInfo(name = "state") val state: String?,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
class CaughtException(cause: Throwable) : RuntimeException("${cause.javaClass.simpleName}(${cause.message})", cause)
|
||||
class CaughtException(
|
||||
override val cause: Throwable
|
||||
) : RuntimeException("${cause.javaClass.simpleName}(${cause.message})", cause)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
import okio.IOException
|
||||
|
||||
class WrapperIOException(override val cause: Exception) : IOException(cause)
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.koitharu.kotatsu.core.github
|
||||
|
||||
import org.koitharu.kotatsu.core.util.ext.digits
|
||||
import org.koitharu.kotatsu.parsers.util.digits
|
||||
import java.util.Locale
|
||||
|
||||
data class VersionId(
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.core.model.parcelable
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.core.os.ParcelCompat
|
||||
import kotlinx.parcelize.Parceler
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
@@ -13,6 +12,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@Parcelize
|
||||
data class ParcelableManga(
|
||||
val manga: Manga,
|
||||
private val withDescription: Boolean = true,
|
||||
) : Parcelable {
|
||||
|
||||
companion object : Parceler<ParcelableManga> {
|
||||
@@ -24,10 +24,10 @@ data class ParcelableManga(
|
||||
parcel.writeString(url)
|
||||
parcel.writeString(publicUrl)
|
||||
parcel.writeFloat(rating)
|
||||
ParcelCompat.writeBoolean(parcel, isNsfw)
|
||||
parcel.writeSerializable(contentRating)
|
||||
parcel.writeString(coverUrl)
|
||||
parcel.writeString(largeCoverUrl)
|
||||
parcel.writeString(description)
|
||||
parcel.writeString(description.takeIf { withDescription })
|
||||
parcel.writeParcelable(ParcelableMangaTags(tags), flags)
|
||||
parcel.writeSerializable(state)
|
||||
parcel.writeString(author)
|
||||
@@ -42,8 +42,8 @@ data class ParcelableManga(
|
||||
url = requireNotNull(parcel.readString()),
|
||||
publicUrl = requireNotNull(parcel.readString()),
|
||||
rating = parcel.readFloat(),
|
||||
isNsfw = ParcelCompat.readBoolean(parcel),
|
||||
coverUrl = requireNotNull(parcel.readString()),
|
||||
contentRating = parcel.readSerializableCompat(),
|
||||
coverUrl = parcel.readString(),
|
||||
largeCoverUrl = parcel.readString(),
|
||||
description = parcel.readString(),
|
||||
tags = requireNotNull(parcel.readParcelableCompat<ParcelableMangaTags>()).tags,
|
||||
@@ -52,6 +52,7 @@ data class ParcelableManga(
|
||||
chapters = null,
|
||||
source = MangaSource(parcel.readString()),
|
||||
),
|
||||
withDescription = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class AppProxySelector(
|
||||
if (type == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY
|
||||
}
|
||||
if (address.isNullOrEmpty() || port == 0) {
|
||||
if (address.isNullOrEmpty() || port < 0 || port > 0xFFFF) {
|
||||
throw ProxyConfigException()
|
||||
}
|
||||
cachedProxy?.let {
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrapperIOException
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders.CONTENT_ENCODING
|
||||
|
||||
class GZipInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val newRequest = chain.request().newBuilder()
|
||||
newRequest.addHeader(CONTENT_ENCODING, "gzip")
|
||||
return try {
|
||||
override fun intercept(chain: Interceptor.Chain): Response = try {
|
||||
val request = chain.request()
|
||||
if (request.body is MultipartBody) {
|
||||
chain.proceed(request)
|
||||
} else {
|
||||
val newRequest = request.newBuilder()
|
||||
newRequest.addHeader(CONTENT_ENCODING, "gzip")
|
||||
chain.proceed(newRequest.build())
|
||||
} catch (e: NullPointerException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw WrapperIOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import androidx.annotation.WorkerThread
|
||||
import androidx.core.util.Predicate
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl
|
||||
import org.koitharu.kotatsu.core.util.ext.newBuilder
|
||||
import org.koitharu.kotatsu.parsers.util.newBuilder
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import org.jsoup.HttpStatusException
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
|
||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||
import org.koitharu.kotatsu.core.util.ext.isHttpOrHttps
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.parsers.util.isHttpOrHttps
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.net.HttpURLConnection
|
||||
import java.util.Collections
|
||||
|
||||
@@ -6,13 +6,13 @@ import dagger.Reusable
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.model.UnknownMangaSource
|
||||
import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.almostEquals
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -7,8 +7,6 @@ import androidx.collection.ArraySet
|
||||
import androidx.core.net.toUri
|
||||
import org.jetbrains.annotations.Blocking
|
||||
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException
|
||||
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.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.Demographic
|
||||
@@ -22,6 +20,7 @@ import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.find
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.parsers.util.splitTwoParts
|
||||
import java.util.EnumSet
|
||||
@@ -82,7 +81,7 @@ class ExternalPluginContentSource(
|
||||
publicUrl = details.publicUrl.ifEmpty { manga.publicUrl },
|
||||
rating = maxOf(details.rating, manga.rating),
|
||||
isNsfw = details.isNsfw,
|
||||
coverUrl = details.coverUrl.ifEmpty { manga.coverUrl },
|
||||
coverUrl = details.coverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||
tags = details.tags + manga.tags,
|
||||
state = details.state ?: manga.state,
|
||||
author = details.author.ifNullOrEmpty { manga.author },
|
||||
|
||||
@@ -4,13 +4,13 @@ import android.content.Context
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import androidx.core.content.edit
|
||||
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
||||
import org.koitharu.kotatsu.core.util.ext.sanitizeHeaderValue
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.settings.utils.validation.DomainValidator
|
||||
|
||||
|
||||
@@ -4,21 +4,8 @@ import androidx.collection.ArrayMap
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.collection.LongSet
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import java.util.Collections
|
||||
import java.util.EnumSet
|
||||
|
||||
inline fun <T> MutableSet(size: Int, init: (index: Int) -> T): MutableSet<T> {
|
||||
val set = ArraySet<T>(size)
|
||||
repeat(size) { index -> set.add(init(index)) }
|
||||
return set
|
||||
}
|
||||
|
||||
inline fun <T> Set(size: Int, init: (index: Int) -> T): Set<T> = when (size) {
|
||||
0 -> emptySet()
|
||||
1 -> Collections.singleton(init(0))
|
||||
else -> MutableSet(size, init)
|
||||
}
|
||||
|
||||
fun <T> Collection<T>.asArrayList(): ArrayList<T> = if (this is ArrayList<*>) {
|
||||
this as ArrayList<T>
|
||||
} else {
|
||||
@@ -76,15 +63,6 @@ fun <T> Iterable<T>.sortedWithSafe(comparator: Comparator<in T>): List<T> = try
|
||||
}
|
||||
}
|
||||
|
||||
fun Collection<*>?.sizeOrZero() = this?.size ?: 0
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R): Array<R> {
|
||||
val result = arrayOfNulls<R>(size)
|
||||
forEachIndexed { index, t -> result[index] = transform(t) }
|
||||
return result as Array<R>
|
||||
}
|
||||
|
||||
fun LongSet.toLongArray(): LongArray {
|
||||
val result = LongArray(size)
|
||||
var i = 0
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
@@ -27,12 +25,6 @@ fun Response.parseJsonOrNull(): JSONObject? {
|
||||
}
|
||||
}
|
||||
|
||||
val HttpUrl.isHttpOrHttps: Boolean
|
||||
get() {
|
||||
val s = scheme.lowercase()
|
||||
return s == "https" || s == "http"
|
||||
}
|
||||
|
||||
fun Response.ensureSuccess() = apply {
|
||||
if (!isSuccessful || code == HttpURLConnection.HTTP_NO_CONTENT) {
|
||||
closeQuietly()
|
||||
@@ -40,26 +32,6 @@ fun Response.ensureSuccess() = apply {
|
||||
}
|
||||
}
|
||||
|
||||
fun Cookie.newBuilder(): Cookie.Builder = Cookie.Builder().also { c ->
|
||||
c.name(name)
|
||||
c.value(value)
|
||||
if (persistent) {
|
||||
c.expiresAt(expiresAt)
|
||||
}
|
||||
if (hostOnly) {
|
||||
c.hostOnlyDomain(domain)
|
||||
} else {
|
||||
c.domain(domain)
|
||||
}
|
||||
c.path(path)
|
||||
if (secure) {
|
||||
c.secure()
|
||||
}
|
||||
if (httpOnly) {
|
||||
c.httpOnly()
|
||||
}
|
||||
}
|
||||
|
||||
fun String.sanitizeHeaderValue(): String {
|
||||
return if (all(Char::isValidForHeaderValue)) {
|
||||
this // fast path
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.util.ext
|
||||
import android.content.Context
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.parsers.util.Set
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
inline fun Long.ifZero(defaultValue: () -> Long): Long = if (this == 0L) defaultValue() else this
|
||||
|
||||
fun longOf(a: Int, b: Int): Long {
|
||||
return a.toLong() shl 32 or (b.toLong() and 0xffffffffL)
|
||||
}
|
||||
|
||||
@@ -2,25 +2,11 @@ package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.database.DatabaseUtils
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.collection.arraySetOf
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.parsers.util.ellipsize
|
||||
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
|
||||
import java.util.UUID
|
||||
|
||||
inline fun <C : CharSequence?> C?.ifNullOrEmpty(defaultValue: () -> C): C {
|
||||
return if (this.isNullOrEmpty()) defaultValue() else this
|
||||
}
|
||||
|
||||
fun String.longHashCode(): Long {
|
||||
var h = 1125899906842597L
|
||||
val len: Int = this.length
|
||||
for (i in 0 until len) {
|
||||
h = 31 * h + this[i].code
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
fun String.toUUIDOrNull(): UUID? = try {
|
||||
UUID.fromString(this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
@@ -28,19 +14,35 @@ fun String.toUUIDOrNull(): UUID? = try {
|
||||
null
|
||||
}
|
||||
|
||||
fun String.digits() = filter { it.isDigit() }
|
||||
|
||||
/**
|
||||
* @param threshold 0 = exact match
|
||||
*/
|
||||
fun String.almostEquals(other: String, @FloatRange(from = 0.0) threshold: Float): Boolean {
|
||||
if (threshold == 0f) {
|
||||
return equals(other, ignoreCase = true)
|
||||
fun String.transliterate(skipMissing: Boolean): String {
|
||||
val cyr = charArrayOf(
|
||||
'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
|
||||
'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я', 'ё', 'ў',
|
||||
)
|
||||
val lat = arrayOf(
|
||||
"a", "b", "v", "g", "d", "e", "zh", "z", "i", "y", "k", "l", "m", "n", "o", "p",
|
||||
"r", "s", "t", "u", "f", "h", "ts", "ch", "sh", "sch", "", "i", "", "e", "ju", "ja", "jo", "w",
|
||||
)
|
||||
return buildString(length + 5) {
|
||||
for (c in this@transliterate) {
|
||||
val p = cyr.binarySearch(c.lowercaseChar())
|
||||
if (p in lat.indices) {
|
||||
if (c.isUpperCase()) {
|
||||
append(lat[p].uppercase())
|
||||
} else {
|
||||
append(lat[p])
|
||||
}
|
||||
} else if (!skipMissing) {
|
||||
append(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
val diff = lowercase().levenshteinDistance(other.lowercase()) / ((length + other.length) / 2f)
|
||||
return diff < threshold
|
||||
}
|
||||
|
||||
fun String.toFileNameSafe(): String = this.transliterate(false)
|
||||
.replace(Regex("[^a-z0-9_\\-]", arraySetOf(RegexOption.IGNORE_CASE)), " ")
|
||||
.replace(Regex("\\s+"), "_")
|
||||
|
||||
fun CharSequence.sanitize(): CharSequence {
|
||||
return filterNot { c -> c.isReplacement() }
|
||||
}
|
||||
@@ -68,10 +70,11 @@ fun <T> Collection<T>.joinToStringWithLimit(context: Context, limit: Int, transf
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("",
|
||||
@Deprecated(
|
||||
"",
|
||||
ReplaceWith(
|
||||
"sqlEscapeString(this)",
|
||||
"android.database.DatabaseUtils.sqlEscapeString"
|
||||
)
|
||||
"android.database.DatabaseUtils.sqlEscapeString",
|
||||
),
|
||||
)
|
||||
fun String.sqlEscape(): String = DatabaseUtils.sqlEscapeString(this)
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
|
||||
import org.koitharu.kotatsu.core.exceptions.SyncApiException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrapperIOException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.io.NullOutputStream
|
||||
@@ -37,6 +38,7 @@ import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException
|
||||
import java.io.ObjectOutputStream
|
||||
import java.net.ConnectException
|
||||
@@ -54,6 +56,8 @@ fun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessag
|
||||
?: resources.getString(R.string.error_occurred)
|
||||
|
||||
private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) {
|
||||
is CaughtException -> cause.getDisplayMessageOrNull(resources)
|
||||
is WrapperIOException -> cause.getDisplayMessageOrNull(resources)
|
||||
is ScrobblerAuthRequiredException -> resources.getString(
|
||||
R.string.scrobbler_auth_required,
|
||||
resources.getString(scrobbler.titleResId),
|
||||
@@ -141,7 +145,8 @@ fun Throwable.getCauseUrl(): String? = when (this) {
|
||||
is ParseException -> url
|
||||
is NotFoundException -> url
|
||||
is TooManyRequestExceptions -> url
|
||||
is CaughtException -> cause?.getCauseUrl()
|
||||
is CaughtException -> cause.getCauseUrl()
|
||||
is WrapperIOException -> cause.getCauseUrl()
|
||||
is NoDataReceivedException -> url
|
||||
is CloudFlareBlockedException -> url
|
||||
is CloudFlareProtectedException -> url
|
||||
@@ -175,7 +180,10 @@ fun Throwable.isReportable(): Boolean {
|
||||
return true
|
||||
}
|
||||
if (this is CaughtException) {
|
||||
return cause?.isReportable() == true
|
||||
return cause.isReportable()
|
||||
}
|
||||
if (this is WrapperIOException) {
|
||||
return cause.isReportable()
|
||||
}
|
||||
if (ExceptionResolver.canResolve(this)) {
|
||||
return false
|
||||
|
||||
@@ -77,7 +77,6 @@ import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.drawable
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
|
||||
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
@@ -113,6 +112,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.ellipsize
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
|
||||
@@ -274,7 +274,7 @@ class DetailsActivity :
|
||||
startActivity(
|
||||
ImageActivity.newIntent(
|
||||
v.context,
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return,
|
||||
manga.source,
|
||||
),
|
||||
scaleUpActivityOptionsOf(v),
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView
|
||||
import org.koitharu.kotatsu.core.util.ext.findActivity
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
|
||||
import org.koitharu.kotatsu.core.util.ext.mapToArray
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.parentView
|
||||
@@ -39,6 +38,7 @@ import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
|
||||
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.format
|
||||
import org.koitharu.kotatsu.parsers.util.mapToArray
|
||||
import org.koitharu.kotatsu.settings.storage.DirectoryModel
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadTask
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
@@ -30,6 +29,7 @@ import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.sizeOrZero
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import org.koitharu.kotatsu.settings.storage.DirectoryModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -60,7 +60,6 @@ import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.getWorkInputData
|
||||
import org.koitharu.kotatsu.core.util.ext.getWorkSpec
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.withTicker
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
@@ -79,6 +78,7 @@ import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.requireBody
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
@@ -199,7 +199,7 @@ class DownloadWorker @AssistedInject constructor(
|
||||
format = task.format ?: settings.preferredDownloadFormat,
|
||||
)
|
||||
val coverUrl = mangaDetails.largeCoverUrl.ifNullOrEmpty { mangaDetails.coverUrl }
|
||||
if (coverUrl.isNotEmpty()) {
|
||||
if (!coverUrl.isNullOrEmpty()) {
|
||||
downloadFile(coverUrl, destination, repo.source).let { file ->
|
||||
output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl))
|
||||
file.deleteAwait()
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.explore.domain
|
||||
import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.almostEquals
|
||||
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
@@ -11,6 +10,7 @@ import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.almostEquals
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.suggestions.domain.TagsBlacklist
|
||||
import javax.inject.Inject
|
||||
@@ -77,7 +77,7 @@ class ExploreRepository @Inject constructor(
|
||||
val list = repository.getList(
|
||||
offset = 0,
|
||||
order = order,
|
||||
filter = MangaListFilter(tags = setOfNotNull(tag))
|
||||
filter = MangaListFilter(tags = setOfNotNull(tag)),
|
||||
).asArrayList()
|
||||
if (settings.isSuggestionsExcludeNsfw) {
|
||||
list.removeAll { it.isNsfw }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.koitharu.kotatsu.explore.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaSourceInfo
|
||||
import org.koitharu.kotatsu.core.util.ext.longHashCode
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.util.longHashCode
|
||||
|
||||
data class MangaSourceItem(
|
||||
val source: MangaSourceInfo,
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.favourites.domain.model
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
|
||||
data class Cover(
|
||||
val url: String,
|
||||
val url: String?,
|
||||
val source: String,
|
||||
) {
|
||||
val mangaSource by lazy { MangaSource(source) }
|
||||
|
||||
@@ -65,7 +65,7 @@ class FavoriteSheet : BaseAdaptiveSheet<SheetFavoriteCategoriesBinding>(), OnLis
|
||||
fun show(fm: FragmentManager, manga: Collection<Manga>) = FavoriteSheet().withArgs(1) {
|
||||
putParcelableArrayList(
|
||||
KEY_MANGA_LIST,
|
||||
manga.mapTo(ArrayList(manga.size), ::ParcelableManga),
|
||||
manga.mapTo(ArrayList(manga.size)) { ParcelableManga(it, withDescription = false) },
|
||||
)
|
||||
}.showDistinct(fm, TAG)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ class FavoriteSheetViewModel @Inject constructor(
|
||||
settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled },
|
||||
) { categories, _, tracker ->
|
||||
mapList(categories, tracker)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(header))
|
||||
}.withErrorHandling()
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(header))
|
||||
|
||||
fun setChecked(categoryId: Long, isChecked: Boolean) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
|
||||
@@ -7,7 +7,7 @@ data class MangaCompactListModel(
|
||||
override val id: Long,
|
||||
override val title: String,
|
||||
val subtitle: String,
|
||||
override val coverUrl: String,
|
||||
override val coverUrl: String?,
|
||||
override val manga: Manga,
|
||||
override val counter: Int,
|
||||
override val progress: ReadingProgress?,
|
||||
|
||||
@@ -8,7 +8,7 @@ data class MangaDetailedListModel(
|
||||
override val id: Long,
|
||||
override val title: String,
|
||||
val subtitle: String?,
|
||||
override val coverUrl: String,
|
||||
override val coverUrl: String?,
|
||||
override val manga: Manga,
|
||||
override val counter: Int,
|
||||
override val progress: ReadingProgress?,
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
data class MangaGridModel(
|
||||
override val id: Long,
|
||||
override val title: String,
|
||||
override val coverUrl: String,
|
||||
override val coverUrl: String?,
|
||||
override val manga: Manga,
|
||||
override val counter: Int,
|
||||
override val progress: ReadingProgress?,
|
||||
|
||||
@@ -11,7 +11,7 @@ sealed class MangaListModel : ListModel {
|
||||
abstract val id: Long
|
||||
abstract val manga: Manga
|
||||
abstract val title: String
|
||||
abstract val coverUrl: String
|
||||
abstract val coverUrl: String?
|
||||
abstract val counter: Int
|
||||
abstract val isFavorite: Boolean
|
||||
abstract val progress: ReadingProgress?
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.koitharu.kotatsu.core.util.ext.crossfade
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.drawable
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||
@@ -40,6 +39,7 @@ import org.koitharu.kotatsu.image.ui.ImageActivity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||
import javax.inject.Inject
|
||||
@@ -100,7 +100,7 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
|
||||
R.id.imageView_cover -> startActivity(
|
||||
ImageActivity.newIntent(
|
||||
v.context,
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return,
|
||||
manga.source,
|
||||
),
|
||||
scaleUpActivityOptionsOf(v),
|
||||
|
||||
@@ -152,7 +152,8 @@ class LocalMangaRepository @Inject constructor(
|
||||
"Manga is not stored on local storage"
|
||||
}.manga
|
||||
LocalMangaUtil(subject).deleteChapters(ids)
|
||||
localStorageChanges.emit(LocalManga(subject))
|
||||
val updated = getDetails(subject)
|
||||
localStorageChanges.emit(LocalManga(updated))
|
||||
}
|
||||
|
||||
suspend fun getRemoteManga(localManga: Manga): Manga? {
|
||||
|
||||
@@ -16,12 +16,12 @@ import okio.use
|
||||
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.subdir
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfWriteable
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import java.io.File
|
||||
|
||||
@@ -22,8 +22,8 @@ import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isRegularFile
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.longHashCode
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileNameSafe
|
||||
import org.koitharu.kotatsu.core.util.ext.toListSorted
|
||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
||||
import org.koitharu.kotatsu.local.data.hasZipExtension
|
||||
@@ -33,10 +33,8 @@ import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.longHashCode
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.toCamelCase
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -61,7 +59,7 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
val mangaInfo = index?.getMangaInfo()
|
||||
if (mangaInfo != null) {
|
||||
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath)
|
||||
mangaInfo.copyInternal(
|
||||
mangaInfo.copy(
|
||||
source = LocalMangaSource,
|
||||
url = rootFile.toUri().toString(),
|
||||
coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() }.orEmpty(),
|
||||
@@ -72,7 +70,7 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
if (path != null && !fileSystem.exists(rootPath / path)) {
|
||||
null
|
||||
} else {
|
||||
c.copyInternal(
|
||||
c.copy(
|
||||
url = path?.let {
|
||||
uri.child(it, resolve = false).toString()
|
||||
} ?: uri.toString(),
|
||||
@@ -85,7 +83,7 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
},
|
||||
)
|
||||
} else {
|
||||
val title = rootFile.nameWithoutExtension.replace("_", " ").toCamelCase()
|
||||
val title = rootFile.name.fileNameToTitle()
|
||||
val coverEntry = fileSystem.findFirstImage(rootPath)
|
||||
val mimeTypeMap = MimeTypeMap.getSingleton()
|
||||
Manga(
|
||||
@@ -116,7 +114,7 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
}.toString().removePrefix(Path.DIRECTORY_SEPARATOR)
|
||||
MangaChapter(
|
||||
id = "$i$s".longHashCode(),
|
||||
name = s.ifEmpty { title },
|
||||
name = s.fileNameToTitle().ifEmpty { title },
|
||||
number = 0f,
|
||||
volume = 0,
|
||||
source = LocalMangaSource,
|
||||
@@ -275,43 +273,8 @@ class LocalMangaParser(private val uri: Uri) {
|
||||
Path.DIRECTORY_SEPARATOR + this
|
||||
}.toPath()
|
||||
|
||||
private fun Manga.copyInternal(
|
||||
url: String = this.url,
|
||||
coverUrl: String = this.coverUrl,
|
||||
largeCoverUrl: String? = this.largeCoverUrl,
|
||||
chapters: List<MangaChapter>? = this.chapters,
|
||||
source: MangaSource = this.source,
|
||||
): Manga = Manga(
|
||||
id = id,
|
||||
title = title,
|
||||
altTitle = altTitle,
|
||||
url = url,
|
||||
publicUrl = publicUrl,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = coverUrl,
|
||||
tags = tags,
|
||||
state = state,
|
||||
author = author,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
description = description,
|
||||
chapters = chapters,
|
||||
source = source,
|
||||
)
|
||||
|
||||
private fun MangaChapter.copyInternal(
|
||||
url: String = this.url,
|
||||
source: MangaSource = this.source,
|
||||
) = MangaChapter(
|
||||
id = id,
|
||||
name = name,
|
||||
number = number,
|
||||
volume = volume,
|
||||
url = url,
|
||||
scanlator = scanlator,
|
||||
uploadDate = uploadDate,
|
||||
branch = branch,
|
||||
source = source,
|
||||
)
|
||||
private fun String.fileNameToTitle() = substringBeforeLast('.')
|
||||
.replace('_', ' ')
|
||||
.replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ import okhttp3.internal.closeQuietly
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileNameSafe
|
||||
import org.koitharu.kotatsu.core.zip.ZipOutput
|
||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
||||
import org.koitharu.kotatsu.local.data.input.LocalMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
import java.io.File
|
||||
|
||||
class LocalMangaDirOutput(
|
||||
@@ -96,7 +96,9 @@ class LocalMangaDirOutput(
|
||||
}
|
||||
|
||||
suspend fun deleteChapters(ids: Set<Long>) = mutex.withLock {
|
||||
val chapters = checkNotNull((index.getMangaInfo() ?: LocalMangaParser(rootFile).getManga(withDetails = true).manga).chapters) {
|
||||
val chapters = checkNotNull(
|
||||
(index.getMangaInfo() ?: LocalMangaParser(rootFile).getManga(withDetails = true).manga).chapters,
|
||||
) {
|
||||
"No chapters found"
|
||||
}.withIndex()
|
||||
val victimsIds = ids.toMutableSet()
|
||||
|
||||
@@ -7,11 +7,11 @@ import kotlinx.coroutines.withContext
|
||||
import okio.Closeable
|
||||
import org.koitharu.kotatsu.core.prefs.DownloadFormat
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileNameSafe
|
||||
import org.koitharu.kotatsu.local.data.input.LocalMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
import java.io.File
|
||||
|
||||
sealed class LocalMangaOutput(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.local.domain
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.fold
|
||||
@@ -13,7 +12,6 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
@@ -26,7 +24,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(manga: Manga): Int {
|
||||
@@ -37,7 +34,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
}
|
||||
val task = getDeletionTask(localManga) ?: return 0
|
||||
localMangaRepository.deleteChapters(task.manga.manga, task.chaptersIds)
|
||||
emitUpdate(localManga)
|
||||
return task.chaptersIds.size
|
||||
}
|
||||
|
||||
@@ -62,7 +58,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
}.buffer().map {
|
||||
runCatchingCancellable {
|
||||
localMangaRepository.deleteChapters(it.manga.manga, it.chaptersIds)
|
||||
emitUpdate(it.manga)
|
||||
it.chaptersIds.size
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
@@ -88,11 +83,6 @@ class DeleteReadChaptersUseCase @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun emitUpdate(subject: LocalManga) {
|
||||
val updated = localMangaRepository.getDetails(subject.manga)
|
||||
localStorageChanges.emit(subject.copy(manga = updated))
|
||||
}
|
||||
|
||||
private suspend fun getAllChapters(manga: LocalManga): List<MangaChapter> = runCatchingCancellable {
|
||||
val remoteManga = checkNotNull(localMangaRepository.getRemoteManga(manga.manga))
|
||||
checkNotNull(mangaRepositoryFactory.create(remoteManga.source).getDetails(remoteManga).chapters)
|
||||
|
||||
@@ -152,7 +152,8 @@ class ImportService : CoroutineIntentService() {
|
||||
private const val CHANNEL_ID = "importing"
|
||||
private const val FOREGROUND_NOTIFICATION_ID = 37
|
||||
|
||||
fun start(context: Context, uris: Iterable<Uri>): Boolean = try {
|
||||
fun start(context: Context, uris: Collection<Uri>): Boolean = try {
|
||||
require(uris.isNotEmpty())
|
||||
for (uri in uris) {
|
||||
val intent = Intent(context, ImportService::class.java)
|
||||
intent.putExtra(DATA_URI, uri.toString())
|
||||
|
||||
@@ -10,11 +10,11 @@ import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.bookmarkKey
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaKey
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.findById
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.util.Collections
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -185,11 +185,8 @@ class PageLoader @Inject constructor(
|
||||
prefetchLock.withLock {
|
||||
while (prefetchQueue.isNotEmpty()) {
|
||||
val page = prefetchQueue.pollFirst() ?: return@launch
|
||||
if (cache.get(page.url) == null) {
|
||||
synchronized(tasks) {
|
||||
tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true)
|
||||
}
|
||||
return@launch
|
||||
synchronized(tasks) {
|
||||
tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,12 +199,14 @@ class PageLoader @Inject constructor(
|
||||
): ProgressDeferred<Uri, Float> {
|
||||
val progress = MutableStateFlow(PROGRESS_UNDEFINED)
|
||||
val deferred = loaderScope.async {
|
||||
if (!skipCache) {
|
||||
cache.get(page.url)?.let { return@async it.toUri() }
|
||||
}
|
||||
counter.incrementAndGet()
|
||||
try {
|
||||
loadPageImpl(page, progress, isPrefetch)
|
||||
loadPageImpl(
|
||||
page = page,
|
||||
progress = progress,
|
||||
isPrefetch = isPrefetch,
|
||||
skipCache = skipCache,
|
||||
)
|
||||
} finally {
|
||||
if (counter.decrementAndGet() == 0) {
|
||||
onIdle()
|
||||
@@ -231,9 +230,13 @@ class PageLoader @Inject constructor(
|
||||
page: MangaPage,
|
||||
progress: MutableStateFlow<Float>,
|
||||
isPrefetch: Boolean,
|
||||
skipCache: Boolean,
|
||||
): Uri = semaphore.withPermit {
|
||||
val pageUrl = getPageUrl(page)
|
||||
check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" }
|
||||
if (!skipCache) {
|
||||
cache.get(pageUrl)?.let { return it.toUri() }
|
||||
}
|
||||
val uri = Uri.parse(pageUrl)
|
||||
return when {
|
||||
uri.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {
|
||||
|
||||
@@ -31,12 +31,12 @@ import okio.source
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileNameSafe
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@@ -209,6 +209,9 @@ class ReaderInfoBarView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun Drawable.drawWithOutline(canvas: Canvas) {
|
||||
if (bounds.isEmpty) {
|
||||
return
|
||||
}
|
||||
var requiredScale = (bounds.width() + paint.strokeWidth * 2f) / bounds.width().toFloat()
|
||||
setTint(colorOutline)
|
||||
canvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) {
|
||||
|
||||
@@ -40,9 +40,7 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
|
||||
import org.koitharu.kotatsu.details.data.MangaDetails
|
||||
import org.koitharu.kotatsu.details.domain.DetailsInteractor
|
||||
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
|
||||
@@ -57,6 +55,8 @@ import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.sizeOrZero
|
||||
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
|
||||
import org.koitharu.kotatsu.reader.domain.DetectReaderModeUseCase
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
|
||||
@@ -8,9 +8,9 @@ import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.mapToArray
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapToArray
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.getCauseUrl
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.explore.domain.ExploreRepository
|
||||
@@ -40,6 +39,7 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.sizeOrZero
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val FILTER_MIN_INTERVAL = 250L
|
||||
|
||||
@@ -21,11 +21,11 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.sizeOrZero
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.settings
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.Preference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -10,12 +11,16 @@ import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchViewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||
|
||||
private val viewModel: RootSettingsViewModel by viewModels()
|
||||
private val activityViewModel: SettingsSearchViewModel by activityViewModels()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_root)
|
||||
@@ -41,6 +46,8 @@ class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
addMenuProvider(SettingsSearchMenuProvider(activityViewModel))
|
||||
addMenuProvider(SettingsMenuProvider(view.context))
|
||||
}
|
||||
|
||||
override fun setTitle(title: CharSequence?) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.search.SettingsItem
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchFragment
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider
|
||||
import org.koitharu.kotatsu.settings.search.SettingsSearchViewModel
|
||||
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
|
||||
@@ -76,8 +75,6 @@ class SettingsActivity :
|
||||
}
|
||||
viewModel.isSearchActive.observe(this, ::toggleSearchMode)
|
||||
viewModel.onNavigateToPreference.observeEvent(this, ::navigateToPreference)
|
||||
addMenuProvider(SettingsSearchMenuProvider(viewModel))
|
||||
addMenuProvider(SettingsMenuProvider(this))
|
||||
}
|
||||
|
||||
override fun onPreferenceStartFragment(
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
@@ -29,10 +30,8 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
addPreferencesFromResource(R.xml.pref_about)
|
||||
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {
|
||||
title = getString(R.string.app_version, BuildConfig.VERSION_NAME)
|
||||
isEnabled = viewModel.isUpdateSupported
|
||||
}
|
||||
findPreference<SwitchPreferenceCompat>(AppSettings.KEY_UPDATES_UNSTABLE)?.run {
|
||||
isVisible = viewModel.isUpdateSupported
|
||||
isEnabled = VersionId(BuildConfig.VERSION_NAME).isStable
|
||||
if (!isEnabled) isChecked = true
|
||||
}
|
||||
@@ -40,9 +39,12 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
findPreference<Preference>(AppSettings.KEY_APP_UPDATE)?.isEnabled = !it
|
||||
}
|
||||
combine(viewModel.isUpdateSupported, viewModel.isLoading, ::Pair)
|
||||
.observe(viewLifecycleOwner) { (isUpdateSupported, isLoading) ->
|
||||
findPreference<Preference>(AppSettings.KEY_UPDATES_UNSTABLE)?.isVisible = isUpdateSupported
|
||||
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.isEnabled = isUpdateSupported && !isLoading
|
||||
|
||||
}
|
||||
viewModel.onUpdateAvailable.observeEvent(viewLifecycleOwner, ::onUpdateAvailable)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
@@ -13,7 +17,10 @@ class AboutSettingsViewModel @Inject constructor(
|
||||
private val appUpdateRepository: AppUpdateRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isUpdateSupported = appUpdateRepository.isUpdateSupported()
|
||||
val isUpdateSupported = flow {
|
||||
emit(appUpdateRepository.isUpdateSupported())
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||
|
||||
val onUpdateAvailable = MutableEventFlow<AppVersion?>()
|
||||
|
||||
fun checkForUpdates() {
|
||||
|
||||
@@ -10,9 +10,9 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.EmptyMangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.mapToArray
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.mapToArray
|
||||
import org.koitharu.kotatsu.settings.utils.AutoCompleteTextViewPreference
|
||||
import org.koitharu.kotatsu.settings.utils.EditTextBindListener
|
||||
import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider
|
||||
@@ -119,6 +119,6 @@ private fun PreferenceFragmentCompat.addPreferencesFromEmptyRepository() {
|
||||
preferenceScreen.addPreference(preference)
|
||||
}
|
||||
|
||||
private fun Array<out String>.toStringArray(): Array<String> {
|
||||
return Array(size) { i -> this[i] as? String ?: "" }
|
||||
private fun Array<out String?>.toStringArray(): Array<String> {
|
||||
return Array(size) { i -> this[i].orEmpty() }
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@ class EditTextDefaultSummaryProvider(
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.koitharu.kotatsu.suggestions.domain
|
||||
|
||||
import org.koitharu.kotatsu.core.util.ext.almostEquals
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.almostEquals
|
||||
|
||||
class TagsBlacklist(
|
||||
private val tags: Set<String>,
|
||||
|
||||
@@ -51,7 +51,6 @@ import org.koitharu.kotatsu.core.model.distinctById
|
||||
import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.almostEquals
|
||||
import org.koitharu.kotatsu.core.util.ext.asArrayList
|
||||
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
||||
import org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag
|
||||
@@ -60,7 +59,6 @@ import org.koitharu.kotatsu.core.util.ext.flatten
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.sanitize
|
||||
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
|
||||
import org.koitharu.kotatsu.core.util.ext.takeMostFrequent
|
||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.trySetForeground
|
||||
@@ -73,7 +71,9 @@ import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.almostEquals
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.parsers.util.sizeOrZero
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import javax.inject.Inject
|
||||
|
||||
class SyncSettings(
|
||||
|
||||
@@ -13,9 +13,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||
import org.koitharu.kotatsu.databinding.PreferenceDialogAutocompletetextviewBinding
|
||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.settings.utils.validation.UrlValidator
|
||||
import org.koitharu.kotatsu.sync.data.SyncSettings
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -11,12 +11,12 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.ifZero
|
||||
import org.koitharu.kotatsu.core.util.ext.mapItems
|
||||
import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
|
||||
import org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.ifZero
|
||||
import org.koitharu.kotatsu.tracker.data.TrackEntity
|
||||
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
|
||||
import org.koitharu.kotatsu.tracker.data.toTrackingLogItem
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.koitharu.kotatsu.tracker.domain.model
|
||||
|
||||
import org.koitharu.kotatsu.core.util.ext.ifZero
|
||||
import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.ifZero
|
||||
|
||||
sealed interface MangaUpdates {
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
data class FeedItem(
|
||||
val id: Long,
|
||||
val imageUrl: String,
|
||||
val imageUrl: String?,
|
||||
val title: String,
|
||||
val manga: Manga,
|
||||
val count: Int,
|
||||
|
||||
@@ -30,7 +30,7 @@ class WidgetUpdater @Inject constructor(
|
||||
private fun updateWidgets(cls: Class<*>) {
|
||||
val intent = Intent(context, cls)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
val ids = AppWidgetManager.getInstance(context)
|
||||
val ids = (AppWidgetManager.getInstance(context) ?: return)
|
||||
.getAppWidgetIds(ComponentName(context, cls))
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||
context.sendBroadcast(intent)
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
|
||||
<bool name="is_color_themes_available">false</bool>
|
||||
<bool name="is_sync_enabled">true</bool>
|
||||
<bool name="is_predictive_back_enabled">true</bool>
|
||||
</resources>
|
||||
|
||||
5
app/src/release/res/values/bools.xml
Normal file
5
app/src/release/res/values/bools.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Disable predictive back due to crashes -->
|
||||
<bool name="is_predictive_back_enabled">false</bool>
|
||||
</resources>
|
||||
5
app/version.properties
Normal file
5
app/version.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
#Wed Jan 22 19:14:17 EET 2025
|
||||
code=1
|
||||
build=8
|
||||
variant=
|
||||
base=7.7
|
||||
@@ -31,7 +31,7 @@ material = "1.12.0"
|
||||
moshi = "1.15.2"
|
||||
okhttp = "4.12.0"
|
||||
okio = "3.9.1"
|
||||
parsers = "8ce6694232"
|
||||
parsers = "51ed1b2db8"
|
||||
preference = "1.2.1"
|
||||
recyclerview = "1.3.2"
|
||||
room = "2.6.1"
|
||||
|
||||
Reference in New Issue
Block a user