From e48452ccff777c8e843d54a52b8b09f76cc0a4a5 Mon Sep 17 00:00:00 2001 From: JRoberts <8711996+jr-simplex@users.noreply.github.com> Date: Mon, 26 Dec 2022 21:46:56 +0400 Subject: [PATCH] android: show what is new in the latest version (#1651) --- .../java/chat/simplex/app/model/SimpleXAPI.kt | 3 + .../app/views/chatlist/ChatHelpView.kt | 10 + .../app/views/chatlist/ChatListView.kt | 9 + .../app/views/helpers/CloseSheetBar.kt | 2 +- .../app/views/onboarding/OnboardingView.kt | 3 + .../app/views/onboarding/WhatsNewView.kt | 259 ++++++++++++++++++ .../views/usersettings/MarkdownHelpView.kt | 5 - .../app/views/usersettings/SettingsView.kt | 7 +- .../app/src/main/res/values/strings.xml | 24 ++ 9 files changed, 313 insertions(+), 9 deletions(-) create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt 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 67adf84a25..a54328b65d 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 @@ -132,6 +132,8 @@ class AppPreferences(val context: Context) { val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name) val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb()) + val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) + private fun mkIntPreference(prefName: String, default: Int) = SharedPreference( get = fun() = sharedPreferences.getInt(prefName, default), @@ -223,6 +225,7 @@ class AppPreferences(val context: Context) { private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt" private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor" + private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt index ff10bbbfeb..7d36074a33 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatHelpView.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.annotatedStringResource +import chat.simplex.app.views.usersettings.MarkdownHelpView import chat.simplex.app.views.usersettings.simplexTeamUri val bold = SpanStyle(fontWeight = FontWeight.Bold) @@ -76,6 +77,15 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) { Text(annotatedStringResource(R.string.desktop_scan_QR_code_from_app_via_scan_QR_code), lineHeight = 22.sp) Text(annotatedStringResource(R.string.mobile_tap_open_in_mobile_app_then_tap_connect_in_app), lineHeight = 22.sp) } + + Column( + Modifier.padding(vertical = 24.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text(stringResource(R.string.markdown_in_messages), style = MaterialTheme.typography.h2) + MarkdownHelpView() + } } } 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 a91795a966..2131c63f93 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 @@ -27,8 +27,11 @@ import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.NewChatSheet +import chat.simplex.app.views.onboarding.WhatsNewView +import chat.simplex.app.views.onboarding.shouldShowWhatsNew import chat.simplex.app.views.usersettings.SettingsView import chat.simplex.app.views.usersettings.simplexTeamUri +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @@ -42,6 +45,12 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: if (animated) newChatSheetState.value = NewChatSheetState.HIDING else newChatSheetState.value = NewChatSheetState.GONE } + LaunchedEffect(Unit) { + if (shouldShowWhatsNew(chatModel)) { + delay(1000L) + ModalManager.shared.showCustomModal { close -> WhatsNewView(close = close) } + } + } LaunchedEffect(chatModel.clearOverlays.value) { if (chatModel.clearOverlays.value && newChatSheetState.value.isVisible()) hideNewChatSheet(false) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt index ad3d598910..f62d8ffdc7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt @@ -30,7 +30,7 @@ fun CloseSheetBar(close: () -> Unit) { @Composable fun AppBarTitle(title: String, withPadding: Boolean = true) { val padding = if (withPadding) - PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING ) + PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING) else PaddingValues(bottom = DEFAULT_PADDING) Text( 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 ffc18d9021..b1a5c09d71 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 @@ -35,6 +35,9 @@ fun CreateProfile(chatModel: ChatModel) { .padding(20.dp) ) { CreateProfilePanel(chatModel) + LaunchedEffect(Unit) { + setLastVersionDefault(chatModel) + } if (savedKeyboardState != keyboardState) { LaunchedEffect(keyboardState) { scope.launch { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt new file mode 100644 index 0000000000..26813ebd64 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt @@ -0,0 +1,259 @@ +package chat.simplex.app.views.onboarding + +import android.content.res.Configuration +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.* +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.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +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.ui.theme.* +import chat.simplex.app.views.helpers.* + +@Composable +fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { + val currentVersion = remember { mutableStateOf(versionDescriptions.lastIndex) } + + @Composable + fun featureDescription(icon: ImageVector, titleId: Int, descrId: Int, link: String?) { + @Composable + fun linkButton(link: String) { + val uriHandler = LocalUriHandler.current + Icon( + Icons.Outlined.OpenInNew, stringResource(titleId), tint = MaterialTheme.colors.primary, + modifier = Modifier + .clickable { uriHandler.openUri(link) } + ) + } + + Column( + horizontalAlignment = Alignment.Start + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon(icon, stringResource(titleId), tint = HighOrLowlight) + Text( + generalGetString(titleId), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium + ) + if (link != null) { + linkButton(link) + } + } + Text(generalGetString(descrId)) + } + } + + @Composable + fun pagination() { + Row( + Modifier + .padding(bottom = 16.dp) + ) { + if (currentVersion.value > 0) { + val prev = currentVersion.value - 1 + Surface(shape = RoundedCornerShape(20.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .clickable { currentVersion.value = prev } + .padding(8.dp) + ) { + Icon(Icons.Outlined.ArrowBackIosNew, "previous", tint = MaterialTheme.colors.primary) + Text(versionDescriptions[prev].version, color = MaterialTheme.colors.primary) + } + } + } + Spacer(Modifier.fillMaxWidth().weight(1f)) + if (currentVersion.value < versionDescriptions.lastIndex) { + val next = currentVersion.value + 1 + Surface(shape = RoundedCornerShape(20.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .clickable { currentVersion.value = next } + .padding(8.dp) + ) { + Text(versionDescriptions[next].version, color = MaterialTheme.colors.primary) + Icon(Icons.Outlined.ArrowForwardIos, "next", tint = MaterialTheme.colors.primary) + } + } + } + } + } + + val v = versionDescriptions[currentVersion.value] + + ModalView(close = close) { + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + String.format(generalGetString(R.string.new_in_version), v.version), + Modifier + .fillMaxWidth() + .padding(DEFAULT_PADDING), + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Normal, + color = HighOrLowlight + ) + + v.features.forEach { feature -> + featureDescription(feature.icon, feature.titleId, feature.descrId, feature.link) + } + + if (!viaSettings) { + Spacer(Modifier.fillMaxHeight().weight(1f)) + Box( + Modifier.fillMaxWidth(), contentAlignment = Alignment.Center + ) { + Text( + generalGetString(R.string.ok), + modifier = Modifier.clickable(onClick = close), + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.primary + ) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + } + + Spacer(Modifier.fillMaxHeight().weight(1f)) + + pagination() + } + } +} + +private data class FeatureDescription( + val icon: ImageVector, + val titleId: Int, + val descrId: Int, + val link: String? = null +) + +private data class VersionDescription( + val version: String, + val features: List +) + +private val versionDescriptions: List = listOf( + VersionDescription( + version = "v4.2", + features = listOf( + FeatureDescription( + icon = Icons.Outlined.VerifiedUser, + titleId = R.string.v4_2_security_assessment, + descrId = R.string.v4_2_security_assessment_desc, + link = "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html" + ), + FeatureDescription( + icon = Icons.Outlined.Group, + titleId = R.string.v4_2_group_links, + descrId = R.string.v4_2_group_links_desc + ), + FeatureDescription( + icon = Icons.Outlined.Check, + titleId = R.string.v4_2_auto_accept_contact_requests, + descrId = R.string.v4_2_auto_accept_contact_requests_desc + ), + ) + ), + VersionDescription( + version = "v4.3", + features = listOf( + FeatureDescription( + icon = Icons.Outlined.Mic, + titleId = R.string.v4_3_voice_messages, + descrId = R.string.v4_3_voice_messages_desc + ), + FeatureDescription( + icon = Icons.Outlined.DeleteForever, + titleId = R.string.v4_3_irreversible_message_deletion, + descrId = R.string.v4_3_irreversible_message_deletion_desc + ), + FeatureDescription( + icon = Icons.Outlined.WifiTethering, + titleId = R.string.v4_3_improved_server_configuration, + descrId = R.string.v4_3_improved_server_configuration_desc + ), + FeatureDescription( + icon = Icons.Outlined.VisibilityOff, + titleId = R.string.v4_3_improved_privacy_and_security, + descrId = R.string.v4_3_improved_privacy_and_security_desc + ), + ) + ), + VersionDescription( + version = "v4.4", + features = listOf( + FeatureDescription( + icon = Icons.Outlined.Timer, + titleId = R.string.v4_4_disappearing_messages, + descrId = R.string.v4_4_disappearing_messages_desc + ), + FeatureDescription( + icon = Icons.Outlined.Pending, + titleId = R.string.v4_4_live_messages, + descrId = R.string.v4_4_live_messages_desc + ), + FeatureDescription( + icon = Icons.Outlined.VerifiedUser, + titleId = R.string.v4_4_verify_connection_security, + descrId = R.string.v4_4_verify_connection_security_desc + ) + ) + ) +) +private val lastVersion = versionDescriptions.last().version + +fun setLastVersionDefault(m: ChatModel) { + m.controller.appPrefs.whatsNewVersion.set(lastVersion) +} + +fun shouldShowWhatsNew(m: ChatModel): Boolean { + val v = m.controller.appPrefs.whatsNewVersion.get() + setLastVersionDefault(m) + return v != lastVersion +} + +@Preview(showBackground = true) +@Preview( + uiMode = Configuration.UI_MODE_NIGHT_YES, + showBackground = true, + name = "Dark Mode" +) +@Composable +fun PreviewWhatsNewView() { + SimpleXTheme { + WhatsNewView( + viaSettings = true, + close = {} + ) + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt index c654e14c11..6a09ba2447 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/MarkdownHelpView.kt @@ -17,18 +17,13 @@ import chat.simplex.app.model.Format import chat.simplex.app.model.FormatColor import chat.simplex.app.ui.theme.DEFAULT_PADDING import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.helpers.AppBarTitle -import chat.simplex.app.views.helpers.generalGetString @Composable fun MarkdownHelpView() { Column( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(horizontal = DEFAULT_PADDING) ) { - AppBarTitle(stringResource(R.string.how_to_use_markdown), false) Text(stringResource(R.string.you_can_use_markdown_to_format_messages__prompt)) Spacer(Modifier.height(DEFAULT_PADDING)) val bold = stringResource(R.string.bold) 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 5a1397657c..c64bb12b11 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 @@ -34,6 +34,7 @@ import chat.simplex.app.views.helpers.* import chat.simplex.app.views.newchat.CreateLinkTab import chat.simplex.app.views.newchat.CreateLinkView import chat.simplex.app.views.onboarding.SimpleXInfo +import chat.simplex.app.views.onboarding.WhatsNewView @Composable fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { @@ -138,9 +139,9 @@ fun SettingsLayout( SectionView(stringResource(R.string.settings_section_title_help)) { SettingsActionItem(Icons.Outlined.HelpOutline, stringResource(R.string.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped) SectionDivider() - SettingsActionItem(Icons.Outlined.Info, stringResource(R.string.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }) + SettingsActionItem(Icons.Outlined.Add, stringResource(R.string.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped) SectionDivider() - SettingsActionItem(Icons.Outlined.TextFormat, stringResource(R.string.markdown_in_messages), showModal { MarkdownHelpView() }) + SettingsActionItem(Icons.Outlined.Info, stringResource(R.string.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }) SectionDivider() SettingsActionItem(Icons.Outlined.Tag, stringResource(R.string.chat_with_the_founder), { uriHandler.openUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped) SectionDivider() @@ -488,7 +489,7 @@ fun PreviewSettingsLayout() { setPerformLA = {}, showModal = { {} }, showSettingsModal = { {} }, - showCustomModal = { {}}, + showCustomModal = { {} }, showTerminal = {}, // showVideoChatPrototype = {} ) diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 273e0ab761..8ace468573 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -1055,4 +1055,28 @@ %d week %d weeks %dw + + + What\'s new + New in %s + Security assessment + SimpleX Chat security was audited by Trail of Bits. + Group links + Admins can create the links to join groups. + Auto-accept contact requests + With optional welcome message. + Voice messages + Max 40 seconds, received instantly. + Irreversible message deletion + Your contacts can allow full message deletion. + Improved server configuration + Add servers by scanning QR codes. + Improved privacy and security + Hide app screen in the recent apps. + Disappearing messages + Sent messages will be deleted after set time. + Live messages + Recipients see updates as you type them. + Verify connection security + Compare security codes with your contacts.