From cb920b490d5ba2bdbd5f412edfb305f7aa015a45 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sun, 12 Apr 2026 23:59:04 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BC=D0=B5=D0=BD=D0=B0=20=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=E2=80=94=20=D0=BA=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BA=D1=83=D0=BB=D1=8F=D1=82=D0=BE=D1=80,=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=D0=B4=D0=B0,=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20+=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B2=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 62 +++- .../com/rosetta/messenger/MainActivity.kt | 17 ++ .../messenger/data/PreferencesManager.kt | 16 ++ .../messenger/ui/settings/AppIconScreen.kt | 270 ++++++++++++++++++ .../messenger/ui/settings/AppearanceScreen.kt | 44 +++ .../main/res/drawable/ic_calc_background.xml | 4 + .../main/res/drawable/ic_calc_foreground.xml | 38 +++ .../main/res/drawable/ic_notes_background.xml | 4 + .../main/res/drawable/ic_notes_foreground.xml | 52 ++++ .../res/drawable/ic_weather_background.xml | 4 + .../res/drawable/ic_weather_foreground.xml | 32 +++ .../mipmap-anydpi-v26/ic_launcher_calc.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_notes.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_weather.xml | 5 + 14 files changed, 554 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/rosetta/messenger/ui/settings/AppIconScreen.kt create mode 100644 app/src/main/res/drawable/ic_calc_background.xml create mode 100644 app/src/main/res/drawable/ic_calc_foreground.xml create mode 100644 app/src/main/res/drawable/ic_notes_background.xml create mode 100644 app/src/main/res/drawable/ic_notes_foreground.xml create mode 100644 app/src/main/res/drawable/ic_weather_background.xml create mode 100644 app/src/main/res/drawable/ic_weather_foreground.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_calc.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_notes.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_weather.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cffe881..2ad9cf4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,10 +47,7 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode|smallestScreenSize|screenLayout" android:windowSoftInputMode="adjustResize" android:screenOrientation="portrait"> - - - - + @@ -65,6 +62,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + = + context.dataStore.data.map { preferences -> + preferences[APP_ICON] ?: "default" + } + + suspend fun setAppIcon(value: String) { + context.dataStore.edit { preferences -> preferences[APP_ICON] = value } + } + // ═════════════════════════════════════════════════════════════ // 🔕 MUTED CHATS // ═════════════════════════════════════════════════════════════ diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/AppIconScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/AppIconScreen.kt new file mode 100644 index 0000000..66402a9 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/AppIconScreen.kt @@ -0,0 +1,270 @@ +package com.rosetta.messenger.ui.settings + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +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.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import compose.icons.TablerIcons +import compose.icons.tablericons.ChevronLeft +import com.rosetta.messenger.R +import com.rosetta.messenger.data.PreferencesManager +import com.rosetta.messenger.ui.onboarding.PrimaryBlue +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +data class AppIconOption( + val id: String, + val label: String, + val subtitle: String, + val aliasName: String, + val iconRes: Int, + val previewBg: Color +) + +private val iconOptions = listOf( + AppIconOption("default", "Rosetta", "Original icon", ".MainActivityDefault", R.drawable.ic_launcher_foreground, Color(0xFF1B1B1B)), + AppIconOption("calculator", "Calculator", "Disguise as calculator", ".MainActivityCalculator", R.drawable.ic_calc_foreground, Color(0xFF795548)), + AppIconOption("weather", "Weather", "Disguise as weather app", ".MainActivityWeather", R.drawable.ic_weather_foreground, Color(0xFF42A5F5)), + AppIconOption("notes", "Notes", "Disguise as notes app", ".MainActivityNotes", R.drawable.ic_notes_foreground, Color(0xFFFFC107)) +) + +@Composable +fun AppIconScreen( + isDarkTheme: Boolean, + onBack: () -> Unit +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val prefs = remember { PreferencesManager(context) } + var currentIcon by remember { mutableStateOf("default") } + + LaunchedEffect(Unit) { + currentIcon = prefs.appIcon.first() + } + + val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF) + val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7) + val textColor = if (isDarkTheme) Color.White else Color.Black + val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) + val dividerColor = if (isDarkTheme) Color(0xFF38383A) else Color(0xFFE5E5EA) + + // Status bar + val view = androidx.compose.ui.platform.LocalView.current + if (!view.isInEditMode) { + DisposableEffect(isDarkTheme) { + val window = (view.context as android.app.Activity).window + val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view) + val prev = insetsController.isAppearanceLightStatusBars + insetsController.isAppearanceLightStatusBars = !isDarkTheme + onDispose { insetsController.isAppearanceLightStatusBars = prev } + } + } + + BackHandler { onBack() } + + Column( + modifier = Modifier + .fillMaxSize() + .background(backgroundColor) + ) { + // ═══════════════════════════════════════════════════════ + // TOP BAR — same style as SafetyScreen + // ═══════════════════════════════════════════════════════ + Surface( + modifier = Modifier.fillMaxWidth(), + color = backgroundColor + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()) + .padding(horizontal = 4.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = onBack) { + Icon( + imageVector = TablerIcons.ChevronLeft, + contentDescription = "Back", + tint = textColor + ) + } + Text( + text = "App Icon", + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + color = textColor, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + + // ═══════════════════════════════════════════════════════ + // CONTENT + // ═══════════════════════════════════════════════════════ + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Spacer(modifier = Modifier.height(8.dp)) + + // Section header + Text( + text = "CHOOSE ICON", + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + color = secondaryTextColor, + letterSpacing = 0.5.sp, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + + // Icon cards in grouped surface (Telegram style) + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(12.dp), + color = surfaceColor + ) { + Column { + iconOptions.forEachIndexed { index, option -> + val isSelected = currentIcon == option.id + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + if (!isSelected) { + scope.launch { + changeAppIcon(context, prefs, option.id) + currentIcon = option.id + } + } + } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Icon preview + Box( + modifier = Modifier + .size(52.dp) + .clip(RoundedCornerShape(12.dp)) + .background(option.previewBg), + contentAlignment = Alignment.Center + ) { + // Default icon has 15% inset built-in — show full size + val iconSize = if (option.id == "default") 52.dp else 36.dp + val scaleType = if (option.id == "default") + android.widget.ImageView.ScaleType.CENTER_CROP + else + android.widget.ImageView.ScaleType.FIT_CENTER + androidx.compose.ui.viewinterop.AndroidView( + factory = { ctx -> + android.widget.ImageView(ctx).apply { + setImageResource(option.iconRes) + this.scaleType = scaleType + } + }, + modifier = Modifier.size(iconSize) + ) + } + + Spacer(modifier = Modifier.width(14.dp)) + + // Label + subtitle + Column(modifier = Modifier.weight(1f)) { + Text( + text = option.label, + color = textColor, + fontSize = 16.sp, + fontWeight = FontWeight.Normal + ) + Text( + text = option.subtitle, + color = secondaryTextColor, + fontSize = 13.sp + ) + } + + // Checkmark + if (isSelected) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = "Selected", + tint = PrimaryBlue, + modifier = Modifier.size(22.dp) + ) + } + } + + // Divider between items (not after last) + if (index < iconOptions.lastIndex) { + Divider( + modifier = Modifier.padding(start = 82.dp), + thickness = 0.5.dp, + color = dividerColor + ) + } + } + } + } + + // Info text below + Text( + text = "The app icon and name on your home screen will change. Rosetta will continue to work normally. The launcher may take a moment to update.", + fontSize = 13.sp, + color = secondaryTextColor, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + lineHeight = 18.sp + ) + + Spacer(modifier = Modifier.height(32.dp)) + } + } +} + +private suspend fun changeAppIcon(context: Context, prefs: PreferencesManager, newIconId: String) { + val pm = context.packageManager + val packageName = context.packageName + + iconOptions.forEach { option -> + val component = ComponentName(packageName, "$packageName${option.aliasName}") + pm.setComponentEnabledSetting( + component, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP + ) + } + + val selected = iconOptions.first { it.id == newIconId } + val component = ComponentName(packageName, "$packageName${selected.aliasName}") + pm.setComponentEnabledSetting( + component, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP + ) + + prefs.setAppIcon(newIconId) + Toast.makeText(context, "Icon changed to ${selected.label}", Toast.LENGTH_SHORT).show() +} diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/AppearanceScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/AppearanceScreen.kt index 0956fef..a216553 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/AppearanceScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/AppearanceScreen.kt @@ -78,6 +78,7 @@ fun AppearanceScreen( onBack: () -> Unit, onBlurColorChange: (String) -> Unit, onToggleTheme: () -> Unit = {}, + onAppIconClick: () -> Unit = {}, accountPublicKey: String = "", accountName: String = "", avatarRepository: AvatarRepository? = null @@ -282,6 +283,49 @@ fun AppearanceScreen( lineHeight = 18.sp ) + Spacer(modifier = Modifier.height(24.dp)) + + // ═══════════════════════════════════════════════════════ + // APP ICON SECTION + // ═══════════════════════════════════════════════════════ + Text( + text = "APP ICON", + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + color = secondaryTextColor, + letterSpacing = 0.5.sp, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onAppIconClick() } + .padding(horizontal = 16.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Change App Icon", + fontSize = 16.sp, + color = textColor + ) + Icon( + imageVector = TablerIcons.ChevronRight, + contentDescription = null, + tint = secondaryTextColor, + modifier = Modifier.size(20.dp) + ) + } + + Text( + text = "Disguise Rosetta as a calculator, weather app, or notes.", + fontSize = 13.sp, + color = secondaryTextColor, + modifier = Modifier.padding(horizontal = 16.dp), + lineHeight = 18.sp + ) + Spacer(modifier = Modifier.height(32.dp)) } } diff --git a/app/src/main/res/drawable/ic_calc_background.xml b/app/src/main/res/drawable/ic_calc_background.xml new file mode 100644 index 0000000..697e55f --- /dev/null +++ b/app/src/main/res/drawable/ic_calc_background.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_calc_foreground.xml b/app/src/main/res/drawable/ic_calc_foreground.xml new file mode 100644 index 0000000..41c843b --- /dev/null +++ b/app/src/main/res/drawable/ic_calc_foreground.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_notes_background.xml b/app/src/main/res/drawable/ic_notes_background.xml new file mode 100644 index 0000000..fdeeb63 --- /dev/null +++ b/app/src/main/res/drawable/ic_notes_background.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_notes_foreground.xml b/app/src/main/res/drawable/ic_notes_foreground.xml new file mode 100644 index 0000000..6acf37d --- /dev/null +++ b/app/src/main/res/drawable/ic_notes_foreground.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_weather_background.xml b/app/src/main/res/drawable/ic_weather_background.xml new file mode 100644 index 0000000..275abac --- /dev/null +++ b/app/src/main/res/drawable/ic_weather_background.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_weather_foreground.xml b/app/src/main/res/drawable/ic_weather_foreground.xml new file mode 100644 index 0000000..7bd9a2d --- /dev/null +++ b/app/src/main/res/drawable/ic_weather_foreground.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_calc.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_calc.xml new file mode 100644 index 0000000..d9ccec9 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_calc.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_notes.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_notes.xml new file mode 100644 index 0000000..87b115f --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_notes.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_weather.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_weather.xml new file mode 100644 index 0000000..e7cd117 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_weather.xml @@ -0,0 +1,5 @@ + + + + +