diff --git a/app/build.gradle b/app/build.gradle
index 36cdee2a3f..f292ec271a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -241,6 +241,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 98f9aa4b5f..f8765ff7bd 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/activity_settings_title" />
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index 6a605bd766..24d60fac8d 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;
@@ -62,6 +65,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 +111,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;
@@ -252,20 +257,17 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
// 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, ConfigSettingsActivity::class.java);
- intent.action = Intent.ACTION_VIEW
+ Intent intent = new Intent(this, DebugActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
- val shortcut = ShortcutInfoCompat.Builder(this, "shortcut_config_settings")
- .setShortLabel("Config Settings")
- .setLongLabel("Configuration Settings")
- .setIcon(IconCompat.createWithResource(this, R.drawable.ic_experience_stop))
+ 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()
+ .build();
- ShortcutManagerCompat.pushDynamicShortcut(this, shortcut)
-
- // Instant apps do not allow this functionality - only the full app
- configurationSettingsManager.allowDisplayOfDebugMenu(true)*/
+ ShortcutManagerCompat.pushDynamicShortcut(this, shortcut);
}
}
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
index f01dc8d5b0..9892d6e77d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
@@ -1,24 +1,44 @@
package org.thoughtcrime.securesms.debugmenu
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.IntrinsicSize
+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.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
+import network.loki.messenger.BuildConfig
+import org.thoughtcrime.securesms.ui.Cell
import org.thoughtcrime.securesms.ui.components.AppBarCloseIcon
import org.thoughtcrime.securesms.ui.components.AppBarText
+import org.thoughtcrime.securesms.ui.components.BackAppBar
+import org.thoughtcrime.securesms.ui.components.DropDown
import org.thoughtcrime.securesms.ui.components.appBarColors
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
){
@@ -28,22 +48,57 @@ fun DebugMenu(
.background(color = LocalColors.current.background)
) {
// App bar
- CenterAlignedTopAppBar(
- modifier = modifier,
- title = {
- AppBarText(title = "Debug Menu")
- },
- colors = appBarColors(LocalColors.current.background),
- navigationIcon = {
- AppBarCloseIcon(onClose = onClose)
- }
- )
+ BackAppBar(title = "Debug Menu", onBack = onClose)
- // Info pane
- Box(
- modifier = Modifier.fillMaxWidth()
- // .background()
- )
+ 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(DebugMenuViewModel.Commands.ChangeEnvironment(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()
+ }
}
}
@@ -52,304 +107,12 @@ fun DebugMenu(
fun PreviewDebugMenu(){
PreviewTheme {
DebugMenu(
+ uiState = DebugMenuViewModel.UIState(
+ currentEnvironment = "Development",
+ environments = listOf("Development", "Production")
+ ),
+ sendCommand = {},
onClose = {}
)
}
-}
-
-/*
-package net.artprocessors.eileen_capstone_ui.debug.settings
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-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.material.icons.Icons
-import androidx.compose.material.icons.outlined.Info
-import androidx.compose.material3.BottomSheetDefaults
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.rememberModalBottomSheetState
-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.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
-import com.jakewharton.processphoenix.ProcessPhoenix
-import net.artprocessors.debug_ui.common.BaseButton
-import net.artprocessors.debug_ui.common.BaseDebugTab
-import net.artprocessors.debug_ui.common.DebugColorBg
-import net.artprocessors.debug_ui.common.DebugColorFill
-import net.artprocessors.debug_ui.common.DebugColorPrimary
-import net.artprocessors.debug_ui.common.DebugColorTextSecondary
-import net.artprocessors.debug_ui.common.DebugColorTextTertiary
-import net.artprocessors.debug_ui.common.DebugTextCaption
-import net.artprocessors.debug_ui.common.DebugTextNormal
-import net.artprocessors.debug_ui.common.DebugTextTitle
-import net.artprocessors.debug_ui.common.LabeledDropDown
-import net.artprocessors.debug_ui.common.ShadowHorizontalSeparator
-import net.artprocessors.debug_ui.common.l
-import net.artprocessors.debug_ui.common.m
-import net.artprocessors.debug_ui.common.s
-import net.artprocessors.debug_ui.common.xl
-import net.artprocessors.debug_ui.common.xs
-import net.artprocessors.debug_ui.common.xxs
-import net.artprocessors.eileen_capstone_ui.debug.model.AppInfo
-import net.artprocessors.eileen_capstone_ui.debug.model.DeviceInfo
-import net.artprocessors.eileen_capstone_ui.debug.model.SettingsCommand
-import net.artprocessors.eileen_capstone_ui.debug.model.SettingsData
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal fun DebugSettingsTabContent(
- modifier: Modifier = Modifier,
- title: String,
- data: SettingsData,
- sendCommand: (SettingsCommand) -> Unit,
- onReset: () -> Unit
-) {
- var showSheet by remember { mutableStateOf(false) }
- val modalBottomSheetState = rememberModalBottomSheetState()
-
- val context = LocalContext.current
-
- BaseDebugTab(
- title = title,
- appBarEndContent = {
- IconButton(
- onClick = { showSheet = true }
- ) {
- Icon(
- imageVector = Icons.Outlined.Info,
- tint = DebugColorPrimary,
- contentDescription = "Additional info"
- )
- }
- }
- ) {
- Spacer(modifier = Modifier.height(m()))
-
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .padding(horizontal = s())
- .verticalScroll(scrollState)
- ) {
- // Environments
- LabeledDropDown(
- selectedText = data.selectedEnv,
- label = "Environment",
- values = data.envNames,
- onValueSelected = {
- sendCommand(SettingsCommand.SetEnvironment(it))
- }
- )
-
- Spacer(modifier = Modifier.height(s()))
-
- // Modes
- LabeledDropDown(
- selectedText = data.selectedMode,
- label = "Mode",
- values = data.modeNames,
- onValueSelected = {
- sendCommand(SettingsCommand.SetMode(it))
- }
- )
-
- Spacer(modifier = Modifier.height(s()))
-
- // Site Status
- LabeledDropDown(
- selectedText = data.selectedSiteStatus,
- label = "Site Status (Doesn't require a 'Reset' to take effect)",
- values = data.siteNames,
- onValueSelected = {
- sendCommand(SettingsCommand.SetSiteStatus(it))
- }
- )
-
- Spacer(modifier = Modifier.height(s()))
-
- // Positioning
- LabeledDropDown(
- selectedText = data.selectedPositioning,
- label = "Positioning System",
- values = data.positioning,
- onValueSelected = {
- sendCommand(SettingsCommand.SetPositioning(it))
- }
- )
- }
-
- ShadowHorizontalSeparator()
-
- Spacer(modifier = Modifier.height(s()))
-
-
- BaseButton(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = s()),
- text = "Reset",
- onClick = {
- // call the custom reset that can be provided by client of this composable
- onReset()
-
- // kill and restart the application
- ProcessPhoenix.triggerRebirth(context)
- },
- )
-
- Spacer(modifier = Modifier.height(xs()))
-
- Text(
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .padding(horizontal = s()),
- text = "Hit 'Reset' for these changes to take effect",
- style = DebugTextCaption,
- color = DebugColorTextSecondary
- )
-
- Spacer(modifier = Modifier.height(s()))
- }
-
- if (showSheet) {
- ModalBottomSheet(
- containerColor = DebugColorBg,
- onDismissRequest = { showSheet = false },
- sheetState = modalBottomSheetState,
- dragHandle = { BottomSheetDefaults.DragHandle(color = DebugColorTextTertiary) },
- ) {
- AboutAppScreen(appInfo = data.aboutData.appInfo, deviceInfo = data.aboutData.deviceInfo)
- }
- }
-}
-
-@Composable
-private fun AboutAppScreen(appInfo: AppInfo, deviceInfo: DeviceInfo) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier.verticalScroll(scrollState)
- ) {
- Text(
- modifier = Modifier
- .fillMaxWidth()
- .padding(bottom = s()),
- text = "About the App",
- textAlign = TextAlign.Center,
- style = DebugTextTitle
- )
-
- HorizontalDivider(color = DebugColorTextTertiary)
-
- Card(
- modifier = Modifier.padding(horizontal = m(), vertical = s()),
- colors = CardDefaults.cardColors(containerColor = DebugColorFill),
- ) {
- Column(
- modifier = Modifier.padding(xs())
- ) {
- val context = LocalContext.current.applicationContext
- val appContextInfo = context.applicationInfo
- val appLabelRes = appContextInfo.labelRes
- with(appInfo) {
- InfoRow(
- data = "Name",
- value = if (appLabelRes == 0) "${appContextInfo.nonLocalizedLabel}" else stringResource(appLabelRes)
- )
- InfoRow(data = "ID", value = context.packageName)
- InfoRow(data = "Variant", value = variant)
- InfoRow(data = "Version", value = version, isLast = true)
- }
- }
- }
-
- Card(
- modifier = Modifier.padding(horizontal = m(), vertical = s()),
- colors = CardDefaults.cardColors(containerColor = DebugColorFill),
- ) {
- Column(
- modifier = Modifier.padding(xs())
- ) {
- with(deviceInfo) {
- InfoRow(data = "Language", value = language)
- InfoRow(data = "Time Zone", value = timezone)
- InfoRow(data = "OS", value = os)
- InfoRow(data = "Hardware", value = hardware, isLast = true)
- }
- }
- }
-
- Text(
- modifier = Modifier
- .padding(horizontal = l())
- .padding(bottom = xl()),
- text = "Powered by Pladia™ · Built by Art Processors\n\n" +
- "Art Processors acknowledges the Palawa, Wurundjeri and all traditional custodians of the lands on which we work. We acknowledge their long history of story telling and pay our respects to their elders past, present and emerging.",
- style = DebugTextCaption.copy(color = DebugColorTextSecondary)
- )
- }
-}
-
-@Composable
-private fun InfoRow(data: String, value: String, isLast: Boolean = false) {
- Row(
- modifier = Modifier
- .padding(xs())
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(xxs()),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(text = data, style = DebugTextNormal)
- Text(
- modifier = Modifier.weight(1f),
- text = value,
- style = DebugTextCaption.copy(color = DebugColorTextSecondary),
- textAlign = TextAlign.End
- )
- }
- if (!isLast) HorizontalDivider(color = DebugColorTextTertiary)
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun PreviewAboutAppScreen() {
- Surface(modifier = Modifier.background(color = DebugColorBg)) {
- AboutAppScreen(
- appInfo = AppInfo(
- variant = "BYOD Test",
- version = "24.2.0 (23111000)"
- ),
- deviceInfo = DeviceInfo(
- language = "English [en]",
- timezone = "Australia/Melbourne",
- os = "Android 14",
- hardware = "Pixel 7"
- )
- )
- }
-}
- */
\ No newline at end of file
+}
\ 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..49353b3158
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt
@@ -0,0 +1,84 @@
+package org.thoughtcrime.securesms.debugmenu
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+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 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 }
+ )
+ )
+ val uiState: StateFlow
+ get() = _uiState
+
+ fun onCommand(command: Commands) {
+ when (command) {
+ is Commands.ChangeEnvironment -> changeEnvironment(command.environment)
+ }
+ }
+
+ private fun changeEnvironment(environment: String) {
+ if(environment == _uiState.value.currentEnvironment) return
+ val env = Environment.entries.firstOrNull { it.label == environment } ?: return
+
+ // show a loading state
+
+ // clear remote and local data, then restart the app
+ viewModelScope.launch {
+ val deletionResultMap: Map? = try {
+ val openGroups =
+ DatabaseComponent.get(application).lokiThreadDatabase().getAllOpenGroups()
+ openGroups.map { it.value.server }.toSet().forEach { server ->
+ OpenGroupApi.deleteAllInboxMessages(server).get()
+ }
+ SnodeAPI.deleteAllMessages().get()
+ } catch (e: Exception) {
+ Log.e(
+ TAG, "Failed to delete network message from debug menu", e
+ )
+ null
+ }
+
+ // If the network data deletion was successful proceed to delete the local data as well.
+ if (deletionResultMap?.values?.all { it } == true) {
+ // save the environment
+ textSecurePreferences.setEnvironment(env)
+
+ // clear local data and restart
+ ApplicationContext.getInstance(application).clearAllData()
+ } else { // the remote deletion failed, show an error
+
+ }
+ }
+ }
+
+ data class UIState(
+ val currentEnvironment: String,
+ val environments: List
+ )
+
+ sealed class Commands {
+ data class ChangeEnvironment(val environment: String) : Commands()
+ }
+}
\ No newline at end of file
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 77f5938d53..bfa85937aa 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt
@@ -68,6 +68,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
@@ -91,7 +92,6 @@ import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.NetworkUtils
import org.thoughtcrime.securesms.util.push
-import org.thoughtcrime.securesms.util.show
import java.io.File
import javax.inject.Inject
@@ -508,6 +508,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
Cell {
Column {
+ // add the debug menu in non release builds
+ if (BuildConfig.BUILD_TYPE != "release") {
+ LargeItemButton(R.string.activity_settings_debug_button_title, R.drawable.ic_settings) { push() }
+ Divider()
+ }
+
Crossfade(if (hasPaths) R.drawable.ic_status else R.drawable.ic_path_yellow, label = "path") {
LargeItemButtonWithDrawable(R.string.activity_path_title, it) { push() }
}
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/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/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 537038921d..d31a433436 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -787,6 +787,7 @@
Enter a display name
Please pick a display name
Please pick a shorter display name
+ Debug Menu
Privacy
Notifications
Message Requests
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/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt
index 5bf109843d..7a60b41bc5 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"
@@ -1554,6 +1558,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