From f342cd6b5668b5cb868e27507b80650e1c9cd163 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 1 Aug 2022 17:00:00 +0300 Subject: [PATCH 1/6] Fix crash on widgets update --- app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt | 5 ++--- .../koitharu/kotatsu/widget/recent/RecentListFactory.kt | 8 ++++---- .../org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index fcbc79742..23b3df2c6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -73,7 +73,7 @@ class KotatsuApp : Application() { appWidgetModule, suggestionsModule, shikimoriModule, - bookmarksModule, + bookmarksModule ) } } @@ -91,8 +91,7 @@ class KotatsuApp : Application() { ReportField.PHONE_MODEL, ReportField.CRASH_CONFIGURATION, ReportField.STACK_TRACE, - ReportField.CUSTOM_DATA, - ReportField.SHARED_PREFERENCES, + ReportField.SHARED_PREFERENCES ) dialog { text = getString(R.string.crash_text) diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt index 04c25f382..eec01cb41 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt @@ -15,12 +15,13 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.replaceWith import org.koitharu.kotatsu.utils.ext.requireBitmap class RecentListFactory( private val context: Context, private val historyRepository: HistoryRepository, - private val coil: ImageLoader + private val coil: ImageLoader, ) : RemoteViewsService.RemoteViewsFactory { private val dataSet = ArrayList() @@ -29,7 +30,7 @@ class RecentListFactory( ) private val coverSize = Size( context.resources.getDimensionPixelSize(R.dimen.widget_cover_width), - context.resources.getDimensionPixelSize(R.dimen.widget_cover_height), + context.resources.getDimensionPixelSize(R.dimen.widget_cover_height) ) override fun onCreate() = Unit @@ -39,9 +40,8 @@ class RecentListFactory( override fun getItemId(position: Int) = dataSet[position].id override fun onDataSetChanged() { - dataSet.clear() val data = runBlocking { historyRepository.getList(0, 10) } - dataSet.addAll(data) + dataSet.replaceWith(data) } override fun hasStableIds() = true diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt index 1676e5a49..edb2eb417 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt @@ -16,6 +16,7 @@ import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.core.prefs.AppWidgetConfig import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.replaceWith import org.koitharu.kotatsu.utils.ext.requireBitmap class ShelfListFactory( @@ -32,7 +33,7 @@ class ShelfListFactory( ) private val coverSize = Size( context.resources.getDimensionPixelSize(R.dimen.widget_cover_width), - context.resources.getDimensionPixelSize(R.dimen.widget_cover_height), + context.resources.getDimensionPixelSize(R.dimen.widget_cover_height) ) override fun onCreate() = Unit @@ -42,7 +43,6 @@ class ShelfListFactory( override fun getItemId(position: Int) = dataSet[position].id override fun onDataSetChanged() { - dataSet.clear() val data = runBlocking { val category = config.categoryId if (category == 0L) { @@ -51,7 +51,7 @@ class ShelfListFactory( favouritesRepository.getManga(category) } } - dataSet.addAll(data) + dataSet.replaceWith(data) } override fun hasStableIds() = true @@ -85,4 +85,4 @@ class ShelfListFactory( override fun getViewTypeCount() = 1 override fun onDestroy() = Unit -} +} \ No newline at end of file From 14f5d5daa4890e613689676dab03baa634094a2f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 1 Aug 2022 17:14:35 +0300 Subject: [PATCH 2/6] Update parsers --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f32d10554..fb02f94c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { minSdkVersion 21 targetSdkVersion 32 versionCode 420 - versionName '3.5.0' + versionName '3.4.8' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -79,7 +79,7 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:dac06ddd0b') { + implementation('com.github.KotatsuApp:kotatsu-parsers:85bfe42ddf') { exclude group: 'org.json', module: 'json' } From ffbe05b2ae11c92a7a143e24358c2f791462edea Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 4 Aug 2022 11:32:50 +0300 Subject: [PATCH 3/6] Fix tracker for multiple branches --- .editorconfig | 1 + .../org/koitharu/kotatsu/core/model/Manga.kt | 30 +++++++++++- .../kotatsu/details/ui/DetailsActivity.kt | 17 ++++--- .../kotatsu/details/ui/DetailsFragment.kt | 49 +++++++++---------- .../details/ui/MangaDetailsDelegate.kt | 29 +---------- .../koitharu/kotatsu/tracker/TrackerModule.kt | 2 +- .../kotatsu/tracker/domain/Tracker.kt | 22 ++++++--- .../kotatsu/tracker/ui/FeedFragment.kt | 33 +++++++++---- .../kotatsu/tracker/ui/FeedViewModel.kt | 9 ++-- app/src/main/res/layout/activity_details.xml | 3 ++ 10 files changed, 113 insertions(+), 82 deletions(-) diff --git a/.editorconfig b/.editorconfig index e99fe5d60..e39c63dcd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,5 +15,6 @@ disabled_rules=no-wildcard-imports,no-unused-imports ij_continuation_indent_size = 4 [{*.kt,*.kts}] +ij_kotlin_allow_trailing_comma_on_call_site = true ij_kotlin_allow_trailing_comma = true ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt index ed5594c42..3097f0ac3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt @@ -1,6 +1,34 @@ package org.koitharu.kotatsu.core.model +import androidx.core.os.LocaleListCompat import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.parsers.util.toTitleCase +import org.koitharu.kotatsu.utils.ext.iterator -fun Collection.ids() = mapToSet { it.id } \ No newline at end of file +fun Collection.ids() = mapToSet { it.id } + +fun Manga.getPreferredBranch(history: MangaHistory?): String? { + val ch = chapters + if (ch.isNullOrEmpty()) { + return null + } + if (history != null) { + val currentChapter = ch.find { it.id == history.chapterId } + if (currentChapter != null) { + return currentChapter.branch + } + } + val groups = ch.groupBy { it.branch } + for (locale in LocaleListCompat.getAdjustedDefault()) { + var language = locale.getDisplayLanguage(locale).toTitleCase(locale) + if (groups.containsKey(language)) { + return language + } + language = locale.getDisplayName(locale).toTitleCase(locale) + if (groups.containsKey(language)) { + return language + } + } + return groups.maxByOrNull { it.value.size }?.key +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index b82206eba..ee07a41d0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -105,7 +105,7 @@ class DetailsActivity : Toast.makeText( this, getString(R.string._s_deleted_from_local_storage, manga.title), - Toast.LENGTH_SHORT + Toast.LENGTH_SHORT, ).show() finishAfterTransition() } @@ -131,7 +131,7 @@ class DetailsActivity : onActionClick = { e.report("DetailsActivity::onError") dismiss() - } + }, ) } else -> { @@ -142,14 +142,14 @@ class DetailsActivity : override fun onWindowInsetsChanged(insets: Insets) { binding.snackbar.updatePadding( - bottom = insets.bottom + bottom = insets.bottom, ) binding.toolbar.updateLayoutParams { topMargin = insets.top } binding.root.updatePadding( left = insets.left, - right = insets.right + right = insets.right, ) } @@ -159,6 +159,7 @@ class DetailsActivity : tab.removeBadge() } else { val badge = tab.orCreateBadge + badge.maxCharacterCount = 3 badge.number = newChapters badge.isVisible = true } @@ -275,8 +276,8 @@ class DetailsActivity : ReaderActivity.newIntent( context = this@DetailsActivity, manga = remoteManga, - state = ReaderState(chapterId, 0, 0) - ) + state = ReaderState(chapterId, 0, 0), + ), ) } setNeutralButton(R.string.download) { _, _ -> @@ -350,8 +351,8 @@ class DetailsActivity : dialogBuilder.setMessage( getString( R.string.large_manga_save_confirm, - resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount) - ) + resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount), + ), ).setPositiveButton(R.string.save) { _, _ -> DownloadService.start(this, manga) } 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 6b2b1afac..21c6541ea 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 @@ -2,7 +2,6 @@ package org.koitharu.kotatsu.details.ui import android.app.ActivityOptions import android.os.Bundle -import android.text.Spanned import android.text.method.LinkMovementMethod import android.view.* import androidx.appcompat.widget.PopupMenu @@ -10,18 +9,15 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.net.toFile import androidx.core.net.toUri -import androidx.core.text.parseAsHtml import androidx.core.view.MenuProvider import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updatePadding import coil.ImageLoader import coil.request.ImageRequest -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 @@ -33,6 +29,7 @@ import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.ui.BookmarksAdapter import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.databinding.FragmentDetailsBinding +import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingInfoBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.history.domain.PROGRESS_NONE @@ -82,6 +79,7 @@ class DetailsFragment : viewModel.bookmarks.observe(viewLifecycleOwner, ::onBookmarksChanged) viewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged) viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged) + viewModel.chapters.observe(viewLifecycleOwner, ::onChaptersChanged) addMenuProvider(DetailsMenuProvider()) } @@ -126,18 +124,6 @@ class DetailsFragment : else -> textViewState.isVisible = false } - // Info containers - val chapters = manga.chapters - if (chapters.isNullOrEmpty()) { - infoLayout.textViewChapters.isVisible = false - } else { - infoLayout.textViewChapters.isVisible = true - infoLayout.textViewChapters.text = resources.getQuantityString( - R.plurals.chapters, - chapters.size, - chapters.size, - ) - } if (manga.hasRating) { infoLayout.textViewRating.text = String.format("%.1f", manga.rating * 5) infoLayout.ratingContainer.isVisible = true @@ -164,14 +150,27 @@ class DetailsFragment : infoLayout.textViewNsfw.isVisible = manga.isNsfw - // Buttons - buttonRead.isEnabled = !manga.chapters.isNullOrEmpty() - // Chips bindTags(manga) } } + private fun onChaptersChanged(chapters: List?) { + val infoLayout = binding.infoLayout + if (chapters.isNullOrEmpty()) { + infoLayout.textViewChapters.isVisible = false + } else { + infoLayout.textViewChapters.isVisible = true + infoLayout.textViewChapters.text = resources.getQuantityString( + R.plurals.chapters, + chapters.size, + chapters.size, + ) + } + // Buttons + binding.buttonRead.isEnabled = !chapters.isNullOrEmpty() + } + private fun onDescriptionChanged(description: CharSequence?) { if (description.isNullOrBlank()) { binding.textViewDescription.setText(R.string.no_description) @@ -266,7 +265,7 @@ class DetailsFragment : context = context ?: return, manga = manga, branch = viewModel.selectedBranchValue, - ) + ), ) } } @@ -276,14 +275,14 @@ class DetailsFragment : context = v.context, source = manga.source, query = manga.author ?: return, - ) + ), ) } R.id.imageView_cover -> { val options = ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.width, v.height) startActivity( ImageActivity.newIntent(v.context, manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }), - options.toBundle() + options.toBundle(), ) } } @@ -309,8 +308,8 @@ class DetailsFragment : c.chapter.branch == branch }?.let { c -> ReaderState(c.chapter.id, 0, 0) - } - ) + }, + ), ) true } @@ -343,7 +342,7 @@ class DetailsFragment : icon = 0, data = tag, ) - } + }, ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt index 3a4eca7ca..9ad1accfa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt @@ -1,11 +1,11 @@ package org.koitharu.kotatsu.details.ui -import androidx.core.os.LocaleListCompat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.koitharu.kotatsu.base.domain.MangaDataRepository import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.core.model.MangaHistory +import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.details.ui.model.ChapterListItem @@ -17,8 +17,6 @@ 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.mapToSet -import org.koitharu.kotatsu.parsers.util.toTitleCase -import org.koitharu.kotatsu.utils.ext.iterator import org.koitharu.kotatsu.utils.ext.printStackTraceDebug class MangaDetailsDelegate( @@ -45,12 +43,7 @@ class MangaDetailsDelegate( manga = MangaRepository(manga.source).getDetails(manga) // find default branch val hist = historyRepository.getOne(manga) - selectedBranch.value = if (hist != null) { - val currentChapter = manga.chapters?.find { it.id == hist.chapterId } - if (currentChapter != null) currentChapter.branch else predictBranch(manga.chapters) - } else { - predictBranch(manga.chapters) - } + selectedBranch.value = manga.getPreferredBranch(hist) mangaData.value = manga relatedManga.value = runCatching { if (manga.source == MangaSource.LOCAL) { @@ -163,22 +156,4 @@ class MangaDetailsDelegate( } return result } - - private fun predictBranch(chapters: List?): String? { - if (chapters.isNullOrEmpty()) { - return null - } - val groups = chapters.groupBy { it.branch } - for (locale in LocaleListCompat.getAdjustedDefault()) { - var language = locale.getDisplayLanguage(locale).toTitleCase(locale) - if (groups.containsKey(language)) { - return language - } - language = locale.getDisplayName(locale).toTitleCase(locale) - if (groups.containsKey(language)) { - return language - } - } - return groups.maxByOrNull { it.value.size }?.key - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt index e15791927..fd6ab2ff6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/TrackerModule.kt @@ -14,7 +14,7 @@ val trackerModule factory { TrackingRepository(get()) } factory { TrackerNotificationChannels(androidContext(), get()) } - factory { Tracker(get(), get(), get()) } + factory { Tracker(get(), get(), get(), get()) } viewModel { FeedViewModel(get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/domain/Tracker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/Tracker.kt index e93902485..a585264d7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/domain/Tracker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/Tracker.kt @@ -1,8 +1,10 @@ package org.koitharu.kotatsu.tracker.domain import androidx.annotation.VisibleForTesting +import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.tracker.domain.model.MangaTracking import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates @@ -12,6 +14,7 @@ import org.koitharu.kotatsu.tracker.work.TrackingItem class Tracker( private val settings: AppSettings, private val repository: TrackingRepository, + private val historyRepository: HistoryRepository, private val channels: TrackerNotificationChannels, ) { @@ -68,7 +71,7 @@ class Tracker( suspend fun fetchUpdates(track: MangaTracking, commit: Boolean): MangaUpdates { val manga = MangaRepository(track.manga.source).getDetails(track.manga) - val updates = compare(track, manga) + val updates = compare(track, manga, getBranch(manga)) if (commit) { repository.saveUpdates(updates) } @@ -78,7 +81,7 @@ class Tracker( @VisibleForTesting suspend fun checkUpdates(manga: Manga, commit: Boolean): MangaUpdates { val track = repository.getTrack(manga) - val updates = compare(track, manga) + val updates = compare(track, manga, getBranch(manga)) if (commit) { repository.saveUpdates(updates) } @@ -90,25 +93,30 @@ class Tracker( repository.deleteTrack(mangaId) } + private suspend fun getBranch(manga: Manga): String? { + val history = historyRepository.getOne(manga) + return manga.getPreferredBranch(history) + } + /** * The main functionality of tracker: check new chapters in [manga] comparing to the [track] */ - private fun compare(track: MangaTracking, manga: Manga): MangaUpdates { + private fun compare(track: MangaTracking, manga: Manga, branch: String?): MangaUpdates { if (track.isEmpty()) { // first check or manga was empty on last check return MangaUpdates(manga, emptyList(), isValid = false) } - val chapters = requireNotNull(manga.chapters) + val chapters = requireNotNull(manga.getChapters(branch)) val newChapters = chapters.takeLastWhile { x -> x.id != track.lastChapterId } return when { newChapters.isEmpty() -> { - return MangaUpdates(manga, emptyList(), isValid = chapters.lastOrNull()?.id == track.lastChapterId) + MangaUpdates(manga, emptyList(), isValid = chapters.lastOrNull()?.id == track.lastChapterId) } newChapters.size == chapters.size -> { - return MangaUpdates(manga, emptyList(), isValid = false) + MangaUpdates(manga, emptyList(), isValid = false) } else -> { - return MangaUpdates(manga, newChapters, isValid = true) + MangaUpdates(manga, newChapters, isValid = true) } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index 3bcc46ea7..6bf0c7052 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -18,6 +18,7 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.main.ui.AppBarOwner +import org.koitharu.kotatsu.main.ui.MainActivity import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.tracker.ui.adapter.FeedAdapter @@ -25,6 +26,7 @@ import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.measureHeight +import org.koitharu.kotatsu.utils.ext.resolveDp class FeedFragment : BaseFragment(), @@ -39,7 +41,7 @@ class FeedFragment : override fun onInflateView( inflater: LayoutInflater, - container: ViewGroup? + container: ViewGroup?, ) = FragmentFeedBinding.inflate(inflater, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -54,7 +56,7 @@ class FeedFragment : paddingVertical = resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer) val decoration = TypedSpacingItemDecoration( FeedAdapter.ITEM_TYPE_FEED to 0, - fallbackSpacing = spacing + fallbackSpacing = spacing, ) addItemDecoration(decoration) } @@ -77,12 +79,25 @@ class FeedFragment : override fun onWindowInsetsChanged(insets: Insets) { val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top - binding.recyclerView.updatePadding( - top = headerHeight + paddingVertical, - left = insets.left + paddingHorizontal, - right = insets.right + paddingHorizontal, - bottom = insets.bottom + paddingVertical, + binding.root.updatePadding( + left = insets.left, + right = insets.right, ) + if (activity is MainActivity) { + binding.recyclerView.updatePadding( + top = headerHeight, + bottom = insets.bottom, + ) + binding.swipeRefreshLayout.setProgressViewOffset( + true, + headerHeight + resources.resolveDp(-72), + headerHeight + resources.resolveDp(10), + ) + } else { + binding.recyclerView.updatePadding( + bottom = insets.bottom, + ) + } } override fun onRetryClick(error: Throwable) = Unit @@ -101,7 +116,7 @@ class FeedFragment : Snackbar.make( binding.recyclerView, R.string.updates_feed_cleared, - Snackbar.LENGTH_LONG + Snackbar.LENGTH_LONG, ).show() } @@ -109,7 +124,7 @@ class FeedFragment : Snackbar.make( binding.recyclerView, e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT + Snackbar.LENGTH_SHORT, ).show() } diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt index 11ae7f6fa..e1d51c294 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt @@ -16,12 +16,13 @@ import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem import org.koitharu.kotatsu.tracker.ui.model.toFeedItem +import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.daysDiff class FeedViewModel( - private val repository: TrackingRepository + private val repository: TrackingRepository, ) : BaseViewModel() { private val logList = MutableStateFlow?>(null) @@ -32,7 +33,7 @@ class FeedViewModel( val onFeedCleared = SingleLiveEvent() val content = combine( logList.filterNotNull(), - hasNextPage + hasNextPage, ) { list, isHasNextPage -> buildList(list.size + 2) { if (list.isEmpty()) { @@ -43,7 +44,7 @@ class FeedViewModel( textPrimary = R.string.text_empty_holder_primary, textSecondary = R.string.text_feed_holder, actionStringRes = 0, - ) + ), ) } else { list.mapListTo(this) @@ -54,7 +55,7 @@ class FeedViewModel( } }.asLiveDataDistinct( viewModelScope.coroutineContext + Dispatchers.Default, - listOf(header, LoadingState) + listOf(header, LoadingState), ) init { diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index 8190e9c82..078fe60cf 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -29,6 +29,9 @@ android:layout_height="match_parent" android:layout_gravity="center_horizontal" android:background="@null" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingHorizontal="@dimen/margin_normal" app:tabGravity="center" app:tabMode="scrollable" /> From c7a97711c0082ab3b48162ff1d6a2076e1d14885 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 4 Aug 2022 11:55:59 +0300 Subject: [PATCH 4/6] Optimize chapters mapping --- .../details/ui/MangaDetailsDelegate.kt | 9 ++++++-- .../details/ui/model/ChapterListItem.kt | 21 +++++++++++++++---- .../ui/model/ListModelConversionExt.kt | 5 +++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt index 9ad1accfa..49bbde5ce 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt @@ -16,7 +16,6 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException 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.mapToSet import org.koitharu.kotatsu.utils.ext.printStackTraceDebug class MangaDetailsDelegate( @@ -84,7 +83,7 @@ class MangaDetailsDelegate( val dateFormat = settings.getDateFormat() val currentIndex = chapters.indexOfFirst { it.id == currentId } val firstNewIndex = chapters.size - newCount - val downloadedIds = downloadedChapters?.mapToSet { it.id } + val downloadedIds = downloadedChapters?.mapTo(HashSet(downloadedChapters.size)) { it.id } for (i in chapters.indices) { val chapter = chapters[i] if (chapter.branch != branch) { @@ -99,6 +98,9 @@ class MangaDetailsDelegate( dateFormat = dateFormat, ) } + if (result.size < chapters.size / 2) { + result.trimToSize() + } return result } @@ -154,6 +156,9 @@ class MangaDetailsDelegate( } result.sortBy { it.chapter.number } } + if (result.size < sourceChapters.size / 2) { + result.trimToSize() + } return result } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt index 2d5b90840..c3e39efec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt @@ -1,13 +1,24 @@ package org.koitharu.kotatsu.details.ui.model +import java.text.DateFormat import org.koitharu.kotatsu.parsers.model.MangaChapter class ChapterListItem( val chapter: MangaChapter, val flags: Int, - val uploadDate: String?, + private val uploadDateMs: Long, + private val dateFormat: DateFormat, ) { + var uploadDate: String? = null + private set + get() { + if (field != null) return field + if (uploadDateMs == 0L) return null + field = dateFormat.format(uploadDateMs) + return field + } + val status: Int get() = flags and MASK_STATUS @@ -32,7 +43,8 @@ class ChapterListItem( if (chapter != other.chapter) return false if (flags != other.flags) return false - if (uploadDate != other.uploadDate) return false + if (uploadDateMs != other.uploadDateMs) return false + if (dateFormat != other.dateFormat) return false return true } @@ -40,7 +52,8 @@ class ChapterListItem( override fun hashCode(): Int { var result = chapter.hashCode() result = 31 * result + flags - result = 31 * result + (uploadDate?.hashCode() ?: 0) + result = 31 * result + uploadDateMs.hashCode() + result = 31 * result + dateFormat.hashCode() return result } @@ -53,4 +66,4 @@ class ChapterListItem( const val FLAG_DOWNLOADED = 32 const val MASK_STATUS = FLAG_UNREAD or FLAG_CURRENT } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt index 22d272346..e15669f94 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt @@ -1,12 +1,12 @@ package org.koitharu.kotatsu.details.ui.model +import java.text.DateFormat import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_MISSING import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_NEW import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_UNREAD import org.koitharu.kotatsu.parsers.model.MangaChapter -import java.text.DateFormat fun MangaChapter.toListItem( isCurrent: Boolean, @@ -25,6 +25,7 @@ fun MangaChapter.toListItem( return ChapterListItem( chapter = this, flags = flags, - uploadDate = if (uploadDate != 0L) dateFormat.format(uploadDate) else null + uploadDateMs = uploadDate, + dateFormat = dateFormat, ) } \ No newline at end of file From 85af73df9916759d3a039fc473d2eeaae235d35f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 4 Aug 2022 13:43:06 +0300 Subject: [PATCH 5/6] Update dependencies --- app/build.gradle | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fb02f94c4..09247a335 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,19 +79,19 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:85bfe42ddf') { + implementation('com.github.KotatsuApp:kotatsu-parsers:e2308214a7') { exclude group: 'org.json', module: 'json' } implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' implementation 'androidx.core:core-ktx:1.8.0' - 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.activity:activity-ktx:1.5.1' + implementation 'androidx.fragment:fragment-ktx:1.5.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-service:2.5.1' + implementation 'androidx.lifecycle:lifecycle-process:2.5.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' @@ -101,11 +101,11 @@ dependencies { implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04' implementation 'com.google.android.material:material:1.7.0-alpha03' //noinspection LifecycleAnnotationProcessorWithJava8 - kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0' + kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1' - implementation 'androidx.room:room-runtime:2.4.2' - implementation 'androidx.room:room-ktx:2.4.2' - kapt 'androidx.room:room-compiler:2.4.2' + implementation 'androidx.room:room-runtime:2.4.3' + implementation 'androidx.room:room-ktx:2.4.3' + kapt 'androidx.room:room-compiler:2.4.3' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' @@ -137,6 +137,6 @@ dependencies { androidTestImplementation 'io.insert-koin:koin-test:3.2.0' androidTestImplementation 'io.insert-koin:koin-test-junit4:3.2.0' - androidTestImplementation 'androidx.room:room-testing:2.4.2' + androidTestImplementation 'androidx.room:room-testing:2.4.3' androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.13.0' } \ No newline at end of file From dff17fd11f40f96abe610e0a488c371ad90825cd Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 6 Aug 2022 18:37:25 +0300 Subject: [PATCH 6/6] Change BaseSavedState to AbsSavedState --- .../kotatsu/base/ui/widgets/CheckableImageView.kt | 8 +++++--- .../kotatsu/settings/utils/SliderPreference.kt | 13 +++++++------ .../org/koitharu/kotatsu/utils/ext/ThrowableExt.kt | 8 +++++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt index 5d601d67c..2d18292cc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/CheckableImageView.kt @@ -10,6 +10,7 @@ import android.widget.Checkable import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatImageView import androidx.core.os.ParcelCompat +import androidx.customview.view.AbsSavedState class CheckableImageView @JvmOverloads constructor( context: Context, @@ -73,7 +74,7 @@ class CheckableImageView @JvmOverloads constructor( fun onCheckedChanged(view: CheckableImageView, isChecked: Boolean) } - private class SavedState : BaseSavedState { + private class SavedState : AbsSavedState { val isChecked: Boolean @@ -81,7 +82,7 @@ class CheckableImageView @JvmOverloads constructor( isChecked = checked } - constructor(source: Parcel) : super(source) { + constructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) { isChecked = ParcelCompat.readBoolean(source) } @@ -91,9 +92,10 @@ class CheckableImageView @JvmOverloads constructor( } companion object { + @Suppress("unused") @JvmField val CREATOR: Creator = object : Creator { - override fun createFromParcel(`in`: Parcel) = SavedState(`in`) + override fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader) override fun newArray(size: Int): Array = arrayOfNulls(size) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt index 0933fe1d0..c0a76a8ac 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt @@ -5,8 +5,8 @@ import android.content.res.TypedArray import android.os.Parcel import android.os.Parcelable import android.util.AttributeSet -import android.view.View import androidx.core.content.withStyledAttributes +import androidx.customview.view.AbsSavedState import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.google.android.material.slider.Slider @@ -40,11 +40,11 @@ class SliderPreference @JvmOverloads constructor( attrs, R.styleable.SliderPreference, defStyleAttr, - defStyleRes + defStyleRes, ) { valueFrom = getFloat( R.styleable.SliderPreference_android_valueFrom, - valueFrom.toFloat() + valueFrom.toFloat(), ).toInt() valueTo = getFloat(R.styleable.SliderPreference_android_valueTo, valueTo.toFloat()).toInt() @@ -117,7 +117,7 @@ class SliderPreference @JvmOverloads constructor( } } - private class SavedState : View.BaseSavedState { + private class SavedState : AbsSavedState { val valueFrom: Int val valueTo: Int @@ -134,7 +134,7 @@ class SliderPreference @JvmOverloads constructor( this.currentValue = currentValue } - constructor(source: Parcel) : super(source) { + constructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) { valueFrom = source.readInt() valueTo = source.readInt() currentValue = source.readInt() @@ -148,9 +148,10 @@ class SliderPreference @JvmOverloads constructor( } companion object { + @Suppress("unused") @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(`in`: Parcel) = SavedState(`in`) + override fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader) override fun newArray(size: Int): Array = arrayOfNulls(size) } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt index 88d60f45c..4597d7d6c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.utils.ext import android.content.ActivityNotFoundException import android.content.res.Resources +import java.net.SocketTimeoutException import okio.FileNotFoundException import org.acra.ktx.sendWithAcra import org.koitharu.kotatsu.R @@ -10,13 +11,13 @@ import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.ParseException -import java.net.SocketTimeoutException fun Throwable.getDisplayMessage(resources: Resources): String = when (this) { is AuthRequiredException -> resources.getString(R.string.auth_required) is CloudFlareProtectedException -> resources.getString(R.string.captcha_required) is ActivityNotFoundException, - is UnsupportedOperationException -> resources.getString(R.string.operation_not_supported) + is UnsupportedOperationException, + -> resources.getString(R.string.operation_not_supported) is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported) is FileNotFoundException -> resources.getString(R.string.file_not_found) is EmptyHistoryException -> resources.getString(R.string.history_is_empty) @@ -37,5 +38,6 @@ fun Throwable.isReportable(): Boolean { } fun Throwable.report(message: String?) { - CaughtException(this, message).sendWithAcra() + val exception = CaughtException(this, message) + exception.sendWithAcra() } \ No newline at end of file