android, desktop: make the form's submit callback optional, as iOS already does

CreateProfile took a required onSubmit, so both existing call sites had to change and a
createProfileFromForm helper existed only to give them somewhere to delegate. The iOS
port of the same change made the callback optional and touched no existing call site;
this does the same. UserPicker and UserProfilesView drop out of the diff, the helper
goes, and the branch the form always had stays where it was, behind a null check.

The trade is that chatModel and close stay parameters even though the invitation path
uses neither, and the callback is nullable. That is the price of not touching two
unrelated files, and it is the shape iOS already ships. Easy to reverse if the cleaner
signature is preferred.
This commit is contained in:
Narasimha-sc
2026-08-06 18:22:56 +00:00
parent 0e005520ae
commit f940796aaa
3 changed files with 17 additions and 22 deletions
@@ -57,8 +57,12 @@ fun bioFitsLimit(bio: String): Boolean {
return chatJsonLength(bio) <= MAX_BIO_LENGTH_BYTES
}
/** [onSubmit] replaces the default create-and-activate action, so the same form can be
* used to create a profile for an invitation - which must not switch the active user
* until the invitation has been moved onto it. Optional, so the existing call sites are
* untouched. */
@Composable
fun CreateProfile(submitting: Boolean = false, onSubmit: (displayName: String, shortDescr: String, image: String?) -> Unit) {
fun CreateProfile(chatModel: ChatModel, close: () -> Unit, submitting: Boolean = false, onSubmit: ((displayName: String, shortDescr: String, image: String?) -> Unit)? = null) {
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val keyboardState by getKeyboardState()
@@ -160,7 +164,15 @@ fun CreateProfile(submitting: Boolean = false, onSubmit: (displayName: String, s
disabled = submitting || !canCreateProfile(displayName.value) || !bioFitsLimit(shortDescr.value),
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
click = { onSubmit(displayName.value, shortDescr.value, profileImage.value) },
click = {
if (onSubmit != null) {
onSubmit(displayName.value, shortDescr.value, profileImage.value)
} else if (chatModel.localUserCreated.value == true) {
createProfileInProfiles(chatModel, displayName.value, shortDescr.value, profileImage.value, close)
} else {
createProfileInNoProfileSetup(displayName.value, profileImage.value, close)
}
},
)
SectionTextFooter(generalGetString(MR.strings.your_profile_is_stored_on_your_device))
SectionTextFooter(generalGetString(MR.strings.profile_is_only_shared_with_your_contacts))
@@ -359,7 +371,7 @@ fun createProfileForInvitation(rhId: Long?, onCreated: suspend (User) -> Unit) {
val modalManager = ModalManager.fullscreen
if (modalManager.isLastModalOpenNotClosing(ModalViewId.CONTEXT_USER_PICKER_NEW_PROFILE)) return
modalManager.showModalCloseable(id = ModalViewId.CONTEXT_USER_PICKER_NEW_PROFILE) { close ->
CreateProfile(submitting = chatModel.creatingProfileForInvitation.value) { displayName, shortDescr, image ->
CreateProfile(chatModel, close, submitting = chatModel.creatingProfileForInvitation.value) { displayName, shortDescr, image ->
if (chatModel.creatingProfileForInvitation.value) return@CreateProfile
chatModel.creatingProfileForInvitation.value = true
// On Main, like the pickers' own handlers: every call here suspends into IO, and the
@@ -408,17 +420,6 @@ fun createProfileForInvitation(rhId: Long?, onCreated: suspend (User) -> Unit) {
}
}
// The two ordinary "add a profile" paths, where the new profile becomes the active
// one. Creating one for an invitation takes neither, which is why the form itself
// no longer chooses.
fun createProfileFromForm(chatModel: ChatModel, displayName: String, shortDescr: String, image: String?, close: () -> Unit) {
if (chatModel.localUserCreated.value == true) {
createProfileInProfiles(chatModel, displayName, shortDescr, image, close)
} else {
createProfileInNoProfileSetup(displayName, image, close)
}
}
fun createProfileInNoProfileSetup(displayName: String, image: String? = null, close: () -> Unit) {
withBGApi {
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, image = image)) ?: return@withBGApi
@@ -30,7 +30,6 @@ import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.platform.*
import chat.simplex.common.views.CreateProfile
import chat.simplex.common.views.createProfileFromForm
import chat.simplex.common.views.localauth.VerticalDivider
import chat.simplex.common.views.remote.*
import chat.simplex.common.views.usersettings.*
@@ -260,9 +259,7 @@ fun UserPicker(
LaunchedEffect(Unit) {
userPickerState.value = AnimatedViewState.HIDING
}
CreateProfile { displayName, shortDescr, image ->
createProfileFromForm(chat.simplex.common.platform.chatModel, displayName, shortDescr, image, close)
}
CreateProfile(chat.simplex.common.platform.chatModel, close)
}
}
}
@@ -28,7 +28,6 @@ import chat.simplex.common.views.chatlist.UserProfilePickerItem
import chat.simplex.common.views.chatlist.UserProfileRow
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.CreateProfile
import chat.simplex.common.views.createProfileFromForm
import chat.simplex.common.views.database.*
import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.res.MR
@@ -50,9 +49,7 @@ fun UserProfilesView(m: ChatModel, search: MutableState<String>, profileHidden:
addUser = {
withAuth {
ModalManager.center.showModalCloseable { close ->
CreateProfile { displayName, shortDescr, image ->
createProfileFromForm(m, displayName, shortDescr, image, close)
}
CreateProfile(m, close)
}
}
},