From d21dff08b8a3c06ed6fd65c28d5ac6953f897e50 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 11:59:52 +0300 Subject: [PATCH 01/12] Invalidate DiskLruCache if broken --- .../koitharu/kotatsu/local/data/PagesCache.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt index 7aee467a5..10eeea740 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt @@ -13,9 +13,9 @@ import java.io.InputStream class PagesCache(context: Context) { private val cacheDir = context.externalCacheDir ?: context.cacheDir - private val lruCache = DiskLruCache.create( - cacheDir.subdir(CacheDir.PAGES.dir), - FileSize.MEGABYTES.convert(200, FileSize.BYTES), + private val lruCache = createDiskLruCacheSafe( + dir = cacheDir.subdir(CacheDir.PAGES.dir), + size = FileSize.MEGABYTES.convert(200, FileSize.BYTES), ) operator fun get(url: String): File? { @@ -60,4 +60,14 @@ class PagesCache(context: Context) { progress.value = (bytesCopied.toDouble() / contentLength.toDouble()).toFloat() } } +} + +private fun createDiskLruCacheSafe(dir: File, size: Long): DiskLruCache { + return try { + DiskLruCache.create(dir, size) + } catch (e: Exception) { + dir.deleteRecursively() + dir.mkdir() + DiskLruCache.create(dir, size) + } } \ No newline at end of file From e048235dad892238d05fccd223d9b2e1d17bf9d4 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 12:05:39 +0300 Subject: [PATCH 02/12] Update dependencies --- app/build.gradle | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1ca65ee45..c55d86047 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 32 - versionCode 411 - versionName '3.3.2' + versionCode 412 + versionName '3.4' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -77,19 +77,19 @@ afterEvaluate { } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation('com.github.nv95:kotatsu-parsers:c92f89f307') { + implementation('com.github.nv95:kotatsu-parsers:da3b0ae0cf') { exclude group: 'org.json', module: 'json' } implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3' implementation 'androidx.core:core-ktx:1.8.0' - implementation 'androidx.activity:activity-ktx:1.5.0-rc01' - implementation 'androidx.fragment:fragment-ktx:1.5.0-rc01' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc02' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-rc02' - implementation 'androidx.lifecycle:lifecycle-service:2.5.0-rc02' - implementation 'androidx.lifecycle:lifecycle-process:2.5.0-rc02' + implementation 'androidx.activity:activity-ktx:1.5.0' + implementation 'androidx.fragment:fragment-ktx:1.5.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0' + implementation 'androidx.lifecycle:lifecycle-service:2.5.0' + implementation 'androidx.lifecycle:lifecycle-process:2.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' @@ -99,7 +99,7 @@ dependencies { implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04' implementation 'com.google.android.material:material:1.7.0-alpha02' //noinspection LifecycleAnnotationProcessorWithJava8 - kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0-rc02' + kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0' implementation 'androidx.room:room-runtime:2.4.2' implementation 'androidx.room:room-ktx:2.4.2' From 3d74d027c153052998594398d21c94dc237b5faa Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 12:13:27 +0300 Subject: [PATCH 03/12] Remove duplicate item from history settings --- app/src/main/res/xml/pref_history.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/res/xml/pref_history.xml b/app/src/main/res/xml/pref_history.xml index 299739240..59dddad36 100644 --- a/app/src/main/res/xml/pref_history.xml +++ b/app/src/main/res/xml/pref_history.xml @@ -56,9 +56,4 @@ - - \ No newline at end of file From 6934daecff5a6e6e76e7331c0ca0ea83c0943d4e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 12:20:48 +0300 Subject: [PATCH 04/12] Preselect new sources based on locale --- .../kotatsu/settings/newsources/NewSourcesViewModel.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesViewModel.kt index 08ef96c49..06567642b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesViewModel.kt @@ -1,10 +1,12 @@ package org.koitharu.kotatsu.settings.newsources +import androidx.core.os.LocaleListCompat import androidx.lifecycle.MutableLiveData import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.model.getLocaleTitle import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem +import org.koitharu.kotatsu.utils.ext.mapToSet class NewSourcesViewModel( private val settings: AppSettings, @@ -30,12 +32,14 @@ class NewSourcesViewModel( } private fun buildList() { + val locales = LocaleListCompat.getDefault().mapToSet { it.language } val hidden = settings.hiddenSources sources.value = initialList.map { + val locale = it.locale SourceConfigItem.SourceItem( source = it, summary = it.getLocaleTitle(), - isEnabled = it.name !in hidden, + isEnabled = it.name !in hidden && (locale == null || locale in locales), isDraggable = false, ) } From be67b36b6ac15ce5f470c50caa50bb0a8b7afcb4 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 12:49:21 +0300 Subject: [PATCH 05/12] Refactor extensions --- .../parcelable/ParcelableMangaChapters.kt | 3 +- .../model/parcelable/ParcelableMangaPages.kt | 3 +- .../model/parcelable/ParcelableMangaTags.kt | 4 +-- .../kotatsu/scrobbling/domain/Scrobbler.kt | 4 +-- .../kotatsu/utils/ext/CollectionExt.kt | 11 ++---- .../koitharu/kotatsu/utils/ext/LocaleExt.kt | 34 ------------------ .../kotatsu/utils/ext/LocaleListExt.kt | 35 +++++++++++++++++++ 7 files changed, 44 insertions(+), 50 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleListExt.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt index db9ebb9c7..473b45320 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaChapters.kt @@ -3,14 +3,13 @@ package org.koitharu.kotatsu.core.model.parcelable import android.os.Parcel import android.os.Parcelable import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.utils.ext.createList class ParcelableMangaChapters( val chapters: List, ) : Parcelable { constructor(parcel: Parcel) : this( - createList(parcel.readInt()) { parcel.readMangaChapter() } + List(parcel.readInt()) { parcel.readMangaChapter() } ) override fun writeToParcel(parcel: Parcel, flags: Int) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt index 4717132f5..3230ec59b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPages.kt @@ -3,14 +3,13 @@ package org.koitharu.kotatsu.core.model.parcelable import android.os.Parcel import android.os.Parcelable import org.koitharu.kotatsu.parsers.model.MangaPage -import org.koitharu.kotatsu.utils.ext.createList class ParcelableMangaPages( val pages: List, ) : Parcelable { constructor(parcel: Parcel) : this( - createList(parcel.readInt()) { parcel.readMangaPage() } + List(parcel.readInt()) { parcel.readMangaPage() } ) override fun writeToParcel(parcel: Parcel, flags: Int) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt index 0ef0f74e0..bd5490e0a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt @@ -3,14 +3,14 @@ package org.koitharu.kotatsu.core.model.parcelable import android.os.Parcel import android.os.Parcelable import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.utils.ext.createSet +import org.koitharu.kotatsu.utils.ext.Set class ParcelableMangaTags( val tags: Set, ) : Parcelable { constructor(parcel: Parcel) : this( - createSet(parcel.readInt()) { parcel.readMangaTag() } + Set(parcel.readInt()) { parcel.readMangaTag() } ) override fun writeToParcel(parcel: Parcel, flags: Int) { diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt index 2fe44e39c..b730d19cd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt @@ -10,7 +10,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.domain.model.* -import org.koitharu.kotatsu.utils.ext.findKey +import org.koitharu.kotatsu.utils.ext.findKeyByValue import org.koitharu.kotatsu.utils.ext.printStackTraceDebug abstract class Scrobbler( @@ -59,7 +59,7 @@ abstract class Scrobbler( scrobbler = scrobblerService, mangaId = mangaId, targetId = targetId, - status = statuses.findKey(status), + status = statuses.findKeyByValue(status), chapter = chapter, comment = comment, rating = rating, diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index f66a6dc22..611bbd442 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -18,25 +18,20 @@ inline fun MutableSet(size: Int, init: (index: Int) -> T): MutableSet { return set } -inline fun createSet(size: Int, init: (index: Int) -> T): Set = when (size) { +@Suppress("FunctionName") +inline fun Set(size: Int, init: (index: Int) -> T): Set = when (size) { 0 -> emptySet() 1 -> Collections.singleton(init(0)) else -> MutableSet(size, init) } -inline fun createList(size: Int, init: (index: Int) -> T): List = when (size) { - 0 -> emptyList() - 1 -> Collections.singletonList(init(0)) - else -> MutableList(size, init) -} - fun List.asArrayList(): ArrayList = if (this is ArrayList<*>) { this as ArrayList } else { ArrayList(this) } -fun Map.findKey(value: V): K? { +fun Map.findKeyByValue(value: V): K? { for ((k, v) in entries) { if (v == value) { return k diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt deleted file mode 100644 index ac878b538..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.koitharu.kotatsu.utils.ext - -import androidx.core.os.LocaleListCompat -import java.util.* - -fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw kotlin.NoSuchElementException() - -fun LocaleListCompat.toList(): List = createList(size()) { i -> getOrThrow(i) } - -operator fun LocaleListCompat.iterator() = object : Iterator { - private var index = 0 - override fun hasNext(): Boolean = index < size() - override fun next(): Locale = getOrThrow(index++) -} - -inline fun > LocaleListCompat.mapTo( - destination: C, - block: (Locale) -> R, -): C { - val len = size() - for (i in 0 until len) { - val item = get(i) ?: continue - destination.add(block(item)) - } - return destination -} - -inline fun LocaleListCompat.map(block: (Locale) -> T): List { - return mapTo(ArrayList(size()), block) -} - -inline fun LocaleListCompat.mapToSet(block: (Locale) -> T): Set { - return mapTo(LinkedHashSet(size()), block) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleListExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleListExt.kt new file mode 100644 index 000000000..b6ae2535c --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleListExt.kt @@ -0,0 +1,35 @@ +package org.koitharu.kotatsu.utils.ext + +import androidx.core.os.LocaleListCompat +import java.util.* + +operator fun LocaleListCompat.iterator(): ListIterator = LocaleListCompatIterator(this) + +fun LocaleListCompat.toList(): List = List(size()) { i -> getOrThrow(i) } + +inline fun LocaleListCompat.map(block: (Locale) -> T): List { + return List(size()) { i -> block(getOrThrow(i)) } +} + +inline fun LocaleListCompat.mapToSet(block: (Locale) -> T): Set { + return Set(size()) { i -> block(getOrThrow(i)) } +} + +fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException() + +private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator { + + private var index = 0 + + override fun hasNext() = index < list.size() + + override fun hasPrevious() = index > 0 + + override fun next() = list.get(index++) ?: throw NoSuchElementException() + + override fun nextIndex() = index + + override fun previous() = list.get(--index) ?: throw NoSuchElementException() + + override fun previousIndex() = index - 1 +} \ No newline at end of file From 9283f419ba0e7925748f1721e388131adb87b560 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 13:12:58 +0300 Subject: [PATCH 06/12] Support images in manga description --- .../org/koitharu/kotatsu/core/ui/uiModule.kt | 3 +++ .../koitharu/kotatsu/details/DetailsModule.kt | 2 +- .../kotatsu/details/ui/DetailsFragment.kt | 12 +++++++-- .../kotatsu/details/ui/DetailsViewModel.kt | 17 ++++++++++++- .../kotatsu/utils/image/CoilImageGetter.kt | 25 +++++++++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt index 61c8425da..c9168abaf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.core.ui +import android.text.Html import coil.ComponentRegistry import coil.ImageLoader import coil.disk.DiskCache @@ -10,6 +11,7 @@ import org.koin.dsl.module import org.koitharu.kotatsu.core.parser.FaviconMapper import org.koitharu.kotatsu.local.data.CacheDir import org.koitharu.kotatsu.local.data.CbzFetcher +import org.koitharu.kotatsu.utils.image.CoilImageGetter val uiModule get() = module { @@ -40,4 +42,5 @@ val uiModule .build() ).build() } + factory { CoilImageGetter(androidContext(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt index 88b40e2be..5091ee0e0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt @@ -8,6 +8,6 @@ val detailsModule get() = module { viewModel { intent -> - DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get(), get(), get()) + DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index 47ee11616..711ccdeb9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -21,6 +21,7 @@ import coil.size.Scale import coil.util.CoilUtils import com.google.android.material.chip.Chip import kotlinx.coroutines.launch +import org.koin.android.ext.android.get import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R @@ -80,6 +81,7 @@ class DetailsFragment : viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged) viewModel.bookmarks.observe(viewLifecycleOwner, ::onBookmarksChanged) viewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged) + viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged) addMenuProvider(DetailsMenuProvider()) } @@ -108,8 +110,6 @@ class DetailsFragment : textViewTitle.text = manga.title textViewSubtitle.textAndVisible = manga.altTitle textViewAuthor.textAndVisible = manga.author - textViewDescription.text = manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank) - ?: getString(R.string.no_description) when (manga.state) { MangaState.FINISHED -> { textViewState.apply { @@ -172,6 +172,14 @@ class DetailsFragment : } } + private fun onDescriptionChanged(description: CharSequence?) { + if (description.isNullOrBlank()) { + binding.textViewDescription.setText(R.string.no_description) + } else { + binding.textViewDescription.text = description + } + } + private fun onHistoryChanged(history: MangaHistory?) { with(binding.buttonRead) { if (history == null) { diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 71a6c49fb..d77ef9aba 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -1,5 +1,7 @@ package org.koitharu.kotatsu.details.ui +import android.text.Html +import androidx.core.text.parseAsHtml import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow import androidx.lifecycle.asLiveData @@ -43,6 +45,7 @@ class DetailsViewModel( private val bookmarksRepository: BookmarksRepository, private val settings: AppSettings, private val scrobbler: Scrobbler, + private val imageGetter: Html.ImageGetter, ) : BaseViewModel() { private val delegate = MangaDetailsDelegate( @@ -79,7 +82,19 @@ class DetailsViewModel( val bookmarks = delegate.manga.flatMapLatest { if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList()) - }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) + }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) + + val description = delegate.manga + .distinctUntilChangedBy { it?.description.orEmpty() } + .transformLatest { + val description = it?.description + if (description.isNullOrEmpty()) { + emit(null) + } else { + emit(description.parseAsHtml()) + emit(description.parseAsHtml(imageGetter = imageGetter)) + } + }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, null) val onMangaRemoved = SingleLiveEvent() val isScrobblingAvailable: Boolean diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt b/app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt new file mode 100644 index 000000000..f88929b23 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/image/CoilImageGetter.kt @@ -0,0 +1,25 @@ +package org.koitharu.kotatsu.utils.image + +import android.content.Context +import android.graphics.drawable.Drawable +import android.text.Html +import coil.ImageLoader +import coil.executeBlocking +import coil.request.ImageRequest + +class CoilImageGetter( + private val context: Context, + private val coil: ImageLoader, +) : Html.ImageGetter { + + override fun getDrawable(source: String?): Drawable? { + return coil.executeBlocking( + ImageRequest.Builder(context) + .data(source) + .allowHardware(false) + .build() + ).drawable?.apply { + setBounds(0, 0, intrinsicHeight, intrinsicHeight) + } + } +} \ No newline at end of file From 6f93440b110efd57bc12735752c52bda822329f8 Mon Sep 17 00:00:00 2001 From: Anupam Malhotra Date: Tue, 5 Jul 2022 17:15:44 +0200 Subject: [PATCH 07/12] Translated using Weblate (Spanish) Currently translated at 95.2% (304 of 319 strings) Co-authored-by: Anupam Malhotra Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/ Translation: Kotatsu/Strings --- app/src/main/res/values-es/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 448aa0ece..1bf64ec78 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -301,4 +301,5 @@ Utilizar la huella dactilar si está disponible Mangas de tus favoritos Sus mangas recientemente leídos + Cerrar sesión \ No newline at end of file From 3ac8dc55584969805a24c98153eccc65c31cd214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 5 Jul 2022 17:15:44 +0200 Subject: [PATCH 08/12] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (320 of 320 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (319 of 319 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/ Translation: Kotatsu/Strings --- app/src/main/res/values-tr/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 678fdc3c4..4b4f904d2 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -303,4 +303,18 @@ Favorilerinizden mangalar Son okuduğunuz mangalar Bildir + İzleme + Oturumu kapat + Okunuyor + Tamamlandı + Okuma ilerleme göstergelerini göster + Verileri sil + Geçmişte ve favorilerde okunma yüzdesini göster + Uygunsuz olarak işaretlenen mangalar asla geçmişe eklenmeyecek ve ilerlemeniz kaydedilmeyecektir + Bazı sorunlarda yardımcı olabilir. Tüm yetkilendirmeler geçersiz kılınacaktır + Beklemede + Bırakıldı + Planlandı + Yeniden okunuyor + Tümünü göster \ No newline at end of file From cfd97ebd3dfefb04f02f48a3a5990d350035850f Mon Sep 17 00:00:00 2001 From: kuragehime Date: Tue, 5 Jul 2022 17:15:45 +0200 Subject: [PATCH 09/12] Translated using Weblate (Japanese) Currently translated at 100.0% (320 of 320 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (319 of 319 strings) Co-authored-by: kuragehime Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/ Translation: Kotatsu/Strings --- app/src/main/res/values-ja/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ebd308ab1..bc61dc708 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -303,4 +303,18 @@ 指紋がある場合は、指紋を使用する お気に入りの漫画 報告 + 読書 + 再読込 + 完了 + 保留中 + 追跡 + ログアウト + 予定 + ドロップ + データの削除 + 履歴とお気に入りに既読率を表示する + いくつかの問題の場合に助けることができる。すべての認証が無効になります + 読書の進行状況インジケーターを表示 + NSFWとマークされたマンガは履歴に追加されず、進行状況も保存されない + すべて表示 \ No newline at end of file From 1e9e7e4cd738b94415db1624ea8fa1360a8bac7c Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Wed, 6 Jul 2022 12:12:12 +0200 Subject: [PATCH 10/12] Translated using Weblate (Finnish) Currently translated at 96.5% (309 of 320 strings) Translated using Weblate (French) Currently translated at 100.0% (320 of 320 strings) Translated using Weblate (Italian) Currently translated at 99.3% (318 of 320 strings) Translated using Weblate (German) Currently translated at 97.8% (313 of 320 strings) Co-authored-by: J. Lavoie Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/ Translation: Kotatsu/Strings --- app/src/main/res/values-de/strings.xml | 12 ++++++++++++ app/src/main/res/values-fi/strings.xml | 6 ++++++ app/src/main/res/values-fr/strings.xml | 15 +++++++++++++++ app/src/main/res/values-it/strings.xml | 15 +++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 36249510e..cf18cd5fd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -302,4 +302,16 @@ Fingerabdruck verwenden, falls vorhanden Manga aus Ihren Favoriten Ihr kürzlich gelesener Manga + Melden + Nachverfolgung + Abmelden + Geplant + In der Warteschleife + Indikatoren für den Lesefortschritt anzeigen + Alle anzeigen + Gelesenen Prozentsatz in Verlauf und Favoriten anzeigen + Kann im Falle einiger Probleme helfen. Alle Berechtigungen werden für ungültig erklärt + Abgeschlossen + Manga, die als NSFW markiert sind, werden nicht in den Verlauf aufgenommen und Ihr Fortschritt wird nicht gespeichert. + Datenlöschung \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 45e41143e..eb44dda48 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -302,4 +302,10 @@ Käytä sormenjälkeä, jos käytettävissä Manga suosikeistasi Äskettäin lukemasi manga + Seuranta + Kirjaudu ulos + Lukemassa + Lukemassa uudelleen + Tietojen poistaminen + Näytä kaikki \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e62ddfdc9..0c178fb89 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -302,4 +302,19 @@ Utiliser l\'empreinte digitale si elle est disponible Vos mangas récemment lus Les mangas de vos favoris + Signaler + Suivi + Planifié + Lecture + Afficher les indicateurs de progression de lecture + Afficher le pourcentage de lecture dans l\'historique et les favoris + Les mangas marqués comme étant pour adultes ne seront jamais ajoutés à l\'historique et votre progression ne sera pas sauvegardée + Peut aider en cas de problème. Toutes les autorisations seront invalidées + Tout afficher + En attente + Abandonné + Suppression des données + Se déconnecter + Terminé + Relecture \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index d132159ae..201ea3b6b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -302,4 +302,19 @@ Usa le impronte digitali se disponibili Manga dai preferiti I manga letti di recente + Segnala + Tracciamento + Lettura + Rilettura + In attesa + Mostrare gli indicatori di progresso della lettura + Eliminazione dei dati + Mostra la percentuale di lettura nella cronologia e nei preferiti + I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i vostri progressi non verranno salvati + Può aiutare in caso di problemi. Tutte le autorizzazioni saranno invalidate + Mostra tutto + Esci + Pianificato + Finito + Abbandonato \ No newline at end of file From a35d7dc5aed7dde5615c10d437b8443f10407a71 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 14:27:14 +0300 Subject: [PATCH 11/12] Ability to resolve errors in manga source settings --- .../settings/SourceSettingsFragment.kt | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt index 4ffa13c5b..233260be9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt @@ -3,25 +3,25 @@ package org.koitharu.kotatsu.settings import android.os.Bundle import android.view.View import androidx.preference.Preference +import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment +import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity -import org.koitharu.kotatsu.utils.ext.printStackTraceDebug -import org.koitharu.kotatsu.utils.ext.serializableArgument -import org.koitharu.kotatsu.utils.ext.viewLifecycleScope -import org.koitharu.kotatsu.utils.ext.withArgs +import org.koitharu.kotatsu.utils.ext.* class SourceSettingsFragment : BasePreferenceFragment(0) { private val source by serializableArgument(EXTRA_SOURCE) private var repository: RemoteMangaRepository? = null + private val exceptionResolver = ExceptionResolver(this) override fun onResume() { super.onResume() @@ -63,6 +63,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) { private fun loadUsername(preference: Preference) = viewLifecycleScope.launch { runCatching { + preference.summary = null withContext(Dispatchers.Default) { requireNotNull(repository?.getAuthProvider()?.getUsername()) } @@ -70,10 +71,28 @@ class SourceSettingsFragment : BasePreferenceFragment(0) { preference.title = getString(R.string.logged_in_as, username) }.onFailure { error -> preference.isEnabled = error is AuthRequiredException + when { + error is AuthRequiredException -> Unit + ExceptionResolver.canResolve(error) -> { + Snackbar.make(listView, error.getDisplayMessage(resources), Snackbar.LENGTH_INDEFINITE) + .setAction(ExceptionResolver.getResolveStringId(error)) { resolveError(error) } + .show() + } + else -> preference.summary = error.getDisplayMessage(resources) + } error.printStackTraceDebug() } } + private fun resolveError(error: Throwable): Unit { + viewLifecycleScope.launch { + if (exceptionResolver.resolve(error)) { + val pref = findPreference(KEY_AUTH) ?: return@launch + loadUsername(pref) + } + } + } + companion object { private const val KEY_AUTH = "auth" From 2c24aba558c5de64bc5cb59e7f6894d892325f56 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 16:15:25 +0300 Subject: [PATCH 12/12] Refactor update checker --- .../kotatsu/settings/AppUpdateChecker.kt | 53 ++++++------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt b/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt index a9e2ab345..603fe1ce5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/AppUpdateChecker.kt @@ -4,9 +4,9 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import androidx.activity.ComponentActivity import androidx.annotation.MainThread +import androidx.core.net.toUri import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -23,9 +23,6 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import java.io.ByteArrayInputStream import java.io.InputStream import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.security.cert.CertificateEncodingException -import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit @@ -74,7 +71,8 @@ class AppUpdateChecker(private val activity: ComponentActivity) { .setTitle(R.string.app_update_available) .setMessage(message) .setPositiveButton(R.string.download) { _, _ -> - activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(version.apkUrl))) + val intent = Intent(Intent.ACTION_VIEW, version.apkUrl.toUri()) + activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser))) } .setNegativeButton(R.string.close, null) .setCancelable(false) @@ -88,42 +86,23 @@ class AppUpdateChecker(private val activity: ComponentActivity) { private val PERIOD = TimeUnit.HOURS.toMillis(6) fun isUpdateSupported(context: Context): Boolean { - return getCertificateSHA1Fingerprint(context) == CERT_SHA1 + return BuildConfig.DEBUG || getCertificateSHA1Fingerprint(context) == CERT_SHA1 } @Suppress("DEPRECATION") @SuppressLint("PackageManagerGetSignatures") - private fun getCertificateSHA1Fingerprint(context: Context): String? { - val packageInfo = try { - context.packageManager.getPackageInfo( - context.packageName, - PackageManager.GET_SIGNATURES - ) - } catch (e: PackageManager.NameNotFoundException) { - e.printStackTraceDebug() - return null - } - val signatures = packageInfo?.signatures - val cert: ByteArray = signatures?.firstOrNull()?.toByteArray() ?: return null + private fun getCertificateSHA1Fingerprint(context: Context): String? = runCatching { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES) + val signatures = requireNotNull(packageInfo?.signatures) + val cert: ByteArray = signatures.first().toByteArray() val input: InputStream = ByteArrayInputStream(cert) - val c = try { - val cf = CertificateFactory.getInstance("X509") - cf.generateCertificate(input) as X509Certificate - } catch (e: CertificateException) { - e.printStackTraceDebug() - return null - } - return try { - val md: MessageDigest = MessageDigest.getInstance("SHA1") - val publicKey: ByteArray = md.digest(c.encoded) - publicKey.byte2HexFormatted() - } catch (e: NoSuchAlgorithmException) { - e.printStackTraceDebug() - null - } catch (e: CertificateEncodingException) { - e.printStackTraceDebug() - null - } - } + val cf = CertificateFactory.getInstance("X509") + val c = cf.generateCertificate(input) as X509Certificate + val md: MessageDigest = MessageDigest.getInstance("SHA1") + val publicKey: ByteArray = md.digest(c.encoded) + return publicKey.byte2HexFormatted() + }.onFailure { error -> + error.printStackTraceDebug() + }.getOrNull() } } \ No newline at end of file