diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 80e6b39f69..eaccd7372e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -100,7 +100,7 @@
android:value="false" />
Unit) {
- var count by remember { mutableStateOf(0) }
- val listState = rememberLazyListState()
-
- var isUrlDialogVisible by remember { mutableStateOf(false) }
-
- if (isUrlDialogVisible) {
- AlertDialog(
- onDismissRequest = { isUrlDialogVisible = false },
- title = stringResource(R.string.urlOpen),
- text = stringResource(R.string.urlOpenBrowser),
- buttons = listOf(
- DialogButtonModel(
- GetString(R.string.activity_landing_terms_of_service),
- GetString(R.string.AccessibilityId_terms_of_service_button),
- ) { open("https://getsession.org/terms-of-service") },
- DialogButtonModel(
- GetString(R.string.activity_landing_privacy_policy),
- GetString(R.string.AccessibilityId_privacy_policy_button),
- ) { open("https://getsession.org/privacy-policy") }
- )
- )
- }
-
- LaunchedEffect(Unit) {
- delay(500.milliseconds)
- while(count < MESSAGES.size) {
- count += 1
- listState.animateScrollToItem(0.coerceAtLeast((count - 1)))
- delay(1500L)
- }
- }
-
- Column {
- Column(modifier = Modifier
- .weight(1f)
- .padding(horizontal = LocalDimensions.current.marginOnboarding)
- ) {
- Spacer(modifier = Modifier.weight(1f))
- Text(
- stringResource(R.string.onboardingBubblePrivacyInYourPocket),
- modifier = Modifier.align(Alignment.CenterHorizontally),
- style = h4,
- textAlign = TextAlign.Center
- )
- Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingMedium))
-
- LazyColumn(
- state = listState,
- modifier = Modifier
- .heightIn(min = LocalDimensions.current.minScrollableViewHeight)
- .fillMaxWidth()
- .weight(3f),
- verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.itemSpacingSmall)
- ) {
- items(
- MESSAGES.take(count),
- key = { it.stringId }
- ) { item ->
- AnimateMessageText(
- stringResource(item.stringId),
- item.isOutgoing
- )
- }
- }
-
- Spacer(modifier = Modifier.weight(1f))
- }
-
- Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.marginLarge)) {
- PrimaryFillButton(
- text = stringResource(R.string.onboardingAccountCreate),
- modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.CenterHorizontally)
- .contentDescription(R.string.AccessibilityId_create_account_button),
- onClick = createAccount
- )
- Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall))
- PrimaryOutlineButton(
- stringResource(R.string.onboardingAccountExists),
- modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.CenterHorizontally)
- .contentDescription(R.string.AccessibilityId_restore_account_button),
- ) { start() }
- BorderlessHtmlButton(
- textId = R.string.onboardingTosPrivacy,
- modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.CenterHorizontally)
- .contentDescription(R.string.AccessibilityId_open_url),
- onClick = { isUrlDialogVisible = true }
- )
- Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall))
- }
- }
- }
-
- private fun open(url: String) {
- Intent(Intent.ACTION_VIEW, Uri.parse(url)).let(::startActivity)
- }
-}
-
-@Composable
-private fun AnimateMessageText(text: String, isOutgoing: Boolean, modifier: Modifier = Modifier) {
- var visible by remember { mutableStateOf(false) }
- LaunchedEffect(Unit) { visible = true }
-
- Box {
- // TODO [SES-2077] Use LazyList itemAnimation when we update to compose 1.7 or so.
- MessageText(text, isOutgoing, Modifier.alpha(0f))
-
- AnimatedVisibility(
- visible = visible,
- enter = fadeIn(animationSpec = tween(durationMillis = 300)) +
- slideInVertically(animationSpec = tween(durationMillis = 300)) { it }
- ) {
- MessageText(text, isOutgoing, modifier)
- }
- }
-}
-
-@Composable
-private fun MessageText(text: String, isOutgoing: Boolean, modifier: Modifier) {
- Box(modifier = modifier then Modifier.fillMaxWidth()) {
- MessageText(
- text,
- color = if (isOutgoing) LocalColors.current.backgroundBubbleSent else LocalColors.current.backgroundBubbleReceived,
- textColor = if (isOutgoing) LocalColors.current.textBubbleSent else LocalColors.current.textBubbleReceived,
- modifier = Modifier.align(if (isOutgoing) Alignment.TopEnd else Alignment.TopStart)
- )
- }
-}
-
-@Composable
-private fun MessageText(
- text: String,
- color: Color,
- modifier: Modifier = Modifier,
- textColor: Color = Color.Unspecified
-) {
- Card(
- modifier = modifier.fillMaxWidth(0.666f),
- shape = RoundedCornerShape(size = 13.dp),
- backgroundColor = color,
- elevation = 0.dp
- ) {
- Text(
- text,
- style = large,
- color = textColor,
- modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
- )
- }
-}
-
-private data class TextData(
- @StringRes val stringId: Int,
- val isOutgoing: Boolean = false
-)
-
-private val MESSAGES = listOf(
- TextData(R.string.onboardingBubbleWelcomeToSession),
- TextData(R.string.onboardingBubbleSessionIsEngineered, isOutgoing = true),
- TextData(R.string.onboardingBubbleNoPhoneNumber),
- TextData(R.string.onboardingBubbleCreatingAnAccountIsEasy, isOutgoing = true)
-)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt
new file mode 100644
index 0000000000..944e0fd4a0
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt
@@ -0,0 +1,236 @@
+package org.thoughtcrime.securesms.onboarding.landing
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.slideInVertically
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.delay
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.ui.AlertDialog
+import org.thoughtcrime.securesms.ui.DialogButtonModel
+import org.thoughtcrime.securesms.ui.GetString
+import org.thoughtcrime.securesms.ui.LocalDimensions
+import org.thoughtcrime.securesms.ui.PreviewTheme
+import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
+import org.thoughtcrime.securesms.ui.color.Colors
+import org.thoughtcrime.securesms.ui.color.LocalColors
+import org.thoughtcrime.securesms.ui.components.BorderlessHtmlButton
+import org.thoughtcrime.securesms.ui.components.PrimaryFillButton
+import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
+import org.thoughtcrime.securesms.ui.contentDescription
+import org.thoughtcrime.securesms.ui.h4
+import org.thoughtcrime.securesms.ui.large
+import kotlin.time.Duration.Companion.milliseconds
+
+@Preview
+@Composable
+private fun PreviewLandingScreen(
+ @PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
+) {
+ PreviewTheme(colors) {
+ LandingScreen({}, {}, {}, {})
+ }
+}
+
+@Composable
+internal fun LandingScreen(
+ createAccount: () -> Unit,
+ loadAccount: () -> Unit,
+ openTerms: () -> Unit,
+ openPrivacyPolicy: () -> Unit,
+) {
+ var count by remember { mutableStateOf(0) }
+ val listState = rememberLazyListState()
+
+ var isUrlDialogVisible by remember { mutableStateOf(false) }
+
+ if (isUrlDialogVisible) {
+ AlertDialog(
+ onDismissRequest = { isUrlDialogVisible = false },
+ title = stringResource(R.string.urlOpen),
+ text = stringResource(R.string.urlOpenBrowser),
+ buttons = listOf(
+ DialogButtonModel(
+ GetString(R.string.activity_landing_terms_of_service),
+ GetString(R.string.AccessibilityId_terms_of_service_button),
+ onClick = openTerms
+ ),
+ DialogButtonModel(
+ GetString(R.string.activity_landing_privacy_policy),
+ GetString(R.string.AccessibilityId_privacy_policy_button),
+ onClick = openPrivacyPolicy
+ )
+ )
+ )
+ }
+
+ LaunchedEffect(Unit) {
+ delay(500.milliseconds)
+ while(count < MESSAGES.size) {
+ count += 1
+ listState.animateScrollToItem(0.coerceAtLeast((count - 1)))
+ delay(1500L)
+ }
+ }
+
+ Column {
+ Column(modifier = Modifier
+ .weight(1f)
+ .padding(horizontal = LocalDimensions.current.marginOnboarding)
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+ Text(
+ stringResource(R.string.onboardingBubblePrivacyInYourPocket),
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = h4,
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingMedium))
+
+ LazyColumn(
+ state = listState,
+ modifier = Modifier
+ .heightIn(min = LocalDimensions.current.minScrollableViewHeight)
+ .fillMaxWidth()
+ .weight(3f),
+ verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.itemSpacingSmall)
+ ) {
+ items(
+ MESSAGES.take(count),
+ key = { it.stringId }
+ ) { item ->
+ AnimateMessageText(
+ stringResource(item.stringId),
+ item.isOutgoing
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+ }
+
+ Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.marginLarge)) {
+ PrimaryFillButton(
+ text = stringResource(R.string.onboardingAccountCreate),
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.CenterHorizontally)
+ .contentDescription(R.string.AccessibilityId_create_account_button),
+ onClick = createAccount
+ )
+ Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall))
+ PrimaryOutlineButton(
+ stringResource(R.string.onboardingAccountExists),
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.CenterHorizontally)
+ .contentDescription(R.string.AccessibilityId_restore_account_button),
+ onClick = loadAccount
+ )
+ BorderlessHtmlButton(
+ textId = R.string.onboardingTosPrivacy,
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.CenterHorizontally)
+ .contentDescription(R.string.AccessibilityId_open_url),
+ onClick = { isUrlDialogVisible = true }
+ )
+ Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall))
+ }
+ }
+}
+
+@Composable
+private fun AnimateMessageText(text: String, isOutgoing: Boolean, modifier: Modifier = Modifier) {
+ var visible by remember { mutableStateOf(false) }
+ LaunchedEffect(Unit) { visible = true }
+
+ Box {
+ // TODO [SES-2077] Use LazyList itemAnimation when we update to compose 1.7 or so.
+ MessageText(text, isOutgoing, Modifier.alpha(0f))
+
+ AnimatedVisibility(
+ visible = visible,
+ enter = fadeIn(animationSpec = tween(durationMillis = 300)) +
+ slideInVertically(animationSpec = tween(durationMillis = 300)) { it }
+ ) {
+ MessageText(text, isOutgoing, modifier)
+ }
+ }
+}
+
+@Composable
+private fun MessageText(text: String, isOutgoing: Boolean, modifier: Modifier) {
+ Box(modifier = modifier then Modifier.fillMaxWidth()) {
+ MessageText(
+ text,
+ color = if (isOutgoing) LocalColors.current.backgroundBubbleSent else LocalColors.current.backgroundBubbleReceived,
+ textColor = if (isOutgoing) LocalColors.current.textBubbleSent else LocalColors.current.textBubbleReceived,
+ modifier = Modifier.align(if (isOutgoing) Alignment.TopEnd else Alignment.TopStart)
+ )
+ }
+}
+
+@Composable
+private fun MessageText(
+ text: String,
+ color: Color,
+ modifier: Modifier = Modifier,
+ textColor: Color = Color.Unspecified
+) {
+ Card(
+ modifier = modifier.fillMaxWidth(0.666f),
+ shape = RoundedCornerShape(size = 13.dp),
+ backgroundColor = color,
+ elevation = 0.dp
+ ) {
+ Text(
+ text,
+ style = large,
+ color = textColor,
+ modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
+ )
+ }
+}
+
+private data class TextData(
+ @StringRes val stringId: Int,
+ val isOutgoing: Boolean = false
+)
+
+private val MESSAGES = listOf(
+ TextData(R.string.onboardingBubbleWelcomeToSession),
+ TextData(R.string.onboardingBubbleSessionIsEngineered, isOutgoing = true),
+ TextData(R.string.onboardingBubbleNoPhoneNumber),
+ TextData(R.string.onboardingBubbleCreatingAnAccountIsEasy, isOutgoing = true)
+)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/LandingActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/LandingActivity.kt
new file mode 100644
index 0000000000..cedde6879a
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/LandingActivity.kt
@@ -0,0 +1,53 @@
+package org.thoughtcrime.securesms.onboarding.landing
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import dagger.hilt.android.AndroidEntryPoint
+import org.session.libsession.utilities.TextSecurePreferences
+import org.thoughtcrime.securesms.BaseActionBarActivity
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
+import org.thoughtcrime.securesms.onboarding.LinkDeviceActivity
+import org.thoughtcrime.securesms.onboarding.pickname.startPickDisplayNameActivity
+import org.thoughtcrime.securesms.service.KeyCachingService
+import org.thoughtcrime.securesms.ui.setComposeContent
+import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
+import org.thoughtcrime.securesms.util.start
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class LandingActivity: BaseActionBarActivity() {
+
+ @Inject lateinit var prefs: TextSecurePreferences
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // We always hit this LandingActivity on launch - but if there is a previous instance of
+ // Session then close this activity to resume the last activity from the previous instance.
+ if (!isTaskRoot) { finish(); return }
+
+ setUpActionBarSessionLogo(true)
+
+ setComposeContent {
+ LandingScreen(
+ createAccount = {
+ prefs.setHasViewedSeed(false)
+ startPickDisplayNameActivity()
+ },
+ loadAccount = { start() },
+ openTerms = { open("https://getsession.org/terms-of-service") },
+ openPrivacyPolicy = { open("https://getsession.org/privacy-policy") }
+ )
+ }
+
+ IdentityKeyUtil.generateIdentityKeyPair(this)
+ TextSecurePreferences.setPasswordDisabled(this, true)
+ // AC: This is a temporary workaround to trick the old code that the screen is unlocked.
+ KeyCachingService.setMasterSecret(applicationContext, Object())
+ }
+
+ private fun open(url: String) {
+ Intent(Intent.ACTION_VIEW, Uri.parse(url)).let(::startActivity)
+ }
+}