android, desktop: profile names (remove full name) (#3177)
* desktop, android: profile names (remove full name) * rename back * disallow spaces only in names * ios: disallow spaces only in names * changes
This commit is contained in:
parent
0d8558a6d0
commit
34e1e44338
@ -80,7 +80,7 @@ struct GroupProfileView: View {
|
|||||||
HStack(spacing: 20) {
|
HStack(spacing: 20) {
|
||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
Button("Save group profile") { saveProfile() }
|
Button("Save group profile") { saveProfile() }
|
||||||
.disabled(groupProfile.displayName == "" || !validNewProfileName())
|
.disabled(!canUpdateProfile())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
|
.frame(maxWidth: .infinity, minHeight: 120, alignment: .leading)
|
||||||
@ -134,6 +134,10 @@ struct GroupProfileView: View {
|
|||||||
.onTapGesture { hideKeyboard() }
|
.onTapGesture { hideKeyboard() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func canUpdateProfile() -> Bool {
|
||||||
|
groupProfile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName()
|
||||||
|
}
|
||||||
|
|
||||||
private func validNewProfileName() -> Bool {
|
private func validNewProfileName() -> Bool {
|
||||||
groupProfile.displayName == groupInfo.groupProfile.displayName
|
groupProfile.displayName == groupInfo.groupProfile.displayName
|
||||||
|| validDisplayName(groupProfile.displayName.trimmingCharacters(in: .whitespaces))
|
|| validDisplayName(groupProfile.displayName.trimmingCharacters(in: .whitespaces))
|
||||||
|
@ -179,7 +179,8 @@ struct AddGroupView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func canCreateProfile() -> Bool {
|
func canCreateProfile() -> Bool {
|
||||||
profile.displayName != "" && validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces))
|
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||||||
|
return name != "" && validDisplayName(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ struct UserProfile: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func canSaveProfile(_ user: User) -> Bool {
|
private func canSaveProfile(_ user: User) -> Bool {
|
||||||
profile.displayName != "" && validNewProfileName(user)
|
profile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveProfile() {
|
func saveProfile() {
|
||||||
|
@ -46,6 +46,7 @@ extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
|
|||||||
extern char *chat_parse_markdown(const char *str);
|
extern char *chat_parse_markdown(const char *str);
|
||||||
extern char *chat_parse_server(const char *str);
|
extern char *chat_parse_server(const char *str);
|
||||||
extern char *chat_password_hash(const char *pwd, const char *salt);
|
extern char *chat_password_hash(const char *pwd, const char *salt);
|
||||||
|
extern char *chat_valid_name(const char *name);
|
||||||
extern char *chat_write_file(const char *path, char *ptr, int length);
|
extern char *chat_write_file(const char *path, char *ptr, int length);
|
||||||
extern char *chat_read_file(const char *path, const char *key, const char *nonce);
|
extern char *chat_read_file(const char *path, const char *key, const char *nonce);
|
||||||
extern char *chat_encrypt_file(const char *from_path, const char *to_path);
|
extern char *chat_encrypt_file(const char *from_path, const char *to_path);
|
||||||
@ -121,6 +122,14 @@ Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, __unused
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_chat_simplex_common_platform_CoreKt_chatValidName(JNIEnv *env, jclass clazz, jstring name) {
|
||||||
|
const char *_name = (*env)->GetStringUTFChars(env, name, JNI_FALSE);
|
||||||
|
jstring res = (*env)->NewStringUTF(env, chat_valid_name(_name));
|
||||||
|
(*env)->ReleaseStringUTFChars(env, name, _name);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jstring path, jobject buffer) {
|
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jstring path, jobject buffer) {
|
||||||
const char *_path = (*env)->GetStringUTFChars(env, path, JNI_FALSE);
|
const char *_path = (*env)->GetStringUTFChars(env, path, JNI_FALSE);
|
||||||
|
@ -21,6 +21,7 @@ extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
|
|||||||
extern char *chat_parse_markdown(const char *str);
|
extern char *chat_parse_markdown(const char *str);
|
||||||
extern char *chat_parse_server(const char *str);
|
extern char *chat_parse_server(const char *str);
|
||||||
extern char *chat_password_hash(const char *pwd, const char *salt);
|
extern char *chat_password_hash(const char *pwd, const char *salt);
|
||||||
|
extern char *chat_valid_name(const char *name);
|
||||||
extern char *chat_write_file(const char *path, char *ptr, int length);
|
extern char *chat_write_file(const char *path, char *ptr, int length);
|
||||||
extern char *chat_read_file(const char *path, const char *key, const char *nonce);
|
extern char *chat_read_file(const char *path, const char *key, const char *nonce);
|
||||||
extern char *chat_encrypt_file(const char *from_path, const char *to_path);
|
extern char *chat_encrypt_file(const char *from_path, const char *to_path);
|
||||||
@ -75,7 +76,7 @@ Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, jclass cla
|
|||||||
jstring res = decode_to_utf8_string(env, chat_migrate_init(_dbPath, _dbKey, _confirm, &_ctrl));
|
jstring res = decode_to_utf8_string(env, chat_migrate_init(_dbPath, _dbKey, _confirm, &_ctrl));
|
||||||
(*env)->ReleaseStringUTFChars(env, dbPath, _dbPath);
|
(*env)->ReleaseStringUTFChars(env, dbPath, _dbPath);
|
||||||
(*env)->ReleaseStringUTFChars(env, dbKey, _dbKey);
|
(*env)->ReleaseStringUTFChars(env, dbKey, _dbKey);
|
||||||
(*env)->ReleaseStringUTFChars(env, dbKey, _confirm);
|
(*env)->ReleaseStringUTFChars(env, confirm, _confirm);
|
||||||
|
|
||||||
// Creating array of Object's (boxed values can be passed, eg. Long instead of long)
|
// Creating array of Object's (boxed values can be passed, eg. Long instead of long)
|
||||||
jobjectArray ret = (jobjectArray)(*env)->NewObjectArray(env, 2, (*env)->FindClass(env, "java/lang/Object"), NULL);
|
jobjectArray ret = (jobjectArray)(*env)->NewObjectArray(env, 2, (*env)->FindClass(env, "java/lang/Object"), NULL);
|
||||||
@ -133,6 +134,14 @@ Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, jclass cl
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_chat_simplex_common_platform_CoreKt_chatValidName(JNIEnv *env, jclass clazz, jstring name) {
|
||||||
|
const char *_name = encode_to_utf8_chars(env, name);
|
||||||
|
jstring res = decode_to_utf8_string(env, chat_valid_name(_name));
|
||||||
|
(*env)->ReleaseStringUTFChars(env, name, _name);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jstring path, jobject buffer) {
|
Java_chat_simplex_common_platform_CoreKt_chatWriteFile(JNIEnv *env, jclass clazz, jstring path, jobject buffer) {
|
||||||
const char *_path = encode_to_utf8_chars(env, path);
|
const char *_path = encode_to_utf8_chars(env, path);
|
||||||
|
@ -17,6 +17,7 @@ import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
|
|||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.CreateFirstProfile
|
||||||
import chat.simplex.common.views.helpers.SimpleButton
|
import chat.simplex.common.views.helpers.SimpleButton
|
||||||
import chat.simplex.common.views.SplashView
|
import chat.simplex.common.views.SplashView
|
||||||
import chat.simplex.common.views.call.ActiveCallView
|
import chat.simplex.common.views.call.ActiveCallView
|
||||||
@ -135,7 +136,7 @@ fun MainScreen() {
|
|||||||
ModalManager.fullscreen.showInView()
|
ModalManager.fullscreen.showInView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {}
|
onboarding == OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
|
||||||
onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
||||||
onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel)
|
onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel)
|
||||||
onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
|
onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
|
||||||
|
@ -20,6 +20,7 @@ external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String
|
|||||||
external fun chatParseMarkdown(str: String): String
|
external fun chatParseMarkdown(str: String): String
|
||||||
external fun chatParseServer(str: String): String
|
external fun chatParseServer(str: String): String
|
||||||
external fun chatPasswordHash(pwd: String, salt: String): String
|
external fun chatPasswordHash(pwd: String, salt: String): String
|
||||||
|
external fun chatValidName(name: String): String
|
||||||
external fun chatWriteFile(path: String, buffer: ByteBuffer): String
|
external fun chatWriteFile(path: String, buffer: ByteBuffer): String
|
||||||
external fun chatReadFile(path: String, key: String, nonce: String): Array<Any>
|
external fun chatReadFile(path: String, key: String, nonce: String): Array<Any>
|
||||||
external fun chatEncryptFile(fromPath: String, toPath: String): String
|
external fun chatEncryptFile(fromPath: String, toPath: String): String
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package chat.simplex.common.views
|
package chat.simplex.common.views
|
||||||
|
|
||||||
|
import SectionTextFooter
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.MaterialTheme.colors
|
import androidx.compose.material.MaterialTheme.colors
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@ -18,30 +18,100 @@ import dev.icerock.moko.resources.compose.painterResource
|
|||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
||||||
import androidx.compose.ui.text.style.*
|
import androidx.compose.ui.text.style.*
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.common.model.Profile
|
import chat.simplex.common.model.Profile
|
||||||
import chat.simplex.common.platform.appPlatform
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.platform.navigationBarsWithImePadding
|
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
import chat.simplex.common.views.onboarding.*
|
||||||
import chat.simplex.common.views.onboarding.ReadableText
|
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
fun isValidDisplayName(name: String) : Boolean {
|
@Composable
|
||||||
return (name.firstOrNull { it.isWhitespace() }) == null && !name.startsWith("@") && !name.startsWith("#")
|
fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
val keyboardState by getKeyboardState()
|
||||||
|
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||||
|
|
||||||
|
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(top = 20.dp)
|
||||||
|
) {
|
||||||
|
val displayName = rememberSaveable { mutableStateOf("") }
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
|
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING)
|
||||||
|
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Text(
|
||||||
|
stringResource(MR.strings.display_name),
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
val name = displayName.value.trim()
|
||||||
|
val validName = mkValidName(name)
|
||||||
|
Spacer(Modifier.height(20.dp))
|
||||||
|
if (name != validName) {
|
||||||
|
IconButton({ showInvalidNameAlert(mkValidName(displayName.value), displayName) }, Modifier.size(20.dp)) {
|
||||||
|
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProfileNameField(displayName, "", { it.trim() == mkValidName(it) }, focusRequester)
|
||||||
|
}
|
||||||
|
SettingsActionItem(
|
||||||
|
painterResource(MR.images.ic_check),
|
||||||
|
stringResource(MR.strings.create_another_profile_button),
|
||||||
|
disabled = !canCreateProfile(displayName.value),
|
||||||
|
textColor = MaterialTheme.colors.primary,
|
||||||
|
iconColor = MaterialTheme.colors.primary,
|
||||||
|
click = { createProfileInProfiles(chatModel, displayName.value, close) },
|
||||||
|
)
|
||||||
|
SectionTextFooter(generalGetString(MR.strings.your_profile_is_stored_on_your_device))
|
||||||
|
SectionTextFooter(generalGetString(MR.strings.profile_is_only_shared_with_your_contacts))
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(300)
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (savedKeyboardState != keyboardState) {
|
||||||
|
LaunchedEffect(keyboardState) {
|
||||||
|
scope.launch {
|
||||||
|
savedKeyboardState = keyboardState
|
||||||
|
scrollState.animateScrollTo(scrollState.maxValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
|
fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
val keyboardState by getKeyboardState()
|
||||||
|
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||||
|
|
||||||
|
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(top = 20.dp)
|
||||||
|
) {
|
||||||
val displayName = rememberSaveable { mutableStateOf("") }
|
val displayName = rememberSaveable { mutableStateOf("") }
|
||||||
val fullName = rememberSaveable { mutableStateOf("") }
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@ -56,77 +126,52 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
})*/
|
})*/
|
||||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING)
|
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING)
|
||||||
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1)
|
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
|
||||||
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1)
|
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||||
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.display_name),
|
stringResource(MR.strings.display_name),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
if (!isValidDisplayName(displayName.value)) {
|
val name = displayName.value.trim()
|
||||||
Text(
|
val validName = mkValidName(name)
|
||||||
stringResource(MR.strings.no_spaces),
|
Spacer(Modifier.height(20.dp))
|
||||||
fontSize = 16.sp,
|
if (name != validName) {
|
||||||
color = Color.Red
|
IconButton({ showInvalidNameAlert(mkValidName(displayName.value), displayName) }, Modifier.size(20.dp)) {
|
||||||
)
|
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester)
|
}
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
ProfileNameField(displayName, "", { it.trim() == mkValidName(it) }, focusRequester)
|
||||||
Text(
|
|
||||||
stringResource(MR.strings.full_name_optional__prompt),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
|
||||||
)
|
|
||||||
ProfileNameField(fullName, "")
|
|
||||||
}
|
}
|
||||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||||
Row {
|
OnboardingButtons(displayName, close)
|
||||||
if (chatModel.users.isEmpty()) {
|
|
||||||
SimpleButtonDecorated(
|
|
||||||
text = stringResource(MR.strings.about_simplex),
|
|
||||||
icon = painterResource(MR.images.ic_arrow_back_ios_new),
|
|
||||||
textDecoration = TextDecoration.None,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
) { chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }
|
|
||||||
}
|
|
||||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
|
||||||
val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
|
|
||||||
val createModifier: Modifier
|
|
||||||
val createColor: Color
|
|
||||||
if (enabled) {
|
|
||||||
createModifier = Modifier.clickable {
|
|
||||||
if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) {
|
|
||||||
createProfileInProfiles(chatModel, displayName.value, fullName.value, close)
|
|
||||||
} else {
|
|
||||||
createProfileOnboarding(chatModel, displayName.value, fullName.value, close)
|
|
||||||
}
|
|
||||||
}.padding(8.dp)
|
|
||||||
createColor = MaterialTheme.colors.primary
|
|
||||||
} else {
|
|
||||||
createModifier = Modifier.padding(8.dp)
|
|
||||||
createColor = MaterialTheme.colors.secondary
|
|
||||||
}
|
|
||||||
Surface(shape = RoundedCornerShape(20.dp), color = Color.Transparent) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) {
|
|
||||||
Text(stringResource(MR.strings.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium)
|
|
||||||
Icon(painterResource(MR.images.ic_arrow_forward_ios), stringResource(MR.strings.create_profile_button), tint = createColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
delay(300)
|
delay(300)
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
setLastVersionDefault(chatModel)
|
||||||
|
}
|
||||||
|
if (savedKeyboardState != keyboardState) {
|
||||||
|
LaunchedEffect(keyboardState) {
|
||||||
|
scope.launch {
|
||||||
|
savedKeyboardState = keyboardState
|
||||||
|
scrollState.animateScrollTo(scrollState.maxValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) {
|
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||||
withApi {
|
withApi {
|
||||||
val user = chatModel.controller.apiCreateActiveUser(
|
val user = chatModel.controller.apiCreateActiveUser(
|
||||||
Profile(displayName, fullName, null)
|
Profile(displayName.trim(), "", null)
|
||||||
) ?: return@withApi
|
) ?: return@withApi
|
||||||
chatModel.currentUser.value = user
|
chatModel.currentUser.value = user
|
||||||
if (chatModel.users.isEmpty()) {
|
if (chatModel.users.isEmpty()) {
|
||||||
@ -142,10 +187,10 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, fullName:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createProfileOnboarding(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) {
|
fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||||
withApi {
|
withApi {
|
||||||
chatModel.controller.apiCreateActiveUser(
|
chatModel.controller.apiCreateActiveUser(
|
||||||
Profile(displayName, fullName, null)
|
Profile(displayName.trim(), "", null)
|
||||||
) ?: return@withApi
|
) ?: return@withApi
|
||||||
val onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
val onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
||||||
if (chatModel.users.isEmpty()) {
|
if (chatModel.users.isEmpty()) {
|
||||||
@ -163,6 +208,28 @@ fun createProfileOnboarding(chatModel: ChatModel, displayName: String, fullName:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OnboardingButtons(displayName: MutableState<String>, close: () -> Unit) {
|
||||||
|
Row {
|
||||||
|
SimpleButtonDecorated(
|
||||||
|
text = stringResource(MR.strings.about_simplex),
|
||||||
|
icon = painterResource(MR.images.ic_arrow_back_ios_new),
|
||||||
|
textDecoration = TextDecoration.None,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
) { chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }
|
||||||
|
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||||
|
val enabled = canCreateProfile(displayName.value)
|
||||||
|
val createModifier: Modifier = Modifier.clickable(enabled) { createProfileOnboarding(chatModel, displayName.value, close) }.padding(8.dp)
|
||||||
|
val createColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||||
|
Surface(shape = RoundedCornerShape(20.dp), color = Color.Transparent) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) {
|
||||||
|
Text(stringResource(MR.strings.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium)
|
||||||
|
Icon(painterResource(MR.images.ic_arrow_forward_ios), stringResource(MR.strings.create_profile_button), tint = createColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isValid: (String) -> Boolean = { true }, focusRequester: FocusRequester? = null) {
|
fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isValid: (String) -> Boolean = { true }, focusRequester: FocusRequester? = null) {
|
||||||
var valid by rememberSaveable { mutableStateOf(true) }
|
var valid by rememberSaveable { mutableStateOf(true) }
|
||||||
@ -195,10 +262,6 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
|
|||||||
onValueChange = { name.value = it },
|
onValueChange = { name.value = it },
|
||||||
modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester),
|
modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester),
|
||||||
textStyle = TextStyle(fontSize = 18.sp, color = colors.onBackground),
|
textStyle = TextStyle(fontSize = 18.sp, color = colors.onBackground),
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
capitalization = KeyboardCapitalization.None,
|
|
||||||
autoCorrect = false
|
|
||||||
),
|
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
cursorBrush = SolidColor(MaterialTheme.colors.secondary)
|
cursorBrush = SolidColor(MaterialTheme.colors.secondary)
|
||||||
)
|
)
|
||||||
@ -211,3 +274,28 @@ fun ProfileNameField(name: MutableState<String>, placeholder: String = "", isVal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun canCreateProfile(displayName: String): Boolean {
|
||||||
|
val name = displayName.trim()
|
||||||
|
return name.isNotEmpty() && mkValidName(name) == name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showInvalidNameAlert(name: String, displayName: MutableState<String>) {
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
title = generalGetString(MR.strings.invalid_name),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AlertManager.shared.showAlertDialog(
|
||||||
|
title = generalGetString(MR.strings.invalid_name),
|
||||||
|
text = generalGetString(MR.strings.correct_name_to).format(name),
|
||||||
|
onConfirm = {
|
||||||
|
displayName.value = name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isValidDisplayName(name: String) : Boolean = mkValidName(name.trim()) == name
|
||||||
|
|
||||||
|
fun mkValidName(s: String): String = chatValidName(s)
|
||||||
|
@ -19,12 +19,12 @@ import androidx.compose.ui.unit.sp
|
|||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.ProfileNameField
|
import chat.simplex.common.views.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.isValidDisplayName
|
|
||||||
import chat.simplex.common.views.onboarding.ReadableText
|
import chat.simplex.common.views.onboarding.ReadableText
|
||||||
import chat.simplex.common.views.usersettings.*
|
import chat.simplex.common.views.usersettings.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@ -65,13 +65,13 @@ fun GroupProfileLayout(
|
|||||||
fullName.value == groupProfile.fullName &&
|
fullName.value == groupProfile.fullName &&
|
||||||
groupProfile.image == profileImage.value
|
groupProfile.image == profileImage.value
|
||||||
val closeWithAlert = {
|
val closeWithAlert = {
|
||||||
if (dataUnchanged || !(displayName.value.isNotEmpty() && isValidDisplayName(displayName.value))) {
|
if (dataUnchanged || !canUpdateProfile(displayName.value, groupProfile)) {
|
||||||
close()
|
close()
|
||||||
} else {
|
} else {
|
||||||
showUnsavedChangesAlert({
|
showUnsavedChangesAlert({
|
||||||
saveProfile(
|
saveProfile(
|
||||||
groupProfile.copy(
|
groupProfile.copy(
|
||||||
displayName = displayName.value,
|
displayName = displayName.value.trim(),
|
||||||
fullName = fullName.value,
|
fullName = fullName.value,
|
||||||
image = profileImage.value
|
image = profileImage.value
|
||||||
)
|
)
|
||||||
@ -125,16 +125,15 @@ fun GroupProfileLayout(
|
|||||||
stringResource(MR.strings.group_display_name_field),
|
stringResource(MR.strings.group_display_name_field),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
if (!isValidDisplayName(displayName.value)) {
|
if (!isValidNewProfileName(displayName.value, groupProfile)) {
|
||||||
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
|
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
|
||||||
Text(
|
IconButton({ showInvalidNameAlert(mkValidName(displayName.value), displayName) }, Modifier.size(20.dp)) {
|
||||||
stringResource(MR.strings.no_spaces),
|
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
|
||||||
fontSize = 16.sp,
|
|
||||||
color = Color.Red
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester)
|
}
|
||||||
|
ProfileNameField(displayName, "", { isValidNewProfileName(it, groupProfile) }, focusRequester)
|
||||||
|
if (groupProfile.fullName.isNotEmpty() && groupProfile.fullName != groupProfile.displayName) {
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.group_full_name_field),
|
stringResource(MR.strings.group_full_name_field),
|
||||||
@ -142,15 +141,16 @@ fun GroupProfileLayout(
|
|||||||
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
||||||
)
|
)
|
||||||
ProfileNameField(fullName)
|
ProfileNameField(fullName)
|
||||||
|
}
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||||
val enabled = !dataUnchanged && displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
|
val enabled = !dataUnchanged && canUpdateProfile(displayName.value, groupProfile)
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.save_group_profile),
|
stringResource(MR.strings.save_group_profile),
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
saveProfile(
|
saveProfile(
|
||||||
groupProfile.copy(
|
groupProfile.copy(
|
||||||
displayName = displayName.value,
|
displayName = displayName.value.trim(),
|
||||||
fullName = fullName.value,
|
fullName = fullName.value,
|
||||||
image = profileImage.value
|
image = profileImage.value
|
||||||
)
|
)
|
||||||
@ -178,6 +178,12 @@ fun GroupProfileLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean =
|
||||||
|
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile)
|
||||||
|
|
||||||
|
private fun isValidNewProfileName(displayName: String, groupProfile: GroupProfile): Boolean =
|
||||||
|
displayName == groupProfile.displayName || isValidDisplayName(displayName.trim())
|
||||||
|
|
||||||
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
|
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
|
||||||
AlertManager.shared.showAlertDialogStacked(
|
AlertManager.shared.showAlertDialogStacked(
|
||||||
title = generalGetString(MR.strings.save_preferences_question),
|
title = generalGetString(MR.strings.save_preferences_question),
|
||||||
|
@ -19,15 +19,14 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.ProfileNameField
|
|
||||||
import chat.simplex.common.views.chat.group.AddGroupMembersView
|
import chat.simplex.common.views.chat.group.AddGroupMembersView
|
||||||
import chat.simplex.common.views.chatlist.setGroupMembers
|
import chat.simplex.common.views.chatlist.setGroupMembers
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.isValidDisplayName
|
|
||||||
import chat.simplex.common.views.onboarding.ReadableText
|
import chat.simplex.common.views.onboarding.ReadableText
|
||||||
import chat.simplex.common.views.usersettings.DeleteImageButton
|
import chat.simplex.common.views.usersettings.DeleteImageButton
|
||||||
import chat.simplex.common.views.usersettings.EditImageButton
|
import chat.simplex.common.views.usersettings.EditImageButton
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -60,7 +59,6 @@ fun AddGroupLayout(createGroup: (GroupProfile) -> Unit, close: () -> Unit) {
|
|||||||
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val displayName = rememberSaveable { mutableStateOf("") }
|
val displayName = rememberSaveable { mutableStateOf("") }
|
||||||
val fullName = rememberSaveable { mutableStateOf("") }
|
|
||||||
val chosenImage = rememberSaveable { mutableStateOf<URI?>(null) }
|
val chosenImage = rememberSaveable { mutableStateOf<URI?>(null) }
|
||||||
val profileImage = rememberSaveable { mutableStateOf<String?>(null) }
|
val profileImage = rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
@ -110,31 +108,22 @@ fun AddGroupLayout(createGroup: (GroupProfile) -> Unit, close: () -> Unit) {
|
|||||||
stringResource(MR.strings.group_display_name_field),
|
stringResource(MR.strings.group_display_name_field),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
if (!isValidDisplayName(displayName.value)) {
|
if (!isValidDisplayName(displayName.value.trim())) {
|
||||||
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
|
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
|
||||||
Text(
|
IconButton({ showInvalidNameAlert(mkValidName(displayName.value.trim()), displayName) }, Modifier.size(20.dp)) {
|
||||||
stringResource(MR.strings.no_spaces),
|
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
|
||||||
fontSize = 16.sp,
|
|
||||||
color = Color.Red
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester)
|
}
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
ProfileNameField(displayName, "", { isValidDisplayName(it.trim()) }, focusRequester)
|
||||||
Text(
|
|
||||||
stringResource(MR.strings.group_full_name_field),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
|
||||||
)
|
|
||||||
ProfileNameField(fullName, "")
|
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
|
val enabled = canCreateProfile(displayName.value)
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
CreateGroupButton(MaterialTheme.colors.primary, Modifier
|
CreateGroupButton(MaterialTheme.colors.primary, Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
createGroup(GroupProfile(
|
createGroup(GroupProfile(
|
||||||
displayName = displayName.value,
|
displayName = displayName.value.trim(),
|
||||||
fullName = fullName.value,
|
fullName = "",
|
||||||
image = profileImage.value
|
image = profileImage.value
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -167,6 +156,8 @@ fun CreateGroupButton(color: Color, modifier: Modifier) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canCreateProfile(displayName: String): Boolean = displayName.trim().isNotEmpty() && isValidDisplayName(displayName.trim())
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewAddGroupLayout() {
|
fun PreviewAddGroupLayout() {
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
package chat.simplex.common.views.onboarding
|
package chat.simplex.common.views.onboarding
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import chat.simplex.common.model.ChatModel
|
|
||||||
import chat.simplex.common.platform.ProvideWindowInsets
|
|
||||||
import chat.simplex.common.views.CreateProfilePanel
|
|
||||||
import chat.simplex.common.platform.getKeyboardState
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
enum class OnboardingStage {
|
enum class OnboardingStage {
|
||||||
Step1_SimpleXInfo,
|
Step1_SimpleXInfo,
|
||||||
Step2_CreateProfile,
|
Step2_CreateProfile,
|
||||||
@ -19,32 +8,3 @@ enum class OnboardingStage {
|
|||||||
Step4_SetNotificationsMode,
|
Step4_SetNotificationsMode,
|
||||||
OnboardingComplete
|
OnboardingComplete
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
val keyboardState by getKeyboardState()
|
|
||||||
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
|
||||||
|
|
||||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(top = 20.dp)
|
|
||||||
) {
|
|
||||||
CreateProfilePanel(chatModel, close)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
setLastVersionDefault(chatModel)
|
|
||||||
}
|
|
||||||
if (savedKeyboardState != keyboardState) {
|
|
||||||
LaunchedEffect(keyboardState) {
|
|
||||||
scope.launch {
|
|
||||||
savedKeyboardState = keyboardState
|
|
||||||
scrollState.animateScrollTo(scrollState.maxValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -364,7 +364,7 @@ fun AppVersionItem(showVersion: () -> Unit) {
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
if (profileOf.fullName.isNotEmpty()) {
|
if (profileOf.fullName.isNotEmpty() && profileOf.fullName != profileOf.displayName) {
|
||||||
Text(
|
Text(
|
||||||
profileOf.fullName,
|
profileOf.fullName,
|
||||||
Modifier.padding(vertical = 5.dp),
|
Modifier.padding(vertical = 5.dp),
|
||||||
|
@ -17,14 +17,12 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.ProfileNameField
|
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.isValidDisplayName
|
|
||||||
import chat.simplex.common.views.onboarding.ReadableText
|
import chat.simplex.common.views.onboarding.ReadableText
|
||||||
import chat.simplex.common.model.ChatModel
|
|
||||||
import chat.simplex.common.model.Profile
|
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@ -39,7 +37,7 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
close,
|
close,
|
||||||
saveProfile = { displayName, fullName, image ->
|
saveProfile = { displayName, fullName, image ->
|
||||||
withApi {
|
withApi {
|
||||||
val updated = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName, fullName = fullName, image = image))
|
val updated = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName.trim(), fullName = fullName, image = image))
|
||||||
if (updated != null) {
|
if (updated != null) {
|
||||||
val (newProfile, _) = updated
|
val (newProfile, _) = updated
|
||||||
chatModel.updateCurrentUser(newProfile)
|
chatModel.updateCurrentUser(newProfile)
|
||||||
@ -89,7 +87,7 @@ fun UserProfileLayout(
|
|||||||
profile.image == profileImage.value
|
profile.image == profileImage.value
|
||||||
|
|
||||||
val closeWithAlert = {
|
val closeWithAlert = {
|
||||||
if (dataUnchanged || !(displayName.value.isNotEmpty() && isValidDisplayName(displayName.value))) {
|
if (dataUnchanged || !canSaveProfile(displayName.value, profile)) {
|
||||||
close()
|
close()
|
||||||
} else {
|
} else {
|
||||||
showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, profileImage.value) }, close)
|
showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, profileImage.value) }, close)
|
||||||
@ -128,16 +126,15 @@ fun UserProfileLayout(
|
|||||||
stringResource(MR.strings.display_name__field),
|
stringResource(MR.strings.display_name__field),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
if (!isValidDisplayName(displayName.value)) {
|
if (!isValidNewProfileName(displayName.value, profile)) {
|
||||||
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
|
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
||||||
Text(
|
IconButton({ showInvalidNameAlert(mkValidName(displayName.value), displayName) }, Modifier.size(20.dp)) {
|
||||||
stringResource(MR.strings.no_spaces),
|
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
|
||||||
fontSize = 16.sp,
|
|
||||||
color = Color.Red
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester)
|
}
|
||||||
|
ProfileNameField(displayName, "", { isValidNewProfileName(it, profile) }, focusRequester)
|
||||||
|
if (showFullName(profile)) {
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.full_name__field),
|
stringResource(MR.strings.full_name__field),
|
||||||
@ -145,19 +142,11 @@ fun UserProfileLayout(
|
|||||||
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
|
||||||
)
|
)
|
||||||
ProfileNameField(fullName)
|
ProfileNameField(fullName)
|
||||||
|
|
||||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
|
||||||
val enabled = !dataUnchanged && displayName.value.isNotEmpty() && isValidDisplayName(displayName.value)
|
|
||||||
val saveModifier: Modifier
|
|
||||||
val saveColor: Color
|
|
||||||
if (enabled) {
|
|
||||||
saveModifier = Modifier
|
|
||||||
.clickable { saveProfile(displayName.value, fullName.value, profileImage.value) }
|
|
||||||
saveColor = MaterialTheme.colors.primary
|
|
||||||
} else {
|
|
||||||
saveModifier = Modifier
|
|
||||||
saveColor = MaterialTheme.colors.secondary
|
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||||
|
val enabled = !dataUnchanged && canSaveProfile(displayName.value, profile)
|
||||||
|
val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, profileImage.value) }
|
||||||
|
val saveColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||||
Text(
|
Text(
|
||||||
stringResource(MR.strings.save_and_notify_contacts),
|
stringResource(MR.strings.save_and_notify_contacts),
|
||||||
modifier = saveModifier,
|
modifier = saveModifier,
|
||||||
@ -216,6 +205,15 @@ private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isValidNewProfileName(displayName: String, profile: Profile): Boolean =
|
||||||
|
displayName == profile.displayName || isValidDisplayName(displayName.trim())
|
||||||
|
|
||||||
|
private fun showFullName(profile: Profile): Boolean =
|
||||||
|
profile.fullName.isNotEmpty() && profile.fullName != profile.displayName
|
||||||
|
|
||||||
|
private fun canSaveProfile(displayName: String, profile: Profile): Boolean =
|
||||||
|
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, profile)
|
||||||
|
|
||||||
@Preview/*(
|
@Preview/*(
|
||||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||||
showBackground = true,
|
showBackground = true,
|
||||||
|
@ -28,9 +28,8 @@ import chat.simplex.common.views.chatlist.UserProfilePickerItem
|
|||||||
import chat.simplex.common.views.chatlist.UserProfileRow
|
import chat.simplex.common.views.chatlist.UserProfileRow
|
||||||
import chat.simplex.common.views.database.PassphraseField
|
import chat.simplex.common.views.database.PassphraseField
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.CreateProfile
|
|
||||||
import chat.simplex.common.model.*
|
|
||||||
import chat.simplex.common.platform.appPlatform
|
import chat.simplex.common.platform.appPlatform
|
||||||
|
import chat.simplex.common.views.CreateProfile
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -675,7 +675,7 @@
|
|||||||
<string name="your_contacts_will_see_it">Your contacts in SimpleX will see it.\nYou can change it in Settings.</string>
|
<string name="your_contacts_will_see_it">Your contacts in SimpleX will see it.\nYou can change it in Settings.</string>
|
||||||
|
|
||||||
<!-- User profile details - UserProfileView.kt -->
|
<!-- User profile details - UserProfileView.kt -->
|
||||||
<string name="display_name__field">Display name:</string>
|
<string name="display_name__field">Profile name:</string>
|
||||||
<string name="full_name__field">Full name:</string>
|
<string name="full_name__field">Full name:</string>
|
||||||
<string name="your_current_profile">Your current profile</string>
|
<string name="your_current_profile">Your current profile</string>
|
||||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.</string>
|
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.</string>
|
||||||
@ -703,11 +703,12 @@
|
|||||||
<string name="create_profile">Create profile</string>
|
<string name="create_profile">Create profile</string>
|
||||||
<string name="your_profile_is_stored_on_your_device">Your profile, contacts and delivered messages are stored on your device.</string>
|
<string name="your_profile_is_stored_on_your_device">Your profile, contacts and delivered messages are stored on your device.</string>
|
||||||
<string name="profile_is_only_shared_with_your_contacts">The profile is only shared with your contacts.</string>
|
<string name="profile_is_only_shared_with_your_contacts">The profile is only shared with your contacts.</string>
|
||||||
<string name="no_spaces">No spaces!</string>
|
|
||||||
<string name="display_name_cannot_contain_whitespace">Display name cannot contain whitespace.</string>
|
<string name="display_name_cannot_contain_whitespace">Display name cannot contain whitespace.</string>
|
||||||
<string name="display_name">Display Name</string>
|
<string name="display_name">Enter your name:</string>
|
||||||
<string name="full_name_optional__prompt">Full Name (optional)</string>
|
|
||||||
<string name="create_profile_button">Create</string>
|
<string name="create_profile_button">Create</string>
|
||||||
|
<string name="create_another_profile_button">Create profile</string>
|
||||||
|
<string name="invalid_name">Invalid name!</string>
|
||||||
|
<string name="correct_name_to">Correct name to %s?</string>
|
||||||
<string name="about_simplex">About SimpleX</string>
|
<string name="about_simplex">About SimpleX</string>
|
||||||
|
|
||||||
<!-- markdown demo - MarkdownHelpView.kt -->
|
<!-- markdown demo - MarkdownHelpView.kt -->
|
||||||
@ -1290,7 +1291,7 @@
|
|||||||
<!-- AddGroupView.kt -->
|
<!-- AddGroupView.kt -->
|
||||||
<string name="create_secret_group_title">Create secret group</string>
|
<string name="create_secret_group_title">Create secret group</string>
|
||||||
<string name="group_is_decentralized">The group is fully decentralized – it is visible only to the members.</string>
|
<string name="group_is_decentralized">The group is fully decentralized – it is visible only to the members.</string>
|
||||||
<string name="group_display_name_field">Group display name:</string>
|
<string name="group_display_name_field">Enter group name:</string>
|
||||||
<string name="group_full_name_field">Group full name:</string>
|
<string name="group_full_name_field">Group full name:</string>
|
||||||
<string name="group_main_profile_sent">Your chat profile will be sent to group members</string>
|
<string name="group_main_profile_sent">Your chat profile will be sent to group members</string>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user