Debug menu

Added a debug menu
It can be accessed from the settings page or via an app shortcut (from the app icon)
This commit is contained in:
ThomasSession 2024-08-23 14:31:50 +10:00
parent 17caa213ee
commit 89b3c616d7
13 changed files with 371 additions and 325 deletions

View File

@ -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'

View File

@ -136,6 +136,10 @@
android:name="org.thoughtcrime.securesms.preferences.SettingsActivity"
android:screenOrientation="portrait"
android:label="@string/activity_settings_title" />
<activity
android:name="org.thoughtcrime.securesms.debugmenu.DebugActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.Session.DayNight.NoActionBar" />
<activity
android:name="org.thoughtcrime.securesms.home.PathActivity"
android:screenOrientation="portrait" />

View File

@ -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);
}
}

View File

@ -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() }
)
}
}
}

View File

@ -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)
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))
}
)
}
}
}
}
// Info pane
Box(
modifier = Modifier.fillMaxWidth()
// .background()
@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"
)
)
}
}
*/

View File

@ -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
)
}

View File

@ -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<UIState>
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<String, Boolean>? = 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<String>
)
sealed class Commands {
data class ChangeEnvironment(val environment: String) : Commands()
}
}

View File

@ -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<DebugActivity>() }
Divider()
}
Crossfade(if (hasPaths) R.drawable.ic_status else R.drawable.ic_path_yellow, label = "path") {
LargeItemButtonWithDrawable(R.string.activity_path_title, it) { push<PathActivity>() }
}

View File

@ -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<String>,
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 = {})
}
}

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:pathData="M25,15.916C19.991,15.916 15.916,19.991 15.916,25C15.916,30.009 19.991,34.084 25,34.084C30.009,34.084 34.084,30.009 34.084,25C34.084,19.991 30.009,15.916 25,15.916ZM25,31.813C21.243,31.813 18.187,28.757 18.187,25C18.187,21.243 21.243,18.187 25,18.187C28.757,18.187 31.813,21.243 31.813,25C31.813,28.757 28.757,31.813 25,31.813Z"
android:fillColor="#000000"/>
<path
android:pathData="M43.106,20.673L40.185,20.038C39.931,19.261 39.617,18.502 39.245,17.772L40.862,15.257C41.151,14.807 41.088,14.217 40.71,13.84L36.16,9.29C35.783,8.913 35.193,8.849 34.743,9.138L32.228,10.755C31.498,10.383 30.739,10.069 29.962,9.815L29.327,6.894C29.213,6.372 28.751,6 28.217,6H21.783C21.249,6 20.787,6.372 20.673,6.894L20.038,9.815C19.261,10.069 18.502,10.383 17.772,10.755L15.257,9.138C14.807,8.849 14.217,8.913 13.84,9.29L9.29,13.84C8.913,14.217 8.849,14.807 9.138,15.257L10.755,17.772C10.383,18.502 10.069,19.261 9.815,20.038L6.894,20.673C6.372,20.787 6,21.249 6,21.783V28.217C6,28.751 6.372,29.213 6.894,29.327L9.815,29.962C10.069,30.739 10.383,31.498 10.755,32.228L9.138,34.743C8.849,35.193 8.913,35.783 9.29,36.16L13.84,40.71C14.217,41.088 14.807,41.151 15.257,40.862L17.772,39.245C18.502,39.617 19.261,39.931 20.038,40.185L20.673,43.106C20.787,43.628 21.249,44 21.783,44H28.217C28.751,44 29.213,43.628 29.327,43.106L29.962,40.185C30.739,39.931 31.498,39.617 32.228,39.245L34.743,40.862C35.193,41.151 35.783,41.088 36.16,40.71L40.71,36.16C41.088,35.783 41.151,35.193 40.862,34.743L39.245,32.228C39.617,31.498 39.931,30.739 40.185,29.962L43.106,29.327C43.628,29.213 44,28.751 44,28.217V21.783C44,21.249 43.628,20.787 43.106,20.673ZM41.729,27.302L39.051,27.884C38.64,27.974 38.312,28.283 38.199,28.689C37.904,29.744 37.481,30.765 36.94,31.723C36.734,32.089 36.746,32.541 36.974,32.895L38.457,35.201L35.202,38.457L32.895,36.974C32.541,36.746 32.089,36.734 31.723,36.94C30.765,37.481 29.744,37.904 28.689,38.199C28.283,38.312 27.974,38.64 27.884,39.051L27.302,41.729H22.698L22.116,39.051C22.026,38.64 21.717,38.312 21.311,38.199C20.256,37.904 19.235,37.481 18.277,36.94C17.911,36.734 17.459,36.747 17.105,36.974L14.799,38.457L11.543,35.201L13.026,32.895C13.254,32.541 13.267,32.089 13.06,31.723C12.519,30.765 12.096,29.744 11.802,28.689C11.689,28.283 11.361,27.974 10.949,27.884L8.271,27.302V22.698L10.949,22.116C11.36,22.026 11.689,21.717 11.802,21.311C12.096,20.256 12.519,19.235 13.059,18.277C13.267,17.911 13.254,17.459 13.026,17.105L11.543,14.799L14.798,11.543L17.105,13.026C17.459,13.254 17.911,13.267 18.277,13.059C19.235,12.519 20.256,12.096 21.311,11.802C21.717,11.689 22.026,11.36 22.116,10.949L22.698,8.271H27.302L27.884,10.949C27.974,11.36 28.283,11.689 28.689,11.802C29.744,12.096 30.765,12.519 31.723,13.059C32.089,13.267 32.541,13.253 32.895,13.026L35.201,11.543L38.457,14.799L36.974,17.105C36.746,17.459 36.733,17.911 36.94,18.277C37.481,19.235 37.904,20.256 38.199,21.311C38.312,21.717 38.639,22.026 39.051,22.116L41.729,22.698V27.302Z"
android:fillColor="#000000"/>
</vector>

View File

@ -787,6 +787,7 @@
<string name="activity_settings_display_name_edit_text_hint">Enter a display name</string>
<string name="activity_settings_display_name_missing_error">Please pick a display name</string>
<string name="activity_settings_display_name_too_long_error">Please pick a shorter display name</string>
<string name="activity_settings_debug_button_title">Debug Menu</string>
<string name="activity_settings_privacy_button_title">Privacy</string>
<string name="activity_settings_notifications_button_title">Notifications</string>
<string name="activity_settings_message_requests_button_title">Message Requests</string>

View File

@ -0,0 +1,5 @@
package org.session.libsession.utilities
enum class Environment(val label: String) {
MAIN_NET("Mainnet"), TEST_NET("Testnet")
}

View File

@ -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