ui: add asset image on create channel view; allow to choose image on create profile (#6891)

* ui: create channel picture

* more centered

* better symmetry

* less diff

* choose image on create profile

* fix padding

* fix padding, fit into screen

* fix button layout

* placeholders

* fix padding

* channel pictures

* adjust asset_dir in scripts

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: shum <github.shum@liber.li>
This commit is contained in:
spaced4ndy
2026-04-27 13:00:14 +00:00
committed by GitHub
parent 3d85480944
commit 08c69e3dfa
18 changed files with 247 additions and 95 deletions
@@ -42,11 +42,14 @@ import chat.simplex.common.views.newchat.darkStops
import chat.simplex.common.views.newchat.gradientPoints
import chat.simplex.common.views.newchat.lightStops
import chat.simplex.common.views.onboarding.*
import chat.simplex.common.views.usersettings.DeleteImageButton
import chat.simplex.common.views.usersettings.EditImageButton
import chat.simplex.common.views.usersettings.SettingsActionItem
import chat.simplex.res.MR
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import java.net.URI
const val MAX_BIO_LENGTH_BYTES = 160
@@ -60,18 +63,63 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
val scrollState = rememberScrollState()
val keyboardState by getKeyboardState()
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 20.dp)
) {
val displayName = rememberSaveable { mutableStateOf("") }
val shortDescr = rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val displayName = rememberSaveable { mutableStateOf("") }
val shortDescr = rememberSaveable { mutableStateOf("") }
val chosenImage = rememberSaveable { mutableStateOf<URI?>(null) }
val profileImage = rememberSaveable { mutableStateOf<String?>(null) }
val focusRequester = remember { FocusRequester() }
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
modifier = Modifier.imePadding(),
sheetContent = {
GetImageBottomSheet(
chosenImage,
onImageChange = { bitmap -> profileImage.value = resizeImageToStrSize(cropToSquare(bitmap), maxDataSize = 12500) },
hideBottomSheet = {
scope.launch { bottomSheetModalState.hide() }
})
},
sheetState = bottomSheetModalState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
Box(
modifier = Modifier.fillMaxSize()
) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING_HALF)
Row(
Modifier
.fillMaxWidth()
.padding(vertical = DEFAULT_PADDING_HALF),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Box(
contentAlignment = Alignment.Center
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(128.dp, image = profileImage.value)
EditImageButton { scope.launch { bottomSheetModalState.show() } }
}
if (profileImage.value != null) {
DeleteImageButton { profileImage.value = null }
}
}
}
// TODO: add 3D asset image next to profile image (fix asset first - trim transparent space)
// if (BuildConfigCommon.SIMPLEX_ASSETS) {
// Image(
// painterResource(if (isInDarkTheme()) MR.images.your_profile_light else MR.images.your_profile),
// contentDescription = null,
// contentScale = ContentScale.Fit,
// modifier = Modifier.height(140.dp)
// )
// }
}
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
AppBarTitle(stringResource(MR.strings.create_profile), withPadding = false, bottomPadding = DEFAULT_PADDING)
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(MR.strings.display_name),
@@ -114,9 +162,9 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
iconColor = MaterialTheme.colors.primary,
click = {
if (chatModel.localUserCreated.value == true) {
createProfileInProfiles(chatModel, displayName.value, shortDescr.value, close)
createProfileInProfiles(chatModel, displayName.value, shortDescr.value, profileImage.value, close)
} else {
createProfileInNoProfileSetup(displayName.value, close)
createProfileInNoProfileSetup(displayName.value, profileImage.value, close)
}
},
)
@@ -137,6 +185,7 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
}
}
}
}
}
@Composable
@@ -304,9 +353,9 @@ private fun CreateFirstProfileDesktop(chatModel: ChatModel, close: () -> Unit) {
}
}
fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
fun createProfileInNoProfileSetup(displayName: String, image: String? = null, close: () -> Unit) {
withBGApi {
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, null)) ?: return@withBGApi
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, image)) ?: return@withBGApi
if (!chatModel.connectedToRemote()) {
chatModel.localUserCreated.value = true
}
@@ -317,11 +366,11 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
}
}
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, close: () -> Unit) {
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, image: String? = null, close: () -> Unit) {
withBGApi {
val rhId = chatModel.remoteHostId()
val user = chatModel.controller.apiCreateActiveUser(
rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, null)
rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, image)
) ?: return@withBGApi
chatModel.currentUser.value = user
if (chatModel.users.isEmpty()) {
@@ -23,6 +23,8 @@ import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.getUserServers
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import androidx.compose.ui.layout.ContentScale
import chat.simplex.common.BuildConfigCommon
import chat.simplex.common.views.*
import chat.simplex.common.views.chat.group.GroupLinkView
import chat.simplex.common.views.chatlist.openGroupChat
@@ -257,22 +259,37 @@ private fun ProfileStepView(
) {
ModalView(close = close) {
ColumnWithScrollBar {
AppBarTitle(generalGetString(MR.strings.create_channel_title))
Box(
AppBarTitle(generalGetString(MR.strings.create_channel_title), bottomPadding = DEFAULT_PADDING_HALF)
Row(
Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
contentAlignment = Alignment.Center
.padding(vertical = DEFAULT_PADDING_HALF),
horizontalArrangement = if (BuildConfigCommon.SIMPLEX_ASSETS) Arrangement.SpaceEvenly else Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(108.dp, image = profileImage.value, icon = MR.images.ic_bigtop_updates_circle_filled)
EditImageButton { scope.launch { bottomSheetModalState.show() } }
}
if (profileImage.value != null) {
DeleteImageButton { profileImage.value = null }
// Padding offsets transparent space built into 3D asset
Box(
modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier,
contentAlignment = Alignment.Center
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(128.dp, image = profileImage.value, icon = MR.images.ic_bigtop_updates_circle_filled)
EditImageButton { scope.launch { bottomSheetModalState.show() } }
}
if (profileImage.value != null) {
DeleteImageButton { profileImage.value = null }
}
}
}
if (BuildConfigCommon.SIMPLEX_ASSETS) {
Image(
painterResource(if (isInDarkTheme()) MR.images.create_channel_light else MR.images.create_channel),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.height(140.dp)
)
}
}
Row(
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF).fillMaxWidth(),
@@ -109,7 +109,11 @@ fun AddGroupLayout(
horizontalArrangement = if (BuildConfigCommon.SIMPLEX_ASSETS) Arrangement.SpaceEvenly else Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Box(contentAlignment = Alignment.Center) {
// Padding offsets transparent space built into 3D asset
Box(
modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier,
contentAlignment = Alignment.Center
) {
Box(contentAlignment = Alignment.TopEnd) {
Box(contentAlignment = Alignment.Center) {
ProfileImage(128.dp, image = profileImage.value, icon = MR.images.ic_supervised_user_circle_filled)
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0,0h24v24H0z" fill="#00000000"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0,0h24v24H0z" fill="#00000000"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B