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 d1b106b29f..4be64a5c68 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 @@ -9,25 +9,27 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.lifecycle.AndroidViewModel import androidx.work.* -import chat.simplex.app.model.* +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.NtfManager import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.SplashView -import chat.simplex.app.views.WelcomeView import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.ChatListView import chat.simplex.app.views.chatlist.openChat import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.connectViaUri import chat.simplex.app.views.newchat.withUriAction +import chat.simplex.app.views.onboarding.* import java.util.concurrent.TimeUnit + //import kotlinx.serialization.decodeFromString class MainActivity: ComponentActivity() { @@ -83,12 +85,18 @@ class SimplexViewModel(application: Application): AndroidViewModel(application) @Composable fun MainPage(chatModel: ChatModel) { Box { - when (chatModel.userCreated.value) { - null -> SplashView() - false -> WelcomeView(chatModel) - true -> + val onboarding = chatModel.onboardingStage.value + val userCreated = chatModel.userCreated.value + when { + onboarding == null || userCreated == null -> SplashView() + onboarding == OnboardingStage.OnboardingComplete && userCreated -> if (chatModel.chatId.value == null) ChatListView(chatModel) else ChatView(chatModel) + onboarding == OnboardingStage.Step1_SimpleXInfo -> + Box(Modifier.padding(horizontal = 20.dp)) { + SimpleXInfo(chatModel, onboarding = true) + } + onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) } ModalManager.shared.showInView() AlertManager.shared.showInView() diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index 770cb0c643..e560e98053 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.* import chat.simplex.app.model.* import chat.simplex.app.views.helpers.getFilesDirectory import chat.simplex.app.views.helpers.withApi +import chat.simplex.app.views.onboarding.OnboardingStage import java.io.BufferedReader import java.io.InputStreamReader import java.util.* @@ -46,7 +47,9 @@ class SimplexApp: Application(), LifecycleEventObserver { ProcessLifecycleOwner.get().lifecycle.addObserver(this) withApi { val user = chatController.apiGetActiveUser() - if (user != null) { + if (user == null) { + chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo + } else { chatController.startChat(user) SimplexService.start(applicationContext) chatController.showBackgroundServiceNotice() diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt index 34907f9a48..94ae6a7371 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt @@ -8,6 +8,7 @@ import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.work.* import chat.simplex.app.views.helpers.withApi +import chat.simplex.app.views.onboarding.OnboardingStage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -65,7 +66,9 @@ class SimplexService: Service() { withApi { try { val user = chatController.apiGetActiveUser() - if (user != null) { + if (user == null) { + chatController.chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo + } else { Log.w(TAG, "Starting foreground service") chatController.startChat(user) chatController.startReceiver() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index acf8469c83..1e928074d0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -12,6 +12,7 @@ import chat.simplex.app.ui.theme.SecretColor import chat.simplex.app.ui.theme.SimplexBlue import chat.simplex.app.views.call.* import chat.simplex.app.views.helpers.generalGetString +import chat.simplex.app.views.onboarding.OnboardingStage import kotlinx.datetime.* import kotlinx.serialization.* import kotlinx.serialization.descriptors.* @@ -20,6 +21,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* class ChatModel(val controller: ChatController) { + val onboardingStage = mutableStateOf(null) val currentUser = mutableStateOf(null) val userCreated = mutableStateOf(null) val chats = mutableStateListOf() diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index c34d68da19..9f2e2835e8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Bolt -import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -18,6 +17,7 @@ import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.views.call.* import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.onboarding.OnboardingStage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.datetime.Clock @@ -49,6 +49,7 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt chatModel.chats.addAll(chats) chatModel.currentUser.value = user chatModel.userCreated.value = true + chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete Log.d(TAG, "started chat") } catch(e: Error) { Log.e(TAG, "failed starting chat $e") @@ -949,7 +950,9 @@ sealed class ChatErrorType { val string: String get() = when (this) { is InvalidConnReq -> "invalidConnReq" is ContactGroups -> "groupNames $groupNames" + is NoActiveUser -> "noActiveUser" } + @Serializable @SerialName("noActiveUser") class NoActiveUser: ChatErrorType() @Serializable @SerialName("invalidConnReq") class InvalidConnReq: ChatErrorType() @Serializable @SerialName("contactGroups") class ContactGroups(val contact: Contact, val groupNames: List): ChatErrorType() } 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 da3d6c07cf..9bbb445e2e 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 @@ -1,15 +1,22 @@ package chat.simplex.app.views -import androidx.compose.foundation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowBackIosNew +import androidx.compose.material.icons.outlined.ArrowForwardIos import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp @@ -18,69 +25,12 @@ import chat.simplex.app.R import chat.simplex.app.SimplexService import chat.simplex.app.model.ChatModel import chat.simplex.app.model.Profile -import chat.simplex.app.views.helpers.getKeyboardState +import chat.simplex.app.ui.theme.HighOrLowlight +import chat.simplex.app.ui.theme.SimpleButton import chat.simplex.app.views.helpers.withApi -import com.google.accompanist.insets.ProvideWindowInsets +import chat.simplex.app.views.onboarding.OnboardingStage +import chat.simplex.app.views.onboarding.ReadableText import com.google.accompanist.insets.navigationBarsWithImePadding -import kotlinx.coroutines.launch - -@Composable -fun WelcomeView(chatModel: ChatModel) { - val scope = rememberCoroutineScope() - val scrollState = rememberScrollState() - val keyboardState by getKeyboardState() - var savedKeyboardState by remember { mutableStateOf(keyboardState) } - - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - Box( - modifier = Modifier - .fillMaxSize() - .background(color = MaterialTheme.colors.background) - ) { - Column( - verticalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .verticalScroll(scrollState) - .fillMaxSize() - .background(color = MaterialTheme.colors.background) - .padding(12.dp) - ) { - Image( - painter = painterResource(R.drawable.logo), - contentDescription = stringResource(R.string.image_descr_simplex_logo), - modifier = Modifier.padding(vertical = 15.dp) - ) - Text( - stringResource(R.string.you_control_your_chat), - style = MaterialTheme.typography.h4, - color = MaterialTheme.colors.onBackground - ) - Text( - stringResource(R.string.the_messaging_and_app_platform_protecting_your_privacy_and_security), - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onBackground - ) - Spacer(Modifier.height(8.dp)) - Text( - stringResource(R.string.we_do_not_store_contacts_or_messages_on_servers), - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onBackground - ) - Spacer(Modifier.height(24.dp)) - CreateProfilePanel(chatModel) - } - if (savedKeyboardState != keyboardState) { - LaunchedEffect(keyboardState) { - scope.launch { - savedKeyboardState = keyboardState - scrollState.animateScrollTo(scrollState.maxValue) - } - } - } - } - } -} - fun isValidDisplayName(name: String) : Boolean { return (name.firstOrNull { it.isWhitespace() }) == null @@ -88,91 +38,106 @@ fun isValidDisplayName(name: String) : Boolean { @Composable fun CreateProfilePanel(chatModel: ChatModel) { - var displayName by remember { mutableStateOf("") } - var fullName by remember { mutableStateOf("") } + val displayName = remember { mutableStateOf("") } + val fullName = remember { mutableStateOf("") } + val focusRequester = remember { FocusRequester() } - Column( - modifier=Modifier.fillMaxSize() - ) { - Text( - stringResource(R.string.create_profile), - style = MaterialTheme.typography.h4, - color = MaterialTheme.colors.onBackground, - modifier = Modifier.padding(vertical = 5.dp) - ) - Text( - stringResource(R.string.your_profile_is_stored_on_your_decide_and_shared_only_with_your_contacts), - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onBackground - ) - Spacer(Modifier.height(10.dp)) - Text( - stringResource(R.string.display_name), - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onBackground, - modifier = Modifier.padding(bottom = 3.dp) - ) - BasicTextField( - value = displayName, - onValueChange = { displayName = it }, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colors.secondary) - .height(40.dp) - .clip(RoundedCornerShape(5.dp)) - .padding(8.dp) - .navigationBarsWithImePadding(), - textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground), - keyboardOptions = KeyboardOptions( - capitalization = KeyboardCapitalization.None, - autoCorrect = false - ), - singleLine = true - ) - val errorText = if(!isValidDisplayName(displayName)) stringResource(R.string.display_name_cannot_contain_whitespace) else "" + Surface(Modifier.background(MaterialTheme.colors.onBackground)) { + Column( + modifier = Modifier.fillMaxSize() + ) { + Text( + stringResource(R.string.create_profile), + style = MaterialTheme.typography.h4, + modifier = Modifier.padding(vertical = 5.dp) + ) + ReadableText(R.string.your_profile_is_stored_on_your_device) + ReadableText(R.string.profile_is_only_shared_with_your_contacts) + Spacer(Modifier.height(10.dp)) + Text( + stringResource(R.string.display_name), + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(bottom = 3.dp) + ) + ProfileNameField(displayName, focusRequester) + val errorText = if (!isValidDisplayName(displayName.value)) stringResource(R.string.display_name_cannot_contain_whitespace) else "" + Text( + errorText, + fontSize = 15.sp, + color = MaterialTheme.colors.error + ) + Spacer(Modifier.height(3.dp)) + Text( + stringResource(R.string.full_name_optional__prompt), + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(bottom = 5.dp) + ) + ProfileNameField(fullName) + Spacer(Modifier.fillMaxHeight().weight(1f)) + Row { + SimpleButton( + text = stringResource(R.string.about_simplex), + icon = Icons.Outlined.ArrowBackIosNew + ) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo } - Text( - errorText, - fontSize = 15.sp, - color = MaterialTheme.colors.error - ) + Spacer(Modifier.fillMaxWidth().weight(1f)) - Spacer(Modifier.height(3.dp)) - Text( - stringResource(R.string.full_name_optional__prompt), - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onBackground, - modifier = Modifier.padding(bottom = 5.dp) - ) - BasicTextField( - value = fullName, - onValueChange = { fullName = it }, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colors.secondary) - .height(40.dp) - .clip(RoundedCornerShape(3.dp)) - .padding(8.dp) - .navigationBarsWithImePadding(), - textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground), - keyboardOptions = KeyboardOptions( - capitalization = KeyboardCapitalization.None, - autoCorrect = false - ), - singleLine = true - ) - Spacer(Modifier.height(20.dp)) - Button(onClick = { - withApi { - val user = chatModel.controller.apiCreateActiveUser( - Profile(displayName, fullName, null) - ) - chatModel.controller.startChat(user) - SimplexService.start(chatModel.controller.appContext) - chatModel.controller.showBackgroundServiceNotice() + val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value) + val createModifier: Modifier + val createColor: Color + if (enabled) { + createModifier = Modifier.padding(8.dp).clickable { createProfile(chatModel, displayName.value, fullName.value) } + createColor = MaterialTheme.colors.primary + } else { + createModifier = Modifier.padding(8.dp) + createColor = HighOrLowlight + } + Surface(shape = RoundedCornerShape(20.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) { + Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor) + Icon(Icons.Outlined.ArrowForwardIos, stringResource(R.string.create_profile_button), tint = createColor) + } + } } - }, - enabled = (displayName.isNotEmpty() && isValidDisplayName(displayName)) - ) { Text(stringResource(R.string.create_profile_button)) } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + } } } + +fun createProfile(chatModel: ChatModel, displayName: String, fullName: String) { + withApi { + val user = chatModel.controller.apiCreateActiveUser( + Profile(displayName, fullName, null) + ) + chatModel.controller.startChat(user) + SimplexService.start(chatModel.controller.appContext) + // TODO show it later? + chatModel.controller.showBackgroundServiceNotice() + chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete + } +} + +@Composable +fun ProfileNameField(name: MutableState, focusRequester: FocusRequester? = null) { + val modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colors.secondary) + .height(40.dp) + .clip(RoundedCornerShape(5.dp)) + .padding(8.dp) + .navigationBarsWithImePadding() + BasicTextField( + value = name.value, + onValueChange = { name.value = it }, + modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester), + textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground), + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrect = false + ), + singleLine = true + ) +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/WebRTC.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/WebRTC.kt index cdf9ebf45c..6484c1d42a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/WebRTC.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/WebRTC.kt @@ -1,7 +1,7 @@ package chat.simplex.app.views.call import chat.simplex.app.R -import chat.simplex.app.model.* +import chat.simplex.app.model.Contact import chat.simplex.app.views.helpers.generalGetString import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt index 32eeba9e32..48917ec05e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt @@ -1,10 +1,11 @@ -import androidx.compose.foundation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.InsertDriveFile import androidx.compose.material.icons.outlined.Close -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt index 8402d4e7fa..d187cf0344 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt @@ -7,7 +7,8 @@ import android.net.Uri import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AttachFile import androidx.compose.material.icons.filled.Edit @@ -24,7 +25,8 @@ import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.views.chat.item.* import chat.simplex.app.views.helpers.* -import kotlinx.coroutines.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking sealed class ComposePreview { object NoPreview: ComposePreview() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt index 496c8323b7..2b969b1ee5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt @@ -6,7 +6,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.outlined.Close -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index 08f2672a15..b4d5aa24f4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -23,8 +23,9 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.R -import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.* +import chat.simplex.app.model.ChatItem +import chat.simplex.app.ui.theme.HighOrLowlight +import chat.simplex.app.ui.theme.SimpleXTheme @Composable fun SendMsgView( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt index e3f342deac..e98f6b2b90 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt @@ -1,12 +1,14 @@ import android.graphics.Bitmap -import androidx.compose.foundation.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.outlined.MoreHoriz -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -16,7 +18,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import chat.simplex.app.R -import chat.simplex.app.model.* +import chat.simplex.app.model.CIFile +import chat.simplex.app.model.CIFileStatus import chat.simplex.app.views.helpers.* @Composable diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index e14e031538..bf59756968 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -18,7 +18,8 @@ import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.* -import androidx.compose.ui.unit.* +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt index 3f981e1de7..e19473d364 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt @@ -1,7 +1,8 @@ import android.graphics.Bitmap import androidx.activity.compose.BackHandler import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index df2a1ee8ef..c207f3de46 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -21,6 +21,7 @@ import chat.simplex.app.ui.theme.ToolbarDark import chat.simplex.app.ui.theme.ToolbarLight import chat.simplex.app.views.helpers.ModalManager import chat.simplex.app.views.newchat.NewChatSheet +import chat.simplex.app.views.onboarding.MakeConnection import chat.simplex.app.views.usersettings.SettingsView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -88,8 +89,7 @@ fun ChatListView(chatModel: ChatModel) { if (chatModel.chats.isNotEmpty()) { ChatList(chatModel) } else { - val user = chatModel.currentUser.value - Help(scaffoldCtrl, displayName = user?.profile?.displayName) + MakeConnection(chatModel) } } if (scaffoldCtrl.expanded.value) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt index b5636a3542..8b709bd6af 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt @@ -9,14 +9,12 @@ import androidx.compose.material.icons.outlined.Link import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import chat.simplex.app.R -import chat.simplex.app.model.* +import chat.simplex.app.model.PendingContactConnection +import chat.simplex.app.model.getTimestampText import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.views.helpers.ChatInfoImage import chat.simplex.app.views.helpers.ProfileImage @Composable diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt index 1140273b0a..e6757f8351 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt @@ -10,7 +10,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import chat.simplex.app.R -import chat.simplex.app.model.* +import chat.simplex.app.model.ChatInfo +import chat.simplex.app.model.getTimestampText import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.views.helpers.ChatInfoImage diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt index 93ca20c5dd..b19f17a2be 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import chat.simplex.app.R -import chat.simplex.app.model.Chat import chat.simplex.app.model.ChatInfo import chat.simplex.app.ui.theme.SimpleXTheme 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 b02f0c535f..1045605cd1 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 @@ -11,7 +11,8 @@ import androidx.compose.runtime.Composable import androidx.core.content.ContextCompat import chat.simplex.app.R import chat.simplex.app.model.CIFile -import java.io.* +import java.io.BufferedOutputStream +import java.io.File fun shareText(cxt: Context, text: String) { val sendIntent: Intent = Intent().apply { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt index 23906df768..a4bc591552 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt @@ -19,19 +19,24 @@ import androidx.compose.ui.unit.dp fun SimpleButton(text: String, icon: ImageVector, color: Color = MaterialTheme.colors.primary, click: () -> Unit) { + SimpleButtonFrame(click) { + Icon( + icon, text, tint = color, + modifier = Modifier.padding(end = 8.dp) + ) + Text(text, style = MaterialTheme.typography.caption, color = color) + } +} + +@Composable +fun SimpleButtonFrame(click: () -> Unit, content: @Composable () -> Unit) { Surface(shape = RoundedCornerShape(20.dp)) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .clickable { click() } .padding(8.dp) - ) { - Icon( - icon, text, tint = color, - modifier = Modifier.padding(end = 8.dp) - ) - Text(text, style = MaterialTheme.typography.caption, color = color) - } + ) { content() } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt new file mode 100644 index 0000000000..07ee6d892b --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt @@ -0,0 +1,70 @@ +package chat.simplex.app.views.onboarding + +import android.content.res.Configuration +import androidx.annotation.StringRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.app.R +import chat.simplex.app.model.User +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.ModalManager +import chat.simplex.app.views.helpers.annotatedStringResource + +@Composable +fun HowItWorks(user: User?, onboardingStage: MutableState? = null) { + Column(Modifier.fillMaxHeight(), horizontalAlignment = Alignment.Start) { + Text(stringResource(R.string.how_simplex_works), style = MaterialTheme.typography.h1, modifier = Modifier.padding(bottom = 8.dp)) + ReadableText(R.string.many_people_asked_how_can_it_deliver) + ReadableText(R.string.to_protect_privacy_simplex_has_ids_for_queues) + ReadableText(R.string.you_control_servers_to_receive_your_contacts_to_send) + ReadableText(R.string.only_client_devices_store_contacts_groups_e2e_encrypted_messages) + if (onboardingStage == null) { + val uriHandler = LocalUriHandler.current + Text( + annotatedStringResource(R.string.read_more_in_github_with_link), + modifier = Modifier.padding(bottom = 12.dp).clickable { uriHandler.openUri("https://github.com/simplex-chat/simplex-chat#readme") }, + lineHeight = 22.sp + ) + } else { + ReadableText(R.string.read_more_in_github) + } + + Spacer(Modifier.fillMaxHeight().weight(1f)) + + if (onboardingStage != null) { + Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) { + OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.shared.closeModal() }) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + } + } +} + +@Composable +fun ReadableText(@StringRes stringResId: Int) { + Text(annotatedStringResource(stringResId), modifier = Modifier.padding(bottom = 12.dp), lineHeight = 22.sp) +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewHowItWorks() { + SimpleXTheme { + HowItWorks(user = null) + } +} \ No newline at end of file diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/MakeConnection.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/MakeConnection.kt new file mode 100644 index 0000000000..0b6290eef4 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/MakeConnection.kt @@ -0,0 +1,172 @@ +package chat.simplex.app.views.onboarding + +import android.Manifest +import android.content.res.Configuration +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import chat.simplex.app.R +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.User +import chat.simplex.app.ui.theme.SimpleButton +import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.newchat.* +import chat.simplex.app.views.usersettings.simplexTeamUri +import com.google.accompanist.permissions.rememberPermissionState + +@Composable +fun MakeConnection(chatModel: ChatModel) { + val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) + MakeConnectionLayout( + chatModel.currentUser.value, + createLink = { + withApi { + // show spinner + chatModel.connReqInvitation = chatModel.controller.apiAddContact() + // hide spinner + if (chatModel.connReqInvitation != null) { + ModalManager.shared.showModal { AddContactView(chatModel) } + } + } + }, + pasteLink = { + ModalManager.shared.showCustomModal { close -> PasteToConnectView(chatModel, close) } + }, + scanCode = { + ModalManager.shared.showCustomModal { close -> ScanToConnectView(chatModel, close) } + cameraPermissionState.launchPermissionRequest() + }, + about = { + chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo + } + ) +} + +@Composable +fun MakeConnectionLayout( + user: User?, + createLink: () -> Unit, + pasteLink: () -> Unit, + scanCode: () -> Unit, + about: () -> Unit +) { + Surface { + Column( + Modifier + .fillMaxSize() + .background(color = MaterialTheme.colors.background) + .padding(20.dp) + ) { + Text( + if (user == null) stringResource(R.string.make_private_connection) + else String.format(stringResource(R.string.personal_welcome), user.profile.displayName), + style = MaterialTheme.typography.h1, + modifier = Modifier.padding(bottom = 8.dp) + ) + Text( + annotatedStringResource(R.string.to_make_your_first_private_connection_choose), + modifier = Modifier.padding(bottom = 16.dp) + ) + ActionRow( + Icons.Outlined.QrCode, + R.string.create_1_time_link_qr_code, + R.string.it_s_secure_to_share__only_one_contact_can_use_it, + createLink + ) + ActionRow( + Icons.Outlined.Link, + R.string.paste_the_link_you_received, + R.string.or_open_the_link_in_the_browser_and_tap_open_in_mobile, + pasteLink + ) + ActionRow( + Icons.Outlined.QrCodeScanner, + R.string.scan_contact_s_qr_code, + R.string.in_person_or_via_a_video_call__the_most_secure_way_to_connect, + scanCode + ) + Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) { + Text(stringResource(R.string.or)) + } + val uriHandler = LocalUriHandler.current + ActionRow( + Icons.Outlined.Tag, + R.string.connect_with_the_developers, + R.string.to_ask_any_questions_and_to_receive_simplex_chat_updates, + { uriHandler.openUri(simplexTeamUri) } + ) + Spacer(Modifier.fillMaxHeight().weight(1f)) + SimpleButton( + text = stringResource(R.string.about_simplex), + icon = Icons.Outlined.ArrowBackIosNew, + click = about + ) + } + } +} + +@Composable +private fun ActionRow(icon: ImageVector, @StringRes titleId: Int, @StringRes textId: Int, action: () -> Unit) { + Row( + Modifier + .clickable { action() } + .padding(bottom = 16.dp) + ) { + Icon(icon, stringResource(titleId), tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(end = 10.dp).size(40.dp)) + Column { + Text(stringResource(titleId), color = MaterialTheme.colors.primary) + Text(annotatedStringResource(textId)) + } + } +// Button(action: action, label: { +// HStack(alignment: .top, spacing: 20) { +// Image(systemName: icon) +// .resizable() +// .scaledToFit() +// .frame(width: 30, height: 30) +// .padding(.leading, 4) +// .padding(.top, 6) +// VStack(alignment: .leading) { +// Group { +// Text(title).font(.headline) +// Text(text).foregroundColor(.primary) +// } +// .multilineTextAlignment(.leading) +// } +// } +// }) +// .padding(.bottom) +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewMakeConnection() { + SimpleXTheme { + MakeConnectionLayout( + user = User.sampleData, + createLink = {}, + pasteLink = {}, + scanCode = {}, + about = {} + ) + } +} 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 new file mode 100644 index 0000000000..08435f75e7 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt @@ -0,0 +1,47 @@ +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 +import chat.simplex.app.model.ChatModel +import chat.simplex.app.views.CreateProfilePanel +import chat.simplex.app.views.helpers.getKeyboardState +import com.google.accompanist.insets.ProvideWindowInsets +import kotlinx.coroutines.launch + +enum class OnboardingStage { + Step1_SimpleXInfo, + Step2_CreateProfile, + OnboardingComplete +} + +@Composable +fun CreateProfile(chatModel: ChatModel) { + val scope = rememberCoroutineScope() + val scrollState = rememberScrollState() + val keyboardState by getKeyboardState() + var savedKeyboardState by remember { mutableStateOf(keyboardState) } + + ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = MaterialTheme.colors.background) + .padding(20.dp) + ) { + CreateProfilePanel(chatModel) + if (savedKeyboardState != keyboardState) { + LaunchedEffect(keyboardState) { + scope.launch { + savedKeyboardState = keyboardState + scrollState.animateScrollTo(scrollState.maxValue) + } + } + } + } + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt new file mode 100644 index 0000000000..3825954bbc --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt @@ -0,0 +1,139 @@ +package chat.simplex.app.views.onboarding + +import android.content.res.Configuration +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowForwardIos +import androidx.compose.material.icons.outlined.Info +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.app.R +import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.User +import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.helpers.ModalManager + +@Composable +fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { + SimpleXInfoLayout( + user = chatModel.currentUser.value, + onboardingStage = if (onboarding) chatModel.onboardingStage else null, + showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } }, + ) +} + +@Composable +fun SimpleXInfoLayout( + user: User?, + onboardingStage: MutableState?, + showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), +) { + Column(Modifier.fillMaxHeight(), horizontalAlignment = Alignment.Start) { + Image( + painter = painterResource(R.drawable.logo), + contentDescription = stringResource(R.string.image_descr_simplex_logo), + modifier = Modifier + .padding(vertical = 20.dp) + .fillMaxWidth(0.80f) + ) + + Text(stringResource(R.string.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 16.dp)) + + InfoRow("🎭", R.string.privacy_redefined, R.string.first_platform_without_user_ids) + InfoRow("📭", R.string.immune_to_spam_and_abuse, R.string.people_can_connect_only_via_links_you_share) + InfoRow("🤝", R.string.decentralized, R.string.opensource_protocol_and_code_anybody_can_run_servers) + + Spacer( + Modifier + .fillMaxHeight() + .weight(1f)) + + if (onboardingStage != null) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + OnboardingActionButton(user, onboardingStage) + } + Spacer( + Modifier + .fillMaxHeight() + .weight(1f)) + } + + Box( + Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), contentAlignment = Alignment.Center) { + SimpleButton(text = stringResource(R.string.how_it_works), icon = Icons.Outlined.Info, + click = showModal { HowItWorks(user, onboardingStage) }) + } + } +} + +@Composable +private fun InfoRow(emoji: String, @StringRes titleId: Int, @StringRes textId: Int) { + Row(Modifier.padding(bottom = 20.dp), verticalAlignment = Alignment.Top) { + Text(emoji, fontSize = 36.sp, modifier = Modifier + .width(60.dp) + .padding(end = 16.dp)) + Column(horizontalAlignment = Alignment.Start) { + Text(stringResource(titleId), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.h3, lineHeight = 24.sp) + Text(stringResource(textId), lineHeight = 24.sp, style = MaterialTheme.typography.caption) + } + } +} + + +@Composable +fun OnboardingActionButton(user: User?, onboardingStage: MutableState, onclick: (() -> Unit)? = null) { + if (user == null) { + ActionButton(R.string.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onboardingStage, onclick) + } else { + ActionButton(R.string.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onboardingStage, onclick) + } +} + +@Composable +private fun ActionButton( + @StringRes labelId: Int, + onboarding: OnboardingStage?, + onboardingStage: MutableState, + onclick: (() -> Unit)? +) { + SimpleButtonFrame(click = { + onclick?.invoke() + onboardingStage.value = onboarding + }) { + Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary) + Icon( + Icons.Outlined.ArrowForwardIos, "next stage", tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(end = 8.dp) + ) + } +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewSimpleXInfo() { + SimpleXTheme { + SimpleXInfoLayout( + user = null, + onboardingStage = null, + showModal = {{}} + ) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index 1c4aac68f6..64f7610bc9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -26,6 +26,7 @@ import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.TerminalView import chat.simplex.app.views.call.VideoCallView import chat.simplex.app.views.helpers.* +import chat.simplex.app.views.onboarding.SimpleXInfo @Composable fun SettingsView(chatModel: ChatModel) { @@ -111,6 +112,15 @@ fun SettingsLayout( Text(stringResource(R.string.how_to_use_simplex_chat)) } Divider(Modifier.padding(horizontal = 8.dp)) + SettingsSectionView(showModal { SimpleXInfo(it, onboarding = false) }) { + Icon( + Icons.Outlined.Info, + contentDescription = stringResource(R.string.icon_descr_help), + ) + Spacer(Modifier.padding(horizontal = 4.dp)) + Text(stringResource(R.string.about_simplex_chat)) + } + Divider(Modifier.padding(horizontal = 8.dp)) SettingsSectionView(showModal { MarkdownHelpView() }) { Icon( Icons.Outlined.TextFormat, diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 7ab856193a..9496535853 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -214,7 +214,8 @@ Настройки Ваш SimpleX адрес - Как использовать SimpleX Chat + Информация о SimpleX Chat + Как использовать Форматирование сообщений Форматирование сообщений Соединиться с разработчиками @@ -256,11 +257,13 @@ Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность. Мы не храним ваши контакты и сообщения (после доставки) на серверах. Создать профиль - Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам. + Ваш профиль, контакты и доставленные сообщения хранятся на вашем устройстве. + Профиль отправляется только вашим контактам. Имя профиля не может содержать пробелы. Имя профиля Полное имя (не обязательно) Создать + О SimpleX Как форматировать @@ -293,4 +296,37 @@ получен ответ… соединяется… соединено + + + Новое поколение приватных сообщений + Более конфиденциальный + Первая в мире платформа без идентификаторов пользователей. + Защищен от спама + С вами можно соединиться только через созданные вами ссылки. + Децентрализованный + Открытый протокол и код - кто угодно может запустить сервер. + Создать профиль + Добавьте контакт + Как это работает + + + Как SimpleX работает + Много пользователей спросили: как SimpleX доставляет сообщения без идентификаторов пользователей? + Чтобы защитить вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта. + Вы определяете через какие серверы вы получаете сообщения, ваши контакты - серверы, которые вы используете для отправки. + Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются с двухуровневым end-to-end шифрованием. + Узнайте больше из нашего GitHub репозитория. + Узнайте больше из нашего GitHub репозитория. + + + Чтобы добавить ваш первый контакт, выберите одно из: + Создать ссылку / QR код + Ей безопасно поделиться - только один контакт может использовать её. + Вставьте полученную ссылку + Или откройте ссылку в браузере и нажмите Open in mobile. + Сосканировать QR код контакта + При встрече или в видеозвонке – самый безопасный способ установить соединение + или + Соединиться с разработчиками + Чтобы задать вопросы и получать уведомления о SimpleX Chat. diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 6e66cb3bb8..5dce4ad6ea 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -219,7 +219,8 @@ Your settings Your SimpleX contact address - How to use SimpleX Chat + About SimpleX Chat + How to use it Markdown help Markdown in messages Connect to the developers @@ -261,11 +262,13 @@ The messaging and application platform protecting your privacy and security. We don\'t store any of your contacts or messages (once delivered) on the servers. Create profile - Your profile is stored on your device and shared only with your contacts. + Your profile, contacts and delivered messages are stored on your device. + The profile is only shared with your contacts. Display name cannot contain whitespace. Display Name - Full Name (Optional) + Full Name (optional) Create + About SimpleX How to use markdown @@ -294,4 +297,37 @@ received answer… connecting… connected + + + The next generation of private messaging + Privacy redefined + The 1st platform without any user identifiers – private by design. + Immune to spam and abuse + People can connect to you only via the links you share. + Decentralized + Open-source protocol and code – anybody can run the servers. + Create your profile + Make a private connection + How it works + + + How SimpleX works + Many people asked: if SimpleX has no user identifiers, how can it deliver messages? + To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + You control through which server(s) to receive the messages, your contacts – the servers you use to message them. + Only client devices store user profiles, contacts, groups, and messages sent with 2-layer end-to-end encryption. + Read more in our GitHub repository. + Read more in our GitHub repository. + + + To make your first private connection, choose one of the following: + Create 1-time link / QR code + It\'s secure to share - only one contact can use it. + Paste the link you received + Or open the link in the browser and tap Open in mobile. + Scan contact\'s QR code + In person or via a video call – the most secure way to connect. + or + Connect with the developers + To ask any questions and to receive SimpleX Chat updates. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index d924c6dbea..9fd55a97c1 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -782,7 +782,7 @@ The 1st platform without any user identifiers – private by design. - Первая в мире платформа без идентификации пользователей. + Первая в мире платформа без идентификаторов пользователей. No comment provided by engineer. diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 40439a9b77..0f86f73a9e 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -552,7 +552,7 @@ "Thank you for installing SimpleX Chat!" = "Спасибо, что установили SimpleX Chat!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Первая в мире платформа без идентификации пользователей."; +"The 1st platform without any user identifiers – private by design." = "Первая в мире платформа без идентификаторов пользователей."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках.";