diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt index 5f2a0617..96ba5d75 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt @@ -47,6 +47,10 @@ object Anilist { var rowOrder: String? = null var activityMergeTime: Int? = null var timezone: String? = null + var animeCustomLists: List? = null + var mangaCustomLists: List? = null + var animeSplitCompletedSectionByFormat: Boolean = false + var mangaSplitCompletedSectionByFormat: Boolean = false val sortBy = listOf( "SCORE_DESC", @@ -183,7 +187,8 @@ object Anilist { "2 days" to 2880, "3 days" to 4320, "1 week" to 10080, - "Always" to 20160 + "2 weeks" to 20160, + "Always" to 29160 ) private val cal: Calendar = Calendar.getInstance() diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistMutations.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistMutations.kt index 411f2922..de0f6e2f 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistMutations.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistMutations.kt @@ -127,6 +127,51 @@ class AnilistMutations { ANIME, MANGA, CHARACTER, STAFF, STUDIO } + suspend fun deleteCustomList(name: String, type: String): Boolean { + val query = """ + mutation (${"$"}name: String, ${"$"}type: MediaType) { + DeleteCustomList(customList: ${"$"}name, type: ${"$"}type) { + deleted + } + } + """.trimIndent() + val variables = """ + { + "name": "$name", + "type": "$type" + } + """.trimIndent() + val result = executeQuery(query, variables) + return result?.get("errors") == null + } + + suspend fun updateCustomLists(animeCustomLists: List?, mangaCustomLists: List?): Boolean { + val query = """ + mutation (${"$"}animeListOptions: MediaListOptionsInput, ${"$"}mangaListOptions: MediaListOptionsInput) { + UpdateUser(animeListOptions: ${"$"}animeListOptions, mangaListOptions: ${"$"}mangaListOptions) { + mediaListOptions { + animeList { + customLists + } + mangaList { + customLists + } + } + } + } + """.trimIndent() + val variables = """ + { + ${animeCustomLists?.let { """"animeListOptions": {"customLists": ${Gson().toJson(it)}}""" } ?: ""} + ${if (animeCustomLists != null && mangaCustomLists != null) "," else ""} + ${mangaCustomLists?.let { """"mangaListOptions": {"customLists": ${Gson().toJson(it)}}""" } ?: ""} + } + """.trimIndent().replace("\n", "").replace(""" """, "").replace(",}", "}") + + val result = executeQuery(query, variables) + return result?.get("errors") == null + } + suspend fun editList( mediaID: Int, progress: Int? = null, @@ -237,7 +282,8 @@ class AnilistMutations { } suspend fun toggleFollow(id: Int): Query.ToggleFollow? { - return executeQuery(""" + return executeQuery( + """ mutation { ToggleFollow(userId: $id) { id @@ -249,7 +295,8 @@ class AnilistMutations { } suspend fun toggleLike(id: Int, type: String): ToggleLike? { - return executeQuery(""" + return executeQuery( + """ mutation Like { ToggleLikeV2(id: $id, type: $type) { __typename diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 2d5407a5..ee579e1b 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -61,8 +61,8 @@ class AnilistQueries { mediaListOptions { scoreFormat rowOrder - animeList { sectionOrder customLists } - mangaList { sectionOrder customLists } + animeList { sectionOrder customLists splitCompletedSectionByFormat } + mangaList { sectionOrder customLists splitCompletedSectionByFormat } } statistics { anime { episodesWatched } @@ -100,6 +100,16 @@ class AnilistQueries { user.mediaListOptions?.let { Anilist.scoreFormat = it.scoreFormat.toString() Anilist.rowOrder = it.rowOrder + + it.animeList?.let { animeList -> + Anilist.animeCustomLists = animeList.customLists + Anilist.animeSplitCompletedSectionByFormat = animeList.splitCompletedSectionByFormat ?: false + } + + it.mangaList?.let { mangaList -> + Anilist.mangaCustomLists = mangaList.customLists + Anilist.mangaSplitCompletedSectionByFormat = mangaList.splitCompletedSectionByFormat ?: false + } } return true } diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 048ba398..d9b44e99 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -293,7 +293,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi binding.mediaTotal.visibility = View.VISIBLE binding.mediaAddToList.text = userStatus } else { - binding.mediaAddToList.setText(R.string.add) + binding.mediaAddToList.setText(R.string.add_list) } total() binding.mediaAddToList.setOnClickListener { diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index 748e476e..2c00bdc3 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -21,7 +21,6 @@ import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R import ani.dantotsu.copyToClipboard -import ani.dantotsu.currContext import ani.dantotsu.databinding.ActivityExtensionsBinding import ani.dantotsu.databinding.DialogRepositoriesBinding import ani.dantotsu.databinding.ItemRepositoryBinding @@ -327,7 +326,7 @@ class ExtensionsActivity : AppCompatActivity() { val alertDialog = AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup) .setTitle(R.string.edit_repositories) .setView(dialogView.root) - .setPositiveButton(getString(R.string.add)) { _, _ -> + .setPositiveButton(getString(R.string.add_list)) { _, _ -> if (!dialogView.repositoryTextBox.text.isNullOrBlank()) processUserInput(dialogView.repositoryTextBox.text.toString(), type) } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsAccountActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsAccountActivity.kt index 1b4cd9b2..2206f174 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsAccountActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsAccountActivity.kt @@ -4,7 +4,6 @@ import android.content.Intent import android.os.Bundle import android.view.HapticFeedbackConstants import android.view.View -import android.widget.ArrayAdapter import android.view.ViewGroup import android.view.animation.AnimationUtils import android.widget.TextView @@ -23,7 +22,6 @@ import ani.dantotsu.loadImage import ani.dantotsu.navBarHeight import ani.dantotsu.openLinkInBrowser import ani.dantotsu.others.CustomBottomDialog -import ani.dantotsu.restartApp import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.startMainActivity @@ -117,6 +115,7 @@ class SettingsAccountActivity : AppCompatActivity() { } else { settingsAnilistAvatar.setImageResource(R.drawable.ic_round_person_24) settingsAnilistUsername.visibility = View.GONE + settingsRecyclerView.visibility = View.GONE settingsAnilistLogin.setText(R.string.login) settingsAnilistLogin.setOnClickListener { Anilist.loginIntent(context) @@ -148,7 +147,7 @@ class SettingsAccountActivity : AppCompatActivity() { reload() } - settingsImageSwitcher.visibility = View.VISIBLE + settingsPresenceSwitcher.visibility = View.VISIBLE var initialStatus = when (PrefManager.getVal(PrefName.DiscordStatus)) { "online" -> R.drawable.discord_status_online "idle" -> R.drawable.discord_status_idle @@ -156,11 +155,11 @@ class SettingsAccountActivity : AppCompatActivity() { "invisible" -> R.drawable.discord_status_invisible else -> R.drawable.discord_status_online } - settingsImageSwitcher.setImageResource(initialStatus) + settingsPresenceSwitcher.setImageResource(initialStatus) val zoomInAnimation = AnimationUtils.loadAnimation(context, R.anim.bounce_zoom) - settingsImageSwitcher.setOnClickListener { + settingsPresenceSwitcher.setOnClickListener { var status = "online" initialStatus = when (initialStatus) { R.drawable.discord_status_online -> { @@ -187,16 +186,16 @@ class SettingsAccountActivity : AppCompatActivity() { } PrefManager.setVal(PrefName.DiscordStatus, status) - settingsImageSwitcher.setImageResource(initialStatus) - settingsImageSwitcher.startAnimation(zoomInAnimation) + settingsPresenceSwitcher.setImageResource(initialStatus) + settingsPresenceSwitcher.startAnimation(zoomInAnimation) } - settingsImageSwitcher.setOnLongClickListener { + settingsPresenceSwitcher.setOnLongClickListener { it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) DiscordDialogFragment().show(supportFragmentManager, "dialog") true } } else { - settingsImageSwitcher.visibility = View.GONE + settingsPresenceSwitcher.visibility = View.GONE settingsDiscordAvatar.setImageResource(R.drawable.ic_round_person_24) settingsDiscordUsername.visibility = View.GONE settingsDiscordLogin.setText(R.string.login) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsAnilistActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsAnilistActivity.kt index a197984e..c9c64825 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsAnilistActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsAnilistActivity.kt @@ -3,7 +3,10 @@ package ani.dantotsu.settings import android.os.Bundle import android.view.ViewGroup import android.widget.ArrayAdapter +import android.widget.LinearLayout +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.children import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager @@ -21,6 +24,9 @@ import ani.dantotsu.navBarHeight import ani.dantotsu.restartApp import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.toast +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout import kotlinx.coroutines.launch class SettingsAnilistActivity : AppCompatActivity() { @@ -160,6 +166,26 @@ class SettingsAnilistActivity : AppCompatActivity() { settingsAnilistRowOrder.clearFocus() } + val containers = listOf(binding.animeCustomListsContainer, binding.mangaCustomListsContainer) + val customLists = listOf(Anilist.animeCustomLists, Anilist.mangaCustomLists) + val buttons = listOf(binding.addAnimeListButton, binding.addMangaListButton) + + containers.forEachIndexed { index, container -> + customLists[index]?.forEach { listName -> + addCustomListItem(listName, container, index == 0) + } + } + + buttons.forEachIndexed { index, button -> + button.setOnClickListener { + addCustomListItem("", containers[index], index == 0) + } + } + + binding.SettingsAnilistCustomListSave.setOnClickListener { + saveCustomLists() + } + val currentTimezone = Anilist.timezone?.let { Anilist.getDisplayTimezone(it) } ?: "(GMT+00:00) London" settingsAnilistTimezone.setText(currentTimezone) settingsAnilistTimezone.setAdapter( @@ -238,4 +264,76 @@ class SettingsAnilistActivity : AppCompatActivity() { LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) } + private fun addCustomListItem(listName: String, container: LinearLayout, isAnime: Boolean) { + val customListItemView = layoutInflater.inflate(R.layout.item_custom_list, container, false) + val textInputLayout = customListItemView.findViewById(R.id.customListItem) + val editText = textInputLayout.editText as? TextInputEditText + editText?.setText(listName) + textInputLayout.setEndIconOnClickListener { + val name = editText?.text.toString() + if (name.isNotEmpty()) { + val listExists = if (isAnime) { + Anilist.animeCustomLists?.contains(name) ?: false + } else { + Anilist.mangaCustomLists?.contains(name) ?: false + } + + if (listExists) { + AlertDialog.Builder(this@SettingsAnilistActivity, R.style.MyPopup) + .setTitle(getString(R.string.delete_custom_list)) + .setMessage(getString(R.string.delete_custom_list_confirm, name)) + .setPositiveButton(getString(R.string.delete)) { _, _ -> + deleteCustomList(name, isAnime) + container.removeView(customListItemView) + } + .setNegativeButton(getString(R.string.cancel), null) + .show() + } else { + container.removeView(customListItemView) + } + } else { + container.removeView(customListItemView) + } + } + container.addView(customListItemView) + } + + private fun deleteCustomList(name: String, isAnime: Boolean) { + lifecycleScope.launch { + val type = if (isAnime) "ANIME" else "MANGA" + val success = anilistMutations.deleteCustomList(name, type) + if (success) { + if (isAnime) { + Anilist.animeCustomLists = Anilist.animeCustomLists?.filter { it != name } + } else { + Anilist.mangaCustomLists = Anilist.mangaCustomLists?.filter { it != name } + } + toast("Custom list deleted") + } else { + toast("Failed to delete custom list") + } + } + } + + private fun saveCustomLists() { + val animeCustomLists = binding.animeCustomListsContainer.children + .mapNotNull { (it.findViewById(R.id.customListItem).editText as? TextInputEditText)?.text?.toString() } + .filter { it.isNotEmpty() } + .toList() + val mangaCustomLists = binding.mangaCustomListsContainer.children + .mapNotNull { (it.findViewById(R.id.customListItem).editText as? TextInputEditText)?.text?.toString() } + .filter { it.isNotEmpty() } + .toList() + + lifecycleScope.launch { + val success = anilistMutations.updateCustomLists(animeCustomLists, mangaCustomLists) + if (success) { + Anilist.animeCustomLists = animeCustomLists + Anilist.mangaCustomLists = mangaCustomLists + toast("Custom lists saved") + } else { + toast("Failed to save custom lists") + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_media.xml b/app/src/main/res/layout-land/activity_media.xml index 08942377..fb9c6cb7 100644 --- a/app/src/main/res/layout-land/activity_media.xml +++ b/app/src/main/res/layout-land/activity_media.xml @@ -122,7 +122,7 @@ android:marqueeRepeatLimit="marquee_forever" android:padding="8dp" android:singleLine="true" - android:text="@string/add" + android:text="@string/add_list" android:textAllCaps="true" android:textColor="?attr/colorSecondary" android:textSize="14sp" diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml index 5fb478ad..89d856c9 100644 --- a/app/src/main/res/layout/activity_media.xml +++ b/app/src/main/res/layout/activity_media.xml @@ -121,7 +121,7 @@ android:marqueeRepeatLimit="marquee_forever" android:padding="8dp" android:singleLine="true" - android:text="@string/add" + android:text="@string/add_list" android:textAllCaps="true" android:textColor="?attr/colorSecondary" android:textSize="14sp" diff --git a/app/src/main/res/layout/activity_settings_accounts.xml b/app/src/main/res/layout/activity_settings_accounts.xml index 512687e3..9a1f9b98 100644 --- a/app/src/main/res/layout/activity_settings_accounts.xml +++ b/app/src/main/res/layout/activity_settings_accounts.xml @@ -265,7 +265,7 @@ + + + + + + + + + + + + + + +