From 649c104d295e9869617c01e95fee0fb5b575a803 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 4 May 2023 19:28:29 +0300 Subject: [PATCH] android: create address during onboarding (#2374) * android: create address during onboarding * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../java/chat/simplex/app/MainActivity.kt | 4 +- .../chat/simplex/app/views/WelcomeView.kt | 2 +- .../chat/simplex/app/views/helpers/Share.kt | 12 ++ .../views/onboarding/CreateSimpleXAddress.kt | 168 ++++++++++++++++++ .../app/views/onboarding/OnboardingView.kt | 5 +- .../app/views/usersettings/UserAddressView.kt | 36 +++- .../app/src/main/res/values/strings.xml | 10 +- 7 files changed, 223 insertions(+), 14 deletions(-) create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 5eec89ee5b..33fa3aef8e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -17,7 +17,6 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -500,7 +499,8 @@ fun MainPage( } onboarding == OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {} - onboarding == OnboardingStage.Step3_SetNotificationsMode -> SetNotificationsMode(chatModel) + onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel) + onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } ModalManager.shared.showInView() val invitation = chatModel.activeCallInvitation.value diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt index 1dc1dca0a7..e82e11d641 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt @@ -125,7 +125,7 @@ fun createProfile(chatModel: ChatModel, displayName: String, fullName: String, c chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) - chatModel.onboardingStage.value = OnboardingStage.Step3_SetNotificationsMode + chatModel.onboardingStage.value = OnboardingStage.Step3_CreateSimpleXAddress SimplexApp.context.chatModel.controller.ntfManager.createNtfChannelsMaybeShowAlert() } else { val users = chatModel.controller.listUsers() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt index 8c05a17ecc..015f05f0e6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Share.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.* import android.net.Uri import android.provider.MediaStore +import android.util.Log import android.webkit.MimeTypeMap import android.widget.Toast import androidx.activity.compose.ManagedActivityResultLauncher @@ -48,6 +49,17 @@ fun copyText(cxt: Context, text: String) { clipboard?.setPrimaryClip(ClipData.newPlainText("text", text)) } +fun sendEmail(context: Context, subject: String, body: CharSequence) { + val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) + emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject) + emailIntent.putExtra(Intent.EXTRA_TEXT, body) + try { + context.startActivity(emailIntent) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, "No activity was found for handling email intent") + } +} + @Composable fun rememberSaveFileLauncher(cxt: Context, ciFile: CIFile?): ManagedActivityResultLauncher = rememberLauncherForActivityResult( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt new file mode 100644 index 0000000000..41a180494e --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/CreateSimpleXAddress.kt @@ -0,0 +1,168 @@ +package chat.simplex.app.views.onboarding + +import SectionBottomSpacer +import android.util.Log +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import chat.simplex.app.R +import chat.simplex.app.TAG +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.UserContactLinkRec +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.QRCode + +@Composable +fun CreateSimpleXAddress(m: ChatModel) { + val context = LocalContext.current + var progressIndicator by remember { mutableStateOf(false) } + val userAddress = remember { m.userAddress } + + CreateSimpleXAddressLayout( + userAddress.value, + share = { address: String -> shareText(context, address) }, + sendEmail = { address -> + sendEmail( + context, + generalGetString(R.string.email_invite_subject), + generalGetString(R.string.email_invite_body).format(address.connReqContact) + ) + }, + createAddress = { + withApi { + progressIndicator = true + val connReqContact = m.controller.apiCreateUserAddress() + if (connReqContact != null) { + m.userAddress.value = UserContactLinkRec(connReqContact) + try { + val u = m.controller.apiSetProfileAddress(true) + if (u != null) { + m.updateUser(u) + } + } catch (e: Exception) { + Log.e(TAG, "CreateSimpleXAddress apiSetProfileAddress: ${e.stackTraceToString()}") + } + progressIndicator = false + } + } + }, + nextStep = { + m.onboardingStage.value = OnboardingStage.Step4_SetNotificationsMode + }, + ) + + if (progressIndicator) { + ProgressIndicator() + } +} + +@Composable +private fun CreateSimpleXAddressLayout( + userAddress: UserContactLinkRec?, + share: (String) -> Unit, + sendEmail: (UserContactLinkRec) -> Unit, + createAddress: () -> Unit, + nextStep: () -> Unit, +) { + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AppBarTitle(stringResource(R.string.simplex_address)) + + Spacer(Modifier.weight(1f)) + + if (userAddress != null) { + QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) + ShareAddressButton { share(userAddress.connReqContact) } + Spacer(Modifier.weight(1f)) + ShareViaEmailButton { sendEmail(userAddress) } + Spacer(Modifier.weight(1f)) + ContinueButton(nextStep) + } else { + CreateAddressButton(createAddress) + TextBelowButton(stringResource(R.string.your_contacts_will_see_it)) + Spacer(Modifier.weight(1f)) + SkipButton(nextStep) + } + SectionBottomSpacer() + } +} + +@Composable +private fun CreateAddressButton(onClick: () -> Unit) { + TextButton(onClick) { + Text(stringResource(R.string.create_simplex_address), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary) + } +} + +@Composable +fun ShareAddressButton(onClick: () -> Unit) { + SimpleButtonFrame(onClick) { + Icon( + painterResource(R.drawable.ic_share_filled), generalGetString(R.string.share_verb), tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(end = 8.dp).size(18.dp) + ) + Text(stringResource(R.string.share_verb), style = MaterialTheme.typography.caption, color = MaterialTheme.colors.primary) + } +} + +@Composable +fun ShareViaEmailButton(onClick: () -> Unit) { + SimpleButtonFrame(onClick) { + Icon( + painterResource(R.drawable.ic_mail), generalGetString(R.string.share_verb), tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(end = 8.dp).size(30.dp) + ) + Text(stringResource(R.string.invite_friends), style = MaterialTheme.typography.h6, color = MaterialTheme.colors.primary) + } +} + +@Composable +private fun ContinueButton(onClick: () -> Unit) { + SimpleButtonIconEnded(stringResource(R.string.continue_to_next_step), painterResource(R.drawable.ic_chevron_right), click = onClick) +} + +@Composable +private fun SkipButton(onClick: () -> Unit) { + SimpleButtonIconEnded(stringResource(R.string.dont_create_address), painterResource(R.drawable.ic_chevron_right), click = onClick) + TextBelowButton(stringResource(R.string.you_can_create_it_later)) +} + +@Composable +private fun TextBelowButton(text: String) { + Text( + text, + Modifier + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING * 3), + style = MaterialTheme.typography.subtitle1, + textAlign = TextAlign.Center, + ) +} + +@Composable +private fun ProgressIndicator() { + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + Modifier + .padding(horizontal = 2.dp) + .size(30.dp), + color = MaterialTheme.colors.secondary, + strokeWidth = 3.dp + ) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt index bd8e30186f..baff692020 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt @@ -1,9 +1,7 @@ package chat.simplex.app.views.onboarding -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -16,7 +14,8 @@ import kotlinx.coroutines.launch enum class OnboardingStage { Step1_SimpleXInfo, Step2_CreateProfile, - Step3_SetNotificationsMode, + Step3_CreateSimpleXAddress, + Step4_SetNotificationsMode, OnboardingComplete } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt index c2dd05a296..c9ce92f10b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -37,7 +36,7 @@ fun UserAddressView( shareViaProfile: Boolean = false, close: () -> Unit ) { - val cxt = LocalContext.current + val context = LocalContext.current val shareViaProfile = remember { mutableStateOf(shareViaProfile) } var progressIndicator by remember { mutableStateOf(false) } val onCloseHandler: MutableState<(close: () -> Unit) -> Unit> = remember { mutableStateOf({ _ -> }) } @@ -71,7 +70,7 @@ fun UserAddressView( chatModel.userAddress.value = UserContactLinkRec(connReqContact) AlertManager.shared.showAlertDialog( - title = generalGetString(R.string.delete_address_with_contacts_question), + title = generalGetString(R.string.share_address_with_contacts_question), text = generalGetString(R.string.add_address_to_your_profile), confirmText = generalGetString(R.string.share_verb), onConfirm = { @@ -95,7 +94,14 @@ fun UserAddressView( } } }, - share = { userAddress: String -> shareText(cxt, userAddress) }, + share = { userAddress: String -> shareText(context, userAddress) }, + sendEmail = { userAddress -> + sendEmail( + context, + generalGetString(R.string.email_invite_subject), + generalGetString(R.string.email_invite_body).format(userAddress.connReqContact) + ) + }, setProfileAddress = ::setProfileAddress, deleteAddress = { AlertManager.shared.showAlertDialog( @@ -125,7 +131,8 @@ fun UserAddressView( savedAAS.value = aas } } - }) + }, + ) } if (viaCreateLinkView) { @@ -163,6 +170,7 @@ private fun UserAddressLayout( createAddress: () -> Unit, learnMore: () -> Unit, share: (String) -> Unit, + sendEmail: (UserContactLinkRec) -> Unit, setProfileAddress: (Boolean) -> Unit, deleteAddress: () -> Unit, saveAas: (AutoAcceptState, MutableState) -> Unit, @@ -194,6 +202,7 @@ private fun UserAddressLayout( SectionView(stringResource(R.string.address_section_title).uppercase()) { QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f)) ShareAddressButton { share(userAddress.connReqContact) } + ShareViaEmailButton { sendEmail(userAddress) } ShareWithContactsButton(shareViaProfile, setProfileAddress) AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } LearnMoreButton(learnMore) @@ -241,6 +250,17 @@ private fun LearnMoreButton(onClick: () -> Unit) { ) } +@Composable +fun ShareViaEmailButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(R.drawable.ic_mail), + stringResource(R.string.invite_friends), + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + @Composable fun ShareWithContactsButton(shareViaProfile: MutableState, setProfileAddress: (Boolean) -> Unit) { PreferenceToggleWithIcon( @@ -416,7 +436,8 @@ fun PreviewUserAddressLayoutNoAddress() { setProfileAddress = { _ -> }, learnMore = {}, shareViaProfile = remember { mutableStateOf(false) }, - onCloseHandler = remember { mutableStateOf({}) } + onCloseHandler = remember { mutableStateOf({}) }, + sendEmail = {}, ) } } @@ -449,7 +470,8 @@ fun PreviewUserAddressLayoutAddressCreated() { setProfileAddress = { _ -> }, learnMore = {}, shareViaProfile = remember { mutableStateOf(false) }, - onCloseHandler = remember { mutableStateOf({}) } + onCloseHandler = remember { mutableStateOf({}) }, + sendEmail = {}, ) } } diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 2eef854303..edad948c4f 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -595,7 +595,6 @@ All your contacts will remain connected. All your contacts will remain connected. Profile update will be sent to your contacts. Share link - Share address with contacts? Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Create an address to let people connect with you. Create SimpleX address @@ -609,6 +608,15 @@ Save settings? Save auto-accept settings Delete address + Invite friends + Let\'s talk in SimpleX Chat + Hi!\nConnect to me via SimpleX Chat: %s + + + Continue + Don\'t create address + You can create it later + Your contacts in SimpleX will see it.\nYou can change it in Settings. Display name: