diff --git a/app/build.gradle b/app/build.gradle
index 19357e704a..a1a6d43ced 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -243,6 +243,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
implementation 'androidx.activity:activity-ktx:1.5.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aa164fe7b5..eac4ef3300 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -136,6 +136,10 @@
android:name="org.thoughtcrime.securesms.preferences.SettingsActivity"
android:screenOrientation="portrait"
android:label="@string/sessionSettings" />
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index b23a6741cb..dca939f558 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
@@ -27,6 +27,9 @@ import android.os.Handler;
import android.os.HandlerThread;
import androidx.annotation.NonNull;
+import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.content.pm.ShortcutManagerCompat;
+import androidx.core.graphics.drawable.IconCompat;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
@@ -42,6 +45,7 @@ import org.session.libsession.snode.SnodeModule;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.ConfigFactoryUpdateListener;
import org.session.libsession.utilities.Device;
+import org.session.libsession.utilities.Environment;
import org.session.libsession.utilities.ProfilePictureUtilities;
import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences;
@@ -62,6 +66,7 @@ import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.database.Storage;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.EmojiSearchData;
+import org.thoughtcrime.securesms.debugmenu.DebugActivity;
import org.thoughtcrime.securesms.dependencies.AppComponent;
import org.thoughtcrime.securesms.dependencies.ConfigFactory;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
@@ -107,6 +112,7 @@ import dagger.hilt.EntryPoints;
import dagger.hilt.android.HiltAndroidApp;
import kotlin.Unit;
import network.loki.messenger.BuildConfig;
+import network.loki.messenger.R;
import network.loki.messenger.libsession_util.ConfigBase;
import network.loki.messenger.libsession_util.UserProfile;
@@ -232,7 +238,8 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase();
- SnodeModule.Companion.configure(apiDB, broadcaster);
+ boolean useTestNet = textSecurePreferences.getEnvironment() == Environment.TEST_NET;
+ SnodeModule.Companion.configure(apiDB, broadcaster, useTestNet);
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
@@ -248,6 +255,22 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create();
HTTP.INSTANCE.setConnectedToNetwork(networkConstraint::isMet);
+
+ // add our shortcut debug menu if we are not in a release build
+ if (BuildConfig.BUILD_TYPE != "release") {
+ // add the config settings shortcut
+ Intent intent = new Intent(this, DebugActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+
+ ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, "shortcut_debug_menu")
+ .setShortLabel("Debug Menu")
+ .setLongLabel("Debug Menu")
+ .setIcon(IconCompat.createWithResource(this, R.drawable.ic_settings))
+ .setIntent(intent)
+ .build();
+
+ ShortcutManagerCompat.pushDynamicShortcut(this, shortcut);
+ }
}
@Override
@@ -486,7 +509,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
// Method to clear the local data - returns true on success otherwise false
/**
- * Clear all local profile data and message history then restart the app after a brief delay.
+ * Clear all local profile data and message history.
* @return true on success, false otherwise.
*/
@SuppressLint("ApplySharedPref")
@@ -498,6 +521,16 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
return false;
}
configFactory.keyPairChanged();
+ return true;
+ }
+
+ /**
+ * Clear all local profile data and message history then restart the app after a brief delay.
+ * @return true on success, false otherwise.
+ */
+ @SuppressLint("ApplySharedPref")
+ public boolean clearAllDataAndRestart() {
+ clearAllData();
Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
return true;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt
index b39fa5b8d7..a3fd6ac1dd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt
@@ -35,7 +35,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana
super.onCreate(savedInstanceState, isReady)
binding = ActivitySelectContactsBinding.inflate(layoutInflater)
setContentView(binding.root)
- supportActionBar!!.title = resources.getString(R.string.contactSelect)
+ supportActionBar!!.title = resources.getString(R.string.membersInvite)
usersToExclude = intent.getStringArrayExtra(usersToExcludeKey)?.toSet() ?: setOf()
val emptyStateText = intent.getStringExtra(emptyStateTextKey)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
index 989be7880b..f6e37da6a3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
@@ -2192,7 +2192,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun showMessageDetail(messages: Set) {
Intent(this, MessageDetailActivity::class.java)
.apply { putExtra(MESSAGE_TIMESTAMP, messages.first().timestamp) }
- .let { handleMessageDetail.launch(it) }
+ .let {
+ handleMessageDetail.launch(it)
+ overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
+ }
endActionMode()
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt
index 8e2b88f25b..a830543ec1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.annotation.SuppressLint
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent.ACTION_UP
@@ -15,6 +16,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
@@ -28,6 +30,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -35,6 +38,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
@@ -58,23 +62,21 @@ import org.thoughtcrime.securesms.ui.Avatar
import org.thoughtcrime.securesms.ui.CarouselNextButton
import org.thoughtcrime.securesms.ui.CarouselPrevButton
import org.thoughtcrime.securesms.ui.Cell
-import org.thoughtcrime.securesms.ui.CellNoMargin
-import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator
import org.thoughtcrime.securesms.ui.LargeItemButton
+import org.thoughtcrime.securesms.ui.TitledText
+import org.thoughtcrime.securesms.ui.setComposeContent
+import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
+import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
-import org.thoughtcrime.securesms.ui.TitledText
import org.thoughtcrime.securesms.ui.theme.ThemeColors
-import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.blackAlpha40
-import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
-import org.thoughtcrime.securesms.ui.setComposeContent
-import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.bold
+import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
import org.thoughtcrime.securesms.ui.theme.monospace
import javax.inject.Inject
@@ -191,8 +193,11 @@ fun CellMetadata(
) {
state.apply {
if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return
- CellWithPaddingAndMargin {
- Column(verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)) {
+ Cell(modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)) {
+ Column(
+ modifier = Modifier.padding(LocalDimensions.current.spacing),
+ verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
+ ) {
TitledText(sent)
TitledText(received)
TitledErrorText(error)
@@ -215,7 +220,7 @@ fun CellButtons(
onResend: (() -> Unit)? = null,
onDelete: () -> Unit = {},
) {
- Cell {
+ Cell(modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)) {
Column {
onReply?.let {
LargeItemButton(
@@ -254,8 +259,11 @@ fun Carousel(attachments: List, onClick: (Int) -> Unit) {
Row {
CarouselPrevButton(pagerState)
Box(modifier = Modifier.weight(1f)) {
- CellCarousel(pagerState, attachments, onClick)
- HorizontalPagerIndicator(pagerState)
+ CarouselPager(pagerState, attachments, onClick)
+ HorizontalPagerIndicator(
+ pagerState = pagerState,
+ modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing)
+ )
ExpandButton(
modifier = Modifier
.align(Alignment.BottomEnd)
@@ -273,12 +281,15 @@ fun Carousel(attachments: List, onClick: (Int) -> Unit) {
ExperimentalGlideComposeApi::class
)
@Composable
-private fun CellCarousel(
+private fun CarouselPager(
pagerState: PagerState,
attachments: List,
onClick: (Int) -> Unit
) {
- CellNoMargin {
+ Cell(
+ modifier = Modifier
+ .clip(MaterialTheme.shapes.small)
+ ) {
HorizontalPager(state = pagerState) { i ->
GlideImage(
contentScale = ContentScale.Crop,
@@ -317,6 +328,33 @@ fun PreviewMessageDetails(
PreviewTheme(colors) {
MessageDetails(
state = MessageDetailsState(
+ imageAttachments = listOf(
+ Attachment(
+ fileDetails = listOf(
+ TitledText(R.string.attachmentsFileId, "Screen Shot 2023-07-06 at 11.35.50 am.png")
+ ),
+ fileName = "Screen Shot 2023-07-06 at 11.35.50 am.png",
+ uri = Uri.parse(""),
+ hasImage = true
+ ),
+ Attachment(
+ fileDetails = listOf(
+ TitledText(R.string.attachmentsFileId, "Screen Shot 2023-07-06 at 11.35.50 am.png")
+ ),
+ fileName = "Screen Shot 2023-07-06 at 11.35.50 am.png",
+ uri = Uri.parse(""),
+ hasImage = true
+ ),
+ Attachment(
+ fileDetails = listOf(
+ TitledText(R.string.attachmentsFileId, "Screen Shot 2023-07-06 at 11.35.50 am.png")
+ ),
+ fileName = "Screen Shot 2023-07-06 at 11.35.50 am.png",
+ uri = Uri.parse(""),
+ hasImage = true
+ )
+
+ ),
nonImageAttachmentFileDetails = listOf(
TitledText(R.string.attachmentsFileId, "Screen Shot 2023-07-06 at 11.35.50 am.png"),
TitledText(R.string.attachmentsFileType, "image/png"),
@@ -337,7 +375,7 @@ fun PreviewMessageDetails(
fun FileDetails(fileDetails: List) {
if (fileDetails.isEmpty()) return
- Cell {
+ Cell(modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)) {
FlowRow(
modifier = Modifier.padding(horizontal = LocalDimensions.current.xsSpacing, vertical = LocalDimensions.current.spacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugActivity.kt
new file mode 100644
index 0000000000..828b3c3a1e
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugActivity.kt
@@ -0,0 +1,21 @@
+package org.thoughtcrime.securesms.debugmenu
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import dagger.hilt.android.AndroidEntryPoint
+import org.thoughtcrime.securesms.ui.setComposeContent
+
+
+@AndroidEntryPoint
+class DebugActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setComposeContent {
+ DebugMenuScreen(
+ onClose = { finish() }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
new file mode 100644
index 0000000000..5eb378ef30
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
@@ -0,0 +1,172 @@
+package org.thoughtcrime.securesms.debugmenu
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import network.loki.messenger.BuildConfig
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel.Commands.ChangeEnvironment
+import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel.Commands.HideEnvironmentWarningDialog
+import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel.Commands.ShowEnvironmentWarningDialog
+import org.thoughtcrime.securesms.ui.AlertDialog
+import org.thoughtcrime.securesms.ui.Cell
+import org.thoughtcrime.securesms.ui.DialogButtonModel
+import org.thoughtcrime.securesms.ui.GetString
+import org.thoughtcrime.securesms.ui.LoadingDialog
+import org.thoughtcrime.securesms.ui.components.BackAppBar
+import org.thoughtcrime.securesms.ui.components.DropDown
+import org.thoughtcrime.securesms.ui.theme.LocalColors
+import org.thoughtcrime.securesms.ui.theme.LocalDimensions
+import org.thoughtcrime.securesms.ui.theme.LocalType
+import org.thoughtcrime.securesms.ui.theme.PreviewTheme
+import org.thoughtcrime.securesms.ui.theme.bold
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DebugMenu(
+ uiState: DebugMenuViewModel.UIState,
+ sendCommand: (DebugMenuViewModel.Commands) -> Unit,
+ modifier: Modifier = Modifier,
+ onClose: () -> Unit
+){
+ val snackbarHostState = remember { SnackbarHostState() }
+
+ Scaffold(
+ modifier = modifier.fillMaxSize(),
+ snackbarHost = {
+ SnackbarHost(hostState = snackbarHostState)
+ }
+ ) { contentPadding ->
+ // display a snackbar when required
+ LaunchedEffect(uiState.snackMessage) {
+ if(!uiState.snackMessage.isNullOrEmpty()){
+ snackbarHostState.showSnackbar(uiState.snackMessage)
+ }
+ }
+
+ // Alert dialogs
+ if (uiState.showEnvironmentWarningDialog) {
+ AlertDialog(
+ onDismissRequest = { sendCommand(HideEnvironmentWarningDialog) },
+ title = "Are you sure you want to switch environments?",
+ text = "Changing this setting will result in all conversations and Snode data being cleared...",
+ showCloseButton = false, // don't display the 'x' button
+ buttons = listOf(
+ DialogButtonModel(
+ text = GetString(R.string.cancel),
+ contentDescription = GetString(R.string.cancel),
+ onClick = { sendCommand(HideEnvironmentWarningDialog) }
+ ),
+ DialogButtonModel(
+ text = GetString(R.string.ok),
+ contentDescription = GetString(R.string.ok),
+ onClick = { sendCommand(ChangeEnvironment) }
+ )
+ )
+ )
+ }
+
+ if (uiState.showEnvironmentLoadingDialog) {
+ LoadingDialog(title = "Changing Environment...")
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(contentPadding)
+ .fillMaxSize()
+ .background(color = LocalColors.current.background)
+ ) {
+ // App bar
+ BackAppBar(title = "Debug Menu", onBack = onClose)
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = LocalDimensions.current.spacing)
+ .verticalScroll(rememberScrollState())
+ ) {
+ // Info pane
+ DebugCell("App Info") {
+ Text(
+ text = "Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - ${
+ BuildConfig.GIT_HASH.take(
+ 6
+ )
+ })",
+ style = LocalType.current.base
+ )
+ }
+
+ // Environment
+ DebugCell("Environment") {
+ DropDown(
+ modifier = Modifier.fillMaxWidth(0.6f),
+ selectedText = uiState.currentEnvironment,
+ values = uiState.environments,
+ onValueSelected = {
+ sendCommand(ShowEnvironmentWarningDialog(it))
+ }
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ColumnScope.DebugCell(
+ title: String,
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit
+) {
+ Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
+
+ Cell {
+ Column(
+ modifier = modifier.padding(LocalDimensions.current.spacing)
+ ) {
+ Text(
+ text = title,
+ style = LocalType.current.large.bold()
+ )
+
+ Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
+
+ content()
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewDebugMenu(){
+ PreviewTheme {
+ DebugMenu(
+ uiState = DebugMenuViewModel.UIState(
+ currentEnvironment = "Development",
+ environments = listOf("Development", "Production"),
+ snackMessage = null,
+ showEnvironmentWarningDialog = false,
+ showEnvironmentLoadingDialog = false
+ ),
+ sendCommand = {},
+ onClose = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuScreen.kt
new file mode 100644
index 0000000000..6c0f22805a
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuScreen.kt
@@ -0,0 +1,23 @@
+package org.thoughtcrime.securesms.debugmenu
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.viewmodel.compose.viewModel
+
+@Composable
+fun DebugMenuScreen(
+ modifier: Modifier = Modifier,
+ debugMenuViewModel: DebugMenuViewModel = viewModel(),
+ onClose: () -> Unit
+) {
+ val uiState by debugMenuViewModel.uiState.collectAsState()
+
+ DebugMenu(
+ modifier = modifier,
+ uiState = uiState,
+ sendCommand = debugMenuViewModel::onCommand,
+ onClose = onClose
+ )
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt
new file mode 100644
index 0000000000..750b3e20c7
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt
@@ -0,0 +1,115 @@
+package org.thoughtcrime.securesms.debugmenu
+
+import android.app.Application
+import android.widget.Toast
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import network.loki.messenger.R
+import org.session.libsession.messaging.open_groups.OpenGroupApi
+import org.session.libsession.snode.SnodeAPI
+import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.ApplicationContext
+import org.session.libsession.utilities.Environment
+import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
+import javax.inject.Inject
+
+@HiltViewModel
+class DebugMenuViewModel @Inject constructor(
+ private val application: Application,
+ private val textSecurePreferences: TextSecurePreferences
+) : ViewModel() {
+ private val TAG = "DebugMenu"
+
+ private val _uiState = MutableStateFlow(
+ UIState(
+ currentEnvironment = textSecurePreferences.getEnvironment().label,
+ environments = Environment.entries.map { it.label },
+ snackMessage = null,
+ showEnvironmentWarningDialog = false,
+ showEnvironmentLoadingDialog = false
+ )
+ )
+ val uiState: StateFlow
+ get() = _uiState
+
+ private var temporaryEnv: Environment? = null
+
+ fun onCommand(command: Commands) {
+ when (command) {
+ is Commands.ChangeEnvironment -> changeEnvironment()
+
+ is Commands.HideEnvironmentWarningDialog -> _uiState.value =
+ _uiState.value.copy(showEnvironmentWarningDialog = false)
+
+ is Commands.ShowEnvironmentWarningDialog ->
+ showEnvironmentWarningDialog(command.environment)
+ }
+ }
+
+ private fun showEnvironmentWarningDialog(environment: String) {
+ if(environment == _uiState.value.currentEnvironment) return
+ val env = Environment.entries.firstOrNull { it.label == environment } ?: return
+
+ temporaryEnv = env
+
+ _uiState.value = _uiState.value.copy(showEnvironmentWarningDialog = true)
+ }
+
+ private fun changeEnvironment() {
+ val env = temporaryEnv ?: return
+
+ // show a loading state
+ _uiState.value = _uiState.value.copy(
+ showEnvironmentWarningDialog = false,
+ showEnvironmentLoadingDialog = true
+ )
+
+ // clear remote and local data, then restart the app
+ viewModelScope.launch {
+ try {
+ ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(application).get()
+ } catch (e: Exception) {
+ // we can ignore fails here as we might be switching environments before the user gets a public key
+ }
+ ApplicationContext.getInstance(application).clearAllData().let { success ->
+ if(success){
+ // save the environment
+ textSecurePreferences.setEnvironment(env)
+ delay(500)
+ ApplicationContext.getInstance(application).restartApplication()
+ } else {
+ _uiState.value = _uiState.value.copy(
+ showEnvironmentWarningDialog = false,
+ showEnvironmentLoadingDialog = false
+ )
+ Log.e(TAG, "Failed to force sync when deleting data")
+ _uiState.value = _uiState.value.copy(snackMessage = "Sorry, something went wrong...")
+ return@launch
+ }
+ }
+ }
+ }
+
+ data class UIState(
+ val currentEnvironment: String,
+ val environments: List,
+ val snackMessage: String?,
+ val showEnvironmentWarningDialog: Boolean,
+ val showEnvironmentLoadingDialog: Boolean
+ )
+
+ sealed class Commands {
+ object ChangeEnvironment : Commands()
+ data class ShowEnvironmentWarningDialog(val environment: String) : Commands()
+ object HideEnvironmentWarningDialog : Commands()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt
index 7eee4eeaa8..1e093bd7e2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt
@@ -47,7 +47,7 @@ internal fun MessageNotificationsScreen(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
- CircularProgressIndicator(LocalColors.current.primary)
+ CircularProgressIndicator(color = LocalColors.current.primary)
}
return
diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotificationsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotificationsViewModel.kt
index a39f270bf2..f1f5bee89f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotificationsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotificationsViewModel.kt
@@ -72,7 +72,7 @@ internal class MessageNotificationsViewModel(
_uiStates.update { it.copy(clearData = true) }
viewModelScope.launch(Dispatchers.IO) {
- ApplicationContext.getInstance(application).clearAllData()
+ ApplicationContext.getInstance(application).clearAllDataAndRestart()
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt
index d67bc97ef2..ca6cf32ce2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt
@@ -127,7 +127,7 @@ class ClearAllDataDialog : DialogFragment() {
}
return
}
- ApplicationContext.getInstance(context).clearAllData().let { success ->
+ ApplicationContext.getInstance(context).clearAllDataAndRestart().let { success ->
withContext(Main) {
if (success) {
dismiss()
@@ -164,7 +164,7 @@ class ClearAllDataDialog : DialogFragment() {
}
else if (deletionResultMap.values.all { it }) {
// ..otherwise if the network data deletion was successful proceed to delete the local data as well.
- ApplicationContext.getInstance(context).clearAllData()
+ ApplicationContext.getInstance(context).clearAllDataAndRestart()
withContext(Main) { dismiss() }
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt
index d9c74a5b4d..bfba7377a7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt
@@ -60,6 +60,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
+import org.session.libsession.utilities.NonTranslatableStringConstants.DEBUG_MENU
import org.session.libsession.utilities.ProfileKeyUtil
import org.session.libsession.utilities.ProfilePictureUtilities
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
@@ -72,6 +73,7 @@ import org.session.libsignal.utilities.Util.SECURE_RANDOM
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection
import org.thoughtcrime.securesms.components.ProfilePictureView
+import org.thoughtcrime.securesms.debugmenu.DebugActivity
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.home.PathActivity
import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
@@ -164,6 +166,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, isReady)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
+
+ // set the toolbar icon to a close icon
+ supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
}
override fun onStart() {
@@ -176,8 +181,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
btnGroupNameDisplay.text = getDisplayName()
publicKeyTextView.text = hexEncodedPublicKey
val gitCommitFirstSixChars = BuildConfig.GIT_HASH.take(6)
-
- val versionDetails = " ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars)"
+ val environment: String = if(BuildConfig.BUILD_TYPE == "release") "" else " - ${prefs.getEnvironment().label}"
+ val versionDetails = " ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars) $environment"
val versionString = Phrase.from(applicationContext, R.string.updateVersion).put(VERSION_KEY, versionDetails).format()
versionTextView.text = versionString
}
@@ -187,6 +192,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
}
+ override fun finish() {
+ super.finish()
+ overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_bottom)
+ }
+
private fun getDisplayName(): String =
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
@@ -484,10 +494,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
@Composable
fun Buttons() {
- Column {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = LocalDimensions.current.spacing)
+ ) {
Row(
modifier = Modifier
- .padding(horizontal = LocalDimensions.current.spacing)
.padding(top = LocalDimensions.current.xxsSpacing),
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
) {
@@ -509,58 +521,50 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
Cell {
Column {
+ // add the debug menu in non release builds
+ if (BuildConfig.BUILD_TYPE != "release") {
+ LargeItemButton(DEBUG_MENU, R.drawable.ic_settings) { push() }
+ Divider()
+ }
+
Crossfade(if (hasPaths) R.drawable.ic_status else R.drawable.ic_path_yellow, label = "path") {
- LargeItemButtonWithDrawable(R.string.onionRoutingPath, it) { show() }
+ LargeItemButtonWithDrawable(R.string.onionRoutingPath, it) { push() }
}
Divider()
- LargeItemButton(
- R.string.sessionPrivacy,
- R.drawable.ic_privacy_icon
- ) { show() }
+
+ LargeItemButton(R.string.sessionPrivacy, R.drawable.ic_privacy_icon) { push() }
Divider()
- LargeItemButton(
- R.string.sessionNotifications,
- R.drawable.ic_speaker,
- Modifier.contentDescription(R.string.AccessibilityId_notifications)
- ) { show() }
+
+ LargeItemButton(R.string.sessionNotifications, R.drawable.ic_speaker, Modifier.contentDescription(R.string.AccessibilityId_notifications)) { push() }
Divider()
- LargeItemButton(
- R.string.sessionConversations,
- R.drawable.ic_conversations,
- Modifier.contentDescription(R.string.AccessibilityId_sessionConversations)
- ) { show() }
+
+ LargeItemButton(R.string.sessionConversations, R.drawable.ic_conversations, Modifier.contentDescription(R.string.AccessibilityId_sessionConversations)) { push() }
Divider()
- LargeItemButton(
- R.string.sessionMessageRequests,
- R.drawable.ic_message_requests,
- Modifier.contentDescription(R.string.AccessibilityId_sessionMessageRequests)
- ) { show() }
+
+ LargeItemButton(R.string.sessionMessageRequests, R.drawable.ic_message_requests, Modifier.contentDescription(R.string.AccessibilityId_sessionMessageRequests)) { push() }
Divider()
- LargeItemButton(
- R.string.sessionAppearance,
- R.drawable.ic_appearance,
- Modifier.contentDescription(R.string.AccessibilityId_sessionAppearance)
- ) { show() }
+
+ LargeItemButton(R.string.sessionAppearance, R.drawable.ic_appearance, Modifier.contentDescription(R.string.AccessibilityId_sessionAppearance)) { push() }
Divider()
+
LargeItemButton(
R.string.sessionInviteAFriend,
R.drawable.ic_invite_friend,
Modifier.contentDescription(R.string.AccessibilityId_sessionInviteAFriend)
) { sendInvitationToUseSession() }
Divider()
+
+ // Only show the recovery password option if the user has not chosen to permanently hide it
if (!prefs.getHidePassword()) {
LargeItemButton(
R.string.sessionRecoveryPassword,
R.drawable.ic_shield_outline,
Modifier.contentDescription(R.string.AccessibilityId_sessionRecoveryPasswordMenuItem)
- ) { show() }
+ ) { push() }
Divider()
}
- LargeItemButton(
- R.string.sessionHelp,
- R.drawable.ic_help,
- Modifier.contentDescription(R.string.AccessibilityId_help)
- ) { show() }
+
+ LargeItemButton(R.string.sessionHelp, R.drawable.ic_help, Modifier.contentDescription(R.string.AccessibilityId_help)) { push() }
Divider()
LargeItemButton(R.string.sessionClearData,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt
index 74e0f9f145..4bc2724d6d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.recoverypassword
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
@@ -25,19 +26,19 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
-import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
-import org.thoughtcrime.securesms.ui.theme.LocalDimensions
-import org.thoughtcrime.securesms.ui.theme.PreviewTheme
-import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
+import org.thoughtcrime.securesms.ui.Cell
import org.thoughtcrime.securesms.ui.SessionShieldIcon
-import org.thoughtcrime.securesms.ui.theme.ThemeColors
-import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.QrImage
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
import org.thoughtcrime.securesms.ui.components.border
import org.thoughtcrime.securesms.ui.contentDescription
+import org.thoughtcrime.securesms.ui.theme.LocalColors
+import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
+import org.thoughtcrime.securesms.ui.theme.PreviewTheme
+import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
+import org.thoughtcrime.securesms.ui.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.monospace
@Composable
@@ -53,6 +54,7 @@ internal fun RecoveryPasswordScreen(
.contentDescription(R.string.AccessibilityId_sessionRecoveryPassword)
.verticalScroll(rememberScrollState())
.padding(bottom = LocalDimensions.current.smallSpacing)
+ .padding(horizontal = LocalDimensions.current.spacing)
) {
RecoveryPasswordCell(mnemonic, seed, copyMnemonic)
HideRecoveryPasswordCell(onHide)
@@ -69,8 +71,10 @@ private fun RecoveryPasswordCell(
mutableStateOf(false)
}
- CellWithPaddingAndMargin {
- Column {
+ Cell {
+ Column(
+ modifier = Modifier.padding(LocalDimensions.current.smallSpacing)
+ ) {
Row {
Text(
stringResource(R.string.sessionRecoveryPassword),
@@ -148,8 +152,10 @@ private fun RecoveryPassword(mnemonic: String) {
@Composable
private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
- CellWithPaddingAndMargin {
- Row {
+ Cell {
+ Row(
+ modifier = Modifier.padding(LocalDimensions.current.smallSpacing)
+ ) {
Column(
Modifier.weight(1f)
) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt
index f271b348db..1dd1df46cb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt
@@ -3,13 +3,16 @@ package org.thoughtcrime.securesms.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@@ -29,6 +32,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
+import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
@@ -48,6 +52,7 @@ class DialogButtonModel(
@Composable
fun AlertDialog(
onDismissRequest: () -> Unit,
+ modifier: Modifier = Modifier,
title: String? = null,
text: String? = null,
buttons: List? = null,
@@ -55,18 +60,10 @@ fun AlertDialog(
content: @Composable () -> Unit = {}
) {
BasicAlertDialog(
+ modifier = modifier,
onDismissRequest = onDismissRequest,
content = {
- Box(
- modifier = Modifier.background(
- color = LocalColors.current.backgroundSecondary,
- shape = MaterialTheme.shapes.small)
- .border(
- width = 1.dp,
- color = LocalColors.current.borders,
- shape = MaterialTheme.shapes.small)
-
- ) {
+ DialogBg {
// only show the 'x' button is required
if (showCloseButton) {
IconButton(
@@ -133,7 +130,7 @@ fun AlertDialog(
@Composable
fun DialogButton(
text: String,
- modifier: Modifier,
+ modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
onClick: () -> Unit
) {
@@ -154,6 +151,62 @@ fun DialogButton(
}
}
+@Composable
+fun DialogBg(
+ content: @Composable BoxScope.() -> Unit
+){
+ Box(
+ modifier = Modifier
+ .background(
+ color = LocalColors.current.backgroundSecondary,
+ shape = MaterialTheme.shapes.small
+ )
+ .border(
+ width = 1.dp,
+ color = LocalColors.current.borders,
+ shape = MaterialTheme.shapes.small
+ )
+
+ ) {
+ content()
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun LoadingDialog(
+ modifier: Modifier = Modifier,
+ title: String? = null,
+){
+ BasicAlertDialog(
+ modifier = modifier,
+ onDismissRequest = {},
+ content = {
+ DialogBg {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(LocalDimensions.current.spacing)
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ )
+
+ Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
+
+ title?.let {
+ Text(
+ it,
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = LocalType.current.large
+ )
+ }
+ }
+ }
+ }
+ )
+}
+
@Preview
@Composable
fun PreviewSimpleDialog() {
@@ -200,3 +253,13 @@ fun PreviewXCloseDialog() {
)
}
}
+
+@Preview
+@Composable
+fun PreviewLoadingDialog() {
+ PreviewTheme {
+ LoadingDialog(
+ title = stringResource(R.string.warning)
+ )
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Carousel.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Carousel.kt
index ea632d46e7..d94cfc929d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/Carousel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Carousel.kt
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -42,14 +43,17 @@ import kotlin.math.sign
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) {
- if (pagerState.pageCount >= 2) Box(
- modifier = Modifier
- .background(color = blackAlpha40, shape = pillShape)
- .align(Alignment.BottomCenter)
- .padding(LocalDimensions.current.xxsSpacing)
- ) {
- Box(modifier = Modifier.padding(LocalDimensions.current.xxsSpacing)) {
+fun BoxScope.HorizontalPagerIndicator(
+ pagerState: PagerState,
+ modifier: Modifier = Modifier
+) {
+ if (pagerState.pageCount >= 2){
+ Box(
+ modifier = modifier
+ .background(color = blackAlpha40, shape = pillShape)
+ .align(Alignment.BottomCenter)
+ .padding(LocalDimensions.current.xxsSpacing)
+ ) {
ClickableHorizontalPagerIndicator(
pagerState = pagerState,
pageCount = pagerState.pageCount
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
index effcdca057..da1dd8e280 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -105,7 +106,7 @@ fun OptionsCard(card: OptionsCardData, callbacks: Callbacks) {
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
- CellNoMargin {
+ Cell {
LazyColumn(
modifier = Modifier.heightIn(max = 5000.dp)
) {
@@ -173,6 +174,38 @@ fun LargeItemButton(
)
}
+@Composable
+fun LargeItemButton(
+ text: String,
+ @DrawableRes icon: Int,
+ modifier: Modifier = Modifier,
+ colors: ButtonColors = transparentButtonColors(),
+ onClick: () -> Unit
+) {
+ ItemButton(
+ text, icon, modifier.heightIn(min = LocalDimensions.current.minLargeItemButtonHeight),
+ LocalType.current.h8, colors, onClick
+ )
+}
+
+@Composable
+fun ItemButton(text: String, icon: Int, modifier: Modifier, textStyle: TextStyle, colors: ButtonColors, onClick: () -> Unit) {
+ ItemButton(
+ text = text,
+ modifier = modifier,
+ icon = {
+ Icon(
+ painter = painterResource(id = icon),
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.Center)
+ )
+ },
+ textStyle = textStyle,
+ colors = colors,
+ onClick = onClick
+ )
+}
+
/**
* Courtesy [ItemButton] implementation that takes a [DrawableRes] for the [icon]
*/
@@ -257,32 +290,19 @@ fun PreviewItemButton() {
@Composable
fun Cell(
- padding: Dp = 0.dp,
- margin: Dp = LocalDimensions.current.spacing,
- content: @Composable () -> Unit
-) {
- CellWithPaddingAndMargin(padding, margin) { content() }
-}
-@Composable
-fun CellNoMargin(content: @Composable () -> Unit) {
- CellWithPaddingAndMargin(padding = 0.dp, margin = 0.dp) { content() }
-}
-
-@Composable
-fun CellWithPaddingAndMargin(
- padding: Dp = LocalDimensions.current.spacing,
- margin: Dp = LocalDimensions.current.spacing,
+ modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Box(
- modifier = Modifier
- .padding(horizontal = margin)
- .background(color = LocalColors.current.backgroundSecondary,
- shape = MaterialTheme.shapes.small)
+ modifier = modifier
+ .background(
+ color = LocalColors.current.backgroundSecondary,
+ shape = MaterialTheme.shapes.small
+ )
.wrapContentHeight()
.fillMaxWidth(),
) {
- Box(Modifier.padding(padding)) { content() }
+ content()
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/CircularProgressIndicator.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/CircularProgressIndicator.kt
index bbad82d29e..f555c82426 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/CircularProgressIndicator.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/CircularProgressIndicator.kt
@@ -8,18 +8,24 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
-fun CircularProgressIndicator(color: Color = LocalContentColor.current) {
+fun CircularProgressIndicator(
+ modifier: Modifier = Modifier,
+ color: Color = LocalContentColor.current
+) {
androidx.compose.material3.CircularProgressIndicator(
- modifier = Modifier.size(40.dp),
+ modifier = modifier.size(40.dp),
color = color,
strokeWidth = 2.dp
)
}
@Composable
-fun SmallCircularProgressIndicator(color: Color = LocalContentColor.current) {
+fun SmallCircularProgressIndicator(
+ modifier: Modifier = Modifier,
+ color: Color = LocalContentColor.current
+) {
androidx.compose.material3.CircularProgressIndicator(
- modifier = Modifier.size(20.dp),
+ modifier = modifier.size(20.dp),
color = color,
strokeWidth = 2.dp
)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt
new file mode 100644
index 0000000000..de7f437356
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/DropDown.kt
@@ -0,0 +1,109 @@
+package org.thoughtcrime.securesms.ui.components
+
+import androidx.compose.foundation.border
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MenuDefaults
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import org.thoughtcrime.securesms.ui.theme.LocalColors
+import org.thoughtcrime.securesms.ui.theme.LocalType
+import org.thoughtcrime.securesms.ui.theme.PreviewTheme
+import org.thoughtcrime.securesms.ui.theme.bold
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DropDown(
+ modifier: Modifier = Modifier,
+ selectedText: String,
+ values: List,
+ onValueSelected: (String) -> Unit
+) {
+ var expanded by remember { mutableStateOf(false) }
+
+ ExposedDropdownMenuBox(
+ modifier = modifier,
+ expanded = expanded,
+ onExpandedChange = {
+ expanded = !expanded
+ }
+ ) {
+ TextField(
+ value = selectedText,
+ onValueChange = {},
+ readOnly = true,
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
+ modifier = Modifier
+ .menuAnchor()
+ .border(
+ 1.dp,
+ color = LocalColors.current.borders,
+ shape = MaterialTheme.shapes.medium
+ ),
+ shape = MaterialTheme.shapes.medium,
+ colors = ExposedDropdownMenuDefaults.textFieldColors(
+ focusedContainerColor = LocalColors.current.backgroundSecondary,
+ unfocusedContainerColor = LocalColors.current.backgroundSecondary,
+ disabledIndicatorColor = Color.Transparent,
+ errorIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ disabledTrailingIconColor = LocalColors.current.primary,
+ errorTrailingIconColor = LocalColors.current.primary,
+ focusedTrailingIconColor = LocalColors.current.primary,
+ unfocusedTrailingIconColor = LocalColors.current.primary,
+ disabledTextColor = LocalColors.current.text,
+ errorTextColor = LocalColors.current.text,
+ focusedTextColor = LocalColors.current.text,
+ unfocusedTextColor = LocalColors.current.text
+ ),
+ textStyle = LocalType.current.base.bold()
+ )
+
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false }
+ ) {
+ values.forEach { item ->
+ DropdownMenuItem(
+ text = {
+ Text(
+ text = item,
+ style = LocalType.current.base
+ )
+ },
+ colors = MenuDefaults.itemColors(
+ textColor = LocalColors.current.text
+ ),
+ onClick = {
+ expanded = false
+ onValueSelected(item)
+ }
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewDropDown() {
+ PreviewTheme {
+ DropDown(
+ selectedText = "Hello",
+ values = listOf("First Item", "Second Item", "Third Item"),
+ onValueSelected = {})
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/SessionTypography.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/SessionTypography.kt
index 602affa6af..50c3957cbd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/SessionTypography.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/SessionTypography.kt
@@ -8,11 +8,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
-fun TextStyle.bold() = TextStyle.Default.copy(
+fun TextStyle.bold() = copy(
fontWeight = FontWeight.Bold
)
-fun TextStyle.monospace() = TextStyle.Default.copy(
+fun TextStyle.monospace() = copy(
fontFamily = FontFamily.Monospace
)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt
index c3b7eaca96..f50ac33e28 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/ActivityUtilities.kt
@@ -53,7 +53,7 @@ fun AppCompatActivity.push(intent: Intent, isForResult: Boolean = false) {
} else {
startActivity(intent)
}
- overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out)
+ overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
fun AppCompatActivity.show(intent: Intent, isForResult: Boolean = false) {
@@ -108,5 +108,5 @@ data class ThemeState (
)
inline fun Activity.show() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) }
-inline fun Activity.push(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out) }
+inline fun Activity.push(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) }
inline fun Context.start(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).apply { addFlags(FLAG_ACTIVITY_SINGLE_TOP) }.let(::startActivity)
diff --git a/app/src/main/res/anim/fade_scale_in.xml b/app/src/main/res/anim/fade_scale_in.xml
new file mode 100644
index 0000000000..3e004af2c2
--- /dev/null
+++ b/app/src/main/res/anim/fade_scale_in.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_to_bottom.xml b/app/src/main/res/anim/slide_to_bottom.xml
new file mode 100644
index 0000000000..0f62dfa487
--- /dev/null
+++ b/app/src/main/res/anim/slide_to_bottom.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml
new file mode 100644
index 0000000000..9fd7185331
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
index c3335c5a99..414394dff7 100644
--- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
+++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
@@ -2,7 +2,6 @@
package org.session.libsession.snode
-import android.os.Build
import com.goterl.lazysodium.exceptions.SodiumException
import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.PwHash
@@ -76,9 +75,7 @@ object SnodeAPI {
// Use port 4433 to enforce pinned certificates
private val seedNodePort = 4443
- private const val useTestnet = false
-
- private val seedNodePool = if (useTestnet) setOf(
+ private val seedNodePool = if (SnodeModule.shared.useTestNet) setOf(
"http://public.loki.foundation:38157"
) else setOf(
"https://seed1.getsession.org:$seedNodePort",
diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeModule.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeModule.kt
index 2deb30998e..a048cc124f 100644
--- a/libsession/src/main/java/org/session/libsession/snode/SnodeModule.kt
+++ b/libsession/src/main/java/org/session/libsession/snode/SnodeModule.kt
@@ -3,16 +3,18 @@ package org.session.libsession.snode
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.Broadcaster
-class SnodeModule(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {
+class SnodeModule(
+ val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster, val useTestNet: Boolean
+) {
companion object {
lateinit var shared: SnodeModule
val isInitialized: Boolean get() = Companion::shared.isInitialized
- fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster) {
+ fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster, useTestNet: Boolean) {
if (isInitialized) { return }
- shared = SnodeModule(storage, broadcaster)
+ shared = SnodeModule(storage, broadcaster, useTestNet)
}
}
}
\ No newline at end of file
diff --git a/libsession/src/main/java/org/session/libsession/utilities/Environment.kt b/libsession/src/main/java/org/session/libsession/utilities/Environment.kt
new file mode 100644
index 0000000000..a5a878cae4
--- /dev/null
+++ b/libsession/src/main/java/org/session/libsession/utilities/Environment.kt
@@ -0,0 +1,5 @@
+package org.session.libsession.utilities
+
+enum class Environment(val label: String) {
+ MAIN_NET("Mainnet"), TEST_NET("Testnet")
+}
\ No newline at end of file
diff --git a/libsession/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt b/libsession/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt
index 5c948c8389..da23762e0c 100644
--- a/libsession/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt
+++ b/libsession/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt
@@ -5,6 +5,7 @@ object NonTranslatableStringConstants {
const val APP_NAME = "Session"
const val ARBISCAN = "Arbiscan"
const val ARBITRUM = "Arbitrum"
+ const val DEBUG_MENU = "Debug Menu"
const val ETHEREUM = "Ethereum"
const val NETWORK_NAME = "Session Network"
const val TOKEN_NAME_LONG = "Session Token"
diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt
index b346da7651..4c974e7d60 100644
--- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt
+++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt
@@ -18,6 +18,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_LIGHT
+import org.session.libsession.utilities.TextSecurePreferences.Companion.ENVIRONMENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME
@@ -190,6 +191,8 @@ interface TextSecurePreferences {
fun setHidePassword(value: Boolean)
fun getLastVersionCheck(): Long
fun setLastVersionCheck()
+ fun getEnvironment(): Environment
+ fun setEnvironment(value: Environment)
companion object {
val TAG = TextSecurePreferences::class.simpleName
@@ -277,6 +280,7 @@ interface TextSecurePreferences {
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
const val SELECTED_ACCENT_COLOR = "selected_accent_color"
const val LAST_VERSION_CHECK = "pref_last_version_check"
+ const val ENVIRONMENT = "debug_environment"
const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config"
@@ -1572,6 +1576,17 @@ class AppTextSecurePreferences @Inject constructor(
setLongPreference(LAST_VERSION_CHECK, System.currentTimeMillis())
}
+ override fun getEnvironment(): Environment {
+ val environment = getStringPreference(ENVIRONMENT, null)
+ return if (environment != null) {
+ Environment.valueOf(environment)
+ } else Environment.MAIN_NET
+ }
+
+ override fun setEnvironment(value: Environment) {
+ setStringPreference(ENVIRONMENT, value.name)
+ }
+
override fun setShownCallNotification(): Boolean {
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
if (previousValue) return false