UI improvements
This commit is contained in:
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 540
|
versionCode 541
|
||||||
versionName '5.0.2'
|
versionName '5.1-a1'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -103,11 +103,6 @@ dependencies {
|
|||||||
//noinspection LifecycleAnnotationProcessorWithJava8
|
//noinspection LifecycleAnnotationProcessorWithJava8
|
||||||
kapt 'androidx.lifecycle:lifecycle-compiler:2.6.1'
|
kapt 'androidx.lifecycle:lifecycle-compiler:2.6.1'
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: check
|
|
||||||
* https://issuetracker.google.com/issues/270245927
|
|
||||||
* https://issuetracker.google.com/issues/280504155
|
|
||||||
*/
|
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.8.1'
|
implementation 'androidx.work:work-runtime-ktx:2.8.1'
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation('com.google.guava:guava:31.1-android') {
|
implementation('com.google.guava:guava:31.1-android') {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.local.data.PagesCache
|
|||||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
|
import org.koitharu.kotatsu.utils.WorkServiceStopHelper
|
||||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ class KotatsuApp : Application(), Configuration.Provider {
|
|||||||
processLifecycleScope.launch(Dispatchers.Default) {
|
processLifecycleScope.launch(Dispatchers.Default) {
|
||||||
setupDatabaseObservers()
|
setupDatabaseObservers()
|
||||||
}
|
}
|
||||||
|
WorkServiceStopHelper(applicationContext).setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||||
|
import org.koitharu.kotatsu.base.ui.util.ReversibleActionObserver
|
||||||
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
|
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
|
||||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||||
import org.koitharu.kotatsu.download.ui.worker.PausingReceiver
|
import org.koitharu.kotatsu.download.ui.worker.PausingReceiver
|
||||||
@@ -60,6 +61,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
|
|||||||
viewModel.items.observe(this) {
|
viewModel.items.observe(this) {
|
||||||
downloadsAdapter.items = it
|
downloadsAdapter.items = it
|
||||||
}
|
}
|
||||||
|
viewModel.onActionDone.observe(this, ReversibleActionObserver(binding.recyclerView))
|
||||||
val menuObserver = Observer<Any> { _ -> invalidateOptionsMenu() }
|
val menuObserver = Observer<Any> { _ -> invalidateOptionsMenu() }
|
||||||
viewModel.hasActiveWorks.observe(this, menuObserver)
|
viewModel.hasActiveWorks.observe(this, menuObserver)
|
||||||
viewModel.hasPausedWorks.observe(this, menuObserver)
|
viewModel.hasPausedWorks.observe(this, menuObserver)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
|
||||||
class DownloadsMenuProvider(
|
class DownloadsMenuProvider(
|
||||||
@@ -20,8 +21,8 @@ class DownloadsMenuProvider(
|
|||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_pause -> viewModel.pauseAll()
|
R.id.action_pause -> viewModel.pauseAll()
|
||||||
R.id.action_resume -> viewModel.resumeAll()
|
R.id.action_resume -> viewModel.resumeAll()
|
||||||
R.id.action_cancel_all -> viewModel.cancelAll()
|
R.id.action_cancel_all -> confirmCancelAll()
|
||||||
R.id.action_remove_completed -> viewModel.removeCompleted()
|
R.id.action_remove_completed -> confirmRemoveCompleted()
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -33,4 +34,30 @@ class DownloadsMenuProvider(
|
|||||||
menu.findItem(R.id.action_resume)?.isVisible = viewModel.hasPausedWorks.value == true
|
menu.findItem(R.id.action_resume)?.isVisible = viewModel.hasPausedWorks.value == true
|
||||||
menu.findItem(R.id.action_cancel_all)?.isVisible = viewModel.hasCancellableWorks.value == true
|
menu.findItem(R.id.action_cancel_all)?.isVisible = viewModel.hasCancellableWorks.value == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun confirmCancelAll() {
|
||||||
|
MaterialAlertDialogBuilder(
|
||||||
|
context,
|
||||||
|
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered,
|
||||||
|
).setTitle(R.string.cancel_all)
|
||||||
|
.setMessage(R.string.cancel_all_downloads_confirm)
|
||||||
|
.setIcon(R.drawable.ic_cancel_multiple)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.confirm) { _, _ ->
|
||||||
|
viewModel.cancelAll()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun confirmRemoveCompleted() {
|
||||||
|
MaterialAlertDialogBuilder(
|
||||||
|
context,
|
||||||
|
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered,
|
||||||
|
).setTitle(R.string.remove_completed)
|
||||||
|
.setMessage(R.string.remove_completed_downloads_confirm)
|
||||||
|
.setIcon(R.drawable.ic_clear_all)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.clear) { _, _ ->
|
||||||
|
viewModel.removeCompleted()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
|
||||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||||
@@ -26,6 +27,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
|||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -45,6 +47,8 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
.mapLatest { it.toDownloadsList() }
|
.mapLatest { it.toDownloadsList() }
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
|
val onActionDone = SingleLiveEvent<ReversibleAction>()
|
||||||
|
|
||||||
val items = works.map {
|
val items = works.map {
|
||||||
it?.toUiList() ?: listOf(LoadingState)
|
it?.toUiList() ?: listOf(LoadingState)
|
||||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||||
@@ -75,12 +79,14 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
workScheduler.cancel(work.id)
|
workScheduler.cancel(work.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onActionDone.emitCall(ReversibleAction(R.string.downloads_cancelled, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelAll() {
|
fun cancelAll() {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
workScheduler.cancelAll()
|
workScheduler.cancelAll()
|
||||||
|
onActionDone.emitCall(ReversibleAction(R.string.downloads_cancelled, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,24 +97,35 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
workScheduler.pause(work.id)
|
workScheduler.pause(work.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onActionDone.call(ReversibleAction(R.string.downloads_paused, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pauseAll() {
|
fun pauseAll() {
|
||||||
val snapshot = works.value ?: return
|
val snapshot = works.value ?: return
|
||||||
|
var isPaused = false
|
||||||
for (work in snapshot) {
|
for (work in snapshot) {
|
||||||
if (work.canPause) {
|
if (work.canPause) {
|
||||||
workScheduler.pause(work.id)
|
workScheduler.pause(work.id)
|
||||||
|
isPaused = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isPaused) {
|
||||||
|
onActionDone.call(ReversibleAction(R.string.downloads_paused, null))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resumeAll() {
|
fun resumeAll() {
|
||||||
val snapshot = works.value ?: return
|
val snapshot = works.value ?: return
|
||||||
|
var isResumed = false
|
||||||
for (work in snapshot) {
|
for (work in snapshot) {
|
||||||
if (work.workState == WorkInfo.State.RUNNING && work.isPaused) {
|
if (work.workState == WorkInfo.State.RUNNING && work.isPaused) {
|
||||||
workScheduler.resume(work.id)
|
workScheduler.resume(work.id)
|
||||||
|
isResumed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isResumed) {
|
||||||
|
onActionDone.call(ReversibleAction(R.string.downloads_resumed, null))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume(ids: Set<Long>) {
|
fun resume(ids: Set<Long>) {
|
||||||
@@ -118,6 +135,7 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
workScheduler.resume(work.id)
|
workScheduler.resume(work.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onActionDone.call(ReversibleAction(R.string.downloads_resumed, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(ids: Set<Long>) {
|
fun remove(ids: Set<Long>) {
|
||||||
@@ -128,12 +146,14 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
workScheduler.delete(work.id)
|
workScheduler.delete(work.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onActionDone.emitCall(ReversibleAction(R.string.downloads_removed, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeCompleted() {
|
fun removeCompleted() {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
workScheduler.removeCompleted()
|
workScheduler.removeCompleted()
|
||||||
|
onActionDone.emitCall(ReversibleAction(R.string.downloads_removed, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +227,7 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
private fun emptyStateList() = listOf(
|
private fun emptyStateList() = listOf(
|
||||||
EmptyState(
|
EmptyState(
|
||||||
icon = R.drawable.ic_empty_common,
|
icon = R.drawable.ic_empty_common,
|
||||||
textPrimary = R.string.text_downloads_holder,
|
textPrimary = R.string.text_downloads_list_holder,
|
||||||
textSecondary = 0,
|
textSecondary = 0,
|
||||||
actionStringRes = 0,
|
actionStringRes = 0,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.google.android.material.badge.BadgeDrawable
|
|||||||
import com.google.android.material.badge.BadgeUtils
|
import com.google.android.material.badge.BadgeUtils
|
||||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
|
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
|
||||||
@@ -44,7 +45,7 @@ private fun BadgeDrawable.align(anchor: View) {
|
|||||||
val extraOffset = if (anchor is CardView) {
|
val extraOffset = if (anchor is CardView) {
|
||||||
(anchor.radius / 2f).toInt()
|
(anchor.radius / 2f).toInt()
|
||||||
} else {
|
} else {
|
||||||
0
|
anchor.resources.getDimensionPixelOffset(materialR.dimen.m3_badge_offset)
|
||||||
}
|
}
|
||||||
horizontalOffset = intrinsicWidth + extraOffset
|
horizontalOffset = intrinsicWidth + extraOffset
|
||||||
verticalOffset = intrinsicHeight + extraOffset
|
verticalOffset = intrinsicHeight + extraOffset
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
|
import androidx.work.WorkInfo
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkQuery
|
||||||
|
import androidx.work.impl.foreground.SystemForegroundService
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround for issue
|
||||||
|
* https://issuetracker.google.com/issues/270245927
|
||||||
|
* https://issuetracker.google.com/issues/280504155
|
||||||
|
*/
|
||||||
|
class WorkServiceStopHelper(
|
||||||
|
private val context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun setup() {
|
||||||
|
processLifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
WorkManager.getInstance(context)
|
||||||
|
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||||
|
.asFlow()
|
||||||
|
.collectLatest {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
delay(1_000)
|
||||||
|
stopWorkerService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
private fun stopWorkerService() {
|
||||||
|
SystemForegroundService.getInstance()?.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
app/src/main/res/drawable/ic_cancel_multiple.xml
Normal file
12
app/src/main/res/drawable/ic_cancel_multiple.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M18.54 9.88L17.12 8.47L15 10.59L12.88 8.47L11.47 9.88L13.59 12L11.47 14.12L12.88 15.54L15 13.41L17.12 15.54L18.54 14.12L16.41 12M2 12C2 9.21 3.64 6.8 6 5.68V3.5C2.5 4.76 0 8.09 0 12S2.5 19.24 6 20.5V18.32C3.64 17.2 2 14.79 2 12M15 3C10.04 3 6 7.04 6 12S10.04 21 15 21 24 16.96 24 12 19.96 3 15 3M15 19C11.14 19 8 15.86 8 12S11.14 5 15 5 22 8.14 22 12 18.86 19 15 19Z" />
|
||||||
|
</vector>
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="62dp"
|
android:minHeight="62dp"
|
||||||
android:textAllCaps="true"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:shapeAppearance="?shapeAppearanceCornerMedium"
|
app:shapeAppearance="?shapeAppearanceCornerMedium"
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Corner.Top"
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Corner.Top"
|
||||||
@@ -46,7 +45,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="62dp"
|
android:minHeight="62dp"
|
||||||
android:textAllCaps="true"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:shapeAppearance="?shapeAppearanceCornerMedium"
|
app:shapeAppearance="?shapeAppearanceCornerMedium"
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Corner.Bottom"
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Corner.Bottom"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_cancel"
|
android:id="@+id/action_cancel"
|
||||||
android:icon="@drawable/abc_ic_clear_material"
|
android:icon="@drawable/ic_cancel_multiple"
|
||||||
android:title="@android:string/cancel"
|
android:title="@android:string/cancel"
|
||||||
app:showAsAction="ifRoom|withText" />
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
|||||||
@@ -450,4 +450,11 @@
|
|||||||
<string name="downloads_wifi_only_summary">Stop downloading when switching to a mobile network</string>
|
<string name="downloads_wifi_only_summary">Stop downloading when switching to a mobile network</string>
|
||||||
<string name="enable">Enable</string>
|
<string name="enable">Enable</string>
|
||||||
<string name="no_thanks">No thanks</string>
|
<string name="no_thanks">No thanks</string>
|
||||||
|
<string name="cancel_all_downloads_confirm">All active downloads will be cancelled, partially downloaded data will be lost</string>
|
||||||
|
<string name="remove_completed_downloads_confirm">Your downloads history will be permanently deleted</string>
|
||||||
|
<string name="text_downloads_list_holder">You don\'t have any downloads</string>
|
||||||
|
<string name="downloads_resumed">Downloads have been resumed</string>
|
||||||
|
<string name="downloads_paused">Downloads have been paused</string>
|
||||||
|
<string name="downloads_removed">Downloads have been removed</string>
|
||||||
|
<string name="downloads_cancelled">Downloads have been cancelled</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user