Feature/debug menu (#1645)

* Reorganised cells

* Clipping content

* Simplifying Cell and leaving responsibility to modifier and content

* Fixing animations

Also fixing compose copy that wasn't set up properly...

* Debug menu

Added a debug menu
It can be accessed from the settings page or via an app shortcut (from the app icon)

* Finalising the debug menu

We can now switch environments between mainnet and testnet

* Update app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt

Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com>

---------

Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com>
This commit is contained in:
ThomasSession 2024-08-26 13:09:37 +10:00 committed by GitHub
parent bfbe4a8fd2
commit 1393335121
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 766 additions and 99 deletions

View File

@ -241,6 +241,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
implementation 'androidx.activity:activity-ktx:1.5.1' implementation 'androidx.activity:activity-ktx:1.5.1'

View File

@ -136,6 +136,10 @@
android:name="org.thoughtcrime.securesms.preferences.SettingsActivity" android:name="org.thoughtcrime.securesms.preferences.SettingsActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:label="@string/activity_settings_title" /> 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 <activity
android:name="org.thoughtcrime.securesms.home.PathActivity" android:name="org.thoughtcrime.securesms.home.PathActivity"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />

View File

@ -27,6 +27,9 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import androidx.annotation.NonNull; 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.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner; 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.Address;
import org.session.libsession.utilities.ConfigFactoryUpdateListener; import org.session.libsession.utilities.ConfigFactoryUpdateListener;
import org.session.libsession.utilities.Device; import org.session.libsession.utilities.Device;
import org.session.libsession.utilities.Environment;
import org.session.libsession.utilities.ProfilePictureUtilities; import org.session.libsession.utilities.ProfilePictureUtilities;
import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences; 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.Storage;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.EmojiSearchData; import org.thoughtcrime.securesms.database.model.EmojiSearchData;
import org.thoughtcrime.securesms.debugmenu.DebugActivity;
import org.thoughtcrime.securesms.dependencies.AppComponent; import org.thoughtcrime.securesms.dependencies.AppComponent;
import org.thoughtcrime.securesms.dependencies.ConfigFactory; import org.thoughtcrime.securesms.dependencies.ConfigFactory;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
@ -107,6 +112,7 @@ import dagger.hilt.EntryPoints;
import dagger.hilt.android.HiltAndroidApp; import dagger.hilt.android.HiltAndroidApp;
import kotlin.Unit; import kotlin.Unit;
import network.loki.messenger.BuildConfig; import network.loki.messenger.BuildConfig;
import network.loki.messenger.R;
import network.loki.messenger.libsession_util.ConfigBase; import network.loki.messenger.libsession_util.ConfigBase;
import network.loki.messenger.libsession_util.UserProfile; import network.loki.messenger.libsession_util.UserProfile;
@ -232,7 +238,8 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier()); messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this); broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase(); LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase();
SnodeModule.Companion.configure(apiDB, broadcaster); boolean useTestNet = textSecurePreferences.getEnvironment() == Environment.TEST_NET;
SnodeModule.Companion.configure(apiDB, broadcaster, useTestNet);
initializeExpiringMessageManager(); initializeExpiringMessageManager();
initializeTypingStatusRepository(); initializeTypingStatusRepository();
initializeTypingStatusSender(); initializeTypingStatusSender();
@ -248,6 +255,22 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create(); NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create();
HTTP.INSTANCE.setConnectedToNetwork(networkConstraint::isMet); 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 @Override
@ -486,7 +509,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
// Method to clear the local data - returns true on success otherwise false // 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. * @return true on success, false otherwise.
*/ */
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
@ -498,6 +521,16 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
return false; return false;
} }
configFactory.keyPairChanged(); 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)); Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
return true; return true;
} }

View File

