feat: implement data and power settings sections
Some checks failed
Android CI / android (push) Failing after 4m5s
Android Release / release (push) Failing after 4m18s
CI / test (push) Failing after 2m20s

This commit is contained in:
2026-04-06 02:24:50 +03:00
parent 09ddb1ef41
commit 53303a5a5b
3 changed files with 315 additions and 13 deletions

View File

@@ -1,5 +1,9 @@
package ru.daemonlord.messenger.ui.settings package ru.daemonlord.messenger.ui.settings
import android.content.Intent
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -22,14 +26,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Chat
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.BatterySaver import androidx.compose.material.icons.filled.BatterySaver
import androidx.compose.material.icons.filled.Chat
import androidx.compose.material.icons.filled.DeleteOutline import androidx.compose.material.icons.filled.DeleteOutline
import androidx.compose.material.icons.filled.Devices import androidx.compose.material.icons.filled.Devices
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Notifications
@@ -58,7 +61,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -66,6 +71,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -77,6 +83,10 @@ import ru.daemonlord.messenger.domain.settings.model.AppLanguage
import ru.daemonlord.messenger.domain.settings.model.AppThemeMode import ru.daemonlord.messenger.domain.settings.model.AppThemeMode
import ru.daemonlord.messenger.ui.account.AccountUiState import ru.daemonlord.messenger.ui.account.AccountUiState
import ru.daemonlord.messenger.ui.account.AccountViewModel import ru.daemonlord.messenger.ui.account.AccountViewModel
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private enum class SettingsFolder { private enum class SettingsFolder {
Account, Account,
@@ -84,7 +94,6 @@ private enum class SettingsFolder {
Privacy, Privacy,
Notifications, Notifications,
Data, Data,
Folders,
Devices, Devices,
Power, Power,
Language, Language,
@@ -200,15 +209,41 @@ private fun SettingsHome(
SettingsCard { SettingsCard {
SettingsRow(Icons.Filled.AccountCircle, stringResource(id = R.string.settings_folder_account), stringResource(id = R.string.settings_account_subtitle)) { onOpenFolder(SettingsFolder.Account) } SettingsRow(Icons.Filled.AccountCircle, stringResource(id = R.string.settings_folder_account), stringResource(id = R.string.settings_account_subtitle)) { onOpenFolder(SettingsFolder.Account) }
SettingsRow(Icons.Filled.Chat, stringResource(id = R.string.settings_folder_chat), stringResource(id = R.string.settings_chat_subtitle)) { onOpenFolder(SettingsFolder.Chat) } SettingsRow(Icons.AutoMirrored.Filled.Chat, stringResource(id = R.string.settings_folder_chat), stringResource(id = R.string.settings_chat_subtitle)) { onOpenFolder(SettingsFolder.Chat) }
SettingsRow(Icons.Filled.Lock, stringResource(id = R.string.settings_folder_privacy), stringResource(id = R.string.settings_privacy_subtitle)) { onOpenFolder(SettingsFolder.Privacy) } SettingsRow(Icons.Filled.Lock, stringResource(id = R.string.settings_folder_privacy), stringResource(id = R.string.settings_privacy_subtitle)) { onOpenFolder(SettingsFolder.Privacy) }
SettingsRow(Icons.Filled.Notifications, stringResource(id = R.string.settings_folder_notifications), stringResource(id = R.string.settings_notifications_subtitle)) { onOpenFolder(SettingsFolder.Notifications) } SettingsRow(Icons.Filled.Notifications, stringResource(id = R.string.settings_folder_notifications), stringResource(id = R.string.settings_notifications_subtitle)) { onOpenFolder(SettingsFolder.Notifications) }
SettingsRow(Icons.Filled.Storage, stringResource(id = R.string.settings_folder_data), stringResource(id = R.string.settings_data_subtitle)) { onOpenFolder(SettingsFolder.Data) } SettingsRow(Icons.Filled.Storage, stringResource(id = R.string.settings_folder_data), stringResource(id = R.string.settings_data_subtitle)) { onOpenFolder(SettingsFolder.Data) }
SettingsRow(Icons.Filled.Folder, stringResource(id = R.string.settings_folder_folders), stringResource(id = R.string.settings_folders_subtitle)) { onOpenFolder(SettingsFolder.Folders) }
SettingsRow(Icons.Filled.Devices, stringResource(id = R.string.settings_folder_devices), stringResource(id = R.string.settings_devices_subtitle)) { onOpenFolder(SettingsFolder.Devices) } SettingsRow(Icons.Filled.Devices, stringResource(id = R.string.settings_folder_devices), stringResource(id = R.string.settings_devices_subtitle)) { onOpenFolder(SettingsFolder.Devices) }
SettingsRow(Icons.Filled.BatterySaver, stringResource(id = R.string.settings_folder_power), stringResource(id = R.string.settings_power_subtitle)) { onOpenFolder(SettingsFolder.Power) } SettingsRow(Icons.Filled.BatterySaver, stringResource(id = R.string.settings_folder_power), stringResource(id = R.string.settings_power_subtitle)) { onOpenFolder(SettingsFolder.Power) }
SettingsRow(Icons.Filled.Language, stringResource(id = R.string.settings_folder_language), languageLabel(state.appLanguage), divider = false) { onOpenFolder(SettingsFolder.Language) } SettingsRow(Icons.Filled.Language, stringResource(id = R.string.settings_folder_language), languageLabel(state.appLanguage), divider = false) { onOpenFolder(SettingsFolder.Language) }
} }
SettingsCard {
Text(
text = stringResource(id = R.string.settings_preferences_header),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelMedium,
)
SettingsToggle(
icon = Icons.Filled.Notifications,
title = stringResource(id = R.string.settings_enable_notifications),
checked = state.notificationsEnabled,
onCheckedChange = {},
enabled = false,
)
SettingsToggle(
icon = Icons.Filled.Visibility,
title = stringResource(id = R.string.settings_show_preview),
checked = state.notificationsPreviewEnabled,
onCheckedChange = {},
enabled = false,
)
SettingsShortcut(
title = stringResource(id = R.string.settings_folder_language),
subtitle = languageLabel(state.appLanguage),
onClick = { onOpenFolder(SettingsFolder.Language) },
)
}
} }
} }
@@ -247,10 +282,9 @@ private fun SettingsFolderView(
SettingsFolder.Chat -> ChatFolder(state, viewModel) SettingsFolder.Chat -> ChatFolder(state, viewModel)
SettingsFolder.Privacy -> PrivacyFolder(state, viewModel) SettingsFolder.Privacy -> PrivacyFolder(state, viewModel)
SettingsFolder.Notifications -> NotificationsFolder(state, viewModel) SettingsFolder.Notifications -> NotificationsFolder(state, viewModel)
SettingsFolder.Data -> DataFolder()
SettingsFolder.Devices -> DevicesFolder(state, viewModel) SettingsFolder.Devices -> DevicesFolder(state, viewModel)
SettingsFolder.Data -> PlaceholderFolder(stringResource(id = R.string.settings_placeholder_data)) SettingsFolder.Power -> PowerFolder()
SettingsFolder.Folders -> PlaceholderFolder(stringResource(id = R.string.settings_placeholder_folders))
SettingsFolder.Power -> PlaceholderFolder(stringResource(id = R.string.settings_placeholder_power))
SettingsFolder.Language -> LanguageFolder(state, viewModel) SettingsFolder.Language -> LanguageFolder(state, viewModel)
} }
@@ -326,7 +360,6 @@ private fun folderTitle(folder: SettingsFolder): String = when (folder) {
SettingsFolder.Privacy -> stringResource(id = R.string.settings_folder_privacy) SettingsFolder.Privacy -> stringResource(id = R.string.settings_folder_privacy)
SettingsFolder.Notifications -> stringResource(id = R.string.settings_folder_notifications) SettingsFolder.Notifications -> stringResource(id = R.string.settings_folder_notifications)
SettingsFolder.Data -> stringResource(id = R.string.settings_folder_data) SettingsFolder.Data -> stringResource(id = R.string.settings_folder_data)
SettingsFolder.Folders -> stringResource(id = R.string.settings_folder_folders)
SettingsFolder.Devices -> stringResource(id = R.string.settings_folder_devices) SettingsFolder.Devices -> stringResource(id = R.string.settings_folder_devices)
SettingsFolder.Power -> stringResource(id = R.string.settings_folder_power) SettingsFolder.Power -> stringResource(id = R.string.settings_folder_power)
SettingsFolder.Language -> stringResource(id = R.string.settings_folder_language) SettingsFolder.Language -> stringResource(id = R.string.settings_folder_language)
@@ -517,8 +550,231 @@ private fun DevicesFolder(state: AccountUiState, viewModel: AccountViewModel) {
} }
@Composable @Composable
private fun PlaceholderFolder(text: String) { private fun DataFolder() {
SettingsCard { Text(text) } val context = LocalContext.current
val scope = rememberCoroutineScope()
var cacheStats by remember { mutableStateOf<SettingsCacheStats?>(null) }
var isClearing by remember { mutableStateOf(false) }
suspend fun refreshStats() {
cacheStats = readSettingsCacheStats(context)
}
LaunchedEffect(Unit) {
refreshStats()
}
SettingsCard {
Text(stringResource(id = R.string.settings_data_usage_title), style = MaterialTheme.typography.titleSmall)
val stats = cacheStats
if (stats == null) {
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
} else {
SettingsMetricRow(
title = stringResource(id = R.string.settings_data_images_cache),
value = formatSettingsBytes(stats.coilImagesBytes),
)
SettingsMetricRow(
title = stringResource(id = R.string.settings_data_media_cache),
value = formatSettingsBytes(stats.exoMediaBytes),
)
SettingsMetricRow(
title = stringResource(id = R.string.settings_data_temp_captures),
value = formatSettingsBytes(stats.capturesBytes),
)
SettingsMetricRow(
title = stringResource(id = R.string.settings_data_total_cache),
value = formatSettingsBytes(stats.totalBytes),
)
}
}
SettingsCard {
Text(stringResource(id = R.string.settings_data_actions_title), style = MaterialTheme.typography.titleSmall)
OutlinedButton(
onClick = {
scope.launch {
isClearing = true
withContext(Dispatchers.IO) {
clearSettingsCacheDirectories(context)
}
refreshStats()
isClearing = false
}
},
enabled = !isClearing,
modifier = Modifier.fillMaxWidth(),
) {
if (isClearing) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp)
} else {
Icon(Icons.Filled.DeleteOutline, contentDescription = null)
}
Text(
text = stringResource(id = R.string.settings_data_clear_cache),
modifier = Modifier.padding(start = 6.dp),
)
}
Text(
text = stringResource(id = R.string.settings_data_clear_hint),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
@Composable
private fun PowerFolder() {
val context = LocalContext.current
val powerManager = remember {
context.getSystemService(PowerManager::class.java)
}
val powerSnapshot = produceState(initialValue = readPowerState(context, powerManager)) {
value = readPowerState(context, powerManager)
}
SettingsCard {
Text(stringResource(id = R.string.settings_power_status_title), style = MaterialTheme.typography.titleSmall)
SettingsMetricRow(
title = stringResource(id = R.string.settings_power_saver_enabled),
value = if (powerSnapshot.value.isPowerSaveMode) {
context.getString(R.string.settings_status_enabled)
} else {
context.getString(R.string.settings_status_disabled)
},
)
SettingsMetricRow(
title = stringResource(id = R.string.settings_power_optimizations),
value = if (powerSnapshot.value.ignoresOptimizations) {
context.getString(R.string.settings_power_not_optimized)
} else {
context.getString(R.string.settings_power_optimized)
},
)
Text(
text = stringResource(id = R.string.settings_power_hint),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
SettingsCard {
Text(stringResource(id = R.string.settings_power_actions_title), style = MaterialTheme.typography.titleSmall)
OutlinedButton(
onClick = {
context.startActivity(Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
},
modifier = Modifier.fillMaxWidth(),
) {
Icon(Icons.Filled.BatterySaver, contentDescription = null)
Text(
text = stringResource(id = R.string.settings_power_open_battery_saver),
modifier = Modifier.padding(start = 6.dp),
)
}
OutlinedButton(
onClick = {
val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
} else {
Settings.ACTION_SETTINGS
}
context.startActivity(Intent(action).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
},
modifier = Modifier.fillMaxWidth(),
) {
Icon(Icons.Filled.Security, contentDescription = null)
Text(
text = stringResource(id = R.string.settings_power_open_optimization_settings),
modifier = Modifier.padding(start = 6.dp),
)
}
}
}
private data class SettingsCacheStats(
val coilImagesBytes: Long,
val exoMediaBytes: Long,
val capturesBytes: Long,
) {
val totalBytes: Long = coilImagesBytes + exoMediaBytes + capturesBytes
}
private data class PowerSnapshot(
val isPowerSaveMode: Boolean,
val ignoresOptimizations: Boolean,
)
@Composable
private fun SettingsMetricRow(title: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
)
}
}
private suspend fun readSettingsCacheStats(context: android.content.Context): SettingsCacheStats = withContext(Dispatchers.IO) {
SettingsCacheStats(
coilImagesBytes = File(context.cacheDir, "coil_images").directorySize(),
exoMediaBytes = File(context.cacheDir, "exo_media_cache").directorySize(),
capturesBytes = File(context.cacheDir, "captures").directorySize(),
)
}
private suspend fun clearSettingsCacheDirectories(context: android.content.Context) = withContext(Dispatchers.IO) {
listOf(
File(context.cacheDir, "coil_images"),
File(context.cacheDir, "exo_media_cache"),
File(context.cacheDir, "captures"),
).forEach { file ->
if (file.exists()) {
file.deleteRecursively()
file.mkdirs()
}
}
}
private fun File.directorySize(): Long {
if (!exists()) return 0L
if (isFile) return length()
return walkTopDown()
.filter { it.isFile }
.sumOf { it.length() }
}
private fun formatSettingsBytes(value: Long): String {
if (value < 1024) return "$value B"
val kb = value / 1024.0
if (kb < 1024) return String.format("%.1f KB", kb)
val mb = kb / 1024.0
if (mb < 1024) return String.format("%.1f MB", mb)
val gb = mb / 1024.0
return String.format("%.1f GB", gb)
}
private fun readPowerState(context: android.content.Context, powerManager: PowerManager?): PowerSnapshot {
val isSaver = powerManager?.isPowerSaveMode == true
val ignores = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
powerManager?.isIgnoringBatteryOptimizations(context.packageName) == true
} else {
true
}
return PowerSnapshot(
isPowerSaveMode = isSaver,
ignoresOptimizations = ignores,
)
} }
@Composable @Composable
@@ -600,7 +856,13 @@ private fun SettingsRow(icon: ImageVector, title: String, subtitle: String, divi
} }
@Composable @Composable
private fun SettingsToggle(icon: ImageVector, title: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) { private fun SettingsToggle(
icon: ImageVector,
title: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
enabled: Boolean = true,
) {
Row( Row(
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(14.dp)).background(MaterialTheme.colorScheme.surfaceContainerHighest).padding(horizontal = 10.dp, vertical = 10.dp), modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(14.dp)).background(MaterialTheme.colorScheme.surfaceContainerHighest).padding(horizontal = 10.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -608,7 +870,7 @@ private fun SettingsToggle(icon: ImageVector, title: String, checked: Boolean, o
) { ) {
Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary) Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
Text(title, modifier = Modifier.weight(1f)) Text(title, modifier = Modifier.weight(1f))
Switch(checked = checked, onCheckedChange = onCheckedChange) Switch(checked = checked, onCheckedChange = onCheckedChange, enabled = enabled)
} }
} }

