diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index a20dca091..225345b66 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app" // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 27 -const val STRAW_VERSION_NAME = "0.1.0-AM" +const val STRAW_VERSION_CODE = 28 +const val STRAW_VERSION_NAME = "0.1.0-AN" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/strawApp/src/main/AndroidManifest.xml b/strawApp/src/main/AndroidManifest.xml index 46abc05b7..87ff57336 100644 --- a/strawApp/src/main/AndroidManifest.xml +++ b/strawApp/src/main/AndroidManifest.xml @@ -14,8 +14,8 @@ diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt index 7b552764e..502314b25 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt @@ -25,14 +25,17 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -118,6 +121,16 @@ fun VideoDetailScreen( var inlinePlaying by remember(streamUrl) { mutableStateOf(false) } LaunchedEffect(streamUrl) { vm.load(streamUrl) } + // The Background button (and the fullscreen audio-only toggle) + // disable the video track on the shared controller, and that state + // sticks. Entering detail = user wants to watch the video — wipe the + // override and let DASH pick the highest renderable video again. + LaunchedEffect(controller, streamUrl) { + controller?.let { + it.trackSelectionParameters = TrackSelectionParameters.Builder(context).build() + } + } + // Swipe-down to minimize. The drag handle is the inline player surface // (the 16:9 box at the top); we translate the WHOLE page with it so the // motion reads as "the video is being tucked away" rather than "this @@ -127,6 +140,25 @@ fun VideoDetailScreen( val dismissThresholdPx = with(density) { 140.dp.toPx() } val dragY = remember { Animatable(0f) } val scope = rememberCoroutineScope() + val playerDragModifier = Modifier.pointerInput(Unit) { + detectVerticalDragGestures( + onDragEnd = { + if (dragY.value > dismissThresholdPx) { + onMinimize() + } else { + scope.launch { dragY.animateTo(0f, spring()) } + } + }, + onDragCancel = { + scope.launch { dragY.animateTo(0f, spring()) } + }, + onVerticalDrag = { _, dy -> + scope.launch { + dragY.snapTo((dragY.value + dy).coerceAtLeast(0f)) + } + }, + ) + } Column( modifier = Modifier @@ -141,49 +173,27 @@ fun VideoDetailScreen( scaleY = s } .statusBarsPadding() - .verticalScroll(rememberScrollState()) - .padding(16.dp), + .verticalScroll(rememberScrollState()), ) { when { state.loading -> Box( - modifier = Modifier.fillMaxWidth().padding(top = 64.dp), + modifier = Modifier + .fillMaxWidth() + .padding(top = 64.dp), contentAlignment = Alignment.Center, ) { CircularProgressIndicator() } state.error != null -> Text( "error: ${state.error}", color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(16.dp), ) else -> { val d = state.detail ?: return@Column - // Drag-to-minimize gesture lives on the player surface - // itself — same pattern YouTube/NewPipe use. Outside the - // 16:9 box the page scrolls normally, so the drag never - // fights with description scrolling. - val playerDragModifier = Modifier.pointerInput(Unit) { - detectVerticalDragGestures( - onDragEnd = { - if (dragY.value > dismissThresholdPx) { - onMinimize() - } else { - scope.launch { - dragY.animateTo(0f, spring()) - } - } - }, - onDragCancel = { - scope.launch { dragY.animateTo(0f, spring()) } - }, - onVerticalDrag = { _, dy -> - scope.launch { - dragY.snapTo( - (dragY.value + dy).coerceAtLeast(0f), - ) - } - }, - ) - } + // Player surface — edge-to-edge, NewPipe/YouTube style. + // Lives outside the 16dp horizontal padding so the + // thumbnail fills the screen width with no gutters. if (inlinePlaying) { InlinePlayer( streamUrl = streamUrl, @@ -194,7 +204,6 @@ fun VideoDetailScreen( modifier = Modifier .fillMaxWidth() .aspectRatio(16f / 9f) - .clip(RoundedCornerShape(8.dp)) .background(Color.Black) .then(playerDragModifier), ) @@ -203,7 +212,7 @@ fun VideoDetailScreen( modifier = Modifier .fillMaxWidth() .aspectRatio(16f / 9f) - .clip(RoundedCornerShape(8.dp)) + .background(Color.Black) .clickable { inlinePlaying = true } .then(playerDragModifier), contentAlignment = Alignment.Center, @@ -229,8 +238,9 @@ fun VideoDetailScreen( } } } - Spacer(modifier = Modifier.height(12.dp)) - + // Everything below the player gets the side gutters + // back; player itself remains edge-to-edge. + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) { Text( text = d.title, style = MaterialTheme.typography.titleLarge, @@ -492,8 +502,14 @@ fun VideoDetailScreen( }, ) } + } // close inner Column (padded body) } } + // Leave room at the bottom for the system nav bar so the last + // related video doesn't tuck under the gesture pill / 3-button + // nav. Compose's `navigationBarsPadding` would push the whole + // surface up; we want the scroll to extend past it instead. + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } diff --git a/strawApp/src/main/res/drawable/ic_launcher_background.xml b/strawApp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..7d786ca64 --- /dev/null +++ b/strawApp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,5 @@ + + + + diff --git a/strawApp/src/main/res/drawable/ic_launcher_foreground.xml b/strawApp/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..9bb57a385 --- /dev/null +++ b/strawApp/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/strawApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/strawApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..6b78462d6 --- /dev/null +++ b/strawApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/strawApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/strawApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..6b78462d6 --- /dev/null +++ b/strawApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/strawApp/src/main/res/mipmap-hdpi/ic_launcher.png b/strawApp/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..c8f3906f9 Binary files /dev/null and b/strawApp/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/strawApp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/strawApp/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..c8f3906f9 Binary files /dev/null and b/strawApp/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/strawApp/src/main/res/mipmap-mdpi/ic_launcher.png b/strawApp/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..fcfab8700 Binary files /dev/null and b/strawApp/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/strawApp/src/main/res/mipmap-mdpi/ic_launcher_round.png b/strawApp/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..fcfab8700 Binary files /dev/null and b/strawApp/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/strawApp/src/main/res/mipmap-xhdpi/ic_launcher.png b/strawApp/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..717e67c8c Binary files /dev/null and b/strawApp/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/strawApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/strawApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..717e67c8c Binary files /dev/null and b/strawApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/strawApp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/strawApp/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..455be2a69 Binary files /dev/null and b/strawApp/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/strawApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/strawApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..455be2a69 Binary files /dev/null and b/strawApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/strawApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/strawApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..edd54e2eb Binary files /dev/null and b/strawApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/strawApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/strawApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..edd54e2eb Binary files /dev/null and b/strawApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