mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
feat(app): expose test tags for Appium
This commit is contained in:
@@ -22,6 +22,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
import androidx.navigation3.runtime.EntryProviderScope
|
||||
@@ -192,7 +194,10 @@ class PurefinActivity : ComponentActivity() {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
onBack = { navigationManager.pop() },
|
||||
modifier = Modifier.fillMaxSize().background(backgroundDark),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundDark)
|
||||
.semantics { testTagsAsResourceId = true },
|
||||
transitionSpec = {
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
@@ -227,7 +232,9 @@ class PurefinActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoginScreen()
|
||||
LoginScreen(
|
||||
modifier = Modifier.semantics { testTagsAsResourceId = true }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
|
||||
@Composable
|
||||
fun AppBottomBar(
|
||||
@@ -16,17 +18,21 @@ fun AppBottomBar(
|
||||
isOnline: Boolean,
|
||||
onTabSelected: (Int) -> Unit
|
||||
) {
|
||||
NavigationBar {
|
||||
NavigationBar(
|
||||
modifier = Modifier.testTag(BottomNavigationTag)
|
||||
) {
|
||||
if (isOnline) {
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == 0,
|
||||
onClick = { onTabSelected(0) },
|
||||
modifier = Modifier.testTag(BottomNavigationHomeTag),
|
||||
icon = { Icon(Icons.Outlined.Home, contentDescription = "Home") },
|
||||
label = { Text("Home") }
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == 1,
|
||||
onClick = { onTabSelected(1) },
|
||||
modifier = Modifier.testTag(BottomNavigationLibrariesTag),
|
||||
icon = { Icon(Icons.Outlined.Collections, contentDescription = "Libraries") },
|
||||
label = { Text("Libraries") }
|
||||
)
|
||||
@@ -34,8 +40,14 @@ fun AppBottomBar(
|
||||
NavigationBarItem(
|
||||
selected = !isOnline || selectedTab == 2,
|
||||
onClick = { onTabSelected(2) },
|
||||
modifier = Modifier.testTag(BottomNavigationDownloadsTag),
|
||||
icon = { Icon(Icons.Outlined.Download, contentDescription = "Downloads") },
|
||||
label = { Text("Downloads") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal const val BottomNavigationTag = "bottom-navigation"
|
||||
internal const val BottomNavigationHomeTag = "bottom-navigation-home"
|
||||
internal const val BottomNavigationLibrariesTag = "bottom-navigation-libraries"
|
||||
internal const val BottomNavigationDownloadsTag = "bottom-navigation-downloads"
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -33,9 +34,10 @@ import hu.bbara.purefin.core.feature.downloads.ActiveDownloadItem
|
||||
internal fun DownloadingItemRow(
|
||||
item: ActiveDownloadItem,
|
||||
onCancel: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
@@ -91,7 +93,10 @@ internal fun DownloadingItemRow(
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { onCancel(item.contentId) }) {
|
||||
IconButton(
|
||||
onClick = { onCancel(item.contentId) },
|
||||
modifier = Modifier.testTag("$DownloadsCancelButtonTagPrefix${item.contentId}")
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = "Cancel download",
|
||||
@@ -101,3 +106,5 @@ internal fun DownloadingItemRow(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val DownloadsCancelButtonTagPrefix = "downloads-cancel-button-"
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@@ -38,7 +39,9 @@ fun DownloadsContent(
|
||||
|
||||
if (isEmpty) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(DownloadsEmptyStateTag),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
@@ -62,7 +65,9 @@ fun DownloadsContent(
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = modifier.background(MaterialTheme.colorScheme.background)
|
||||
modifier = modifier
|
||||
.testTag(DownloadsGridTag)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
if (activeDownloads.value.isNotEmpty()) {
|
||||
item(key = "downloading-header", span = { GridItemSpan(maxLineSpan) }) {
|
||||
@@ -80,7 +85,8 @@ fun DownloadsContent(
|
||||
) { item ->
|
||||
DownloadingItemRow(
|
||||
item = item,
|
||||
onCancel = { viewModel.cancelDownload(it) }
|
||||
onCancel = { viewModel.cancelDownload(it) },
|
||||
modifier = Modifier.testTag("$DownloadsActiveItemTagPrefix${item.contentId}")
|
||||
)
|
||||
}
|
||||
if (downloads.value.isNotEmpty()) {
|
||||
@@ -101,7 +107,13 @@ fun DownloadsContent(
|
||||
onMovieSelected = viewModel::onMovieSelected,
|
||||
onSeriesSelected = viewModel::onSeriesSelected,
|
||||
onEpisodeSelected = { _, _, _ -> },
|
||||
modifier = Modifier.testTag("$DownloadsItemTagPrefix${item.id}")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val DownloadsEmptyStateTag = "downloads-empty-state"
|
||||
internal const val DownloadsGridTag = "downloads-grid"
|
||||
internal const val DownloadsActiveItemTagPrefix = "downloads-active-item-"
|
||||
internal const val DownloadsItemTagPrefix = "downloads-item-"
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -71,6 +72,7 @@ internal fun EpisodeTopBar(
|
||||
),
|
||||
modifier = Modifier
|
||||
.height(52.dp)
|
||||
.testTag(EpisodeSeriesButtonTag)
|
||||
.clip(CircleShape)
|
||||
) {
|
||||
Text(
|
||||
@@ -123,7 +125,9 @@ internal fun EpisodeDetails(
|
||||
text = mediaPlayButtonText(episode.progress, episode.watched),
|
||||
progress = mediaPlaybackProgress(episode.progress),
|
||||
onClick = playAction,
|
||||
modifier = Modifier.sizeIn(maxWidth = 200.dp)
|
||||
modifier = Modifier
|
||||
.sizeIn(maxWidth = 200.dp)
|
||||
.testTag(EpisodePlayButtonTag)
|
||||
)
|
||||
VerticalDivider(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
@@ -143,6 +147,7 @@ internal fun EpisodeDetails(
|
||||
is DownloadState.Failed -> Icons.Outlined.Download
|
||||
},
|
||||
height = 48.dp,
|
||||
modifier = Modifier.testTag(EpisodeDownloadButtonTag),
|
||||
onClick = onDownloadClick
|
||||
)
|
||||
}
|
||||
@@ -168,3 +173,7 @@ internal fun EpisodeDetails(
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
internal const val EpisodeSeriesButtonTag = "episode-series-button"
|
||||
internal const val EpisodePlayButtonTag = "episode-play-button"
|
||||
internal const val EpisodeDownloadButtonTag = "episode-download-button"
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.model.LibraryUiModel
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
@@ -80,7 +81,9 @@ fun HomeContent(
|
||||
PullToRefreshBox(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
modifier = modifier.fillMaxSize()
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(HomeContentTag)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -89,7 +92,9 @@ fun HomeContent(
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(HomeContentViewportTag),
|
||||
contentPadding = PaddingValues(top = 16.dp, bottom = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
@@ -97,7 +102,8 @@ fun HomeContent(
|
||||
item(key = "featured") {
|
||||
SuggestionsSection(
|
||||
items = suggestions,
|
||||
onItemOpen = { item -> onMediaSelected(item) }
|
||||
onItemOpen = { item -> onMediaSelected(item) },
|
||||
modifier = Modifier.testTag(HomeFeaturedSectionTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -107,7 +113,8 @@ fun HomeContent(
|
||||
ContinueWatchingSection(
|
||||
items = continueWatching,
|
||||
onMarkAsWatched = onMarkAsWatched,
|
||||
onMediaSelected = onMediaSelected
|
||||
onMediaSelected = onMediaSelected,
|
||||
modifier = Modifier.testTag(HomeContinueWatchingSectionTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -116,7 +123,8 @@ fun HomeContent(
|
||||
item(key = "next-up") {
|
||||
NextUpSection(
|
||||
items = nextUp,
|
||||
onMediaSelected = onMediaSelected
|
||||
onMediaSelected = onMediaSelected,
|
||||
modifier = Modifier.testTag(HomeNextUpSectionTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -129,7 +137,8 @@ fun HomeContent(
|
||||
library = library,
|
||||
items = libraryContent[library.id].orEmpty(),
|
||||
onLibrarySelected = onLibrarySelected,
|
||||
onMediaSelected = onMediaSelected
|
||||
onMediaSelected = onMediaSelected,
|
||||
modifier = Modifier.testTag("$HomeLibrarySectionTagPrefix${library.id}")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -137,11 +146,20 @@ fun HomeContent(
|
||||
item(key = "empty-state") {
|
||||
HomeEmptyState(
|
||||
onRefresh = onRefresh,
|
||||
onBrowseLibrariesClick = onBrowseLibrariesClick
|
||||
onBrowseLibrariesClick = onBrowseLibrariesClick,
|
||||
modifier = Modifier.testTag(HomeEmptyStateTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val HomeContentTag = "home-content"
|
||||
internal const val HomeContentViewportTag = "home-content-viewport"
|
||||
internal const val HomeFeaturedSectionTag = "home-section-featured"
|
||||
internal const val HomeContinueWatchingSectionTag = "home-section-continue-watching"
|
||||
internal const val HomeNextUpSectionTag = "home-section-next-up"
|
||||
internal const val HomeLibrarySectionTagPrefix = "home-section-library-"
|
||||
internal const val HomeEmptyStateTag = "home-empty-state"
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.ui.common.header.SectionHeader
|
||||
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
|
||||
@@ -41,7 +42,9 @@ fun ContinueWatchingSection(
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(HomeContinueWatchingRowTag),
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
||||
@@ -49,9 +52,13 @@ fun ContinueWatchingSection(
|
||||
item = item,
|
||||
sharedBoundsKey = homeMediaSharedBoundsKey("continue-$index", item.id),
|
||||
onMediaSelected = onMediaSelected,
|
||||
onMarkAsWatched = onMarkAsWatched
|
||||
onMarkAsWatched = onMarkAsWatched,
|
||||
modifier = Modifier.testTag("$HomeContinueWatchingItemTagPrefix$index")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val HomeContinueWatchingRowTag = "home-continue-watching-row"
|
||||
internal const val HomeContinueWatchingItemTagPrefix = "home-continue-watching-item-"
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
@@ -48,13 +49,16 @@ fun SuggestionsSection(
|
||||
pageSize = PageSize.Fixed(320.dp),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
pageSpacing = 16.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(HomeFeaturedPagerTag)
|
||||
) { page ->
|
||||
val item = items[page]
|
||||
SuggestionCard(
|
||||
item = item,
|
||||
sharedBoundsKey = homeMediaSharedBoundsKey("suggestion-$page", item.id),
|
||||
onClick = { onItemOpen(item) }
|
||||
onClick = { onItemOpen(item) },
|
||||
modifier = Modifier.testTag("$HomeFeaturedItemTagPrefix$page")
|
||||
)
|
||||
}
|
||||
if (items.size > 1) {
|
||||
@@ -86,3 +90,6 @@ fun SuggestionsSection(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val HomeFeaturedPagerTag = "home-featured-pager"
|
||||
internal const val HomeFeaturedItemTagPrefix = "home-featured-item-"
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.ui.common.header.SectionHeader
|
||||
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
|
||||
@@ -47,7 +48,9 @@ fun LibraryPosterSection(
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("$HomeLibraryRowTagPrefix${library.id}"),
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
||||
@@ -57,9 +60,13 @@ fun LibraryPosterSection(
|
||||
origin = "library-${library.id}-$index",
|
||||
mediaId = item.id
|
||||
),
|
||||
onMediaSelected = onMediaSelected
|
||||
onMediaSelected = onMediaSelected,
|
||||
modifier = Modifier.testTag("$HomeLibraryItemTagPrefix${library.id}-$index")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val HomeLibraryRowTagPrefix = "home-library-row-"
|
||||
internal const val HomeLibraryItemTagPrefix = "home-library-item-"
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.ui.common.header.SectionHeader
|
||||
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
|
||||
@@ -40,16 +41,22 @@ fun NextUpSection(
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(HomeNextUpRowTag),
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
||||
NextUpCard(
|
||||
uiModel = item,
|
||||
sharedBoundsKey = homeMediaSharedBoundsKey("next-up-$index", item.id),
|
||||
onMediaSelected = onMediaSelected
|
||||
onMediaSelected = onMediaSelected,
|
||||
modifier = Modifier.testTag("$HomeNextUpItemTagPrefix$index")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val HomeNextUpRowTag = "home-next-up-row"
|
||||
internal const val HomeNextUpItemTagPrefix = "home-next-up-item-"
|
||||
|
||||
@@ -47,6 +47,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -147,6 +148,7 @@ private fun SearchScreenContent(
|
||||
) },
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(SearchScreenTag)
|
||||
.background(scheme.background)
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
@@ -261,7 +263,10 @@ private fun SearchField(
|
||||
},
|
||||
trailingIcon = {
|
||||
if (query.isNotBlank()) {
|
||||
IconButton(onClick = { onQueryChange("") }) {
|
||||
IconButton(
|
||||
onClick = { onQueryChange("") },
|
||||
modifier = Modifier.testTag(SearchClearButtonTag)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = "Clear search",
|
||||
@@ -286,6 +291,7 @@ private fun SearchField(
|
||||
),
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(SearchQueryFieldTag)
|
||||
.height(68.dp)
|
||||
)
|
||||
}
|
||||
@@ -298,18 +304,23 @@ private fun SearchResultsGrid(
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = modifier.fillMaxWidth()
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(SearchResultsGridTag)
|
||||
) {
|
||||
results.chunked(2).forEach { rowItems ->
|
||||
results.chunked(2).forEachIndexed { rowIndex, rowItems ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
rowItems.forEach { item ->
|
||||
rowItems.forEachIndexed { columnIndex, item ->
|
||||
val itemIndex = rowIndex * 2 + columnIndex
|
||||
SearchResultCard(
|
||||
item = item,
|
||||
onClick = { onResultClick(item) },
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.testTag("$SearchResultItemTagPrefix$itemIndex")
|
||||
)
|
||||
}
|
||||
if (rowItems.size == 1) {
|
||||
@@ -367,6 +378,7 @@ private fun GenreSelector(
|
||||
FilterChip(
|
||||
selected = false,
|
||||
onClick = { showGenres = true },
|
||||
modifier = Modifier.testTag(SearchGenreSelectorTag),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Tune,
|
||||
@@ -388,11 +400,12 @@ private fun GenreSelector(
|
||||
)
|
||||
}
|
||||
)
|
||||
visibleGenres.forEach { genre ->
|
||||
visibleGenres.forEachIndexed { index, genre ->
|
||||
GenreChip(
|
||||
genre = genre,
|
||||
selected = genre.name in selectedGenreNames,
|
||||
onClick = { onGenreSelected(genre) }
|
||||
onClick = { onGenreSelected(genre) },
|
||||
modifier = Modifier.testTag("$SearchGenreChipTagPrefix$index")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -400,6 +413,7 @@ private fun GenreSelector(
|
||||
if (showGenres) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showGenres = false },
|
||||
modifier = Modifier.testTag(SearchGenreDialogTag),
|
||||
title = {
|
||||
Text(text = "Select genres")
|
||||
},
|
||||
@@ -412,11 +426,12 @@ private fun GenreSelector(
|
||||
.heightIn(max = 360.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
genres.forEach { genre ->
|
||||
genres.forEachIndexed { index, genre ->
|
||||
GenreChip(
|
||||
genre = genre,
|
||||
selected = genre.name in selectedGenreNames,
|
||||
onClick = { onGenreSelected(genre) }
|
||||
onClick = { onGenreSelected(genre) },
|
||||
modifier = Modifier.testTag("$SearchGenreDialogChipTagPrefix$index")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -514,3 +529,13 @@ private val previewSearchResults = listOf(
|
||||
type = MediaKind.MOVIE
|
||||
)
|
||||
)
|
||||
|
||||
internal const val SearchScreenTag = "search-screen"
|
||||
internal const val SearchQueryFieldTag = "search-query-field"
|
||||
internal const val SearchClearButtonTag = "search-clear-button"
|
||||
internal const val SearchResultsGridTag = "search-results-grid"
|
||||
internal const val SearchResultItemTagPrefix = "search-result-item-"
|
||||
internal const val SearchGenreSelectorTag = "search-genre-selector"
|
||||
internal const val SearchGenreChipTagPrefix = "search-genre-chip-"
|
||||
internal const val SearchGenreDialogTag = "search-genre-dialog"
|
||||
internal const val SearchGenreDialogChipTagPrefix = "search-genre-dialog-chip-"
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.model.LibraryUiModel
|
||||
|
||||
@@ -17,7 +18,7 @@ fun LibrariesContent(
|
||||
onLibrarySelected: (LibraryUiModel) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BoxWithConstraints(modifier = modifier) {
|
||||
BoxWithConstraints(modifier = modifier.testTag(LibrariesOverviewGridTag)) {
|
||||
val minCellSize = if (maxWidth >= 600.dp) 220.dp else 160.dp
|
||||
|
||||
LazyVerticalGrid(
|
||||
@@ -29,9 +30,13 @@ fun LibrariesContent(
|
||||
items(items, key = { it.id }) { item ->
|
||||
LibraryListItem(
|
||||
uiModel = item,
|
||||
onClick = { onLibrarySelected(item) }
|
||||
onClick = { onLibrarySelected(item) },
|
||||
modifier = Modifier.testTag("$LibrariesOverviewItemTagPrefix${item.id}")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val LibrariesOverviewGridTag = "libraries-overview-grid"
|
||||
internal const val LibrariesOverviewItemTagPrefix = "libraries-overview-item-"
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.core.feature.browse.library.LibraryViewModel
|
||||
@@ -42,7 +43,7 @@ fun LibraryScreen(
|
||||
val libraryItems = viewModel.contents.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
modifier = modifier.testTag(LibraryScreenTag),
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
topBar = {
|
||||
@@ -68,7 +69,11 @@ internal fun LibraryPosterGrid(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: LibraryViewModel = hiltViewModel()
|
||||
) {
|
||||
BoxWithConstraints(modifier = modifier.background(MaterialTheme.colorScheme.background)) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.testTag(LibraryPosterGridTag)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
val minCellSize = if (maxWidth >= 600.dp) 220.dp else 120.dp
|
||||
|
||||
LazyVerticalGrid(
|
||||
@@ -88,7 +93,8 @@ internal fun LibraryPosterGrid(
|
||||
is SeriesUiModel -> viewModel.onSeriesSelected(item.id)
|
||||
is EpisodeUiModel -> Unit
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.testTag("$LibraryPosterItemTagPrefix${item.id}")
|
||||
) {
|
||||
when (item) {
|
||||
is MovieUiModel, is EpisodeUiModel -> {
|
||||
@@ -107,3 +113,7 @@ internal fun LibraryPosterGrid(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val LibraryScreenTag = "library-screen"
|
||||
internal const val LibraryPosterGridTag = "library-poster-grid"
|
||||
internal const val LibraryPosterItemTagPrefix = "library-poster-item-"
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -47,6 +48,7 @@ fun LoginContent(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(LoginContentTag)
|
||||
.background(scheme.background)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp, vertical = 20.dp),
|
||||
@@ -84,6 +86,7 @@ fun LoginContent(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(LoginErrorMessageTag)
|
||||
.background(
|
||||
scheme.errorContainer,
|
||||
RoundedCornerShape(12.dp)
|
||||
@@ -121,13 +124,15 @@ private fun ServerSearchContent(
|
||||
modifier = Modifier.padding(top = 2.dp, bottom = 16.dp).fillMaxWidth()
|
||||
)
|
||||
|
||||
PurefinComplexTextField(
|
||||
label = "Server URL",
|
||||
value = state.serverUrl,
|
||||
onValueChange = callbacks.onServerUrlChange,
|
||||
placeholder = "http://192.168.1.100:8096",
|
||||
leadingIcon = Icons.Default.Storage
|
||||
)
|
||||
Column(modifier = Modifier.testTag(LoginServerUrlFieldTag)) {
|
||||
PurefinComplexTextField(
|
||||
label = "Server URL",
|
||||
value = state.serverUrl,
|
||||
onValueChange = callbacks.onServerUrlChange,
|
||||
placeholder = "http://192.168.1.100:8096",
|
||||
leadingIcon = Icons.Default.Storage
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
@@ -137,6 +142,7 @@ private fun ServerSearchContent(
|
||||
enabled = !state.isSearching,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(LoginFindServerButtonTag)
|
||||
.height(48.dp)
|
||||
)
|
||||
|
||||
@@ -149,10 +155,12 @@ private fun ServerSearchContent(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
state.discoveredServers.forEach { server ->
|
||||
state.discoveredServers.forEachIndexed { index, server ->
|
||||
TextButton(
|
||||
onClick = { callbacks.onDiscoveredServerClick(server) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("$LoginDiscoveredServerTagPrefix$index")
|
||||
) {
|
||||
Text(
|
||||
text = server.name?.let { "$it\n${server.address}" } ?: server.address,
|
||||
@@ -186,7 +194,9 @@ private fun LoginPhaseContent(
|
||||
)
|
||||
TextButton(
|
||||
onClick = callbacks.onChangeServer,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(LoginChangeServerButtonTag)
|
||||
) {
|
||||
Text("Change server")
|
||||
}
|
||||
@@ -199,7 +209,9 @@ private fun LoginPhaseContent(
|
||||
color = scheme.primary,
|
||||
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(LoginQuickConnectCodeTag)
|
||||
)
|
||||
Text(
|
||||
text = "Approve this code in another Jellyfin client.",
|
||||
@@ -209,14 +221,20 @@ private fun LoginPhaseContent(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 4.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
TextButton(onClick = callbacks.onCancelQuickConnect) {
|
||||
TextButton(
|
||||
onClick = callbacks.onCancelQuickConnect,
|
||||
modifier = Modifier.testTag(LoginQuickConnectCancelButtonTag)
|
||||
) {
|
||||
Text("Cancel Quick Connect")
|
||||
}
|
||||
} ?: PurefinTextButton(
|
||||
content = { Text("Quick Connect") },
|
||||
onClick = callbacks.onQuickConnect,
|
||||
enabled = !state.isQuickConnecting,
|
||||
modifier = Modifier.fillMaxWidth().height(48.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(LoginQuickConnectButtonTag)
|
||||
.height(48.dp)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
@@ -238,23 +256,27 @@ private fun LoginPhaseContent(
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
PurefinComplexTextField(
|
||||
label = "Username",
|
||||
value = state.username,
|
||||
onValueChange = callbacks.onUsernameChange,
|
||||
placeholder = "Enter your username",
|
||||
leadingIcon = Icons.Default.Person
|
||||
)
|
||||
Column(modifier = Modifier.testTag(LoginUsernameFieldTag)) {
|
||||
PurefinComplexTextField(
|
||||
label = "Username",
|
||||
value = state.username,
|
||||
onValueChange = callbacks.onUsernameChange,
|
||||
placeholder = "Enter your username",
|
||||
leadingIcon = Icons.Default.Person
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
PurefinPasswordField(
|
||||
label = "Password",
|
||||
value = state.password,
|
||||
onValueChange = callbacks.onPasswordChange,
|
||||
placeholder = "Enter your password",
|
||||
leadingIcon = Icons.Default.Lock,
|
||||
)
|
||||
Column(modifier = Modifier.testTag(LoginPasswordFieldTag)) {
|
||||
PurefinPasswordField(
|
||||
label = "Password",
|
||||
value = state.password,
|
||||
onValueChange = callbacks.onPasswordChange,
|
||||
placeholder = "Enter your password",
|
||||
leadingIcon = Icons.Default.Lock,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
@@ -264,6 +286,20 @@ private fun LoginPhaseContent(
|
||||
enabled = !state.isQuickConnecting,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(LoginConnectButtonTag)
|
||||
.height(48.dp)
|
||||
)
|
||||
}
|
||||
|
||||
internal const val LoginContentTag = "login-content"
|
||||
internal const val LoginFindServerButtonTag = "login-find-server-button"
|
||||
internal const val LoginServerUrlFieldTag = "login-server-url-field"
|
||||
internal const val LoginDiscoveredServerTagPrefix = "login-discovered-server-"
|
||||
internal const val LoginChangeServerButtonTag = "login-change-server-button"
|
||||
internal const val LoginUsernameFieldTag = "login-username-field"
|
||||
internal const val LoginPasswordFieldTag = "login-password-field"
|
||||
internal const val LoginQuickConnectButtonTag = "login-quick-connect-button"
|
||||
internal const val LoginQuickConnectCodeTag = "login-quick-connect-code"
|
||||
internal const val LoginQuickConnectCancelButtonTag = "login-quick-connect-cancel-button"
|
||||
internal const val LoginConnectButtonTag = "login-connect-button"
|
||||
internal const val LoginErrorMessageTag = "login-error-message"
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -85,7 +86,9 @@ internal fun MovieDetails(
|
||||
text = mediaPlayButtonText(movie.progress, movie.watched),
|
||||
progress = mediaPlaybackProgress(movie.progress),
|
||||
onClick = playAction,
|
||||
modifier = Modifier.sizeIn(maxWidth = 200.dp)
|
||||
modifier = Modifier
|
||||
.sizeIn(maxWidth = 200.dp)
|
||||
.testTag(MoviePlayButtonTag)
|
||||
)
|
||||
VerticalDivider(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
@@ -105,6 +108,7 @@ internal fun MovieDetails(
|
||||
is DownloadState.Failed -> Icons.Outlined.Download
|
||||
},
|
||||
height = 48.dp,
|
||||
modifier = Modifier.testTag(MovieDownloadButtonTag),
|
||||
onClick = onDownloadClick
|
||||
)
|
||||
}
|
||||
@@ -130,3 +134,6 @@ internal fun MovieDetails(
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
internal const val MoviePlayButtonTag = "movie-play-button"
|
||||
internal const val MovieDownloadButtonTag = "movie-download-button"
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.player.model.PlayerUiState
|
||||
@@ -70,6 +71,7 @@ fun PlayerControlsOverlay(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(PlayerControlsOverlayTag)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(
|
||||
@@ -149,6 +151,7 @@ private fun TopBar(
|
||||
GhostIconButton(
|
||||
icon = Icons.Outlined.PlaylistPlay,
|
||||
contentDescription = "Queue",
|
||||
modifier = Modifier.testTag(PlayerQueueButtonTag),
|
||||
onClick = onOpenQueue
|
||||
)
|
||||
GhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = onCast)
|
||||
@@ -192,7 +195,8 @@ private fun BottomSection(
|
||||
adMarkers = uiState.ads,
|
||||
onSeek = onScrub,
|
||||
onScrubStarted = onScrubStart,
|
||||
onScrubFinished = onScrubFinished
|
||||
onScrubFinished = onScrubFinished,
|
||||
modifier = Modifier.testTag(PlayerSeekBarTag)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Box(
|
||||
@@ -209,30 +213,35 @@ private fun BottomSection(
|
||||
icon = Icons.Outlined.SkipPrevious,
|
||||
contentDescription = "Previous",
|
||||
onClick = onPrevious,
|
||||
modifier = Modifier.testTag(PlayerPreviousButtonTag),
|
||||
size = 64
|
||||
)
|
||||
PurefinIconButton(
|
||||
icon = Icons.Outlined.Replay10,
|
||||
contentDescription = "Seek backward",
|
||||
onClick = onSeekBackward,
|
||||
modifier = Modifier.testTag(PlayerSeekBackwardButtonTag),
|
||||
size = 64
|
||||
)
|
||||
PurefinIconButton(
|
||||
icon = if (uiState.isPlaying) Icons.Outlined.Pause else Icons.Outlined.PlayArrow,
|
||||
contentDescription = "Play/Pause",
|
||||
onClick = onPlayPause,
|
||||
modifier = Modifier.testTag(PlayerPlayPauseButtonTag),
|
||||
size = 64
|
||||
)
|
||||
PurefinIconButton(
|
||||
icon = Icons.Outlined.Forward30,
|
||||
contentDescription = "Seek forward",
|
||||
onClick = onSeekForward,
|
||||
modifier = Modifier.testTag(PlayerSeekForwardButtonTag),
|
||||
size = 64
|
||||
)
|
||||
PurefinIconButton(
|
||||
icon = Icons.Outlined.SkipNext,
|
||||
contentDescription = "Next",
|
||||
onClick = onNext,
|
||||
modifier = Modifier.testTag(PlayerNextButtonTag),
|
||||
size = 64
|
||||
)
|
||||
}
|
||||
@@ -263,7 +272,9 @@ private fun BottomSection(
|
||||
if (uiState.activeSkippableSegmentEndMs != null) {
|
||||
SkipSegmentButton(
|
||||
onClick = onSkipSegment,
|
||||
modifier = Modifier.align(Alignment.CenterEnd)
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.testTag(PlayerSkipSegmentButtonTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -271,6 +282,16 @@ private fun BottomSection(
|
||||
}
|
||||
}
|
||||
|
||||
internal const val PlayerControlsOverlayTag = "player-controls-overlay"
|
||||
internal const val PlayerQueueButtonTag = "player_queue_button"
|
||||
internal const val PlayerSeekBarTag = "player_seek_bar"
|
||||
internal const val PlayerPreviousButtonTag = "player_previous_button"
|
||||
internal const val PlayerSeekBackwardButtonTag = "player_seek_backward_button"
|
||||
internal const val PlayerPlayPauseButtonTag = "player_play_pause_button"
|
||||
internal const val PlayerSeekForwardButtonTag = "player_seek_forward_button"
|
||||
internal const val PlayerNextButtonTag = "player_next_button"
|
||||
internal const val PlayerSkipSegmentButtonTag = "player_skip_segment_button"
|
||||
|
||||
@Composable
|
||||
fun SkipSegmentButton(
|
||||
onClick: () -> Unit,
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -40,7 +41,12 @@ fun PlayerQueuePanel(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(PlayerPlaylistPanelTag),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
@@ -78,13 +84,23 @@ fun PlayerQueuePanel(
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.testTag(PlayerPlaylistRowTag)
|
||||
) {
|
||||
items(uiState.queue, key = { item -> item.id }) { item ->
|
||||
QueueRow(
|
||||
title = item.title,
|
||||
artworkUrl = item.artworkUrl,
|
||||
isCurrent = item.isCurrent,
|
||||
onClick = { onSelect(item.id) }
|
||||
onClick = { onSelect(item.id) },
|
||||
modifier = Modifier.testTag(
|
||||
if (item.isCurrent) {
|
||||
PlayerPlaylistCurrentItemTag
|
||||
} else {
|
||||
"${PlayerPlaylistItemTagPrefix}${item.id}"
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -106,11 +122,12 @@ private fun QueueRow(
|
||||
subtitle: String? = null,
|
||||
artworkUrl: String?,
|
||||
isCurrent: Boolean,
|
||||
onClick: () -> Unit
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
Row(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(
|
||||
@@ -160,3 +177,8 @@ private fun QueueRow(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal const val PlayerPlaylistPanelTag = "player_playlist_panel"
|
||||
internal const val PlayerPlaylistRowTag = "player_playlist_row"
|
||||
internal const val PlayerPlaylistCurrentItemTag = "player_playlist_current_item"
|
||||
internal const val PlayerPlaylistItemTagPrefix = "player_playlist_item_"
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -63,7 +64,7 @@ fun QualitySelectionButton(
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
modifier = modifier.testTag(PlayerQualityButtonTag)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ fun AudioSelectionButton(
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
modifier = modifier.testTag(PlayerAudioButtonTag)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ fun SubtitlesSelectionButton(
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
modifier = modifier.testTag(PlayerSubtitlesButtonTag)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,6 +140,7 @@ private fun TrackSelectionPanel(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(PlayerTrackPanelTag)
|
||||
.fillMaxHeight()
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
.navigationBarsPadding()
|
||||
@@ -199,12 +201,20 @@ private fun TrackSelectionPanel(
|
||||
.heightIn(max = 292.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
options.forEach { option ->
|
||||
options.forEachIndexed { index, option ->
|
||||
val selected = option.id == selectedId
|
||||
TrackOptionItem(
|
||||
label = option.label,
|
||||
selected = selected,
|
||||
onClick = { onSelect(option) }
|
||||
onClick = { onSelect(option) },
|
||||
modifier = Modifier.testTag(
|
||||
when {
|
||||
selected -> PlayerTrackSelectedItemTag
|
||||
index == 0 -> PlayerTrackFirstItemTag
|
||||
index == options.lastIndex -> PlayerTrackLastItemTag
|
||||
else -> "$PlayerTrackItemTagPrefix$index"
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -213,6 +223,15 @@ private fun TrackSelectionPanel(
|
||||
}
|
||||
}
|
||||
|
||||
internal const val PlayerQualityButtonTag = "player_quality_button"
|
||||
internal const val PlayerAudioButtonTag = "player_audio_button"
|
||||
internal const val PlayerSubtitlesButtonTag = "player_subtitles_button"
|
||||
internal const val PlayerTrackPanelTag = "player_track_panel"
|
||||
internal const val PlayerTrackSelectedItemTag = "player_track_selected_item"
|
||||
internal const val PlayerTrackFirstItemTag = "player_track_first_item"
|
||||
internal const val PlayerTrackLastItemTag = "player_track_last_item"
|
||||
internal const val PlayerTrackItemTagPrefix = "player_track_item_"
|
||||
|
||||
@Composable
|
||||
private fun TrackOptionItem(
|
||||
label: String,
|
||||
|
||||
@@ -50,6 +50,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -146,14 +147,17 @@ internal fun SeriesActionButtons(
|
||||
text = mediaPlayButtonText(nextUpEpisode?.progress, nextUpEpisode?.watched),
|
||||
progress = mediaPlaybackProgress(nextUpEpisode?.progress),
|
||||
onClick = playAction ?: {},
|
||||
modifier = Modifier.sizeIn(maxWidth = 200.dp)
|
||||
modifier = Modifier
|
||||
.sizeIn(maxWidth = 200.dp)
|
||||
.testTag(SeriesPlayButtonTag)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
iconColor = MaterialTheme.colorScheme.onSurface,
|
||||
icon = Icons.Outlined.Add,
|
||||
height = 48.dp
|
||||
height = 48.dp,
|
||||
modifier = Modifier.testTag(SeriesAddButtonTag)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
MediaActionButton(
|
||||
@@ -165,6 +169,7 @@ internal fun SeriesActionButtons(
|
||||
else -> Icons.Outlined.Download
|
||||
},
|
||||
height = 48.dp,
|
||||
modifier = Modifier.testTag(SeriesDownloadButtonTag),
|
||||
onClick = { showDownloadDialog = true }
|
||||
)
|
||||
}
|
||||
@@ -200,6 +205,7 @@ private fun DownloadOptionsDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
modifier = Modifier.testTag(SeriesDownloadDialogTag),
|
||||
title = { Text("Download") },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
@@ -211,7 +217,10 @@ private fun DownloadOptionsDialog(
|
||||
},
|
||||
confirmButton = {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SEASON) }) {
|
||||
TextButton(
|
||||
onClick = { onDownloadOptionSelected(SeriesDownloadOption.SEASON) },
|
||||
modifier = Modifier.testTag(SeriesDownloadSeasonButtonTag)
|
||||
) {
|
||||
Text(
|
||||
when (seasonDownloadState) {
|
||||
is DownloadState.Downloaded -> "$selectedSeasonName Downloaded"
|
||||
@@ -220,7 +229,10 @@ private fun DownloadOptionsDialog(
|
||||
}
|
||||
)
|
||||
}
|
||||
TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SERIES) }) {
|
||||
TextButton(
|
||||
onClick = { onDownloadOptionSelected(SeriesDownloadOption.SERIES) },
|
||||
modifier = Modifier.testTag(SeriesDownloadAllButtonTag)
|
||||
) {
|
||||
Text("Download All")
|
||||
}
|
||||
}
|
||||
@@ -229,6 +241,7 @@ private fun DownloadOptionsDialog(
|
||||
if (isSmartDownloadEnabled) {
|
||||
TextButton(
|
||||
onClick = { onDownloadOptionSelected(SeriesDownloadOption.DELETE_SMART) },
|
||||
modifier = Modifier.testTag(SeriesSmartDownloadButtonTag),
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
@@ -236,7 +249,10 @@ private fun DownloadOptionsDialog(
|
||||
Text("Delete Smart Downloads")
|
||||
}
|
||||
} else {
|
||||
TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SMART) }) {
|
||||
TextButton(
|
||||
onClick = { onDownloadOptionSelected(SeriesDownloadOption.SMART) },
|
||||
modifier = Modifier.testTag(SeriesSmartDownloadButtonTag)
|
||||
) {
|
||||
Text("Smart Download")
|
||||
}
|
||||
}
|
||||
@@ -258,11 +274,13 @@ internal fun SeasonTabs(
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
seasons.forEach { season ->
|
||||
seasons.forEachIndexed { index, season ->
|
||||
SeasonTab(
|
||||
name = season.name,
|
||||
isSelected = season == selectedSeason,
|
||||
modifier = Modifier.clickable { onSelect(season) }
|
||||
modifier = Modifier
|
||||
.testTag("$SeriesSeasonTabTagPrefix$index")
|
||||
.clickable { onSelect(season) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -313,7 +331,7 @@ internal fun EpisodeCarousel(episodes: List<Episode>, modifier: Modifier = Modif
|
||||
|
||||
LazyRow(
|
||||
state = listState,
|
||||
modifier = modifier,
|
||||
modifier = modifier.testTag(SeriesEpisodeCarouselTag),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
items(episodes, key = { episode -> episode.id }) { episode ->
|
||||
@@ -332,6 +350,7 @@ private fun EpisodeCard(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(260.dp)
|
||||
.testTag("$SeriesEpisodeCardTagPrefix${episode.id}")
|
||||
.clickable { viewModel.onSelectEpisode(
|
||||
seriesId = episode.seriesId,
|
||||
seasonId = episode.seasonId,
|
||||
@@ -416,6 +435,17 @@ private fun EpisodeCard(
|
||||
}
|
||||
}
|
||||
|
||||
internal const val SeriesPlayButtonTag = "series-play-button"
|
||||
internal const val SeriesAddButtonTag = "series-add-button"
|
||||
internal const val SeriesDownloadButtonTag = "series-download-button"
|
||||
internal const val SeriesDownloadDialogTag = "series-download-dialog"
|
||||
internal const val SeriesDownloadSeasonButtonTag = "series-download-season-button"
|
||||
internal const val SeriesDownloadAllButtonTag = "series-download-all-button"
|
||||
internal const val SeriesSmartDownloadButtonTag = "series-smart-download-button"
|
||||
internal const val SeriesSeasonTabTagPrefix = "series-season-tab-"
|
||||
internal const val SeriesEpisodeCarouselTag = "series-episode-carousel"
|
||||
internal const val SeriesEpisodeCardTagPrefix = "series-episode-card-"
|
||||
|
||||
@Composable
|
||||
internal fun CastRow(cast: List<CastMember>, modifier: Modifier = Modifier) {
|
||||
//TODO fix
|
||||
|
||||
Reference in New Issue
Block a user