Remove manga from trackers (#9535)
* Dialog for service tracker removal added, anilist query prepared * added API delete requests for Mal and Kitsu * implement and fix tracker delete for anilist, shikimori, mangaupdates * implement and test mal delete request * Update to dialog text to reflect current tracker * finish kitsu api request and block bangumi tracker removal * Change delete flag into interface, localise strings, clean up logs * Add shikimori delete compatibility for already existing entries * update track delete dialog prompt to include checkbox, update strings * Update i18n/src/main/res/values/strings.xml Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> * Update i18n/src/main/res/values/strings.xml --------- Co-authored-by: unknown <zaghdane@fireflow.de> Co-authored-by: arkon <arkon@users.noreply.github.com> Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
parent
7f0ed58b54
commit
b36b3bfcab
13 changed files with 258 additions and 24 deletions
|
@ -0,0 +1,11 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For track services api that support deleting a manga entry for a user's list
|
||||||
|
*/
|
||||||
|
interface DeletableTrackService {
|
||||||
|
|
||||||
|
suspend fun delete(track: Track): Track
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import android.graphics.Color
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
@ -12,7 +13,7 @@ import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class Anilist(id: Long) : TrackService(id) {
|
class Anilist(id: Long) : TrackService(id), DeletableTrackService {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -167,6 +168,15 @@ class Anilist(id: Long) : TrackService(id) {
|
||||||
return api.updateLibManga(track)
|
return api.updateLibManga(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(track: Track): Track {
|
||||||
|
if (track.library_id == null || track.library_id!! == 0L) {
|
||||||
|
val libManga = api.findLibManga(track, getUsername().toInt()) ?: return track
|
||||||
|
track.library_id = libManga.library_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.deleteLibManga(track)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
val remoteTrack = api.findLibManga(track, getUsername().toInt())
|
val remoteTrack = api.findLibManga(track, getUsername().toInt())
|
||||||
return if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
|
|
|
@ -110,6 +110,27 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteLibManga(track: Track): Track {
|
||||||
|
return withIOContext {
|
||||||
|
val query = """
|
||||||
|
|mutation DeleteManga(${'$'}listId: Int) {
|
||||||
|
|DeleteMediaListEntry(id: ${'$'}listId) {
|
||||||
|
|deleted
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
val payload = buildJsonObject {
|
||||||
|
put("query", query)
|
||||||
|
putJsonObject("variables") {
|
||||||
|
put("listId", track.library_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
|
.awaitSuccess()
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
suspend fun search(search: String): List<TrackSearch> {
|
suspend fun search(search: String): List<TrackSearch> {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val query = """
|
val query = """
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.graphics.Color
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
@ -12,7 +13,7 @@ import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
class Kitsu(id: Long) : TrackService(id) {
|
class Kitsu(id: Long) : TrackService(id), DeletableTrackService {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -93,6 +94,10 @@ class Kitsu(id: Long) : TrackService(id) {
|
||||||
return api.updateLibManga(track)
|
return api.updateLibManga(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(track: Track): Track {
|
||||||
|
return api.removeLibManga(track)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
val remoteTrack = api.findLibManga(track, getUserId())
|
val remoteTrack = api.findLibManga(track, getUserId())
|
||||||
return if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.track.kitsu
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
|
@ -123,6 +124,21 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun removeLibManga(track: Track): Track {
|
||||||
|
return withIOContext {
|
||||||
|
authClient.newCall(
|
||||||
|
DELETE(
|
||||||
|
"${baseUrl}library-entries/${track.media_id}",
|
||||||
|
headers = headersOf(
|
||||||
|
"Content-Type",
|
||||||
|
"application/vnd.api+json",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
suspend fun search(query: String): List<TrackSearch> {
|
suspend fun search(query: String): List<TrackSearch> {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
|
|
|
@ -4,12 +4,13 @@ import android.graphics.Color
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
|
||||||
class MangaUpdates(id: Long) : TrackService(id) {
|
class MangaUpdates(id: Long) : TrackService(id), DeletableTrackService {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING_LIST = 0
|
const val READING_LIST = 0
|
||||||
|
@ -66,6 +67,11 @@ class MangaUpdates(id: Long) : TrackService(id) {
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(track: Track): Track {
|
||||||
|
api.deleteSeriesFromList(track)
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
return try {
|
return try {
|
||||||
val (series, rating) = api.getSeriesListItem(track)
|
val (series, rating) = api.getSeriesListItem(track)
|
||||||
|
|
|
@ -106,6 +106,19 @@ class MangaUpdatesApi(
|
||||||
updateSeriesRating(track)
|
updateSeriesRating(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteSeriesFromList(track: Track) {
|
||||||
|
val body = buildJsonArray {
|
||||||
|
add(track.media_id)
|
||||||
|
}
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
url = "$baseUrl/v1/lists/series/delete",
|
||||||
|
body = body.toString().toRequestBody(contentType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getSeriesRating(track: Track): Rating? {
|
private suspend fun getSeriesRating(track: Track): Rating? {
|
||||||
return try {
|
return try {
|
||||||
with(json) {
|
with(json) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.graphics.Color
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
@ -11,7 +12,7 @@ import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MyAnimeList(id: Long) : TrackService(id) {
|
class MyAnimeList(id: Long) : TrackService(id), DeletableTrackService {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -90,6 +91,10 @@ class MyAnimeList(id: Long) : TrackService(id) {
|
||||||
return api.updateItem(track)
|
return api.updateItem(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(track: Track): Track {
|
||||||
|
return api.deleteItem(track)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
val remoteTrack = api.findListItem(track)
|
val remoteTrack = api.findListItem(track)
|
||||||
return if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
|
|
|
@ -158,6 +158,20 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteItem(track: Track): Track {
|
||||||
|
return withIOContext {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(mangaUrl(track.media_id).toString())
|
||||||
|
.delete()
|
||||||
|
.build()
|
||||||
|
with(json) {
|
||||||
|
authClient.newCall(request)
|
||||||
|
.awaitSuccess()
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun findListItem(track: Track): Track? {
|
suspend fun findListItem(track: Track): Track? {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val uri = "$baseApiUrl/manga".toUri().buildUpon()
|
val uri = "$baseApiUrl/manga".toUri().buildUpon()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.graphics.Color
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
@ -11,7 +12,7 @@ import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class Shikimori(id: Long) : TrackService(id) {
|
class Shikimori(id: Long) : TrackService(id), DeletableTrackService {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -57,6 +58,10 @@ class Shikimori(id: Long) : TrackService(id) {
|
||||||
return api.updateLibManga(track, getUsername())
|
return api.updateLibManga(track, getUsername())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(track: Track): Track {
|
||||||
|
return api.deleteLibManga(track)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
val remoteTrack = api.findLibManga(track, getUsername())
|
val remoteTrack = api.findLibManga(track, getUsername())
|
||||||
return if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
|
@ -83,6 +88,7 @@ class Shikimori(id: Long) : TrackService(id) {
|
||||||
|
|
||||||
override suspend fun refresh(track: Track): Track {
|
override suspend fun refresh(track: Track): Track {
|
||||||
api.findLibManga(track, getUsername())?.let { remoteTrack ->
|
api.findLibManga(track, getUsername())?.let { remoteTrack ->
|
||||||
|
track.library_id = remoteTrack.library_id
|
||||||
track.copyPersonalFrom(remoteTrack)
|
track.copyPersonalFrom(remoteTrack)
|
||||||
track.total_chapters = remoteTrack.total_chapters
|
track.total_chapters = remoteTrack.total_chapters
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
|
@ -35,6 +36,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track, user_id: String): Track {
|
suspend fun addLibManga(track: Track, user_id: String): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
|
with(json) {
|
||||||
val payload = buildJsonObject {
|
val payload = buildJsonObject {
|
||||||
putJsonObject("user_rate") {
|
putJsonObject("user_rate") {
|
||||||
put("user_id", user_id)
|
put("user_id", user_id)
|
||||||
|
@ -51,12 +53,28 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
body = payload.toString().toRequestBody(jsonMime),
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
),
|
),
|
||||||
).awaitSuccess()
|
).awaitSuccess()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
.let {
|
||||||
|
track.library_id = it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request
|
||||||
|
}
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
|
suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
|
||||||
|
|
||||||
|
suspend fun deleteLibManga(track: Track): Track {
|
||||||
|
return withIOContext {
|
||||||
|
authClient.newCall(
|
||||||
|
DELETE(
|
||||||
|
"$apiUrl/v2/user_rates/${track.library_id}",
|
||||||
|
),
|
||||||
|
).awaitSuccess()
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun search(search: String): List<TrackSearch> {
|
suspend fun search(search: String): List<TrackSearch> {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val url = "$apiUrl/mangas".toUri().buildUpon()
|
val url = "$apiUrl/mangas".toUri().buildUpon()
|
||||||
|
@ -96,6 +114,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
title = mangas["name"]!!.jsonPrimitive.content
|
title = mangas["name"]!!.jsonPrimitive.content
|
||||||
media_id = obj["id"]!!.jsonPrimitive.long
|
media_id = obj["id"]!!.jsonPrimitive.long
|
||||||
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
||||||
|
library_id = obj["id"]!!.jsonPrimitive.long
|
||||||
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
||||||
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
||||||
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
|
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.manga.track
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -48,6 +50,7 @@ import eu.kanade.presentation.track.TrackServiceSearch
|
||||||
import eu.kanade.presentation.track.TrackStatusSelector
|
import eu.kanade.presentation.track.TrackStatusSelector
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
|
@ -157,7 +160,16 @@ data class TrackInfoDialogHomeScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onOpenInBrowser = { openTrackerInBrowser(context, it) },
|
onOpenInBrowser = { openTrackerInBrowser(context, it) },
|
||||||
) { sm.unregisterTracking(it.service.id) }
|
onRemoved = {
|
||||||
|
navigator.push(
|
||||||
|
TrackServiceRemoveScreen(
|
||||||
|
mangaId = mangaId,
|
||||||
|
track = it.track!!,
|
||||||
|
serviceId = it.service.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,7 +186,6 @@ data class TrackInfoDialogHomeScreen(
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
private val getTracks: GetTracks = Injekt.get(),
|
private val getTracks: GetTracks = Injekt.get(),
|
||||||
private val deleteTrack: DeleteTrack = Injekt.get(),
|
|
||||||
) : StateScreenModel<Model.State>(State()) {
|
) : StateScreenModel<Model.State>(State()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -204,10 +215,6 @@ data class TrackInfoDialogHomeScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterTracking(serviceId: Long) {
|
|
||||||
coroutineScope.launchNonCancellable { deleteTrack.await(mangaId, serviceId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun refreshTrackers() {
|
private suspend fun refreshTrackers() {
|
||||||
val insertTrack = Injekt.get<InsertTrack>()
|
val insertTrack = Injekt.get<InsertTrack>()
|
||||||
val getMangaWithChapters = Injekt.get<GetMangaWithChapters>()
|
val getMangaWithChapters = Injekt.get<GetMangaWithChapters>()
|
||||||
|
@ -723,3 +730,100 @@ data class TrackServiceSearchScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class TrackServiceRemoveScreen(
|
||||||
|
private val mangaId: Long,
|
||||||
|
private val track: Track,
|
||||||
|
private val serviceId: Long,
|
||||||
|
) : Screen() {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val sm = rememberScreenModel {
|
||||||
|
Model(
|
||||||
|
mangaId = mangaId,
|
||||||
|
track = track,
|
||||||
|
service = Injekt.get<TrackManager>().getService(serviceId)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val serviceName = stringResource(sm.getServiceNameRes())
|
||||||
|
var removeRemoteTrack by remember { mutableStateOf(false) }
|
||||||
|
AlertDialogContent(
|
||||||
|
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.track_delete_title, serviceName),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.track_delete_text, serviceName),
|
||||||
|
)
|
||||||
|
if (sm.isServiceDeletable()) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(checked = removeRemoteTrack, onCheckedChange = { removeRemoteTrack = it })
|
||||||
|
Text(text = stringResource(R.string.track_delete_remote_text, serviceName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttons = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
MaterialTheme.padding.small,
|
||||||
|
Alignment.End,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
TextButton(onClick = navigator::pop) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
sm.unregisterTracking(serviceId)
|
||||||
|
if (removeRemoteTrack) sm.deleteMangaFromService()
|
||||||
|
navigator.pop()
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Model(
|
||||||
|
private val mangaId: Long,
|
||||||
|
private val track: Track,
|
||||||
|
private val service: TrackService,
|
||||||
|
private val deleteTrack: DeleteTrack = Injekt.get(),
|
||||||
|
) : ScreenModel {
|
||||||
|
|
||||||
|
fun getServiceNameRes() = service.nameRes()
|
||||||
|
|
||||||
|
fun isServiceDeletable() = service is DeletableTrackService
|
||||||
|
|
||||||
|
fun deleteMangaFromService() {
|
||||||
|
coroutineScope.launchNonCancellable {
|
||||||
|
(service as DeletableTrackService).delete(track.toDbTrack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterTracking(serviceId: Long) {
|
||||||
|
coroutineScope.launchNonCancellable { deleteTrack.await(mangaId, serviceId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
<string name="action_pin">Pin</string>
|
<string name="action_pin">Pin</string>
|
||||||
<string name="action_unpin">Unpin</string>
|
<string name="action_unpin">Unpin</string>
|
||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
|
<string name="action_ok">OK</string>
|
||||||
<string name="action_cancel_all">Cancel all</string>
|
<string name="action_cancel_all">Cancel all</string>
|
||||||
<string name="cancel_all_for_series">Cancel all for this series</string>
|
<string name="cancel_all_for_series">Cancel all for this series</string>
|
||||||
<string name="action_sort">Sort</string>
|
<string name="action_sort">Sort</string>
|
||||||
|
@ -754,6 +755,9 @@
|
||||||
<string name="track_remove_date_conf_title">Remove date?</string>
|
<string name="track_remove_date_conf_title">Remove date?</string>
|
||||||
<string name="track_remove_start_date_conf_text">This will remove your previously selected start date from %s</string>
|
<string name="track_remove_start_date_conf_text">This will remove your previously selected start date from %s</string>
|
||||||
<string name="track_remove_finish_date_conf_text">This will remove your previously selected finish date from %s</string>
|
<string name="track_remove_finish_date_conf_text">This will remove your previously selected finish date from %s</string>
|
||||||
|
<string name="track_delete_title">Remove %s tracking?</string>
|
||||||
|
<string name="track_delete_text">This will remove the tracking locally.</string>
|
||||||
|
<string name="track_delete_remote_text">Also remove from %s</string>
|
||||||
|
|
||||||
<!-- Category activity -->
|
<!-- Category activity -->
|
||||||
<string name="error_category_exists">A category with this name already exists!</string>
|
<string name="error_category_exists">A category with this name already exists!</string>
|
||||||
|
|
Reference in a new issue