View File

@@ -260,6 +260,26 @@
<string name="settings_folders_subtitle">Сортировка чатов по папкам</string> <string name="settings_folders_subtitle">Сортировка чатов по папкам</string>
<string name="settings_devices_subtitle">Управление активными сессиями</string> <string name="settings_devices_subtitle">Управление активными сессиями</string>
<string name="settings_power_subtitle">Экономия энергии при низком заряде</string> <string name="settings_power_subtitle">Экономия энергии при низком заряде</string>
<string name="settings_preferences_header">Быстрые настройки</string>
<string name="settings_data_usage_title">Кэшированные медиа</string>
<string name="settings_data_images_cache">Кэш изображений</string>
<string name="settings_data_media_cache">Кэш видео и аудио</string>
<string name="settings_data_temp_captures">Временные записи</string>
<string name="settings_data_total_cache">Всего кэша</string>
<string name="settings_data_actions_title">Действия с данными</string>
<string name="settings_data_clear_cache">Очистить кэш медиа</string>
<string name="settings_data_clear_hint">Это удалит временные копии медиа и освободит место. Сами чаты и файлы на сервере не пострадают.</string>
<string name="settings_power_status_title">Состояние энергосбережения</string>
<string name="settings_power_saver_enabled">Режим энергосбережения</string>
<string name="settings_power_optimizations">Оптимизация батареи</string>
<string name="settings_power_not_optimized">Без ограничений</string>
<string name="settings_power_optimized">Оптимизируется системой</string>
<string name="settings_power_hint">Используйте системные настройки, если уведомления или синхронизация в фоне становятся слишком агрессивно ограничены.</string>
<string name="settings_power_actions_title">Действия</string>
<string name="settings_power_open_battery_saver">Открыть настройки энергосбережения</string>
<string name="settings_power_open_optimization_settings">Открыть настройки оптимизации</string>
<string name="settings_status_enabled">Включено</string>
<string name="settings_status_disabled">Выключено</string>
<string name="settings_placeholder_data">Этот раздел будет расширен на следующем шаге.</string> <string name="settings_placeholder_data">Этот раздел будет расширен на следующем шаге.</string>
<string name="settings_placeholder_folders">Управление папками чатов будет добавлено на следующей итерации.</string> <string name="settings_placeholder_folders">Управление папками чатов будет добавлено на следующей итерации.</string>
<string name="settings_placeholder_power">Настройки энергосбережения будут добавлены отдельным этапом.</string> <string name="settings_placeholder_power">Настройки энергосбережения будут добавлены отдельным этапом.</string>

View File

@@ -260,6 +260,26 @@
<string name="settings_folders_subtitle">Sort chats by folders</string> <string name="settings_folders_subtitle">Sort chats by folders</string>
<string name="settings_devices_subtitle">Manage active sessions</string> <string name="settings_devices_subtitle">Manage active sessions</string>
<string name="settings_power_subtitle">Save power on low battery</string> <string name="settings_power_subtitle">Save power on low battery</string>
<string name="settings_preferences_header">Quick preferences</string>
<string name="settings_data_usage_title">Cached media</string>
<string name="settings_data_images_cache">Image cache</string>
<string name="settings_data_media_cache">Video and audio cache</string>
<string name="settings_data_temp_captures">Temporary captures</string>
<string name="settings_data_total_cache">Total cached data</string>
<string name="settings_data_actions_title">Data actions</string>
<string name="settings_data_clear_cache">Clear cached media</string>
<string name="settings_data_clear_hint">This clears temporary media copies and frees local space. Chats and server files stay intact.</string>
<string name="settings_power_status_title">Power status</string>
<string name="settings_power_saver_enabled">Battery saver</string>
<string name="settings_power_optimizations">Battery optimization</string>
<string name="settings_power_not_optimized">Not restricted</string>
<string name="settings_power_optimized">Optimized by the system</string>
<string name="settings_power_hint">Use system settings if message delivery or background sync become aggressive under battery restrictions.</string>
<string name="settings_power_actions_title">Power actions</string>
<string name="settings_power_open_battery_saver">Open battery saver settings</string>
<string name="settings_power_open_optimization_settings">Open optimization settings</string>
<string name="settings_status_enabled">Enabled</string>
<string name="settings_status_disabled">Disabled</string>
<string name="settings_placeholder_data">This section will be expanded in the next step.</string> <string name="settings_placeholder_data">This section will be expanded in the next step.</string>
<string name="settings_placeholder_folders">Chat folders management will be added in next iteration.</string> <string name="settings_placeholder_folders">Chat folders management will be added in next iteration.</string>
<string name="settings_placeholder_power">Power saving settings will be added in a separate step.</string> <string name="settings_placeholder_power">Power saving settings will be added in a separate step.</string>