Finalising the debug menu

We can now switch environments between mainnet and testnet
This commit is contained in:
ThomasSession 2024-08-26 12:02:46 +10:00
parent 89b3c616d7
commit bc001e3a45
11 changed files with 257 additions and 91 deletions

View File

@ -45,6 +45,7 @@ import org.session.libsession.snode.SnodeModule;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.ConfigFactoryUpdateListener;
import org.session.libsession.utilities.Device;
import org.session.libsession.utilities.Environment;
import org.session.libsession.utilities.ProfilePictureUtilities;
import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences;
@ -237,7 +238,8 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase();
SnodeModule.Companion.configure(apiDB, broadcaster);
boolean useTestNet = textSecurePreferences.getEnvironment() == Environment.TEST_NET;
SnodeModule.Companion.configure(apiDB, broadcaster, useTestNet);
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
@ -507,7 +509,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
// Method to clear the local data - returns true on success otherwise false
/**
* Clear all local profile data and message history then restart the app after a brief delay.
* Clear all local profile data and message history.
* @return true on success, false otherwise.
*/
@SuppressLint("ApplySharedPref")
@ -519,6 +521,16 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
return false;
}
configFactory.keyPairChanged();
return true;
}
/**
* Clear all local profile data and message history then restart the app after a brief delay.
* @return true on success, false otherwise.
*/
@SuppressLint("ApplySharedPref")
public boolean clearAllDataAndRestart() {
clearAllData();
Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
return true;
}

View File

@ -1,33 +1,37 @@
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.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.text.font.FontWeight
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.components.AppBarCloseIcon
import org.thoughtcrime.securesms.ui.components.AppBarText
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.components.appBarColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
@ -42,37 +46,84 @@ fun DebugMenu(
modifier: Modifier = Modifier,
onClose: () -> Unit
){
Column(
modifier = modifier
.fillMaxSize()
.background(color = LocalColors.current.background)
) {
// App bar
BackAppBar(title = "Debug Menu", onBack = onClose)
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, // 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(horizontal = LocalDimensions.current.spacing)
.verticalScroll(rememberScrollState())
.padding(contentPadding)
.fillMaxSize()
.background(color = LocalColors.current.background)
) {
// Info pane
DebugCell("App Info"){
Text(
text = "Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - ${BuildConfig.GIT_HASH.take(6)})",
style = LocalType.current.base
)
}
// App bar
BackAppBar(title = "Debug Menu", onBack = onClose)
// Environment
DebugCell("Environment"){
DropDown(
modifier = Modifier.fillMaxWidth(0.6f),
selectedText = uiState.currentEnvironment,
values = uiState.environments,
onValueSelected = {
sendCommand(DebugMenuViewModel.Commands.ChangeEnvironment(it))
}
)
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))
}
)
}
}
}
}
@ -109,7 +160,10 @@ fun PreviewDebugMenu(){
DebugMenu(
uiState = DebugMenuViewModel.UIState(
currentEnvironment = "Development",
environments = listOf("Development", "Production")
environments = listOf("Development", "Production"),
snackMessage = null,
showEnvironmentWarningDialog = false,
showEnvironmentLoadingDialog = false
),
sendCommand = {},
onClose = {}

View File

@ -1,12 +1,17 @@
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
@ -14,6 +19,7 @@ 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
@ -26,59 +32,84 @@ class DebugMenuViewModel @Inject constructor(
private val _uiState = MutableStateFlow(
UIState(
currentEnvironment = textSecurePreferences.getEnvironment().label,
environments = Environment.entries.map { it.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(command.environment)
is Commands.ChangeEnvironment -> changeEnvironment()
is Commands.HideEnvironmentWarningDialog -> _uiState.value =
_uiState.value.copy(showEnvironmentWarningDialog = false)
is Commands.ShowEnvironmentWarningDialog ->
showEnvironmentWarningDialog(command.environment)
}
}
private fun changeEnvironment(environment: String) {
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 {
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()
try {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(application).get()
} catch (e: Exception) {
Log.e(
TAG, "Failed to delete network message from debug menu", e
)
null
// we can ignore fails here as we might be switching environments before the user gets a public key
}
// 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
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 environments: List<String>,
val snackMessage: String?,
val showEnvironmentWarningDialog: Boolean,
val showEnvironmentLoadingDialog: Boolean
)
sealed class Commands {
data class ChangeEnvironment(val environment: String) : 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(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(LocalColors.current.primary)
CircularProgressIndicator(color = LocalColors.current.primary)
}
return

View File

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

View File

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

View File

@ -177,7 +177,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
btnGroupNameDisplay.text = getDisplayName()
publicKeyTextView.text = hexEncodedPublicKey
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 {

View File

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

View File

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

View File

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

View File

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