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