diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt index ab78e7d5a..f3334a338 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt @@ -60,7 +60,7 @@ class ScrobblingSelectorSheet : lateinit var coil: ImageLoader private var collapsibleActionViewCallback: CollapseActionViewCallback? = null - + private var paginationScrollListener: PaginationScrollListener? = null private val viewModel by viewModels() override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingSelectorBinding { @@ -77,7 +77,11 @@ class ScrobblingSelectorSheet : adapter = listAdapter addItemDecoration(decoration) addItemDecoration(TypedListSpacingDecoration(context, false)) - addOnScrollListener(PaginationScrollListener(4, this@ScrobblingSelectorSheet)) + addOnScrollListener( + PaginationScrollListener(4, this@ScrobblingSelectorSheet).also { + paginationScrollListener = it + }, + ) } binding.buttonDone.setOnClickListener(this) initOptionsMenu() @@ -112,6 +116,7 @@ class ScrobblingSelectorSheet : override fun onDestroyView() { super.onDestroyView() collapsibleActionViewCallback = null + paginationScrollListener = null } override fun onCurrentListChanged(previousList: MutableList, currentList: MutableList) { @@ -124,6 +129,7 @@ class ScrobblingSelectorSheet : currentList.indexOfFirst { it is ScrobblerManga && it.id == selectedId }.coerceAtLeast(0) } rv.post(RecyclerViewScrollCallback(rv, target, if (target == 0) 0 else rv.height / 3)) + paginationScrollListener?.postInvalidate(rv) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt index 0542c61d2..d6c016482 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R @@ -59,7 +58,7 @@ class ScrobblingSelectorViewModel @Inject constructor( get() = availableScrobblers[selectedScrobblerIndex.requireValue()] val content: StateFlow> = combine( - scrobblerMangaList.map { it.distinctBy { x -> x.id } }, + scrobblerMangaList, listError, hasNextPage, ) { list, error, isHasNextPage -> @@ -127,14 +126,17 @@ class ScrobblingSelectorViewModel @Inject constructor( runCatchingCancellable { currentScrobbler.findManga(checkNotNull(searchQuery.value), offset) }.onSuccess { list -> - if (!append) { - scrobblerMangaList.value = list - } else if (list.isNotEmpty()) { - scrobblerMangaList.value += list - } - hasNextPage.value = list.isNotEmpty() + val newList = (if (append) { + scrobblerMangaList.value + list + } else { + list + }).distinctBy { x -> x.id } + val changed = newList != scrobblerMangaList.value + scrobblerMangaList.value = newList + hasNextPage.value = changed && newList.isNotEmpty() }.onFailure { error -> error.printStackTraceDebug() + hasNextPage.value = false listError.value = error } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt index 302fce4f6..9fe5b3f35 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt @@ -1,8 +1,14 @@ package org.koitharu.kotatsu.scrobbling.kitsu.data import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Response +import okhttp3.internal.closeQuietly +import okio.IOException import org.koitharu.kotatsu.core.network.CommonHeaders +import org.koitharu.kotatsu.parsers.util.mimeType +import org.koitharu.kotatsu.parsers.util.parseHtml +import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService @@ -23,13 +29,23 @@ class KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor { } val response = chain.proceed(request.build()) if (!isAuthRequest && response.code == HttpURLConnection.HTTP_UNAUTHORIZED) { + response.closeQuietly() throw ScrobblerAuthRequiredException(ScrobblerService.KITSU) } + if (response.mimeType?.toMediaTypeOrNull()?.subtype == SUBTYPE_HTML) { + val message = runCatchingCancellable { + response.parseHtml().title().takeUnless { it.isEmpty() } + }.onFailure { + response.closeQuietly() + }.getOrNull() ?: "Invalid response (${response.code})" + throw IOException(message) + } return response } companion object { const val VND_JSON = "application/vnd.api+json" + const val SUBTYPE_HTML = "html" } } diff --git a/app/src/main/res/layout/activity_kitsu_auth.xml b/app/src/main/res/layout/activity_kitsu_auth.xml index b2d1eecb4..45de42782 100644 --- a/app/src/main/res/layout/activity_kitsu_auth.xml +++ b/app/src/main/res/layout/activity_kitsu_auth.xml @@ -56,12 +56,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="emailAddress" + android:hint="@string/email" android:imeOptions="actionDone" android:inputType="textEmailAddress" android:maxLength="512" android:singleLine="true" - android:textSize="16sp" - tools:hint="Email" /> + android:textSize="16sp" /> @@ -83,12 +83,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="password" + android:hint="@string/password" android:imeOptions="actionDone" android:inputType="textPassword" android:maxLength="512" android:singleLine="true" - android:textSize="16sp" - tools:hint="Password" /> + android:textSize="16sp" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8063c02f..8e8ba530a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -767,4 +767,5 @@ Automatically delete old backup files to save storage space Handle links Handle manga links from external applications (e.g. web browser). You may also need to enable it manually in the application\'s system settings + Email