mirror of
https://github.com/jellyfin/jellyfin-android.git
synced 2026-10-26 16:45:26 +00:00
Show warning when download location is inaccessible
This commit is contained in:
1 parent
44176051e6
commit
3e0571bcb8
4 files changed
+151
-38
No files matched your search
@@ -22,6 +22,11 @@ class StorageManager(
|
||||
DocumentFile.fromTreeUri(context, it)
|
||||
}
|
||||
|
||||
fun isStorageLocationAccessible(): Boolean {
|
||||
val documentFile = getStorageLocation()
|
||||
return documentFile != null && documentFile.exists() && documentFile.canWrite()
|
||||
}
|
||||
|
||||
fun changeStorageLocation(location: Uri): Boolean {
|
||||
if (appPreferences.storageLocation?.toUri() == location) return true
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.jellyfin.mobile.downloads
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -32,6 +34,12 @@ class DownloadsViewModel : ViewModel(), KoinComponent {
|
||||
.flowOn(Dispatchers.IO)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
|
||||
private val _storageLocation = MutableStateFlow(storageManager.getStorageLocation())
|
||||
val storageLocation = _storageLocation.asStateFlow()
|
||||
|
||||
private val _storageLocationAccessible = MutableStateFlow(storageManager.isStorageLocationAccessible())
|
||||
val storageLocationAccessible = _storageLocationAccessible.asStateFlow()
|
||||
|
||||
fun openDownload(download: DownloadEntity) {
|
||||
when (download.item.mediaType) {
|
||||
MediaType.VIDEO -> {
|
||||
@@ -80,4 +88,10 @@ class DownloadsViewModel : ViewModel(), KoinComponent {
|
||||
downloadManager.delete(download.id, deleteFiles)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeStorageLocation(uri: android.net.Uri) {
|
||||
storageManager.changeStorageLocation(uri)
|
||||
_storageLocation.value = storageManager.getStorageLocation()
|
||||
_storageLocationAccessible.value = storageManager.isStorageLocationAccessible()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.jellyfin.mobile.ui.screens.downloads
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
@@ -15,9 +17,11 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Icon
|
||||
@@ -25,6 +29,7 @@ import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
@@ -42,6 +47,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -54,12 +60,18 @@ fun DownloadsScreen(
|
||||
onBackPressed: () -> Unit = {},
|
||||
) {
|
||||
val downloads by viewModel.downloads.collectAsState()
|
||||
val storageLocation by viewModel.storageLocation.collectAsState()
|
||||
val storageLocationAccessible by viewModel.storageLocationAccessible.collectAsState()
|
||||
val selection = remember { mutableStateSetOf<Long>() }
|
||||
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
val selectionMode = selection.isNotEmpty()
|
||||
|
||||
val storageLocationPicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||
if (uri != null) viewModel.changeStorageLocation(uri)
|
||||
}
|
||||
|
||||
BackHandler(enabled = selectionMode) {
|
||||
selection.clear()
|
||||
}
|
||||
@@ -193,53 +205,131 @@ fun DownloadsScreen(
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
if (downloads.isEmpty()) {
|
||||
DownloadsEmpty(
|
||||
contentPadding = innerPadding,
|
||||
)
|
||||
} else {
|
||||
DownloadsList(
|
||||
downloads = downloads,
|
||||
onOpen = { viewModel.openDownload(it) },
|
||||
onDownload = { viewModel.download(it) },
|
||||
selection = selection,
|
||||
onToggleSelection = { download ->
|
||||
if (selection.contains(download.id)) selection.remove(download.id)
|
||||
else selection.add(download.id)
|
||||
},
|
||||
contentPadding = innerPadding,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
) {
|
||||
if (storageLocation == null) {
|
||||
StorageLocationNotSetCard(
|
||||
onFix = { storageLocationPicker.launch(null) },
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
} else if (!storageLocationAccessible) {
|
||||
StorageLocationInaccessibleCard(
|
||||
onFix = { storageLocationPicker.launch(null) },
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (downloads.isEmpty()) {
|
||||
DownloadsEmpty(modifier = Modifier.weight(1f))
|
||||
} else {
|
||||
DownloadsList(
|
||||
downloads = downloads,
|
||||
onOpen = { viewModel.openDownload(it) },
|
||||
onDownload = { viewModel.download(it) },
|
||||
selection = selection,
|
||||
onToggleSelection = { download ->
|
||||
if (selection.contains(download.id)) selection.remove(download.id)
|
||||
else selection.add(download.id)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DownloadsEmpty(
|
||||
fun StorageLocationNotSetCard(
|
||||
onFix: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues.Zero,
|
||||
) = Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
elevation = 4.dp,
|
||||
backgroundColor = MaterialTheme.colors.error.copy(alpha = 0.2f),
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(contentPadding),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.download_location_not_set),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.download_location_not_set_description),
|
||||
style = MaterialTheme.typography.body2,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
TextButton(
|
||||
onClick = onFix,
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.downloads_empty),
|
||||
style = MaterialTheme.typography.h6,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.downloads_empty_description),
|
||||
style = MaterialTheme.typography.body2,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(text = stringResource(R.string.select_folder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StorageLocationInaccessibleCard(
|
||||
onFix: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) = Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
elevation = 4.dp,
|
||||
backgroundColor = MaterialTheme.colors.error.copy(alpha = 0.2f),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.download_location_inaccessible),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.download_location_inaccessible_description),
|
||||
style = MaterialTheme.typography.body2,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
TextButton(
|
||||
onClick = onFix,
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
) {
|
||||
Text(text = stringResource(R.string.select_folder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DownloadsEmpty(
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues.Zero,
|
||||
) = Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(contentPadding),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.downloads_empty),
|
||||
style = MaterialTheme.typography.h6,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.downloads_empty_description),
|
||||
style = MaterialTheme.typography.body2,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -176,4 +176,8 @@
|
||||
<string name="select_all">Select all</string>
|
||||
<string name="deselect_all">Deselect all</string>
|
||||
<string name="selected_count">%1$d selected</string>
|
||||
<string name="download_location_not_set">No download location</string>
|
||||
<string name="download_location_not_set_description">To download media, please select a folder where the files should be stored.</string>
|
||||
<string name="download_location_inaccessible">Download location inaccessible</string>
|
||||
<string name="download_location_inaccessible_description">The selected download location is no longer accessible. Please select a different folder to resume downloads.</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user