@ -2085,7 +2085,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun showMessageDetail(messages: Set<MessageRecord>) { override fun showMessageDetail(messages: Set<MessageRecord>) {
Intent(this, MessageDetailActivity::class.java) Intent(this, MessageDetailActivity::class.java)
.apply { putExtra(MESSAGE_TIMESTAMP, messages.first().timestamp) } .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() endActionMode()
} }

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent.ACTION_UP 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.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth 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.shape.CircleShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -35,6 +38,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource 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.CarouselNextButton
import org.thoughtcrime.securesms.ui.CarouselPrevButton import org.thoughtcrime.securesms.ui.CarouselPrevButton
import org.thoughtcrime.securesms.ui.Cell 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.Divider
import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator
import org.thoughtcrime.securesms.ui.LargeItemButton 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.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider 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.ThemeColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.blackAlpha40 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.bold
import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
import org.thoughtcrime.securesms.ui.theme.monospace import org.thoughtcrime.securesms.ui.theme.monospace
import javax.inject.Inject import javax.inject.Inject
@ -191,8 +193,11 @@ fun CellMetadata(
) { ) {
state.apply { state.apply {
if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return
CellWithPaddingAndMargin { Cell(modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)) {
Column(verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)) { Column(
modifier = Modifier.padding(LocalDimensions.current.spacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
TitledText(sent) TitledText(sent)
TitledText(received) TitledText(received)
TitledErrorText(error) TitledErrorText(error)
@ -215,7 +220,7 @@ fun CellButtons(
onResend: (() -> Unit)? = null, onResend: (() -> Unit)? = null,
onDelete: () -> Unit = {}, onDelete: () -> Unit = {},
) { ) {
Cell { Cell(modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)) {
Column { Column {
onReply?.let { onReply?.let {
LargeItemButton( LargeItemButton(
@ -254,8 +259,11 @@ fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
Row { Row {
CarouselPrevButton(pagerState) CarouselPrevButton(pagerState)
Box(modifier = Modifier.weight(1f)) { Box(modifier = Modifier.weight(1f)) {
CellCarousel(pagerState, attachments, onClick) CarouselPager(pagerState, attachments, onClick)
HorizontalPagerIndicator(pagerState) HorizontalPagerIndicator(
pagerState = pagerState,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing)
)
ExpandButton( ExpandButton(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
@ -273,12 +281,15 @@ fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
ExperimentalGlideComposeApi::class ExperimentalGlideComposeApi::class
) )
@Composable @Composable
private fun CellCarousel( private fun CarouselPager(
pagerState: PagerState, pagerState: PagerState,
attachments: List<Attachment>, attachments: List<Attachment>,
onClick: (Int) -> Unit onClick: (Int) -> Unit
) { ) {
CellNoMargin { Cell(
modifier = Modifier
.clip(MaterialTheme.shapes.small)
) {
HorizontalPager(state = pagerState) { i -> HorizontalPager(state = pagerState) { i ->
GlideImage( GlideImage(
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
@ -317,6 +328,33 @@ fun PreviewMessageDetails(
PreviewTheme(colors) { PreviewTheme(colors) {
MessageDetails( MessageDetails(
state = MessageDetailsState( state = MessageDetailsState(
imageAttachments = listOf(
Attachment(
fileDetails = listOf(
TitledText(R.string.message_details_header__file_id, "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.message_details_header__file_id, "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.message_details_header__file_id, "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( nonImageAttachmentFileDetails = listOf(
TitledText(R.string.message_details_header__file_id, "Screen Shot 2023-07-06 at 11.35.50 am.png"), TitledText(R.string.message_details_header__file_id, "Screen Shot 2023-07-06 at 11.35.50 am.png"),
TitledText(R.string.message_details_header__file_type, "image/png"), TitledText(R.string.message_details_header__file_type, "image/png"),
@ -337,7 +375,7 @@ fun PreviewMessageDetails(
fun FileDetails(fileDetails: List<TitledText>) { fun FileDetails(fileDetails: List<TitledText>) {
if (fileDetails.isEmpty()) return if (fileDetails.isEmpty()) return
Cell { Cell(modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)) {
FlowRow( FlowRow(
modifier = Modifier.padding(horizontal = LocalDimensions.current.xsSpacing, vertical = LocalDimensions.current.spacing), modifier = Modifier.padding(horizontal = LocalDimensions.current.xsSpacing, vertical = LocalDimensions.current.spacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing) verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)

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

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

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,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<UIState>
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<String>,
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()
}
}

View File

@ -45,7 +45,7 @@ internal fun MessageNotificationsScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
CircularProgressIndicator(LocalColors.current.primary) CircularProgressIndicator(color = LocalColors.current.primary)
} }
return return

View File

@ -72,7 +72,7 @@ internal class MessageNotificationsViewModel(
_uiStates.update { it.copy(clearData = true) } _uiStates.update { it.copy(clearData = true) }
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
ApplicationContext.getInstance(application).clearAllData() ApplicationContext.getInstance(application).clearAllDataAndRestart()
} }
} }

View File

@ -125,7 +125,7 @@ class ClearAllDataDialog : DialogFragment() {
} }
return return
} }
ApplicationContext.getInstance(context).clearAllData().let { success -> ApplicationContext.getInstance(context).clearAllDataAndRestart().let { success ->
withContext(Main) { withContext(Main) {
if (success) { if (success) {
dismiss() dismiss()
@ -162,7 +162,7 @@ class ClearAllDataDialog : DialogFragment() {
} }
else if (deletionResultMap.values.all { it }) { else if (deletionResultMap.values.all { it }) {
// ..otherwise if the network data deletion was successful proceed to delete the local data as well. // ..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() } withContext(Main) { dismiss() }
} }
} }

View File

@ -68,6 +68,7 @@ import org.session.libsignal.utilities.Util.SECURE_RANDOM
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.avatar.AvatarSelection
import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.components.ProfilePictureView
import org.thoughtcrime.securesms.debugmenu.DebugActivity
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.home.PathActivity import org.thoughtcrime.securesms.home.PathActivity
import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity 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.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.NetworkUtils import org.thoughtcrime.securesms.util.NetworkUtils
import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -162,6 +162,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
binding = ActivitySettingsBinding.inflate(layoutInflater) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
// set the toolbar icon to a close icon
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
} }
override fun onStart() { override fun onStart() {
@ -174,7 +177,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
btnGroupNameDisplay.text = getDisplayName() btnGroupNameDisplay.text = getDisplayName()
publicKeyTextView.text = hexEncodedPublicKey publicKeyTextView.text = hexEncodedPublicKey
val gitCommitFirstSixChars = BuildConfig.GIT_HASH.take(6) val gitCommitFirstSixChars = BuildConfig.GIT_HASH.take(6)
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars)") val environment: String = if(BuildConfig.BUILD_TYPE == "release") "" else " - ${prefs.getEnvironment().label}"
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars) $environment")
} }
binding.composeView.setThemedContent { binding.composeView.setThemedContent {
@ -182,6 +186,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
} }
} }
override fun finish() {
super.finish()
overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_bottom)
}
private fun getDisplayName(): String = private fun getDisplayName(): String =
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey) TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
@ -473,10 +482,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
@Composable @Composable
fun Buttons() { fun Buttons() {
Column { Column(
Row(
modifier = Modifier modifier = Modifier
.padding(horizontal = LocalDimensions.current.spacing) .padding(horizontal = LocalDimensions.current.spacing)
) {
Row(
modifier = Modifier
.padding(top = LocalDimensions.current.xxsSpacing), .padding(top = LocalDimensions.current.xxsSpacing),
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing), horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
) { ) {
@ -498,27 +509,33 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
Cell { Cell {
Column { 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") { Crossfade(if (hasPaths) R.drawable.ic_status else R.drawable.ic_path_yellow, label = "path") {
LargeItemButtonWithDrawable(R.string.activity_path_title, it) { show<PathActivity>() } LargeItemButtonWithDrawable(R.string.activity_path_title, it) { push<PathActivity>() }
} }
Divider() Divider()
LargeItemButton(R.string.activity_settings_privacy_button_title, R.drawable.ic_privacy_icon) { show<PrivacySettingsActivity>() } LargeItemButton(R.string.activity_settings_privacy_button_title, R.drawable.ic_privacy_icon) { push<PrivacySettingsActivity>() }
Divider() Divider()
LargeItemButton(R.string.activity_settings_notifications_button_title, R.drawable.ic_speaker, Modifier.contentDescription(R.string.AccessibilityId_notifications)) { show<NotificationSettingsActivity>() } LargeItemButton(R.string.activity_settings_notifications_button_title, R.drawable.ic_speaker, Modifier.contentDescription(R.string.AccessibilityId_notifications)) { push<NotificationSettingsActivity>() }
Divider() Divider()
LargeItemButton(R.string.activity_settings_conversations_button_title, R.drawable.ic_conversations, Modifier.contentDescription(R.string.AccessibilityId_conversations)) { show<ChatSettingsActivity>() } LargeItemButton(R.string.activity_settings_conversations_button_title, R.drawable.ic_conversations, Modifier.contentDescription(R.string.AccessibilityId_conversations)) { push<ChatSettingsActivity>() }
Divider() Divider()
LargeItemButton(R.string.activity_settings_message_requests_button_title, R.drawable.ic_message_requests, Modifier.contentDescription(R.string.AccessibilityId_message_requests)) { show<MessageRequestsActivity>() } LargeItemButton(R.string.activity_settings_message_requests_button_title, R.drawable.ic_message_requests, Modifier.contentDescription(R.string.AccessibilityId_message_requests)) { push<MessageRequestsActivity>() }
Divider() Divider()
LargeItemButton(R.string.activity_settings_message_appearance_button_title, R.drawable.ic_appearance, Modifier.contentDescription(R.string.AccessibilityId_appearance)) { show<AppearanceSettingsActivity>() } LargeItemButton(R.string.activity_settings_message_appearance_button_title, R.drawable.ic_appearance, Modifier.contentDescription(R.string.AccessibilityId_appearance)) { push<AppearanceSettingsActivity>() }
Divider() Divider()
LargeItemButton(R.string.activity_settings_invite_button_title, R.drawable.ic_invite_friend, Modifier.contentDescription(R.string.AccessibilityId_invite_friend)) { sendInvitationToUseSession() } LargeItemButton(R.string.activity_settings_invite_button_title, R.drawable.ic_invite_friend, Modifier.contentDescription(R.string.AccessibilityId_invite_friend)) { sendInvitationToUseSession() }
Divider() Divider()
if (!prefs.getHidePassword()) { if (!prefs.getHidePassword()) {
LargeItemButton(R.string.sessionRecoveryPassword, R.drawable.ic_shield_outline, Modifier.contentDescription(R.string.AccessibilityId_recovery_password_menu_item)) { show<RecoveryPasswordActivity>() } LargeItemButton(R.string.sessionRecoveryPassword, R.drawable.ic_shield_outline, Modifier.contentDescription(R.string.AccessibilityId_recovery_password_menu_item)) { push<RecoveryPasswordActivity>() }
Divider() Divider()
} }
LargeItemButton(R.string.activity_settings_help_button, R.drawable.ic_help, Modifier.contentDescription(R.string.AccessibilityId_help)) { show<HelpSettingsActivity>() } LargeItemButton(R.string.activity_settings_help_button, R.drawable.ic_help, Modifier.contentDescription(R.string.AccessibilityId_help)) { push<HelpSettingsActivity>() }
Divider() Divider()
LargeItemButton(R.string.activity_settings_clear_all_data_button_title, R.drawable.ic_delete, Modifier.contentDescription(R.string.AccessibilityId_clear_data), dangerButtonColors()) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") } LargeItemButton(R.string.activity_settings_clear_all_data_button_title, R.drawable.ic_delete, Modifier.contentDescription(R.string.AccessibilityId_clear_data), dangerButtonColors()) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") }
} }

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.recoverypassword
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height 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.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.Cell
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.SessionShieldIcon 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.QrImage
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
import org.thoughtcrime.securesms.ui.components.border import org.thoughtcrime.securesms.ui.components.border
import org.thoughtcrime.securesms.ui.contentDescription 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.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 import org.thoughtcrime.securesms.ui.theme.monospace
@Composable @Composable
@ -53,6 +54,7 @@ internal fun RecoveryPasswordScreen(
.contentDescription(R.string.AccessibilityId_recovery_password) .contentDescription(R.string.AccessibilityId_recovery_password)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(bottom = LocalDimensions.current.smallSpacing) .padding(bottom = LocalDimensions.current.smallSpacing)
.padding(horizontal = LocalDimensions.current.spacing)
) { ) {
RecoveryPasswordCell(mnemonic, seed, copyMnemonic) RecoveryPasswordCell(mnemonic, seed, copyMnemonic)
HideRecoveryPasswordCell(onHide) HideRecoveryPasswordCell(onHide)
@ -69,8 +71,10 @@ private fun RecoveryPasswordCell(
mutableStateOf(false) mutableStateOf(false)
} }
CellWithPaddingAndMargin { Cell {
Column { Column(
modifier = Modifier.padding(LocalDimensions.current.smallSpacing)
) {
Row { Row {
Text( Text(
stringResource(R.string.sessionRecoveryPassword), stringResource(R.string.sessionRecoveryPassword),
@ -148,8 +152,10 @@ private fun RecoveryPassword(mnemonic: String) {
@Composable @Composable
private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) { private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
CellWithPaddingAndMargin { Cell {
Row { Row(
modifier = Modifier.padding(LocalDimensions.current.smallSpacing)
) {
Column( Column(
Modifier.weight(1f) Modifier.weight(1f)
) { ) {

View File

@ -3,13 +3,16 @@ package org.thoughtcrime.securesms.ui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import network.loki.messenger.R 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.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.LocalType
@ -48,6 +52,7 @@ class DialogButtonModel(
@Composable @Composable
fun AlertDialog( fun AlertDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
title: String? = null, title: String? = null,
text: String? = null, text: String? = null,
buttons: List<DialogButtonModel>? = null, buttons: List<DialogButtonModel>? = null,
@ -55,18 +60,10 @@ fun AlertDialog(
content: @Composable () -> Unit = {} content: @Composable () -> Unit = {}
) { ) {
BasicAlertDialog( BasicAlertDialog(
modifier = modifier,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
content = { content = {
Box( DialogBg {
modifier = Modifier.background(
color = LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small)
.border(
width = 1.dp,
color = LocalColors.current.borders,
shape = MaterialTheme.shapes.small)
) {
// only show the 'x' button is required // only show the 'x' button is required
if (showCloseButton) { if (showCloseButton) {
IconButton( IconButton(
@ -133,7 +130,7 @@ fun AlertDialog(
@Composable @Composable
fun DialogButton( fun DialogButton(
text: String, text: String,
modifier: Modifier, modifier: Modifier = Modifier,
color: Color = Color.Unspecified, color: Color = Color.Unspecified,
onClick: () -> Unit 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 @Preview
@Composable @Composable
fun PreviewSimpleDialog() { fun PreviewSimpleDialog() {
@ -200,3 +253,13 @@ fun PreviewXCloseDialog() {
) )
} }
} }
@Preview
@Composable
fun PreviewLoadingDialog() {
PreviewTheme {
LoadingDialog(
title = stringResource(R.string.warning)
)
}
}

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -42,14 +43,17 @@ import kotlin.math.sign
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) { fun BoxScope.HorizontalPagerIndicator(
if (pagerState.pageCount >= 2) Box( pagerState: PagerState,
modifier = Modifier modifier: Modifier = Modifier
) {
if (pagerState.pageCount >= 2){
Box(
modifier = modifier
.background(color = blackAlpha40, shape = pillShape) .background(color = blackAlpha40, shape = pillShape)
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.padding(LocalDimensions.current.xxsSpacing) .padding(LocalDimensions.current.xxsSpacing)
) { ) {
Box(modifier = Modifier.padding(LocalDimensions.current.xxsSpacing)) {
ClickableHorizontalPagerIndicator( ClickableHorizontalPagerIndicator(
pagerState = pagerState, pagerState = pagerState,
pageCount = pagerState.pageCount pageCount = pagerState.pageCount

View File

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -105,7 +106,7 @@ fun <T> OptionsCard(card: OptionsCardData<T>, callbacks: Callbacks<T>) {
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing)) Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
CellNoMargin { Cell {
LazyColumn( LazyColumn(
modifier = Modifier.heightIn(max = 5000.dp) modifier = Modifier.heightIn(max = 5000.dp)
) { ) {
@ -257,32 +258,19 @@ fun PrewviewItemButton() {
@Composable @Composable
fun Cell( fun Cell(
padding: Dp = 0.dp, modifier: Modifier = Modifier,
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,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
Box( Box(
modifier = Modifier modifier = modifier
.padding(horizontal = margin) .background(
.background(color = LocalColors.current.backgroundSecondary, color = LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small) shape = MaterialTheme.shapes.small
)
.wrapContentHeight() .wrapContentHeight()
.fillMaxWidth(), .fillMaxWidth(),
) { ) {
Box(Modifier.padding(padding)) { content() } content()
} }
} }

View File

@ -8,18 +8,24 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun CircularProgressIndicator(color: Color = LocalContentColor.current) { fun CircularProgressIndicator(
modifier: Modifier = Modifier,
color: Color = LocalContentColor.current
) {
androidx.compose.material3.CircularProgressIndicator( androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(40.dp), modifier = modifier.size(40.dp),
color = color, color = color,
strokeWidth = 2.dp strokeWidth = 2.dp
) )
} }
@Composable @Composable
fun SmallCircularProgressIndicator(color: Color = LocalContentColor.current) { fun SmallCircularProgressIndicator(
modifier: Modifier = Modifier,
color: Color = LocalContentColor.current
) {
androidx.compose.material3.CircularProgressIndicator( androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(20.dp), modifier = modifier.size(20.dp),
color = color, color = color,
strokeWidth = 2.dp strokeWidth = 2.dp
) )

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

@ -8,11 +8,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
fun TextStyle.bold() = TextStyle.Default.copy( fun TextStyle.bold() = copy(
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
fun TextStyle.monospace() = TextStyle.Default.copy( fun TextStyle.monospace() = copy(
fontFamily = FontFamily.Monospace fontFamily = FontFamily.Monospace
) )

View File

@ -53,7 +53,7 @@ fun AppCompatActivity.push(intent: Intent, isForResult: Boolean = false) {
} else { } else {
startActivity(intent) 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) { fun AppCompatActivity.show(intent: Intent, isForResult: Boolean = false) {
@ -108,5 +108,5 @@ data class ThemeState (
) )
inline fun <reified T: Activity> Activity.show() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) } inline fun <reified T: Activity> Activity.show() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) }
inline fun <reified T: Activity> 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 <reified T: Activity> 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 <reified T: Activity> Context.start(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).apply { addFlags(FLAG_ACTIVITY_SINGLE_TOP) }.let(::startActivity) inline fun <reified T: Activity> Context.start(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).apply { addFlags(FLAG_ACTIVITY_SINGLE_TOP) }.let(::startActivity)

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:duration="250"
android:fromXScale="0.85"
android:fromYScale="0.85"
android:toXScale="1.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%" />
<alpha
android:duration="250"
android:fromAlpha="0.6"
android:toAlpha="1" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:duration="250"
android:fromYDelta="0%"
android:toYDelta="100%" />
</set>

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_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_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_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_privacy_button_title">Privacy</string>
<string name="activity_settings_notifications_button_title">Notifications</string> <string name="activity_settings_notifications_button_title">Notifications</string>
<string name="activity_settings_message_requests_button_title">Message Requests</string> <string name="activity_settings_message_requests_button_title">Message Requests</string>

View File

@ -2,7 +2,6 @@
package org.session.libsession.snode package org.session.libsession.snode
import android.os.Build
import com.goterl.lazysodium.exceptions.SodiumException import com.goterl.lazysodium.exceptions.SodiumException
import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.PwHash import com.goterl.lazysodium.interfaces.PwHash
@ -76,9 +75,7 @@ object SnodeAPI {
// Use port 4433 to enforce pinned certificates // Use port 4433 to enforce pinned certificates
private val seedNodePort = 4443 private val seedNodePort = 4443
private const val useTestnet = false private val seedNodePool = if (SnodeModule.shared.useTestNet) setOf(
private val seedNodePool = if (useTestnet) setOf(
"http://public.loki.foundation:38157" "http://public.loki.foundation:38157"
) else setOf( ) else setOf(
"https://seed1.getsession.org:$seedNodePort", "https://seed1.getsession.org:$seedNodePort",

View File

@ -3,16 +3,18 @@ package org.session.libsession.snode
import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.Broadcaster 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 { companion object {
lateinit var shared: SnodeModule lateinit var shared: SnodeModule
val isInitialized: Boolean get() = Companion::shared.isInitialized val isInitialized: Boolean get() = Companion::shared.isInitialized
fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster) { fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster, useTestNet: Boolean) {
if (isInitialized) { return } if (isInitialized) { return }
shared = SnodeModule(storage, broadcaster) shared = SnodeModule(storage, broadcaster, useTestNet)
} }
} }
} }

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.CALL_NOTIFICATIONS_ENABLED
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK 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.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.FOLLOW_SYSTEM_SETTINGS
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME
@ -190,6 +191,8 @@ interface TextSecurePreferences {
fun setHidePassword(value: Boolean) fun setHidePassword(value: Boolean)
fun getLastVersionCheck(): Long fun getLastVersionCheck(): Long
fun setLastVersionCheck() fun setLastVersionCheck()
fun getEnvironment(): Environment
fun setEnvironment(value: Environment)
companion object { companion object {
val TAG = TextSecurePreferences::class.simpleName val TAG = TextSecurePreferences::class.simpleName
@ -277,6 +280,7 @@ interface TextSecurePreferences {
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated" const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
const val SELECTED_ACCENT_COLOR = "selected_accent_color" const val SELECTED_ACCENT_COLOR = "selected_accent_color"
const val LAST_VERSION_CHECK = "pref_last_version_check" 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_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_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()) 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 { override fun setShownCallNotification(): Boolean {
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false) val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
if (previousValue) return false if (previousValue) return false