Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ class AudioPlayer {
}
}

/**
* Sets the specified playback speed and toggles the player state,
* if it is playing it will pause and vice-versa.
*/
fun togglePlay(playbackSpeed: Float) {
player.setPlaybackSpeed(playbackSpeed)

when (player.isPlaying) {
true -> pause()
false -> play()
}
}

fun play() {
player.play()
startPositionCheck()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import coil3.compose.AsyncImage
* when the image is displayed.
*
* @param image The [Bitmap] image to be displayed. Can be null, in which case no image is shown.
* @param enabled If the image is enabled, defaults to `true`
*/
@Composable
fun FadeInAsyncImage(image: Bitmap?) {
fun FadeInAsyncImage(image: Bitmap?, enabled: Boolean = true) {
var isVisible by rememberSaveable { mutableStateOf(false) }

// This prevents the background from "flashing" due to recompositions (probably)
Expand All @@ -38,6 +39,10 @@ fun FadeInAsyncImage(image: Bitmap?) {
isVisible = true
}

LaunchedEffect(enabled) {
isVisible = enabled
}

AnimatedVisibility(
modifier = Modifier.fillMaxSize(),
visible = isVisible,
Expand Down
16 changes: 1 addition & 15 deletions app/src/androidTest/java/cc/wordview/app/misc/AppSettingsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cc.wordview.app.ComposeTest
import cc.wordview.app.settings.AppSettings
import me.zhanghai.compose.preference.LocalPreferenceFlow
import me.zhanghai.compose.preference.ProvidePreferenceLocals
import org.junit.Test
Expand Down Expand Up @@ -52,19 +53,4 @@ class AppSettingsTest : ComposeTest() {
assert(langTag.length == 2)
}
}

@Test
fun getComposerMode() {
setup {
val composerMode = AppSettings.composerMode.get()
}
}

@Test
fun getComposerModePassingPreferences() {
setup {
val preferences by LocalPreferenceFlow.current.collectAsStateWithLifecycle()
val composerMode = AppSettings.composerMode.get(preferences)
}
}
}
22 changes: 22 additions & 0 deletions app/src/main/java/cc/wordview/app/settings/AppSettings.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Arthur Araujo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package cc.wordview.app.settings

object AppSettings {
val language = Setting(key = "language", defaultValue = "ja")
}
24 changes: 24 additions & 0 deletions app/src/main/java/cc/wordview/app/settings/PlayerSettings.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2025 Arthur Araujo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package cc.wordview.app.settings

object PlayerSettings {
val playbackSpeed = Setting(key = "player_playback_speed", defaultValue = 1.0f)
val backgroundImage = Setting(key = "player_background_image", defaultValue = true)
val composerMode = Setting(key = "player_composer_mode", defaultValue = false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package cc.wordview.app.misc
package cc.wordview.app.settings

import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
Expand All @@ -24,31 +24,26 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import me.zhanghai.compose.preference.LocalPreferenceFlow
import me.zhanghai.compose.preference.Preferences

object AppSettings {
val language = Setting(key = "language", defaultValue = "ja")
val composerMode = Setting(key = "composer_mode", defaultValue = false)
}

class Setting<T>(val key: String, val defaultValue: T) {
/**
* Returns the saved setting value, if the value was not yet registered, this will
* return the `defaultValue`
*/
@SuppressLint("ComposableNaming")
@Composable
fun get(): T {
val preferences by LocalPreferenceFlow.current.collectAsStateWithLifecycle()
return get(preferences)
}
/**
* Returns the saved setting value, if the value was not yet registered, this will
* return the `defaultValue`
*/
@SuppressLint("ComposableNaming")
@Composable
fun get(): T {
val preferences by LocalPreferenceFlow.current.collectAsStateWithLifecycle()
return get(preferences)
}

/**
* Using the specified preferences instance, returns the saved
* setting value, if the value was not yet registered, this will
* return the `defaultValue`
*
* @param preferences The preferences that will be used
*/
fun get(preferences: Preferences): T {
return preferences.get(key) ?: defaultValue
}
/**
* Using the specified preferences instance, returns the saved
* setting value, if the value was not yet registered, this will
* return the `defaultValue`
*
* @param preferences The preferences that will be used
*/
fun get(preferences: Preferences): T {
return preferences.get(key) ?: defaultValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Carpenter
import androidx.compose.material.icons.outlined.ImportExport
import androidx.compose.material.icons.outlined.NetworkPing
import androidx.compose.material.icons.outlined.Translate
Expand All @@ -39,18 +38,16 @@ import cc.wordview.app.components.ui.BackTopAppBar
import cc.wordview.app.extensions.localizedDisplayName
import cc.wordview.app.log.WordViewTree
import cc.wordview.app.log.exportLogs
import cc.wordview.app.misc.AppSettings
import cc.wordview.app.settings.AppSettings
import cc.wordview.app.ui.theme.poppinsFamily
import cc.wordview.gengolex.Language
import com.composegears.tiamat.compose.back
import com.composegears.tiamat.compose.navController
import com.composegears.tiamat.compose.navDestination
import com.composegears.tiamat.navigation.NavDestination
import me.zhanghai.compose.preference.ListPreferenceType
import me.zhanghai.compose.preference.basicPreference
import me.zhanghai.compose.preference.listPreference
import me.zhanghai.compose.preference.preference
import me.zhanghai.compose.preference.switchPreference
import timber.log.Timber

val SettingsScreen: NavDestination<Unit> by navDestination {
Expand Down Expand Up @@ -152,26 +149,6 @@ val SettingsScreen: NavDestination<Unit> by navDestination {
exportLogs(context, logs)
}
)
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
if (BuildConfig.BUILD_TYPE == "debug") {
switchPreference(
key = AppSettings.composerMode.key,
defaultValue = AppSettings.composerMode.defaultValue,
title = {
Text(
text = stringResource(R.string.composer_mode),
fontFamily = poppinsFamily
)
},
icon = {
Icon(
imageVector = Icons.Outlined.Carpenter,
contentDescription = null
)
},
summary = { Text(text = stringResource(R.string.provides_more_information_in_the_player_that_helps_writing_lyrics)) }
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import cc.wordview.app.R
import cc.wordview.app.api.getStoredJwt
import cc.wordview.app.components.ui.OneTimeEffect
import cc.wordview.app.extensions.getFlag
import cc.wordview.app.misc.AppSettings
import cc.wordview.app.settings.AppSettings
import cc.wordview.app.ui.activities.home.composables.ProfileScreen
import cc.wordview.app.ui.activities.home.composables.SettingsScreen
import cc.wordview.app.ui.activities.home.composables.history.HistoryScreen
Expand All @@ -64,7 +62,6 @@ import com.composegears.tiamat.compose.navDestination
import com.composegears.tiamat.compose.navigate
import com.composegears.tiamat.navigation.NavDestination
import kotlinx.coroutines.launch
import timber.log.Timber

@OptIn(ExperimentalMaterial3Api::class)
val HomeScreen: NavDestination<Unit> by navDestination {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import me.vponomarenko.compose.shimmer.shimmer
import androidx.compose.ui.res.stringResource
import cc.wordview.app.database.entity.ViewedVideo
import cc.wordview.app.components.extensions.openActivity
import cc.wordview.app.misc.AppSettings
import cc.wordview.app.settings.AppSettings
import cc.wordview.app.ui.components.ContinueWatchingCard

@OptIn(ExperimentalMaterial3Api::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cc.wordview.app.components.extensions.setOrientationSensorLandscape
import cc.wordview.app.components.ui.CircularProgressIndicator
import cc.wordview.app.misc.AppSettings
import cc.wordview.app.settings.AppSettings
import cc.wordview.app.ui.activities.WordViewActivity
import cc.wordview.app.ui.activities.player.composables.ErrorScreen
import cc.wordview.app.ui.activities.player.composables.Player
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -35,6 +33,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ClosedCaption
import androidx.compose.material.icons.filled.ClosedCaptionOff
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.SkipNext
import androidx.compose.material.icons.filled.SkipPrevious
import androidx.compose.material3.ExperimentalMaterial3Api
Expand All @@ -56,12 +55,10 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cc.wordview.app.misc.AppSettings
import cc.wordview.app.components.ui.CircularProgressIndicator
import cc.wordview.app.components.ui.CrossfadeIconButton
import cc.wordview.app.components.ui.FadeInAsyncImage
Expand All @@ -72,9 +69,9 @@ import cc.wordview.app.components.ui.OneTimeEffect
import cc.wordview.app.components.ui.PlayerTopBar
import cc.wordview.app.components.ui.Seekbar
import cc.wordview.app.components.ui.findBiggerCutout
import cc.wordview.app.settings.PlayerSettings
import cc.wordview.app.ui.components.TextCue


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingValues) {
Expand All @@ -87,9 +84,12 @@ fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingVal
val activity = LocalActivity.current!!
val density = LocalDensity.current

val composerMode = AppSettings.composerMode.get()
val composerMode = PlayerSettings.composerMode.get()
val playbackSpeed = PlayerSettings.playbackSpeed.get()
val backgroundImage = PlayerSettings.backgroundImage.get()

var captionsEnabled by remember { mutableStateOf(true) }
var showSettings by remember { mutableStateOf(false) }

LaunchedEffect(uiState.finalized) {
if (uiState.finalized) {
Expand All @@ -103,14 +103,27 @@ fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingVal
}

BackHandler { back() }
OneTimeEffect { uiState.player.togglePlay() }
OneTimeEffect { uiState.player.togglePlay(playbackSpeed) }

Box(
modifier = Modifier
.fillMaxSize()
.testTag("interface")
) {
FadeInAsyncImage(uiState.videoStream.getHQThumbnail())
FadeInAsyncImage(
image = uiState.videoStream.getHQThumbnail(),
enabled = backgroundImage,
)

if (showSettings) {
PlayerSettingsBottomSheet(
onDismissRequest = {
uiState.player.togglePlay(playbackSpeed)
showSettings = false
}
)
}

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
Expand Down Expand Up @@ -187,6 +200,18 @@ fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingVal
contentDescription = "Back"
)
}
IconButton(
onClick = {
uiState.player.pause()
showSettings = true
},
modifier = Modifier.testTag("settings-toggle-button"),
) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = "Settings"
)
}
},
)
Seekbar(
Expand Down
Loading
Loading