diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d7527cf..a2d2590 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,6 +101,7 @@ + @@ -469,34 +470,1180 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/kotlin/org/dokiteam/doki/core/parser/MangaLinkResolver.kt b/app/src/main/kotlin/org/dokiteam/doki/core/parser/MangaLinkResolver.kt index 030d0de..8c03647 100644 --- a/app/src/main/kotlin/org/dokiteam/doki/core/parser/MangaLinkResolver.kt +++ b/app/src/main/kotlin/org/dokiteam/doki/core/parser/MangaLinkResolver.kt @@ -118,7 +118,9 @@ class MangaLinkResolver @Inject constructor( companion object { fun isValidLink(str: String): Boolean { - return str.isHttpUrl() || str.startsWith("doki://", ignoreCase = true) + return str.isHttpUrl() || + str.startsWith("doki://", ignoreCase = true) || + str.startsWith("kotatsu://", ignoreCase = true) } } } diff --git a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/anilist/data/AniListRepository.kt b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/anilist/data/AniListRepository.kt index cfddde7..b8c2c97 100644 --- a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/anilist/data/AniListRepository.kt +++ b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/anilist/data/AniListRepository.kt @@ -28,7 +28,7 @@ import javax.inject.Inject import javax.inject.Singleton import kotlin.math.roundToInt -private const val REDIRECT_URI = "doki://anilist-auth" +private const val REDIRECT_URI = "kotatsu://anilist-auth" private const val BASE_URL = "https://anilist.co/api/v2/" private const val ENDPOINT = "https://graphql.anilist.co" private const val MANGA_PAGE_SIZE = 10 diff --git a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/kitsu/ui/KitsuAuthActivity.kt b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/kitsu/ui/KitsuAuthActivity.kt index f9ce976..968f330 100644 --- a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/kitsu/ui/KitsuAuthActivity.kt +++ b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/kitsu/ui/KitsuAuthActivity.kt @@ -103,7 +103,7 @@ class KitsuAuthActivity : BaseActivity(), private fun continueAuth() { val email = viewBinding.editEmail.text?.toString()?.trim().orEmpty() val password = viewBinding.editPassword.text?.toString()?.trim().orEmpty() - val url = "doki://kitsu-auth?code=" + "$email;$password".urlEncoded() + val url = "kotatsu://kitsu-auth?code=" + "$email;$password".urlEncoded() val intent = Intent(Intent.ACTION_VIEW, url.toUri()) startActivity(intent) finishAfterTransition() diff --git a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/mal/data/MALRepository.kt b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/mal/data/MALRepository.kt index 0b5f467..19b64dc 100644 --- a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/mal/data/MALRepository.kt +++ b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/mal/data/MALRepository.kt @@ -26,7 +26,7 @@ import java.security.SecureRandom import javax.inject.Inject import javax.inject.Singleton -private const val REDIRECT_URI = "doki://mal-auth" +private const val REDIRECT_URI = "kotatsu://mal-auth" private const val BASE_WEB_URL = "https://myanimelist.net" private const val BASE_API_URL = "https://api.myanimelist.net/v2" diff --git a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/shikimori/data/ShikimoriRepository.kt b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/shikimori/data/ShikimoriRepository.kt index 3aae7d6..9cc527e 100644 --- a/app/src/main/kotlin/org/dokiteam/doki/scrobbling/shikimori/data/ShikimoriRepository.kt +++ b/app/src/main/kotlin/org/dokiteam/doki/scrobbling/shikimori/data/ShikimoriRepository.kt @@ -28,7 +28,7 @@ import javax.inject.Inject import javax.inject.Singleton private const val DOMAIN = "shikimori.one" -private const val REDIRECT_URI = "doki://shikimori-auth" +private const val REDIRECT_URI = "kotatsu://shikimori-auth" private const val BASE_URL = "https://$DOMAIN/" private const val MANGA_PAGE_SIZE = 10 diff --git a/app/src/main/kotlin/org/dokiteam/doki/tracker/work/TrackWorker.kt b/app/src/main/kotlin/org/dokiteam/doki/tracker/work/TrackWorker.kt index f15a619..951985e 100644 --- a/app/src/main/kotlin/org/dokiteam/doki/tracker/work/TrackWorker.kt +++ b/app/src/main/kotlin/org/dokiteam/doki/tracker/work/TrackWorker.kt @@ -33,9 +33,8 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit @@ -95,7 +94,7 @@ class TrackWorker @AssistedInject constructor( doWorkImpl(isFullRun = isForeground && TAG_ONESHOT in tags) } catch (e: CancellationException) { throw e - } catch (e: Throwable) { + } catch (e: Throwable) { e.printStackTraceDebug() Result.failure() } finally { @@ -105,74 +104,102 @@ class TrackWorker @AssistedInject constructor( } } - private suspend fun doWorkImpl(isFullRun: Boolean): Result { - if (!settings.isTrackerEnabled) { - return Result.success() - } - val tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE) - if (tracks.isEmpty()) { - return Result.success() - } + private suspend fun doWorkImpl(isFullRun: Boolean): Result { + if (!settings.isTrackerEnabled) { + return Result.success() + } + val tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE) + if (tracks.isEmpty()) { + return Result.success() + } - val notifications = checkUpdatesAsync(tracks) - if (notifications.isNotEmpty() && applicationContext.checkNotificationPermission(null)) { - val groupNotification = notificationHelper.createGroupNotification(notifications) - notifications.forEach { notificationManager.notify(it.tag, it.id, it.notification) } - if (groupNotification != null) { - notificationManager.notify(TAG, TrackerNotificationHelper.GROUP_NOTIFICATION_ID, groupNotification) - } - } - return Result.success() - } + checkUpdatesAsync(tracks) + return Result.success() + } - @CheckResult - private suspend fun checkUpdatesAsync(tracks: List): List { - val semaphore = Semaphore(MAX_PARALLELISM) - return channelFlow { - for (track in tracks) { - launch { - semaphore.withPermit { - send( - runCatchingCancellable { - checkNewChaptersUseCase.invoke(track) - }.getOrElse { error -> - MangaUpdates.Failure( - manga = track.manga, - error = error, - ) - }, - ) - } - } - } - }.onEachIndexed { index, it -> - if (applicationContext.checkNotificationPermission(WORKER_CHANNEL_ID)) { - notificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1)) - } - when (it) { - is MangaUpdates.Failure -> { - val e = it.error - if (e is CloudFlareException) { - captchaHandler.handle(e) - } - } + @CheckResult + private suspend fun checkUpdatesAsync(tracks: List) { + val semaphore = Semaphore(MAX_PARALLELISM) + val groupNotifications = mutableListOf() - is MangaUpdates.Success -> processDownload(it) - } - }.mapNotNull { - when (it) { - is MangaUpdates.Failure -> null - is MangaUpdates.Success -> if (it.isValid && it.isNotEmpty()) { - notificationHelper.createNotification( - manga = it.manga, - newChapters = it.newChapters, - ) - } else { - null - } - } - }.toList() - } + try { + channelFlow { + for (track in tracks) { + launch { + semaphore.withPermit { + send( + runCatchingCancellable { + checkNewChaptersUseCase.invoke(track) + }.getOrElse { error -> + MangaUpdates.Failure( + manga = track.manga, + error = error, + ) + }, + ) + } + } + } + }.onEachIndexed { index, it -> + if (applicationContext.checkNotificationPermission(WORKER_CHANNEL_ID)) { + notificationManager.notify( + WORKER_NOTIFICATION_ID, + createWorkerNotification(tracks.size, index + 1) + ) + } + + when (it) { + is MangaUpdates.Failure -> { + val e = it.error + if (e is CloudFlareException) { + captchaHandler.handle(e) + } + } + + is MangaUpdates.Success -> { + processDownload(it) + + if (it.isValid && it.isNotEmpty()) { + val notificationInfo = notificationHelper.createNotification( + manga = it.manga, + newChapters = it.newChapters, + ) + + if (notificationInfo != null && + applicationContext.checkNotificationPermission(TrackerNotificationHelper.CHANNEL_ID)) { + notificationManager.notify( + notificationInfo.tag, + notificationInfo.id, + notificationInfo.notification + ) + + synchronized(groupNotifications) { + groupNotifications.add(notificationInfo) + } + } + } + } + } + }.collect() + + } catch (e: CancellationException) { + e.printStackTraceDebug() + } finally { + withContext(NonCancellable) { + if (groupNotifications.size > 1 && + applicationContext.checkNotificationPermission(TrackerNotificationHelper.CHANNEL_ID)) { + val groupNotification = notificationHelper.createGroupNotification(groupNotifications) + if (groupNotification != null) { + notificationManager.notify( + TAG, + TrackerNotificationHelper.GROUP_NOTIFICATION_ID, + groupNotification + ) + } + } + } + } + } override suspend fun getForegroundInfo(): ForegroundInfo { val channel = NotificationChannelCompat.Builder(