Straw phase I: SB segment count chip + clear-history buttons
Detail screen: VideoDetailViewModel now also fetches SponsorBlock segment count for the user's currently-enabled categories alongside RYD. When >0, detail screen shows a "⏭ N skip(s)" AssistChip next to the like/dislike chips. Lets the user see at-a-glance whether SB is going to do anything on this video before tapping Play. Settings screen: "History" section at the bottom with two OutlinedButtons — "Clear watch history" and "Clear searches". Each calls the corresponding HistoryStore.clear* method. List updates immediately via the StateFlow. Empty-category set on SB now means "skip the API call" (was already checking this in PlayerViewModel.resolve, now also in VideoDetailViewModel).
This commit is contained in:
parent
15b81303e2
commit
b2d63bb353
3 changed files with 32 additions and 0 deletions
|
|
@ -115,6 +115,12 @@ fun VideoDetailScreen(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (d.sbSegmentCount > 0) {
|
||||||
|
AssistChip(
|
||||||
|
onClick = {},
|
||||||
|
label = { Text("⏭ ${d.sbSegmentCount} skip${if (d.sbSegmentCount == 1) "" else "s"}") },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ package com.sulkta.straw.feature.detail
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.sulkta.straw.data.History
|
import com.sulkta.straw.data.History
|
||||||
|
import com.sulkta.straw.data.Settings
|
||||||
import com.sulkta.straw.data.WatchHistoryItem
|
import com.sulkta.straw.data.WatchHistoryItem
|
||||||
import com.sulkta.straw.net.RydClient
|
import com.sulkta.straw.net.RydClient
|
||||||
import com.sulkta.straw.net.RydVotes
|
import com.sulkta.straw.net.RydVotes
|
||||||
|
import com.sulkta.straw.net.SponsorBlockClient
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -27,6 +29,7 @@ data class VideoDetail(
|
||||||
val description: String,
|
val description: String,
|
||||||
val thumbnail: String?,
|
val thumbnail: String?,
|
||||||
val ryd: RydVotes? = null,
|
val ryd: RydVotes? = null,
|
||||||
|
val sbSegmentCount: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class VideoDetailUiState(
|
data class VideoDetailUiState(
|
||||||
|
|
@ -70,6 +73,10 @@ class VideoDetailViewModel : ViewModel() {
|
||||||
val ryd = withContext(Dispatchers.IO) {
|
val ryd = withContext(Dispatchers.IO) {
|
||||||
runCatching { RydClient.fetch(videoId) }.getOrNull()
|
runCatching { RydClient.fetch(videoId) }.getOrNull()
|
||||||
}
|
}
|
||||||
|
val sbCats = Settings.get().sbCategories.value.map { it.key }
|
||||||
|
val sbCount = if (sbCats.isEmpty()) 0 else withContext(Dispatchers.IO) {
|
||||||
|
runCatching { SponsorBlockClient.fetch(videoId, sbCats).size }.getOrDefault(0)
|
||||||
|
}
|
||||||
_ui.value = VideoDetailUiState(
|
_ui.value = VideoDetailUiState(
|
||||||
loading = false,
|
loading = false,
|
||||||
detail = VideoDetail(
|
detail = VideoDetail(
|
||||||
|
|
@ -80,6 +87,7 @@ class VideoDetailViewModel : ViewModel() {
|
||||||
description = info.description?.content ?: "",
|
description = info.description?.content ?: "",
|
||||||
thumbnail = thumb,
|
thumbnail = thumb,
|
||||||
ryd = ryd,
|
ryd = ryd,
|
||||||
|
sbSegmentCount = sbCount,
|
||||||
),
|
),
|
||||||
streamInfo = info,
|
streamInfo = info,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -26,6 +27,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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 com.sulkta.straw.data.History
|
||||||
import com.sulkta.straw.data.SbCategory
|
import com.sulkta.straw.data.SbCategory
|
||||||
import com.sulkta.straw.data.Settings
|
import com.sulkta.straw.data.Settings
|
||||||
|
|
||||||
|
|
@ -68,6 +70,22 @@ fun SettingsScreen() {
|
||||||
)
|
)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
Text(
|
||||||
|
"History",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
OutlinedButton(onClick = { History.get().clearWatches() }) {
|
||||||
|
Text("Clear watch history")
|
||||||
|
}
|
||||||
|
OutlinedButton(onClick = { History.get().clearSearches() }) {
|
||||||
|
Text("Clear searches")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue