refactor: Clean up OnboardingScreen code for improved readability and maintainability
This commit is contained in:
@@ -39,6 +39,7 @@ import com.rosetta.messenger.ui.chats.ChatsListScreen
|
|||||||
import com.rosetta.messenger.ui.chats.SearchScreen
|
import com.rosetta.messenger.ui.chats.SearchScreen
|
||||||
import com.rosetta.messenger.ui.components.OptimizedEmojiCache
|
import com.rosetta.messenger.ui.components.OptimizedEmojiCache
|
||||||
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
|
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
|
||||||
|
import com.rosetta.messenger.ui.settings.SettingsScreen
|
||||||
import com.rosetta.messenger.ui.splash.SplashScreen
|
import com.rosetta.messenger.ui.splash.SplashScreen
|
||||||
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -437,15 +438,18 @@ fun MainScreen(
|
|||||||
// Навигация между экранами
|
// Навигация между экранами
|
||||||
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
|
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
|
||||||
var showSearchScreen by remember { mutableStateOf(false) }
|
var showSearchScreen by remember { mutableStateOf(false) }
|
||||||
|
var showSettingsScreen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 🔥 TELEGRAM-STYLE анимация - чистый slide БЕЗ прозрачности
|
// 🔥 TELEGRAM-STYLE анимация - чистый slide БЕЗ прозрачности
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = Triple(selectedUser, showSearchScreen, Unit),
|
targetState = Triple(selectedUser, showSearchScreen, showSettingsScreen),
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
val isEnteringChat = targetState.first != null && initialState.first == null
|
val isEnteringChat = targetState.first != null && initialState.first == null
|
||||||
val isExitingChat = targetState.first == null && initialState.first != null
|
val isExitingChat = targetState.first == null && initialState.first != null
|
||||||
val isEnteringSearch = targetState.second && !initialState.second
|
val isEnteringSearch = targetState.second && !initialState.second
|
||||||
val isExitingSearch = !targetState.second && initialState.second
|
val isExitingSearch = !targetState.second && initialState.second
|
||||||
|
val isEnteringSettings = targetState.third && !initialState.third
|
||||||
|
val isExitingSettings = !targetState.third && initialState.third
|
||||||
|
|
||||||
when {
|
when {
|
||||||
// 🚀 Вход в чат - плавный fade
|
// 🚀 Вход в чат - плавный fade
|
||||||
@@ -478,6 +482,18 @@ fun MainScreen(
|
|||||||
fadeOut(animationSpec = tween(150))
|
fadeOut(animationSpec = tween(150))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⚙️ Вход в Settings - плавный fade
|
||||||
|
isEnteringSettings -> {
|
||||||
|
fadeIn(animationSpec = tween(200)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(150))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔙 Выход из Settings - плавный fade
|
||||||
|
isExitingSettings -> {
|
||||||
|
fadeIn(animationSpec = tween(200)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(150))
|
||||||
|
}
|
||||||
|
|
||||||
// Default - мгновенный переход
|
// Default - мгновенный переход
|
||||||
else -> {
|
else -> {
|
||||||
EnterTransition.None togetherWith ExitTransition.None
|
EnterTransition.None togetherWith ExitTransition.None
|
||||||
@@ -485,7 +501,7 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
label = "screenNavigation"
|
label = "screenNavigation"
|
||||||
) { (currentUser, isSearchOpen, _) ->
|
) { (currentUser, isSearchOpen, isSettingsOpen) ->
|
||||||
when {
|
when {
|
||||||
currentUser != null -> {
|
currentUser != null -> {
|
||||||
// Экран чата
|
// Экран чата
|
||||||
@@ -525,6 +541,41 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
isSettingsOpen -> {
|
||||||
|
// Экран настроек
|
||||||
|
SettingsScreen(
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
accountName = accountName,
|
||||||
|
accountPhone = accountPhone,
|
||||||
|
accountPublicKey = accountPublicKey,
|
||||||
|
onBack = { showSettingsScreen = false },
|
||||||
|
onToggleTheme = onToggleTheme,
|
||||||
|
onProfileClick = {
|
||||||
|
// TODO: Navigate to profile editor
|
||||||
|
},
|
||||||
|
onPrivacySecurityClick = {
|
||||||
|
// TODO: Navigate to privacy settings
|
||||||
|
},
|
||||||
|
onNotificationsClick = {
|
||||||
|
// TODO: Navigate to notifications settings
|
||||||
|
},
|
||||||
|
onDataStorageClick = {
|
||||||
|
// TODO: Navigate to data storage settings
|
||||||
|
},
|
||||||
|
onChatSettingsClick = {
|
||||||
|
// TODO: Navigate to chat settings
|
||||||
|
},
|
||||||
|
onLanguageClick = {
|
||||||
|
// TODO: Navigate to language selection
|
||||||
|
},
|
||||||
|
onHelpClick = {
|
||||||
|
// TODO: Navigate to help center
|
||||||
|
},
|
||||||
|
onAboutClick = {
|
||||||
|
// TODO: Show about dialog
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Список чатов
|
// Список чатов
|
||||||
ChatsListScreen(
|
ChatsListScreen(
|
||||||
@@ -558,9 +609,7 @@ fun MainScreen(
|
|||||||
online = 1
|
online = 1
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onSettingsClick = {
|
onSettingsClick = { showSettingsScreen = true },
|
||||||
// TODO: Navigate to settings
|
|
||||||
},
|
|
||||||
onInviteFriendsClick = {
|
onInviteFriendsClick = {
|
||||||
// TODO: Share invite link
|
// TODO: Share invite link
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,38 +5,178 @@ import androidx.datastore.core.DataStore
|
|||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "rosetta_preferences")
|
private val Context.dataStore: DataStore<Preferences> by
|
||||||
|
preferencesDataStore(name = "rosetta_preferences")
|
||||||
|
|
||||||
class PreferencesManager(private val context: Context) {
|
class PreferencesManager(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// Onboarding & Theme
|
||||||
val HAS_SEEN_ONBOARDING = booleanPreferencesKey("has_seen_onboarding")
|
val HAS_SEEN_ONBOARDING = booleanPreferencesKey("has_seen_onboarding")
|
||||||
val IS_DARK_THEME = booleanPreferencesKey("is_dark_theme")
|
val IS_DARK_THEME = booleanPreferencesKey("is_dark_theme")
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
val NOTIFICATIONS_ENABLED = booleanPreferencesKey("notifications_enabled")
|
||||||
|
val NOTIFICATION_SOUND_ENABLED = booleanPreferencesKey("notification_sound_enabled")
|
||||||
|
val NOTIFICATION_VIBRATE_ENABLED = booleanPreferencesKey("notification_vibrate_enabled")
|
||||||
|
val NOTIFICATION_PREVIEW_ENABLED = booleanPreferencesKey("notification_preview_enabled")
|
||||||
|
|
||||||
|
// Chat Settings
|
||||||
|
val MESSAGE_TEXT_SIZE = intPreferencesKey("message_text_size") // 0=small, 1=medium, 2=large
|
||||||
|
val SEND_BY_ENTER = booleanPreferencesKey("send_by_enter")
|
||||||
|
val AUTO_DOWNLOAD_PHOTOS = booleanPreferencesKey("auto_download_photos")
|
||||||
|
val AUTO_DOWNLOAD_VIDEOS = booleanPreferencesKey("auto_download_videos")
|
||||||
|
val AUTO_DOWNLOAD_FILES = booleanPreferencesKey("auto_download_files")
|
||||||
|
|
||||||
|
// Privacy
|
||||||
|
val SHOW_ONLINE_STATUS = booleanPreferencesKey("show_online_status")
|
||||||
|
val SHOW_READ_RECEIPTS = booleanPreferencesKey("show_read_receipts")
|
||||||
|
val SHOW_TYPING_INDICATOR = booleanPreferencesKey("show_typing_indicator")
|
||||||
|
|
||||||
|
// Language
|
||||||
|
val APP_LANGUAGE = stringPreferencesKey("app_language") // "en", "ru", etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasSeenOnboarding: Flow<Boolean> = context.dataStore.data
|
// ═════════════════════════════════════════════════════════════
|
||||||
.map { preferences ->
|
// 🎨 ONBOARDING & THEME
|
||||||
preferences[HAS_SEEN_ONBOARDING] ?: false
|
// ═════════════════════════════════════════════════════════════
|
||||||
}
|
|
||||||
|
|
||||||
val isDarkTheme: Flow<Boolean> = context.dataStore.data
|
val hasSeenOnboarding: Flow<Boolean> =
|
||||||
.map { preferences ->
|
context.dataStore.data.map { preferences -> preferences[HAS_SEEN_ONBOARDING] ?: false }
|
||||||
|
|
||||||
|
val isDarkTheme: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[IS_DARK_THEME] ?: true // Default to dark theme like Telegram
|
preferences[IS_DARK_THEME] ?: true // Default to dark theme like Telegram
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setHasSeenOnboarding(value: Boolean) {
|
suspend fun setHasSeenOnboarding(value: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences -> preferences[HAS_SEEN_ONBOARDING] = value }
|
||||||
preferences[HAS_SEEN_ONBOARDING] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setDarkTheme(value: Boolean) {
|
suspend fun setDarkTheme(value: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences -> preferences[IS_DARK_THEME] = value }
|
||||||
preferences[IS_DARK_THEME] = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🔔 NOTIFICATIONS
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
val notificationsEnabled: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[NOTIFICATIONS_ENABLED] ?: true }
|
||||||
|
|
||||||
|
val notificationSoundEnabled: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[NOTIFICATION_SOUND_ENABLED] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationVibrateEnabled: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[NOTIFICATION_VIBRATE_ENABLED] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationPreviewEnabled: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[NOTIFICATION_PREVIEW_ENABLED] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setNotificationsEnabled(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[NOTIFICATIONS_ENABLED] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setNotificationSoundEnabled(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[NOTIFICATION_SOUND_ENABLED] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setNotificationVibrateEnabled(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[NOTIFICATION_VIBRATE_ENABLED] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setNotificationPreviewEnabled(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[NOTIFICATION_PREVIEW_ENABLED] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 💬 CHAT SETTINGS
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
val messageTextSize: Flow<Int> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[MESSAGE_TEXT_SIZE] ?: 1 // Default medium
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendByEnter: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[SEND_BY_ENTER] ?: false }
|
||||||
|
|
||||||
|
val autoDownloadPhotos: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[AUTO_DOWNLOAD_PHOTOS] ?: true }
|
||||||
|
|
||||||
|
val autoDownloadVideos: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[AUTO_DOWNLOAD_VIDEOS] ?: false }
|
||||||
|
|
||||||
|
val autoDownloadFiles: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[AUTO_DOWNLOAD_FILES] ?: false }
|
||||||
|
|
||||||
|
suspend fun setMessageTextSize(value: Int) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[MESSAGE_TEXT_SIZE] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setSendByEnter(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[SEND_BY_ENTER] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setAutoDownloadPhotos(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[AUTO_DOWNLOAD_PHOTOS] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setAutoDownloadVideos(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[AUTO_DOWNLOAD_VIDEOS] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setAutoDownloadFiles(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[AUTO_DOWNLOAD_FILES] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🔐 PRIVACY
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
val showOnlineStatus: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[SHOW_ONLINE_STATUS] ?: true }
|
||||||
|
|
||||||
|
val showReadReceipts: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[SHOW_READ_RECEIPTS] ?: true }
|
||||||
|
|
||||||
|
val showTypingIndicator: Flow<Boolean> =
|
||||||
|
context.dataStore.data.map { preferences -> preferences[SHOW_TYPING_INDICATOR] ?: true }
|
||||||
|
|
||||||
|
suspend fun setShowOnlineStatus(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[SHOW_ONLINE_STATUS] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setShowReadReceipts(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[SHOW_READ_RECEIPTS] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setShowTypingIndicator(value: Boolean) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[SHOW_TYPING_INDICATOR] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🌐 LANGUAGE
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
val appLanguage: Flow<String> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[APP_LANGUAGE] ?: "en" // Default English
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setAppLanguage(value: String) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[APP_LANGUAGE] = value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,11 @@ fun OnboardingScreen(
|
|||||||
val b = (b1 + (b2 - b1) * navProgress).toInt()
|
val b = (b1 + (b2 - b1) * navProgress).toInt()
|
||||||
|
|
||||||
window.navigationBarColor =
|
window.navigationBarColor =
|
||||||
(0xFF000000 or (r.toLong() shl 16) or (g.toLong() shl 8) or b.toLong()).toInt()
|
(0xFF000000 or
|
||||||
|
(r.toLong() shl 16) or
|
||||||
|
(g.toLong() shl 8) or
|
||||||
|
b.toLong())
|
||||||
|
.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,14 +152,16 @@ fun OnboardingScreen(
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
val window = (view.context as android.app.Activity).window
|
val window = (view.context as android.app.Activity).window
|
||||||
window.navigationBarColor = if (isDarkTheme) 0xFF1E1E1E.toInt() else 0xFFFFFFFF.toInt()
|
window.navigationBarColor =
|
||||||
|
if (isDarkTheme) 0xFF1E1E1E.toInt() else 0xFFFFFFFF.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val backgroundColor by
|
val backgroundColor by
|
||||||
animateColorAsState(
|
animateColorAsState(
|
||||||
targetValue =
|
targetValue =
|
||||||
if (isDarkTheme) OnboardingBackground else OnboardingBackgroundLight,
|
if (isDarkTheme) OnboardingBackground
|
||||||
|
else OnboardingBackgroundLight,
|
||||||
animationSpec =
|
animationSpec =
|
||||||
if (!hasInitialized) snap()
|
if (!hasInitialized) snap()
|
||||||
else tween(800, easing = FastOutSlowInEasing),
|
else tween(800, easing = FastOutSlowInEasing),
|
||||||
@@ -227,7 +233,8 @@ fun OnboardingScreen(
|
|||||||
onThemeToggle()
|
onThemeToggle()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.align(Alignment.TopEnd).padding(16.dp).statusBarsPadding()
|
modifier =
|
||||||
|
Modifier.align(Alignment.TopEnd).padding(16.dp).statusBarsPadding()
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -255,7 +262,8 @@ fun OnboardingScreen(
|
|||||||
Modifier.fillMaxWidth().height(150.dp).graphicsLayer {
|
Modifier.fillMaxWidth().height(150.dp).graphicsLayer {
|
||||||
// Hardware acceleration for entire pager
|
// Hardware acceleration for entire pager
|
||||||
compositingStrategy =
|
compositingStrategy =
|
||||||
androidx.compose.ui.graphics.CompositingStrategy.Offscreen
|
androidx.compose.ui.graphics
|
||||||
|
.CompositingStrategy.Offscreen
|
||||||
},
|
},
|
||||||
// Pre-load adjacent pages for smooth swiping
|
// Pre-load adjacent pages for smooth swiping
|
||||||
beyondBoundsPageCount = 2,
|
beyondBoundsPageCount = 2,
|
||||||
@@ -263,12 +271,19 @@ fun OnboardingScreen(
|
|||||||
PagerDefaults.flingBehavior(
|
PagerDefaults.flingBehavior(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
pagerSnapDistance =
|
pagerSnapDistance =
|
||||||
PagerSnapDistance.atMost(0), // Snap to nearest page
|
PagerSnapDistance.atMost(
|
||||||
lowVelocityAnimationSpec = snap(), // Instant!
|
1
|
||||||
snapAnimationSpec = snap(), // No animation!
|
), // Нужен более значительный свайп
|
||||||
positionalThreshold =
|
lowVelocityAnimationSpec =
|
||||||
0.4f // Увеличен порог с 0.5 до 0.4 (нужно больше
|
tween(
|
||||||
// свайпнуть)
|
durationMillis = 400,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
),
|
||||||
|
snapAnimationSpec =
|
||||||
|
spring(
|
||||||
|
dampingRatio = 0.8f,
|
||||||
|
stiffness = 380f
|
||||||
|
)
|
||||||
)
|
)
|
||||||
) { page ->
|
) { page ->
|
||||||
OnboardingPageContent(
|
OnboardingPageContent(
|
||||||
@@ -278,7 +293,8 @@ fun OnboardingScreen(
|
|||||||
highlightColor = PrimaryBlue,
|
highlightColor = PrimaryBlue,
|
||||||
pageOffset =
|
pageOffset =
|
||||||
((pagerState.currentPage - page) +
|
((pagerState.currentPage - page) +
|
||||||
pagerState.currentPageOffsetFraction)
|
pagerState
|
||||||
|
.currentPageOffsetFraction)
|
||||||
.absoluteValue
|
.absoluteValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -315,14 +331,16 @@ fun ThemeToggleButton(
|
|||||||
val rotation by
|
val rotation by
|
||||||
animateFloatAsState(
|
animateFloatAsState(
|
||||||
targetValue = if (isDarkTheme) 360f else 0f,
|
targetValue = if (isDarkTheme) 360f else 0f,
|
||||||
animationSpec = spring(dampingRatio = 0.6f, stiffness = Spring.StiffnessLow),
|
animationSpec =
|
||||||
|
spring(dampingRatio = 0.6f, stiffness = Spring.StiffnessLow),
|
||||||
label = "rotation"
|
label = "rotation"
|
||||||
)
|
)
|
||||||
|
|
||||||
val scale by
|
val scale by
|
||||||
animateFloatAsState(
|
animateFloatAsState(
|
||||||
targetValue = 1f,
|
targetValue = 1f,
|
||||||
animationSpec = spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium),
|
animationSpec =
|
||||||
|
spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium),
|
||||||
label = "scale"
|
label = "scale"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -361,9 +379,11 @@ fun ThemeToggleButton(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector =
|
imageVector =
|
||||||
if (isDarkTheme) Icons.Default.LightMode else Icons.Default.DarkMode,
|
if (isDarkTheme) Icons.Default.LightMode
|
||||||
|
else Icons.Default.DarkMode,
|
||||||
contentDescription =
|
contentDescription =
|
||||||
if (isDarkTheme) "Switch to Light Mode" else "Switch to Dark Mode",
|
if (isDarkTheme) "Switch to Light Mode"
|
||||||
|
else "Switch to Dark Mode",
|
||||||
tint = iconColor,
|
tint = iconColor,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
@@ -432,7 +452,8 @@ fun AnimatedRosettaLogo(
|
|||||||
targetValue = 1.1f,
|
targetValue = 1.1f,
|
||||||
animationSpec =
|
animationSpec =
|
||||||
infiniteRepeatable(
|
infiniteRepeatable(
|
||||||
animation = tween(800, easing = FastOutSlowInEasing),
|
animation =
|
||||||
|
tween(800, easing = FastOutSlowInEasing),
|
||||||
repeatMode = RepeatMode.Reverse
|
repeatMode = RepeatMode.Reverse
|
||||||
),
|
),
|
||||||
label = "pulseScale"
|
label = "pulseScale"
|
||||||
@@ -455,7 +476,9 @@ fun AnimatedRosettaLogo(
|
|||||||
Modifier.size(180.dp)
|
Modifier.size(180.dp)
|
||||||
.scale(if (currentPage == 0) pulseScale else 1f)
|
.scale(if (currentPage == 0) pulseScale else 1f)
|
||||||
.background(
|
.background(
|
||||||
color = Color(0xFF54A9EB).copy(alpha = 0.2f),
|
color =
|
||||||
|
Color(0xFF54A9EB)
|
||||||
|
.copy(alpha = 0.2f),
|
||||||
shape = CircleShape
|
shape = CircleShape
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -477,7 +500,8 @@ fun AnimatedRosettaLogo(
|
|||||||
alpha = if (currentPage == 1) 1f else 0f
|
alpha = if (currentPage == 1) 1f else 0f
|
||||||
// Hardware layer optimization
|
// Hardware layer optimization
|
||||||
compositingStrategy =
|
compositingStrategy =
|
||||||
androidx.compose.ui.graphics.CompositingStrategy.Offscreen
|
androidx.compose.ui.graphics
|
||||||
|
.CompositingStrategy.Offscreen
|
||||||
// Disable clipping for performance
|
// Disable clipping for performance
|
||||||
clip = false
|
clip = false
|
||||||
},
|
},
|
||||||
@@ -496,7 +520,8 @@ fun AnimatedRosettaLogo(
|
|||||||
Modifier.fillMaxSize().graphicsLayer {
|
Modifier.fillMaxSize().graphicsLayer {
|
||||||
alpha = if (currentPage == 2) 1f else 0f
|
alpha = if (currentPage == 2) 1f else 0f
|
||||||
compositingStrategy =
|
compositingStrategy =
|
||||||
androidx.compose.ui.graphics.CompositingStrategy.Offscreen
|
androidx.compose.ui.graphics
|
||||||
|
.CompositingStrategy.Offscreen
|
||||||
clip = false
|
clip = false
|
||||||
},
|
},
|
||||||
maintainOriginalImageBounds = true,
|
maintainOriginalImageBounds = true,
|
||||||
@@ -513,7 +538,8 @@ fun AnimatedRosettaLogo(
|
|||||||
Modifier.fillMaxSize().graphicsLayer {
|
Modifier.fillMaxSize().graphicsLayer {
|
||||||
alpha = if (currentPage == 3) 1f else 0f
|
alpha = if (currentPage == 3) 1f else 0f
|
||||||
compositingStrategy =
|
compositingStrategy =
|
||||||
androidx.compose.ui.graphics.CompositingStrategy.Offscreen
|
androidx.compose.ui.graphics
|
||||||
|
.CompositingStrategy.Offscreen
|
||||||
clip = false
|
clip = false
|
||||||
},
|
},
|
||||||
maintainOriginalImageBounds = true,
|
maintainOriginalImageBounds = true,
|
||||||
@@ -530,7 +556,8 @@ fun AnimatedRosettaLogo(
|
|||||||
Modifier.fillMaxSize().graphicsLayer {
|
Modifier.fillMaxSize().graphicsLayer {
|
||||||
alpha = if (currentPage == 4) 1f else 0f
|
alpha = if (currentPage == 4) 1f else 0f
|
||||||
compositingStrategy =
|
compositingStrategy =
|
||||||
androidx.compose.ui.graphics.CompositingStrategy.Offscreen
|
androidx.compose.ui.graphics
|
||||||
|
.CompositingStrategy.Offscreen
|
||||||
clip = false
|
clip = false
|
||||||
},
|
},
|
||||||
maintainOriginalImageBounds = true,
|
maintainOriginalImageBounds = true,
|
||||||
@@ -588,17 +615,33 @@ fun OnboardingPageContent(
|
|||||||
|
|
||||||
// Find and highlight words
|
// Find and highlight words
|
||||||
page.highlightWords.forEach { word ->
|
page.highlightWords.forEach { word ->
|
||||||
val startIndex = description.indexOf(word, currentIndex, ignoreCase = true)
|
val startIndex =
|
||||||
|
description.indexOf(word, currentIndex, ignoreCase = true)
|
||||||
if (startIndex >= 0) {
|
if (startIndex >= 0) {
|
||||||
// Add text before the word
|
// Add text before the word
|
||||||
if (startIndex > currentIndex) {
|
if (startIndex > currentIndex) {
|
||||||
withStyle(SpanStyle(color = secondaryTextColor)) {
|
withStyle(SpanStyle(color = secondaryTextColor)) {
|
||||||
append(description.substring(currentIndex, startIndex))
|
append(
|
||||||
|
description.substring(
|
||||||
|
currentIndex,
|
||||||
|
startIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add highlighted word
|
// Add highlighted word
|
||||||
withStyle(SpanStyle(color = highlightColor, fontWeight = FontWeight.SemiBold)) {
|
withStyle(
|
||||||
append(description.substring(startIndex, startIndex + word.length))
|
SpanStyle(
|
||||||
|
color = highlightColor,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append(
|
||||||
|
description.substring(
|
||||||
|
startIndex,
|
||||||
|
startIndex + word.length
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
currentIndex = startIndex + word.length
|
currentIndex = startIndex + word.length
|
||||||
}
|
}
|
||||||
@@ -648,7 +691,10 @@ fun PagerIndicator(
|
|||||||
Modifier.height(8.dp)
|
Modifier.height(8.dp)
|
||||||
.width(width)
|
.width(width)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(if (isSelected) selectedColor else unselectedColor)
|
.background(
|
||||||
|
if (isSelected) selectedColor
|
||||||
|
else unselectedColor
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,14 +734,21 @@ fun StartMessagingButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
|||||||
Brush.linearGradient(
|
Brush.linearGradient(
|
||||||
colors =
|
colors =
|
||||||
listOf(
|
listOf(
|
||||||
Color.White.copy(alpha = 0f),
|
Color.White.copy(
|
||||||
Color.White.copy(alpha = 0.3f),
|
alpha = 0f
|
||||||
Color.White.copy(alpha = 0f)
|
),
|
||||||
|
Color.White.copy(
|
||||||
|
alpha = 0.3f
|
||||||
|
),
|
||||||
|
Color.White.copy(
|
||||||
|
alpha = 0f
|
||||||
|
)
|
||||||
),
|
),
|
||||||
start = Offset(shimmerStart, 0f),
|
start = Offset(shimmerStart, 0f),
|
||||||
end =
|
end =
|
||||||
Offset(
|
Offset(
|
||||||
shimmerStart + shimmerWidth,
|
shimmerStart +
|
||||||
|
shimmerWidth,
|
||||||
size.height
|
size.height
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,505 @@
|
|||||||
|
package com.rosetta.messenger.ui.settings
|
||||||
|
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.*
|
||||||
|
import androidx.compose.material.icons.outlined.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(
|
||||||
|
isDarkTheme: Boolean,
|
||||||
|
accountName: String,
|
||||||
|
accountPhone: String,
|
||||||
|
accountPublicKey: String,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onToggleTheme: () -> Unit,
|
||||||
|
onProfileClick: () -> Unit = {},
|
||||||
|
onPrivacySecurityClick: () -> Unit = {},
|
||||||
|
onNotificationsClick: () -> Unit = {},
|
||||||
|
onDataStorageClick: () -> Unit = {},
|
||||||
|
onChatSettingsClick: () -> Unit = {},
|
||||||
|
onLanguageClick: () -> Unit = {},
|
||||||
|
onHelpClick: () -> Unit = {},
|
||||||
|
onAboutClick: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
// Цвета в зависимости от темы
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF0F0F0F) else Color.White
|
||||||
|
val surfaceColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF5F5F5)
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color(0xFF1A1A1A)
|
||||||
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF999999) else Color(0xFF666666)
|
||||||
|
val dividerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
|
||||||
|
val iconTintColor = if (isDarkTheme) Color(0xFF999999) else Color(0xFF666666)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🎨 TOP BAR
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "Settings",
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = textColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(containerColor = backgroundColor)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 📱 CONTENT
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 👤 PROFILE SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
Surface(
|
||||||
|
modifier =
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable(onClick = onProfileClick),
|
||||||
|
color = surfaceColor
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Avatar
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier.size(64.dp).clip(CircleShape).background(PrimaryBlue),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = getInitials(accountName),
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
// Name and Phone
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = accountName,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(text = accountPhone, fontSize = 14.sp, color = secondaryTextColor)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(
|
||||||
|
text = accountPublicKey.take(12) + "...",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = secondaryTextColor.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ChevronRight,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTintColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🎨 APPEARANCE SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "Appearance", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsToggleItem(
|
||||||
|
icon =
|
||||||
|
if (isDarkTheme) Icons.Outlined.DarkMode
|
||||||
|
else Icons.Outlined.LightMode,
|
||||||
|
title = "Dark Mode",
|
||||||
|
subtitle = if (isDarkTheme) "Enabled" else "Disabled",
|
||||||
|
isChecked = isDarkTheme,
|
||||||
|
onToggle = onToggleTheme,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🔐 PRIVACY & SECURITY SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "Privacy & Security", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Lock,
|
||||||
|
title = "Privacy Settings",
|
||||||
|
subtitle = "Control who can see your info",
|
||||||
|
onClick = onPrivacySecurityClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Security,
|
||||||
|
title = "Security",
|
||||||
|
subtitle = "Passcode, 2FA, sessions",
|
||||||
|
onClick = onPrivacySecurityClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Block,
|
||||||
|
title = "Blocked Users",
|
||||||
|
subtitle = "Manage blocked contacts",
|
||||||
|
onClick = onPrivacySecurityClick,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🔔 NOTIFICATIONS SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "Notifications & Sounds", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Notifications,
|
||||||
|
title = "Notifications",
|
||||||
|
subtitle = "Messages, groups, channels",
|
||||||
|
onClick = onNotificationsClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.VolumeUp,
|
||||||
|
title = "Sounds",
|
||||||
|
subtitle = "Notification sounds",
|
||||||
|
onClick = onNotificationsClick,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 💾 DATA & STORAGE SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "Data & Storage", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Storage,
|
||||||
|
title = "Storage Usage",
|
||||||
|
subtitle = "Clear cache, manage data",
|
||||||
|
onClick = onDataStorageClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.DataUsage,
|
||||||
|
title = "Network Usage",
|
||||||
|
subtitle = "Mobile and Wi-Fi",
|
||||||
|
onClick = onDataStorageClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.DownloadForOffline,
|
||||||
|
title = "Auto-Download",
|
||||||
|
subtitle = "Photos, videos, files",
|
||||||
|
onClick = onDataStorageClick,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 💬 CHAT SETTINGS SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "Chat Settings", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.ChatBubbleOutline,
|
||||||
|
title = "Chat Background",
|
||||||
|
subtitle = "Set wallpaper for chats",
|
||||||
|
onClick = onChatSettingsClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.TextFields,
|
||||||
|
title = "Message Text Size",
|
||||||
|
subtitle = "Adjust text size in chats",
|
||||||
|
onClick = onChatSettingsClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.EmojiEmotions,
|
||||||
|
title = "Stickers & Emojis",
|
||||||
|
subtitle = "Manage sticker packs",
|
||||||
|
onClick = onChatSettingsClick,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 🌐 GENERAL SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "General", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Language,
|
||||||
|
title = "Language",
|
||||||
|
subtitle = "English",
|
||||||
|
onClick = onLanguageClick,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// ℹ️ SUPPORT SECTION
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
SettingsSectionTitle(title = "Support", isDarkTheme = isDarkTheme)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
color = surfaceColor,
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.HelpOutline,
|
||||||
|
title = "Help Center",
|
||||||
|
subtitle = "FAQ and support",
|
||||||
|
onClick = onHelpClick,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
showDivider = true
|
||||||
|
)
|
||||||
|
SettingsNavigationItem(
|
||||||
|
icon = Icons.Outlined.Info,
|
||||||
|
title = "About",
|
||||||
|
subtitle = "Version 1.0.0",
|
||||||
|
onClick = onAboutClick,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 📦 HELPER COMPONENTS
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SettingsSectionTitle(title: String, isDarkTheme: Boolean) {
|
||||||
|
val textColor = if (isDarkTheme) Color(0xFF666666) else Color(0xFF999999)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = title.uppercase(),
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = textColor,
|
||||||
|
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SettingsNavigationItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
isDarkTheme: Boolean,
|
||||||
|
showDivider: Boolean = false
|
||||||
|
) {
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color(0xFF1A1A1A)
|
||||||
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF999999) else Color(0xFF666666)
|
||||||
|
val iconTintColor = if (isDarkTheme) Color(0xFF999999) else Color(0xFF666666)
|
||||||
|
val dividerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Icon
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTintColor,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
// Title and Subtitle
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(text = title, fontSize = 16.sp, color = textColor)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(text = subtitle, fontSize = 13.sp, color = secondaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ChevronRight,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTintColor.copy(alpha = 0.5f),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDivider) {
|
||||||
|
Divider(
|
||||||
|
color = dividerColor,
|
||||||
|
thickness = 0.5.dp,
|
||||||
|
modifier = Modifier.padding(start = 56.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SettingsToggleItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
isChecked: Boolean,
|
||||||
|
onToggle: () -> Unit,
|
||||||
|
isDarkTheme: Boolean
|
||||||
|
) {
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color(0xFF1A1A1A)
|
||||||
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF999999) else Color(0xFF666666)
|
||||||
|
val iconTintColor = if (isDarkTheme) Color(0xFF999999) else Color(0xFF666666)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
.clickable(onClick = onToggle)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Icon
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTintColor,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
// Title and Subtitle
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(text = title, fontSize = 16.sp, color = textColor)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(text = subtitle, fontSize = 13.sp, color = secondaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Switch
|
||||||
|
Switch(
|
||||||
|
checked = isChecked,
|
||||||
|
onCheckedChange = { onToggle() },
|
||||||
|
colors =
|
||||||
|
SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = Color.White,
|
||||||
|
checkedTrackColor = PrimaryBlue,
|
||||||
|
uncheckedThumbColor = Color.White,
|
||||||
|
uncheckedTrackColor =
|
||||||
|
if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFCCCCCC)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInitials(name: String): String {
|
||||||
|
if (name.isBlank()) return "?"
|
||||||
|
val parts = name.trim().split(" ").filter { it.isNotEmpty() }
|
||||||
|
return when {
|
||||||
|
parts.isEmpty() -> "?"
|
||||||
|
parts.size == 1 -> parts[0].take(2).uppercase()
|
||||||
|
else -> (parts[0].first().toString() + parts[1].first().toString()).uppercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user