feat: Themes, WiP

This commit is contained in:
Stefan Schüller 2022-02-12 16:41:30 +01:00
parent 5518b96c46
commit 604a813b84
14 changed files with 147 additions and 91 deletions

View File

@ -29,6 +29,7 @@ x CI pipeline (gradle?)
- Add NSFW filter - Add NSFW filter
- Translate all strings - Translate all strings
- Swipe player down and up - Swipe player down and up
- implement preferences using data stores
Issues: Issues:
- Server change doesn't work until restart (retrofit) - Server change doesn't work until restart (retrofit)

View File

@ -3,8 +3,7 @@ package net.schueller.peertube.common
object Constants { object Constants {
const val PREF_LANG_APP_KEY = "pref_language_app" const val PREF_LANG_APP_KEY = "pref_language_app"
const val PREF_THEME_KEY = "pref_theme"
const val PREF_DARK_MODE_KEY = "pref_dark_mode"
const val PREF_SHOW_NSFW_KEY = "pref_show_nsfw" const val PREF_SHOW_NSFW_KEY = "pref_show_nsfw"
const val PREF_VIDEO_LANG_KEY = "pref_show_nsfw" const val PREF_VIDEO_LANG_KEY = "pref_show_nsfw"
const val PREF_VIDEO_SPEED_KEY = "pref_video_speed" const val PREF_VIDEO_SPEED_KEY = "pref_video_speed"
@ -43,7 +42,14 @@ object Constants {
const val APP_BACKGROUND_AUDIO_INTENT = "BACKGROUND_AUDIO" const val APP_BACKGROUND_AUDIO_INTENT = "BACKGROUND_AUDIO"
const val PREF_THEME_KEY = "pref_theme"
const val PREF_DARK_MODE_KEY = "pref_dark_mode_2"
const val PREF_DARK_MODE_DARK = "dark"
const val PREF_DARK_MODE_LIGHT = "light"
const val PREF_DARK_MODE_AUTO = "auto"
// legacy color prefs // legacy color prefs
const val COLOR_PREF_DEFAULT = "AppTheme.RED"
const val COLOR_PREF_RED = "AppTheme.RED" const val COLOR_PREF_RED = "AppTheme.RED"
const val COLOR_PREF_PINK = "AppTheme.PINK" const val COLOR_PREF_PINK = "AppTheme.PINK"
const val COLOR_PREF_PURPLE = "AppTheme.PURPLE" const val COLOR_PREF_PURPLE = "AppTheme.PURPLE"

View File

@ -19,6 +19,8 @@ import net.schueller.peertube.feature_server_address.domain.repository.ServerAdd
import net.schueller.peertube.feature_server_address.domain.repository.ServerRepository import net.schueller.peertube.feature_server_address.domain.repository.ServerRepository
import net.schueller.peertube.feature_video.domain.repository.VideoRepository import net.schueller.peertube.feature_video.domain.repository.VideoRepository
import net.schueller.peertube.feature_server_address.domain.use_case.* import net.schueller.peertube.feature_server_address.domain.use_case.*
import net.schueller.peertube.feature_settings.settings.data.repository.SettingsRepositoryImpl
import net.schueller.peertube.feature_settings.settings.domain.repository.SettingsRepository
import net.schueller.peertube.feature_video.data.remote.auth.LoginService import net.schueller.peertube.feature_video.data.remote.auth.LoginService
import net.schueller.peertube.feature_video.data.remote.auth.Session import net.schueller.peertube.feature_video.data.remote.auth.Session
import net.schueller.peertube.feature_video.data.repository.RetrofitInstance import net.schueller.peertube.feature_video.data.repository.RetrofitInstance
@ -78,6 +80,12 @@ object AppModule {
return VideoRepositoryImpl(api) return VideoRepositoryImpl(api)
} }
@Provides
@Singleton
fun provideSettingsRepository(@ApplicationContext context: Context): SettingsRepository {
return SettingsRepositoryImpl(context)
}
@Provides @Provides
@Singleton @Singleton
fun provideServerInstanceApi(): ServerInstanceApi { fun provideServerInstanceApi(): ServerInstanceApi {

View File

@ -0,0 +1,35 @@
package net.schueller.peertube.feature_settings.settings.data.repository
import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import net.schueller.peertube.feature_settings.settings.domain.repository.SettingsRepository
import net.schueller.peertube.presentation.dataStore
class SettingsRepositoryImpl(private val context: Context) : SettingsRepository {
override suspend fun getBooleanSettings(key: String, default: Boolean): Flow<Boolean> {
val dataStoreKey = booleanPreferencesKey(key)
return context.dataStore.data.map { preferences ->
preferences[dataStoreKey] ?: default
}
}
override suspend fun getStringSettings(key: String, default: String): Flow<String> {
val dataStoreKey = stringPreferencesKey(key)
return context.dataStore.data.map { preferences ->
preferences[dataStoreKey] ?: default
}
}
override suspend fun getIntegerSettings(key: String, default: Int): Flow<Int> {
val dataStoreKey = intPreferencesKey(key)
return context.dataStore.data.map { preferences ->
preferences[dataStoreKey] ?: default
}
}
}

View File

@ -0,0 +1,11 @@
package net.schueller.peertube.feature_settings.settings.domain.repository
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
suspend fun getBooleanSettings(key: String, default: Boolean): Flow<Boolean>
suspend fun getStringSettings(key: String, default: String): Flow<String>
suspend fun getIntegerSettings(key: String, default: Int): Flow<Int>
}

View File

@ -1,4 +1,4 @@
package net.schueller.peertube.feature_settings.settings package net.schueller.peertube.feature_settings.settings.presentation
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -7,7 +7,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.jamal.composeprefs.ui.GroupHeader
import com.jamal.composeprefs.ui.PrefsScreen import com.jamal.composeprefs.ui.PrefsScreen
import com.jamal.composeprefs.ui.prefs.* import com.jamal.composeprefs.ui.prefs.*
import net.schueller.peertube.presentation.dataStore import net.schueller.peertube.presentation.dataStore

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
@ -15,6 +16,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@ -35,6 +37,7 @@ import net.schueller.peertube.feature_video.presentation.video.components.appBar
import net.schueller.peertube.feature_video.presentation.video.components.videoPlay.VideoPlayScreen import net.schueller.peertube.feature_video.presentation.video.components.videoPlay.VideoPlayScreen
import net.schueller.peertube.feature_video.presentation.video.events.VideoPlayEvent import net.schueller.peertube.feature_video.presentation.video.events.VideoPlayEvent
import net.schueller.peertube.feature_video.presentation.video.player.ExoPlayerHolder import net.schueller.peertube.feature_video.presentation.video.player.ExoPlayerHolder
import net.schueller.peertube.presentation.ui.theme.PeertubeTheme
import kotlin.math.roundToInt import kotlin.math.roundToInt
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)

View File

@ -15,10 +15,8 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
@ -27,7 +25,6 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import coil.annotation.ExperimentalCoilApi
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
@ -40,7 +37,7 @@ import net.schueller.peertube.common.Constants.PREF_BACKGROUND_STOP_KEY
import net.schueller.peertube.common.Constants.PREF_BACK_PAUSE_KEY import net.schueller.peertube.common.Constants.PREF_BACK_PAUSE_KEY
import net.schueller.peertube.common.VideoHelper import net.schueller.peertube.common.VideoHelper
import net.schueller.peertube.feature_server_address.presentation.address_add_edit.AddEditAddressScreen import net.schueller.peertube.feature_server_address.presentation.address_add_edit.AddEditAddressScreen
import net.schueller.peertube.feature_settings.settings.SettingsScreen import net.schueller.peertube.feature_settings.settings.presentation.SettingsScreen
import net.schueller.peertube.presentation.ui.theme.PeertubeTheme import net.schueller.peertube.presentation.ui.theme.PeertubeTheme
import net.schueller.peertube.feature_video.presentation.video.VideoListScreen import net.schueller.peertube.feature_video.presentation.video.VideoListScreen
import net.schueller.peertube.feature_server_address.presentation.ServerAddressScreen import net.schueller.peertube.feature_server_address.presentation.ServerAddressScreen
@ -64,7 +61,8 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalPermissionsApi::class, @OptIn(ExperimentalPermissionsApi::class,
androidx.compose.material.ExperimentalMaterialApi::class, androidx.compose.material.ExperimentalMaterialApi::class,
androidx.compose.ui.ExperimentalComposeUiApi::class androidx.compose.ui.ExperimentalComposeUiApi::class,
coil.annotation.ExperimentalCoilApi::class
) )
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -95,7 +93,9 @@ class MainActivity : ComponentActivity() {
// } // }
// ) // )
Surface(color = MaterialTheme.colors.background) { Surface(
color = MaterialTheme.colors.background
) {
val navController = rememberNavController() val navController = rememberNavController()
NavHost( NavHost(
navController = navController, navController = navController,

View File

@ -1,97 +1,78 @@
package net.schueller.peertube.presentation.ui.theme package net.schueller.peertube.presentation.ui.theme
import android.content.Context
import android.util.Log import android.util.Log
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import net.schueller.peertube.common.Constants
import net.schueller.peertube.common.Constants.COLOR_PREF_BLUE import net.schueller.peertube.common.Constants.COLOR_PREF_BLUE
import net.schueller.peertube.common.Constants.COLOR_PREF_GREEN import net.schueller.peertube.common.Constants.COLOR_PREF_GREEN
import net.schueller.peertube.common.Constants.COLOR_PREF_RED import net.schueller.peertube.common.Constants.COLOR_PREF_RED
import net.schueller.peertube.presentation.dataStore import net.schueller.peertube.common.Constants.PREF_DARK_MODE_AUTO
import net.schueller.peertube.common.Constants.PREF_DARK_MODE_DARK
@Composable @Composable
fun PeertubeTheme( fun PeertubeTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(), useDarkTheme: Boolean = isSystemInDarkTheme(),
viewModel: ThemeViewModel = hiltViewModel(),
content: @Composable() () -> Unit content: @Composable() () -> Unit
) { ) {
val context = LocalContext.current val useDarkMode = if (viewModel.themeState.value.darkMode == PREF_DARK_MODE_AUTO) {
val sharedPreferences = context.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE)
// TODO: get pref out of dataStore, migrate old prefs
// LocalContext.current.dataStore.data.map {
//
// }
// val EXAMPLE_COUNTER = booleanPreferencesKey(Constants.PREF_DARK_MODE_KEY)
// val useDarkMode: Flow<Boolean> = LocalContext.current.dataStore.data
// .map { preferences ->
// preferences[EXAMPLE_COUNTER] ?: useDarkTheme
// }
//
val useDarkMode = sharedPreferences.getBoolean(
Constants.PREF_DARK_MODE_KEY,
useDarkTheme useDarkTheme
) } else viewModel.themeState.value.darkMode == PREF_DARK_MODE_DARK
Log.v("TH", "userdark: "+useDarkMode)
val theme = sharedPreferences.getString(
Constants.PREF_THEME_KEY,
COLOR_PREF_BLUE
)
Log.v("TH", "theme: "+theme)
Log.v("TH", "useDarkMode : "+useDarkMode)
Log.v("TH", "viewModel : "+viewModel.themeState.value.currentTheme)
// Support existing saved preferences in older version // Support existing saved preferences in older version
// https://material-foundation.github.io/material-theme-builder/ // https://material-foundation.github.io/material-theme-builder/
val colors = if (!useDarkMode) { val colors = if (!useDarkMode) {
when (theme) { when (viewModel.themeState.value.currentTheme) {
COLOR_PREF_BLUE -> { COLOR_PREF_BLUE -> {
Log.v("TH", "use COLOR_PREF_BLUE : "+COLOR_PREF_BLUE)
net.schueller.peertube.presentation.ui.theme.colors.blue.LightThemeColors net.schueller.peertube.presentation.ui.theme.colors.blue.LightThemeColors
} }
COLOR_PREF_RED -> { COLOR_PREF_RED -> {
Log.v("TH", "use COLOR_PREF_RED : "+COLOR_PREF_RED)
net.schueller.peertube.presentation.ui.theme.colors.red.LightThemeColors net.schueller.peertube.presentation.ui.theme.colors.red.LightThemeColors
} }
COLOR_PREF_GREEN -> { COLOR_PREF_GREEN -> {
Log.v("TH", "use COLOR_PREF_GREEN : "+COLOR_PREF_GREEN)
net.schueller.peertube.presentation.ui.theme.colors.green.LightThemeColors net.schueller.peertube.presentation.ui.theme.colors.green.LightThemeColors
} }
else -> { else -> {
Log.v("TH", "else : ")
net.schueller.peertube.presentation.ui.theme.colors.def.LightThemeColors net.schueller.peertube.presentation.ui.theme.colors.def.LightThemeColors
} }
} }
} else { } else {
when (theme) { when (viewModel.themeState.value.currentTheme) {
COLOR_PREF_BLUE -> { COLOR_PREF_BLUE -> {
Log.v("TH", "use COLOR_PREF_BLUE : "+COLOR_PREF_BLUE)
net.schueller.peertube.presentation.ui.theme.colors.blue.DarkThemeColors net.schueller.peertube.presentation.ui.theme.colors.blue.DarkThemeColors
} }
COLOR_PREF_RED -> { COLOR_PREF_RED -> {
Log.v("TH", "use COLOR_PREF_RED : "+COLOR_PREF_RED)
net.schueller.peertube.presentation.ui.theme.colors.red.DarkThemeColors net.schueller.peertube.presentation.ui.theme.colors.red.DarkThemeColors
} }
COLOR_PREF_GREEN -> { COLOR_PREF_GREEN -> {
Log.v("TH", "use green : "+COLOR_PREF_GREEN) Log.v("TH", "use COLOR_PREF_GREEN : "+COLOR_PREF_GREEN)
net.schueller.peertube.presentation.ui.theme.colors.green.DarkThemeColors net.schueller.peertube.presentation.ui.theme.colors.green.DarkThemeColors
} }
else -> { else -> {
Log.v("TH", "else : ")
net.schueller.peertube.presentation.ui.theme.colors.def.DarkThemeColors net.schueller.peertube.presentation.ui.theme.colors.def.DarkThemeColors
} }
} }
} }
Log.v("TH", "colors : " + colors.primary)
MaterialTheme( MaterialTheme(
colorScheme = colors, colorScheme = colors,
typography = AppTypography, typography = AppTypography,

View File

@ -0,0 +1,9 @@
package net.schueller.peertube.presentation.ui.theme
import net.schueller.peertube.common.Constants.COLOR_PREF_DEFAULT
import net.schueller.peertube.common.Constants.PREF_DARK_MODE_AUTO
data class ThemeState(
val darkMode: String = PREF_DARK_MODE_AUTO,
val currentTheme: String = COLOR_PREF_DEFAULT
)

View File

@ -0,0 +1,39 @@
package net.schueller.peertube.presentation.ui.theme
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import net.schueller.peertube.common.Constants.COLOR_PREF_DEFAULT
import net.schueller.peertube.common.Constants.PREF_DARK_MODE_AUTO
import net.schueller.peertube.common.Constants.PREF_DARK_MODE_KEY
import net.schueller.peertube.common.Constants.PREF_THEME_KEY
import net.schueller.peertube.feature_settings.settings.domain.repository.SettingsRepository
import javax.inject.Inject
@HiltViewModel
class ThemeViewModel @Inject constructor(private val settingsRepository: SettingsRepository): ViewModel() {
private val _themeState = mutableStateOf(ThemeState())
val themeState: State<ThemeState> = _themeState
init {
viewModelScope.launch {
settingsRepository
.getStringSettings(PREF_THEME_KEY, COLOR_PREF_DEFAULT).collect {
_themeState.value = _themeState.value.copy(
currentTheme = it
)
}
settingsRepository
.getStringSettings(PREF_DARK_MODE_KEY, PREF_DARK_MODE_AUTO).collect {
_themeState.value = _themeState.value.copy(
darkMode = it
)
}
}
}
}

View File

@ -1,16 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Peertube" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -1,10 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources> </resources>

View File

@ -1,17 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <style name="Theme.Peertube" parent="android:Theme.Material.Light.NoActionBar">
<style name="Theme.Peertube" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <item name="android:statusBarColor">@android:color/black</item>
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style> </style>
<style name="Theme.Peertube.NoActionBar"> <style name="Theme.Peertube.NoActionBar">
@ -19,7 +8,4 @@
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
</style> </style>
<style name="Theme.Peertube.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.Peertube.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources> </resources>