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 @@
+
+
+
+
+