feat: view subscriptions in settings
This commit is contained in:
parent
f1d16ba16a
commit
6c1176a182
14 changed files with 221 additions and 15 deletions
|
@ -143,6 +143,8 @@ data class FileUrl(
|
||||||
operator fun get(url: String?, headers: Map<String, String> = mapOf()): FileUrl? {
|
operator fun get(url: String?, headers: Map<String, String> = mapOf()): FileUrl? {
|
||||||
return FileUrl(url ?: return null, headers)
|
return FileUrl(url ?: return null, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -294,6 +294,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
||||||
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.titleName.findValidName() == title }
|
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.titleName.findValidName() == title }
|
||||||
val download = tDownloads.firstOrNull() ?: continue
|
val download = tDownloads.firstOrNull() ?: continue
|
||||||
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
||||||
|
if (offlineAnimeModel.title == "unknown") offlineAnimeModel.title = title
|
||||||
newAnimeDownloads += offlineAnimeModel
|
newAnimeDownloads += offlineAnimeModel
|
||||||
}
|
}
|
||||||
downloads = newAnimeDownloads
|
downloads = newAnimeDownloads
|
||||||
|
|
|
@ -3,7 +3,7 @@ package ani.dantotsu.download.anime
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
||||||
data class OfflineAnimeModel(
|
data class OfflineAnimeModel(
|
||||||
val title: String,
|
var title: String,
|
||||||
val score: String,
|
val score: String,
|
||||||
val totalEpisode: String,
|
val totalEpisode: String,
|
||||||
val totalEpisodeList: String,
|
val totalEpisodeList: String,
|
||||||
|
|
|
@ -5,14 +5,18 @@ import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaNameAdapter
|
import ani.dantotsu.media.MediaNameAdapter
|
||||||
import ani.dantotsu.media.Selected
|
import ani.dantotsu.media.Selected
|
||||||
|
import ani.dantotsu.media.emptyMedia
|
||||||
import ani.dantotsu.parsers.AnimeParser
|
import ani.dantotsu.parsers.AnimeParser
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
import ani.dantotsu.parsers.BaseParser
|
||||||
import ani.dantotsu.parsers.Episode
|
import ani.dantotsu.parsers.Episode
|
||||||
import ani.dantotsu.parsers.MangaChapter
|
import ani.dantotsu.parsers.MangaChapter
|
||||||
import ani.dantotsu.parsers.MangaParser
|
import ani.dantotsu.parsers.MangaParser
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
@ -50,16 +54,18 @@ class SubscriptionHelper {
|
||||||
|
|
||||||
suspend fun getEpisode(
|
suspend fun getEpisode(
|
||||||
parser: AnimeParser,
|
parser: AnimeParser,
|
||||||
id: Int
|
subscribeMedia: SubscribeMedia
|
||||||
): Episode? {
|
): Episode? {
|
||||||
|
|
||||||
val selected = loadSelected(id)
|
val selected = loadSelected(subscribeMedia.id)
|
||||||
val ep = withTimeoutOrNull(10 * 1000) {
|
val ep = withTimeoutOrNull(10 * 1000) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
val show = parser.loadSavedShowResponse(id) ?: throw Exception(
|
val show = parser.loadSavedShowResponse(subscribeMedia.id)
|
||||||
|
?: forceLoadShowResponse(subscribeMedia, selected, parser)
|
||||||
|
?: throw Exception(
|
||||||
currContext()?.getString(
|
currContext()?.getString(
|
||||||
R.string.failed_to_load_data,
|
R.string.failed_to_load_data,
|
||||||
id
|
subscribeMedia.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
show.sAnime?.let {
|
show.sAnime?.let {
|
||||||
|
@ -73,7 +79,7 @@ class SubscriptionHelper {
|
||||||
|
|
||||||
return ep?.apply {
|
return ep?.apply {
|
||||||
selected.latest = number.toFloat()
|
selected.latest = number.toFloat()
|
||||||
saveSelected(id, selected)
|
saveSelected(subscribeMedia.id, selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,15 +95,17 @@ class SubscriptionHelper {
|
||||||
|
|
||||||
suspend fun getChapter(
|
suspend fun getChapter(
|
||||||
parser: MangaParser,
|
parser: MangaParser,
|
||||||
id: Int
|
subscribeMedia: SubscribeMedia
|
||||||
): MangaChapter? {
|
): MangaChapter? {
|
||||||
val selected = loadSelected(id)
|
val selected = loadSelected(subscribeMedia.id)
|
||||||
val chp = withTimeoutOrNull(10 * 1000) {
|
val chp = withTimeoutOrNull(10 * 1000) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
val show = parser.loadSavedShowResponse(id) ?: throw Exception(
|
val show = parser.loadSavedShowResponse(subscribeMedia.id)
|
||||||
|
?: forceLoadShowResponse(subscribeMedia, selected, parser)
|
||||||
|
?: throw Exception(
|
||||||
currContext()?.getString(
|
currContext()?.getString(
|
||||||
R.string.failed_to_load_data,
|
R.string.failed_to_load_data,
|
||||||
id
|
subscribeMedia.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
show.sManga?.let {
|
show.sManga?.let {
|
||||||
|
@ -111,10 +119,28 @@ class SubscriptionHelper {
|
||||||
|
|
||||||
return chp?.apply {
|
return chp?.apply {
|
||||||
selected.latest = MediaNameAdapter.findChapterNumber(number) ?: 0f
|
selected.latest = MediaNameAdapter.findChapterNumber(number) ?: 0f
|
||||||
saveSelected(id, selected)
|
saveSelected(subscribeMedia.id, selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun forceLoadShowResponse(subscribeMedia: SubscribeMedia, selected: Selected, parser: BaseParser): ShowResponse? {
|
||||||
|
val tempMedia = Media(
|
||||||
|
id = subscribeMedia.id,
|
||||||
|
name = null,
|
||||||
|
nameRomaji = subscribeMedia.name,
|
||||||
|
userPreferredName = subscribeMedia.name,
|
||||||
|
isAdult = subscribeMedia.isAdult,
|
||||||
|
isFav = false,
|
||||||
|
isListPrivate = false,
|
||||||
|
userScore = 0,
|
||||||
|
userRepeat = 0,
|
||||||
|
format = null,
|
||||||
|
selected = selected
|
||||||
|
)
|
||||||
|
parser.autoSearch(tempMedia)
|
||||||
|
return parser.loadSavedShowResponse(subscribeMedia.id)
|
||||||
|
}
|
||||||
|
|
||||||
data class SubscribeMedia(
|
data class SubscribeMedia(
|
||||||
val isAnime: Boolean,
|
val isAnime: Boolean,
|
||||||
val isAdult: Boolean,
|
val isAdult: Boolean,
|
||||||
|
@ -134,6 +160,19 @@ class SubscriptionHelper {
|
||||||
) as? Map<Int, SubscribeMedia>)
|
) as? Map<Int, SubscribeMedia>)
|
||||||
?: mapOf<Int, SubscribeMedia>().also { PrefManager.setCustomVal(SUBSCRIPTIONS, it) }
|
?: mapOf<Int, SubscribeMedia>().also { PrefManager.setCustomVal(SUBSCRIPTIONS, it) }
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun deleteSubscription(id: Int, showSnack: Boolean = false) {
|
||||||
|
val data = PrefManager.getNullableCustomVal(
|
||||||
|
SUBSCRIPTIONS,
|
||||||
|
null,
|
||||||
|
Map::class.java
|
||||||
|
) as? MutableMap<Int, SubscribeMedia>
|
||||||
|
?: mutableMapOf()
|
||||||
|
data.remove(id)
|
||||||
|
PrefManager.setCustomVal(SUBSCRIPTIONS, data)
|
||||||
|
if (showSnack) toast(R.string.subscription_deleted)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun saveSubscription(media: Media, subscribed: Boolean) {
|
fun saveSubscription(media: Media, subscribed: Boolean) {
|
||||||
val data = PrefManager.getNullableCustomVal(
|
val data = PrefManager.getNullableCustomVal(
|
||||||
|
|
|
@ -98,7 +98,7 @@ class SubscriptionNotificationTask : Task {
|
||||||
val ep: Episode? =
|
val ep: Episode? =
|
||||||
SubscriptionHelper.getEpisode(
|
SubscriptionHelper.getEpisode(
|
||||||
parser,
|
parser,
|
||||||
media.id
|
media
|
||||||
)
|
)
|
||||||
if (ep != null) context.getString(R.string.episode) + "${ep.number}${
|
if (ep != null) context.getString(R.string.episode) + "${ep.number}${
|
||||||
if (ep.title != null) " : ${ep.title}" else ""
|
if (ep.title != null) " : ${ep.title}" else ""
|
||||||
|
@ -113,7 +113,7 @@ class SubscriptionNotificationTask : Task {
|
||||||
val ep: MangaChapter? =
|
val ep: MangaChapter? =
|
||||||
SubscriptionHelper.getChapter(
|
SubscriptionHelper.getChapter(
|
||||||
parser,
|
parser,
|
||||||
media.id
|
media
|
||||||
)
|
)
|
||||||
if (ep != null) ep.number + " " + context.getString(R.string.just_released) to null
|
if (ep != null) ep.number + " " + context.getString(R.string.just_released) to null
|
||||||
else null
|
else null
|
||||||
|
|
|
@ -18,6 +18,7 @@ import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.notifications.TaskScheduler
|
import ani.dantotsu.notifications.TaskScheduler
|
||||||
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
|
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
|
||||||
import ani.dantotsu.notifications.comment.CommentNotificationWorker
|
import ani.dantotsu.notifications.comment.CommentNotificationWorker
|
||||||
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
||||||
import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker
|
import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker
|
||||||
import ani.dantotsu.openSettings
|
import ani.dantotsu.openSettings
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
@ -102,6 +103,19 @@ class SettingsNotificationActivity : AppCompatActivity() {
|
||||||
).scheduleAllTasks(context)
|
).scheduleAllTasks(context)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
Settings(
|
||||||
|
type = 1,
|
||||||
|
name = getString(R.string.view_subscriptions),
|
||||||
|
desc = getString(R.string.view_subscriptions_desc),
|
||||||
|
icon = R.drawable.ic_round_search_24,
|
||||||
|
onClick = {
|
||||||
|
val subscriptions = SubscriptionHelper.getSubscriptions()
|
||||||
|
SubscriptionsBottomDialog.newInstance(subscriptions).show(
|
||||||
|
supportFragmentManager,
|
||||||
|
"subscriptions"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
Settings(
|
Settings(
|
||||||
type = 1,
|
type = 1,
|
||||||
name = getString(R.string.anilist_notification_filters),
|
name = getString(R.string.anilist_notification_filters),
|
||||||
|
|
52
app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt
Normal file
52
app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.ItemSubscriptionBinding
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import com.xwray.groupie.Item
|
||||||
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
|
|
||||||
|
class SubscriptionItem(
|
||||||
|
val id: Int,
|
||||||
|
private val media: SubscriptionHelper.Companion.SubscribeMedia,
|
||||||
|
private val adapter: GroupieAdapter
|
||||||
|
) : BindableItem<ItemSubscriptionBinding>() {
|
||||||
|
private lateinit var binding: ItemSubscriptionBinding
|
||||||
|
override fun bind(p0: ItemSubscriptionBinding, p1: Int) {
|
||||||
|
val context = p0.root.context
|
||||||
|
binding = p0
|
||||||
|
val parserName = if (media.isAnime)
|
||||||
|
SubscriptionHelper.getAnimeParser(media.id).name
|
||||||
|
else
|
||||||
|
SubscriptionHelper.getMangaParser(media.id).name
|
||||||
|
val mediaName = media.name
|
||||||
|
val showName = "$mediaName - $parserName"
|
||||||
|
binding.subscriptionName.text = showName
|
||||||
|
binding.root.setOnClickListener {
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
context,
|
||||||
|
Intent(context, MediaDetailsActivity::class.java).putExtra(
|
||||||
|
"mediaId", media.id
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.deleteSubscription.setOnClickListener {
|
||||||
|
SubscriptionHelper.deleteSubscription(id, true)
|
||||||
|
adapter.remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayout(): Int {
|
||||||
|
return R.layout.item_subscription
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initializeViewBinding(p0: View): ItemSubscriptionBinding {
|
||||||
|
return ItemSubscriptionBinding.bind(p0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.BottomSheetRecyclerBinding
|
||||||
|
import ani.dantotsu.notifications.subscription.SubscriptionHelper
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
|
||||||
|
class SubscriptionsBottomDialog : BottomSheetDialogFragment() {
|
||||||
|
private var _binding: BottomSheetRecyclerBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private val adapter: GroupieAdapter = GroupieAdapter()
|
||||||
|
private var subscriptions: Map<Int, SubscriptionHelper.Companion.SubscribeMedia> = mapOf()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
_binding = BottomSheetRecyclerBinding.inflate(inflater, container, false)
|
||||||
|
return _binding?.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
binding.repliesRecyclerView.adapter = adapter
|
||||||
|
binding.repliesRecyclerView.layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
val context = requireContext()
|
||||||
|
binding.title.text = context.getString(R.string.subscriptions)
|
||||||
|
binding.replyButton.visibility = View.GONE
|
||||||
|
subscriptions.forEach { (id, media) ->
|
||||||
|
adapter.add(SubscriptionItem(id, media, adapter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
_binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(subscriptions: Map<Int, SubscriptionHelper.Companion.SubscribeMedia>): SubscriptionsBottomDialog {
|
||||||
|
val dialog = SubscriptionsBottomDialog()
|
||||||
|
dialog.subscriptions = subscriptions
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -395,9 +395,11 @@ object PrefManager {
|
||||||
val obj = ois.readObject() as T?
|
val obj = ois.readObject() as T?
|
||||||
obj
|
obj
|
||||||
} else {
|
} else {
|
||||||
|
Logger.log("Serialized data is null (key: $key)")
|
||||||
default
|
default
|
||||||
}
|
}
|
||||||
} catch (e: java.io.InvalidClassException) {
|
} catch (e: java.io.InvalidClassException) {
|
||||||
|
Logger.log(e)
|
||||||
try {
|
try {
|
||||||
getPrefLocation(location).edit().remove(key).apply()
|
getPrefLocation(location).edit().remove(key).apply()
|
||||||
default
|
default
|
||||||
|
|
|
@ -85,5 +85,7 @@ interface SAnime : Serializable {
|
||||||
fun create(): SAnime {
|
fun create(): SAnime {
|
||||||
return SAnimeImpl()
|
return SAnimeImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,5 +84,7 @@ interface SManga : Serializable {
|
||||||
fun create(): SManga {
|
fun create(): SManga {
|
||||||
return SMangaImpl()
|
return SMangaImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val serialVersionUID = 1L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,12 @@
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:fontFamily="@font/poppins_bold"
|
android:fontFamily="@font/poppins_bold"
|
||||||
android:text="@string/notifications"
|
android:text="@string/notifications"
|
||||||
android:textSize="28sp" />
|
android:textSize="27sp" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="96dp"
|
android:layout_width="96dp"
|
||||||
android:layout_height="96dp"
|
android:layout_height="96dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="14dp"
|
||||||
android:layout_marginBottom="20dp"
|
android:layout_marginBottom="20dp"
|
||||||
android:padding="24dp"
|
android:padding="24dp"
|
||||||
app:srcCompat="@drawable/ic_round_notifications_none_24"
|
app:srcCompat="@drawable/ic_round_notifications_none_24"
|
||||||
|
|
32
app/src/main/res/layout/item_subscription.xml
Normal file
32
app/src/main/res/layout/item_subscription.xml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subscriptionName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:text="@string/placeholder"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/deleteSubscription"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:contentDescription="@string/delete"
|
||||||
|
android:src="@drawable/ic_circle_cancel"
|
||||||
|
app:tint="?attr/colorOnBackground" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -991,4 +991,8 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="replies">Replies</string>
|
<string name="replies">Replies</string>
|
||||||
<string name="open_rules">Open Rules</string>
|
<string name="open_rules">Open Rules</string>
|
||||||
<string name="post_to_anilist_warning">By posting to AniList, you agree to the rules and guidelines of AniList</string>
|
<string name="post_to_anilist_warning">By posting to AniList, you agree to the rules and guidelines of AniList</string>
|
||||||
|
<string name="view_subscriptions">View Subscriptions</string>
|
||||||
|
<string name="view_subscriptions_desc">View and edit all your subscriptions</string>
|
||||||
|
<string name="subscriptions">Subscriptions</string>
|
||||||
|
<string name="subscription_deleted">Subscription Deleted</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue