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
84 changes: 84 additions & 0 deletions app/src/main/java/com/tucavr/FeatureFlags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,32 @@ object FeatureFlags {
// vr_player_app_vulkan.cpp::shouldDrawAmbientHalo. Desligado por padrão:
// nunca validado em headset real até agora.
AMBIENT_MODE("ambient_mode", defaultEnabled = false),

// RF-ENV-007: Modo Noturno (aquecimento ~3000K para redução de luz azul)
NIGHT_MODE("night_mode", defaultEnabled = false),
}

/** Chave usada para persistir o brilho do ambiente 3D/Skybox (0.0f a 1.0f). Padrão: 1.0f (100%). */
const val KEY_ENVIRONMENT_BRIGHTNESS = "environment_brightness"

/** Chave usada para persistir a intensidade do Screen Glow (0=Off, 1=Subtle, 2=Strong). Padrão: 2. */
const val KEY_SCREEN_GLOW_INTENSITY = "screen_glow_intensity"

/** Chave usada para persistir a temperatura de cor em Kelvin (2700K a 6500K). Padrão: 6500.0f. */
const val KEY_COLOR_TEMPERATURE = "color_temperature"

const val SCREEN_GLOW_OFF = 0
const val SCREEN_GLOW_SUBTLE = 1
const val SCREEN_GLOW_STRONG = 2

const val SCREEN_GLOW_INTENSITY_OFF = 0.0f
const val SCREEN_GLOW_INTENSITY_SUBTLE = 0.45f
const val SCREEN_GLOW_INTENSITY_STRONG = 0.85f

fun screenGlowModeToFloat(mode: Int): Float = when (mode) {
SCREEN_GLOW_SUBTLE -> SCREEN_GLOW_INTENSITY_SUBTLE
SCREEN_GLOW_STRONG -> SCREEN_GLOW_INTENSITY_STRONG
else -> SCREEN_GLOW_INTENSITY_OFF
}

/** Chave usada para persistir o modo de áudio espacial como Int (0/1/2). */
Expand Down Expand Up @@ -256,4 +282,62 @@ object FeatureFlags {
.putInt(KEY_PACKED_ALPHA_CHOKE, choke.coerceIn(0, 100))
.apply()
}

/** Lê o brilho do ambiente virtual (0.0f a 1.0f). Padrão: 1.0f (100%). */
fun getEnvironmentBrightness(context: Context): Float =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.getFloat(KEY_ENVIRONMENT_BRIGHTNESS, 1.0f)

/** Persiste o brilho do ambiente virtual. */
fun setEnvironmentBrightness(context: Context, brightness: Float) {
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putFloat(KEY_ENVIRONMENT_BRIGHTNESS, brightness.coerceIn(0.0f, 1.0f))
.apply()
}

/** Lê o modo de intensidade do Screen Glow (0=Off, 1=Subtle, 2=Strong). Padrão: 2. */
fun getScreenGlowIntensityMode(context: Context): Int {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
if (prefs.contains(KEY_SCREEN_GLOW_INTENSITY)) {
return prefs.getInt(KEY_SCREEN_GLOW_INTENSITY, SCREEN_GLOW_STRONG)
}
return if (isEnabled(context, Flag.AMBIENT_MODE)) SCREEN_GLOW_STRONG else SCREEN_GLOW_OFF
}

/** Persiste o modo de intensidade do Screen Glow e sincroniza com a flag AMBIENT_MODE. */
fun setScreenGlowIntensityMode(context: Context, mode: Int) {
val clamped = mode.coerceIn(SCREEN_GLOW_OFF, SCREEN_GLOW_STRONG)
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putInt(KEY_SCREEN_GLOW_INTENSITY, clamped)
.putBoolean(Flag.AMBIENT_MODE.key, clamped != SCREEN_GLOW_OFF)
.apply()
}

/** Lê a intensidade float do Screen Glow (0.0f, 0.45f ou 0.85f). */
fun getScreenGlowIntensityFloat(context: Context): Float =
screenGlowModeToFloat(getScreenGlowIntensityMode(context))

/** Lê a temperatura de cor em Kelvin (2700K a 6500K). Padrão: 6500K. */
fun getColorTemperature(context: Context): Float =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.getFloat(KEY_COLOR_TEMPERATURE, 6500.0f)

/** Persiste a temperatura de cor em Kelvin. */
fun setColorTemperature(context: Context, kelvin: Float) {
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putFloat(KEY_COLOR_TEMPERATURE, kelvin.coerceIn(2700.0f, 6500.0f))
.apply()
}

/** Lê se o Modo Noturno está ativo. Padrão: false. */
fun isNightModeEnabled(context: Context): Boolean =
isEnabled(context, Flag.NIGHT_MODE)

/** Persiste o estado do Modo Noturno. */
fun setNightModeEnabled(context: Context, enabled: Boolean) {
setEnabled(context, Flag.NIGHT_MODE, enabled)
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/tucavr/VRActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,12 @@ class VRActivity : NativeActivity() {
// docs/reports/MODO-AMBIENTE.md: empurra o estado persistido do halo de luz ambiente
nativeSetAmbientMode(FeatureFlags.isEnabled(this, FeatureFlags.Flag.AMBIENT_MODE))

// RF-ENV-007: Ajuste de Iluminação e Cor do Ambiente (Fase 0.5 §4)
nativeSetEnvironmentBrightness(FeatureFlags.getEnvironmentBrightness(this))
nativeSetScreenGlowIntensity(FeatureFlags.getScreenGlowIntensityFloat(this))
nativeSetColorTemperature(FeatureFlags.getColorTemperature(this))
nativeSetNightMode(FeatureFlags.isNightModeEnabled(this))

// Fase 0.3 Seção 2: empurra o estado persistido do toggle de Passthrough.
// A capacidade real (extensão XR_FB_passthrough presente) só é conhecida
// depois que o C++ cria o XrInstance; o painel de controles consulta
Expand Down Expand Up @@ -1848,6 +1854,11 @@ class VRActivity : NativeActivity() {
// frame. Vulkan-only, GLES aceita a chamada mas não aplica. Só tem efeito visual
// com ambiente Void ativo, fora de modos esfera e sem Passthrough ligado.
external fun nativeSetAmbientMode(enabled: Boolean)
// RF-ENV-007: Ajuste de Iluminação e Cor do Ambiente (Fase 0.5 §4)
external fun nativeSetEnvironmentBrightness(brightness: Float)
external fun nativeSetScreenGlowIntensity(intensity: Float)
external fun nativeSetColorTemperature(kelvin: Float)
external fun nativeSetNightMode(enabled: Boolean)
// Fase 0.3 Seção 2: Passthrough / Mixed Reality (Vulkan-only, XR_FB_passthrough).
external fun nativeSetPassthroughEnabled(enabled: Boolean)
external fun nativeIsPassthroughSupported(): Boolean
Expand Down
203 changes: 199 additions & 4 deletions app/src/main/java/com/tucavr/screens/SettingsScreen.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.tucavr.screens

import android.content.Context
import android.content.res.ColorStateList
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.SeekBar
import android.widget.TextView
import com.tucavr.FeatureFlags
import com.tucavr.R
import com.tucavr.UpscalingModeStore
Expand All @@ -15,6 +18,7 @@ import com.tucavr.designsystem.VoidFilterChip
import com.tucavr.designsystem.VoidPanelChrome
import com.tucavr.designsystem.VoidText
import com.tucavr.designsystem.VoidTheme
import kotlin.math.roundToInt

/**
* Tela de Configurações — Seções "Vídeo" (Upscaling MQSR/SGSR1 e Foveated Rendering),
Expand Down Expand Up @@ -61,12 +65,21 @@ class SettingsScreen(
content.addView(buildUpscalingRow())
content.addView(buildFoveationRow())

// Seção Iluminação e Ambiente (RF-ENV-007)
content.addView(
VoidText.title(context, context.getString(R.string.settings_section_lighting), sizeSp = 18f).apply {
setPadding(0, VoidTheme.dpToPx(context, 8f), 0, VoidTheme.dpToPx(context, 8f))
}
)
content.addView(buildEnvironmentBrightnessRow())
content.addView(buildScreenGlowRow())
content.addView(buildColorTemperatureRow())
content.addView(
buildFlagRow(
labelRes = R.string.settings_ambient_mode_label,
descriptionRes = R.string.settings_ambient_mode_description,
flag = FeatureFlags.Flag.AMBIENT_MODE,
onChanged = { enabled -> activity.nativeSetAmbientMode(enabled) }
labelRes = R.string.settings_night_mode_label,
descriptionRes = R.string.settings_night_mode_description,
flag = FeatureFlags.Flag.NIGHT_MODE,
onChanged = { enabled -> activity.nativeSetNightMode(enabled) }
)
)

Expand Down Expand Up @@ -416,4 +429,186 @@ class SettingsScreen(

addView(chipContainer)
}

private fun buildEnvironmentBrightnessRow(): LinearLayout = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = VoidTheme.dpToPx(context, 16f) }

val currentBrightness = FeatureFlags.getEnvironmentBrightness(context)
val initialPercent = (currentBrightness * 100f).roundToInt().coerceIn(0, 100)

val headerRow = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
}

val titleView = VoidText.title(context, context.getString(R.string.settings_environment_brightness_label), sizeSp = 16f).apply {
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
}
val valueView = VoidText.body(
context,
context.getString(R.string.settings_environment_brightness_percent, initialPercent),
sizeSp = 14f,
secondary = true
)
headerRow.addView(titleView)
headerRow.addView(valueView)
addView(headerRow)

addView(
VoidText.body(context, context.getString(R.string.settings_environment_brightness_description), sizeSp = 14f, secondary = true).apply {
setPadding(0, VoidTheme.dpToPx(context, 2f), 0, VoidTheme.dpToPx(context, 4f))
}
)

val seekBar = SeekBar(context).apply {
max = 100
progress = initialPercent
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
VoidTheme.dpToPx(context, 44f)
)
progressTintList = ColorStateList.valueOf(VoidTheme.colorAccent)
thumbTintList = ColorStateList.valueOf(VoidTheme.colorAccent)
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(sb: SeekBar?, prog: Int, fromUser: Boolean) {
val brightness = prog / 100f
valueView.text = context.getString(R.string.settings_environment_brightness_percent, prog)
if (fromUser) {
FeatureFlags.setEnvironmentBrightness(context, brightness)
activity.nativeSetEnvironmentBrightness(brightness)
}
}
override fun onStartTrackingTouch(sb: SeekBar?) {}
override fun onStopTrackingTouch(sb: SeekBar?) {}
})
}
addView(seekBar)
}

private fun buildScreenGlowRow(): LinearLayout = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = VoidTheme.dpToPx(context, 16f) }

addView(
VoidText.title(context, context.getString(R.string.settings_screen_glow_label), sizeSp = 16f).apply {
setPadding(0, 0, 0, VoidTheme.dpToPx(context, 4f))
}
)

addView(
VoidText.body(context, context.getString(R.string.settings_screen_glow_description), sizeSp = 14f, secondary = true).apply {
setPadding(0, 0, 0, VoidTheme.dpToPx(context, 8f))
}
)

val chipContainer = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
}

val modes = listOf(
FeatureFlags.SCREEN_GLOW_OFF to R.string.settings_screen_glow_off,
FeatureFlags.SCREEN_GLOW_SUBTLE to R.string.settings_screen_glow_subtle,
FeatureFlags.SCREEN_GLOW_STRONG to R.string.settings_screen_glow_strong
)

val chips = mutableListOf<VoidFilterChip>()
val currentMode = FeatureFlags.getScreenGlowIntensityMode(context)

modes.forEach { (mode, labelRes) ->
val chip = VoidFilterChip(
context = context,
text = context.getString(labelRes),
isSelectedChip = (mode == currentMode)
).apply {
layoutParams = LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f
).apply {
val margin = VoidTheme.dpToPx(context, 4f)
setMargins(margin, 0, margin, 0)
}
setOnClickListener {
chips.forEach { it.setSelectedState(false) }
setSelectedState(true)
FeatureFlags.setScreenGlowIntensityMode(context, mode)
val intensity = FeatureFlags.screenGlowModeToFloat(mode)
activity.nativeSetScreenGlowIntensity(intensity)
activity.nativeSetAmbientMode(mode != FeatureFlags.SCREEN_GLOW_OFF)
}
}
chips.add(chip)
chipContainer.addView(chip)
}

addView(chipContainer)
}

private fun buildColorTemperatureRow(): LinearLayout = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = VoidTheme.dpToPx(context, 16f) }

val currentKelvin = FeatureFlags.getColorTemperature(context)
val initialProgress = ((currentKelvin - 2700f) / 100f).roundToInt().coerceIn(0, 38)

val headerRow = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
}

val titleView = VoidText.title(context, context.getString(R.string.settings_color_temperature_label), sizeSp = 16f).apply {
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
}
val valueView = VoidText.body(
context,
context.getString(R.string.settings_color_temperature_kelvin, currentKelvin.toInt()),
sizeSp = 14f,
secondary = true
)
headerRow.addView(titleView)
headerRow.addView(valueView)
addView(headerRow)

addView(
VoidText.body(context, context.getString(R.string.settings_color_temperature_description), sizeSp = 14f, secondary = true).apply {
setPadding(0, VoidTheme.dpToPx(context, 2f), 0, VoidTheme.dpToPx(context, 4f))
}
)

val seekBar = SeekBar(context).apply {
max = 38
progress = initialProgress
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
VoidTheme.dpToPx(context, 44f)
)
progressTintList = ColorStateList.valueOf(VoidTheme.colorAccent)
thumbTintList = ColorStateList.valueOf(VoidTheme.colorAccent)
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(sb: SeekBar?, prog: Int, fromUser: Boolean) {
val kelvin = 2700f + prog * 100f
valueView.text = context.getString(R.string.settings_color_temperature_kelvin, kelvin.toInt())
if (fromUser) {
FeatureFlags.setColorTemperature(context, kelvin)
activity.nativeSetColorTemperature(kelvin)
}
}
override fun onStartTrackingTouch(sb: SeekBar?) {}
override fun onStopTrackingTouch(sb: SeekBar?) {}
})
}
addView(seekBar)
}
}
16 changes: 16 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@
<string name="settings_foveated_mode_medium">Medio</string>
<string name="settings_foveated_mode_high">Alto</string>
<string name="settings_foveated_mode_auto">Automático (adaptativo)</string>
<string name="settings_ambient_mode_label">Modo Ambiente</string>
<string name="settings_ambient_mode_description">Añade un halo de luz suave detrás de la pantalla, con el color del propio video. Solo visible en el entorno Void. Solo Vulkan.</string>
<string name="settings_section_lighting">Iluminación y Entorno</string>
<string name="settings_environment_brightness_label">Brillo del Entorno</string>
<string name="settings_environment_brightness_description">Controla la intensidad de la iluminación de la sala virtual y skybox (0%% a 100%%).</string>
<string name="settings_environment_brightness_percent">%1$d%%</string>
<string name="settings_screen_glow_label">Luz Reactiva de Pantalla (Screen Glow)</string>
<string name="settings_screen_glow_description">Refleja el color dinámico del video en las paredes de la sala virtual y el espacio circundante.</string>
<string name="settings_screen_glow_off">Desactivado</string>
<string name="settings_screen_glow_subtle">Sutil</string>
<string name="settings_screen_glow_strong">Fuerte</string>
<string name="settings_color_temperature_label">Temperatura de Color</string>
<string name="settings_color_temperature_description">Ajusta la calidez del color de la pantalla de video de cálido a neutro (2700K a 6500K).</string>
<string name="settings_color_temperature_kelvin">%1$dK</string>
<string name="settings_night_mode_label">Modo Nocturno</string>
<string name="settings_night_mode_description">Reduce la emisión de luz azul (~3000K) para comodidad visual en entornos oscuros.</string>
<string name="quality_warning_thermal">Límite térmico alcanzado: reduciendo calidad de renderizado para enfriar el dispositivo.</string>
<string name="quality_warning_8k60">El video 8K con alta tasa de cuadros puede experimentar caídas de cuadros en hardware móvil.</string>
<string name="settings_pause_on_exit_label">Pausar al salir</string>
Expand Down
Loading
Loading