ChooseServerOperators

This commit is contained in:
Avently
2024-11-21 18:01:02 +07:00
parent 6e4da97c04
commit b73d9df014
11 changed files with 416 additions and 60 deletions
@@ -15,7 +15,6 @@ import androidx.compose.ui.draw.*
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp
@@ -42,7 +41,6 @@ import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.math.absoluteValue
@Composable
fun AppScreen() {
@@ -194,6 +192,13 @@ fun MainScreen() {
OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
OnboardingStage.LinkAMobile -> LinkAMobile()
OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
OnboardingStage.Step3_ChooseServerOperators -> {
val modalData = remember { ModalData() }
modalData.ChooseServerOperators(true) {}
if (appPlatform.isDesktop) {
ModalManager.fullscreen.showInView()
}
}
// Ensure backwards compatibility with old onboarding stage for address creation, otherwise notification setup would be skipped
OnboardingStage.Step3_CreateSimpleXAddress -> SetNotificationsMode(chatModel)
OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
@@ -31,6 +31,7 @@ import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration
import chat.simplex.res.MR
import com.russhwolf.settings.Settings
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
@@ -3604,48 +3605,51 @@ enum class OperatorTag {
@SerialName("demo") Demo
}
@Serializable
data class ServerOperatorInfo(
val description: String,
val website: String,
val logo: String,
val largeLogo: String,
val logoDarkMode: String,
val largeLogoDarkMode: String
val logo: ImageResource,
val largeLogo: ImageResource,
val logoDarkMode: ImageResource,
val largeLogoDarkMode: ImageResource
)
// TODO: Check logos
val operatorsInfo: Map<OperatorTag, ServerOperatorInfo> = mapOf(
OperatorTag.SimpleX to ServerOperatorInfo(
description = "SimpleX Chat preset servers",
website = "https://simplex.chat",
logo = "decentralized",
largeLogo = "logo",
logoDarkMode = "decentralized-light",
largeLogoDarkMode = "logo-light"
logo = MR.images.decentralized,
largeLogo = MR.images.logo,
logoDarkMode = MR.images.decentralized_light,
largeLogoDarkMode = MR.images.logo_light
),
OperatorTag.Flux to ServerOperatorInfo(
description = "Flux is the largest decentralized cloud infrastructure, leveraging a global network of user-operated computational nodes. Flux offers a powerful, scalable, and affordable platform designed to support individuals, businesses, and cutting-edge technologies like AI. With high uptime and worldwide distribution, Flux ensures reliable, accessible cloud computing for all.",
website = "https://runonflux.com",
logo = "flux_logo_symbol",
largeLogo = "flux_logo",
logoDarkMode = "flux_logo_symbol",
largeLogoDarkMode = "flux_logo-light"
logo = MR.images.decentralized,
largeLogo = MR.images.logo,
logoDarkMode = MR.images.decentralized_light,
largeLogoDarkMode = MR.images.logo_light
// LALAL
// logo = "flux_logo_symbol",
// largeLogo = "flux_logo",
// logoDarkMode = "flux_logo_symbol",
// largeLogoDarkMode = "flux_logo-light"
),
OperatorTag.XYZ to ServerOperatorInfo(
description = "XYZ servers",
website = "XYZ website",
logo = "shield",
largeLogo = "logo",
logoDarkMode = "shield",
largeLogoDarkMode = "logo-light"
logo = MR.images.shield,
largeLogo = MR.images.logo,
logoDarkMode = MR.images.shield,
largeLogoDarkMode = MR.images.logo_light
),
OperatorTag.Demo to ServerOperatorInfo(
description = "Demo operator",
website = "Demo website",
logo = "decentralized",
largeLogo = "logo",
logoDarkMode = "decentralized-light",
largeLogoDarkMode = "logo-light"
logo = MR.images.decentralized,
largeLogo = MR.images.logo,
logoDarkMode = MR.images.decentralized_light,
largeLogoDarkMode = MR.images.logo_light
)
)
@@ -3727,10 +3731,10 @@ data class ServerOperator(
val dummyOperatorInfo = ServerOperatorInfo(
description = "Default",
website = "Default",
logo = "decentralized",
largeLogo = "logo",
logoDarkMode = "decentralized-light",
largeLogoDarkMode = "logo-light"
logo = MR.images.decentralized,
largeLogo = MR.images.logo,
logoDarkMode = MR.images.decentralized_light,
largeLogoDarkMode = MR.images.logo_light
)
val sampleData1 = ServerOperator(
@@ -3810,11 +3814,11 @@ data class ServerOperator(
}
}
val logo: String get() {
val logo: ImageResource get() {
return if (CurrentColors.value.base == DefaultTheme.LIGHT) info.logo else info.logoDarkMode
}
val largeLogo: String get() {
val largeLogo: ImageResource get() {
return if (CurrentColors.value.base == DefaultTheme.LIGHT) info.largeLogo else info.largeLogoDarkMode
}
}
@@ -138,13 +138,12 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
}
} else if (startChat().await()) {
val savedOnboardingStage = appPreferences.onboardingStage.get()
val next = if (appPlatform.isAndroid) {
OnboardingStage.Step4_SetNotificationsMode
} else {
OnboardingStage.OnboardingComplete
}
val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
next
if (appPlatform.isAndroid) {
OnboardingStage.Step4_SetNotificationsMode
} else {
OnboardingStage.OnboardingComplete
}
} else {
savedOnboardingStage
}
@@ -165,7 +165,7 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
if (!chatModel.connectedToRemote()) {
chatModel.localUserCreated.value = true
}
controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode)
controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_ChooseServerOperators)
controller.startChat(user)
controller.switchUIRemoteHost(null)
close()
@@ -204,7 +204,7 @@ fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: ()
onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) {
OnboardingStage.Step2_5_SetupDatabasePassphrase
} else {
OnboardingStage.Step4_SetNotificationsMode
OnboardingStage.Step3_ChooseServerOperators
})
} else {
// the next two lines are only needed for failure case when because of the database error the app gets stuck on on-boarding screen,
@@ -1,9 +1,336 @@
package chat.simplex.common.views.onboarding
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import SectionBottomSpacer
import SectionTextFooter
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ServerOperator
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.changeNotificationsMode
import chat.simplex.common.views.usersettings.networkAndServers.ConditionsTextView
import chat.simplex.common.views.usersettings.networkAndServers.UsageConditionsView
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@Composable
fun ChooseServerOperators(onboarding: Boolean) {
Column { }
}
fun ModalData.ChooseServerOperators(onboarding: Boolean, close: () -> Unit) {
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
ModalView({}, showClose = false, endButtons = {
IconButton({ (if (onboarding) ModalManager.fullscreen else ModalManager.start).showModal { ChooseServerOperatorsInfoView() } }) {
Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary)
}
}) {
val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } }
val selectedOperatorIds = remember { stateGetOrPut("selectedOperatorIds") { serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() } }
val selectedOperators = remember { derivedStateOf { serverOperators.value.filter { selectedOperatorIds.value.contains(it.operatorId) } } }
ColumnWithScrollBar(
Modifier
.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer),
maxIntrinsicSize = true
) {
Box(Modifier.align(Alignment.CenterHorizontally)) {
AppBarTitle(stringResource(MR.strings.onboarding_choose_server_operators))
}
Column((
if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier)
.padding(horizontal = DEFAULT_PADDING)
) {
Text(stringResource(MR.strings.onboarding_select_network_operators_to_use))
Spacer(Modifier.height(DEFAULT_PADDING))
}
Spacer(Modifier.weight(1f))
Column((
if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier)
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
) {
serverOperators.value.forEachIndexed { index, srvOperator ->
OperatorCheckView(srvOperator, selectedOperatorIds)
if (index != serverOperators.value.lastIndex) {
Spacer(Modifier.height(DEFAULT_PADDING))
}
}
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
SectionTextFooter(annotatedStringResource(MR.strings.onboarding_network_operators_configure_via_settings), textAlign = TextAlign.Center)
}
Spacer(Modifier.weight(1f))
val reviewForOperators = selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted }
val canReviewLater = reviewForOperators.all { it.conditionsAcceptance.usageAllowed }
val currEnabledOperatorIds = serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet()
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
val enabled = selectedOperatorIds.value.isNotEmpty()
when {
reviewForOperators.isNotEmpty() -> ReviewConditionsButton(enabled, onboarding, selectedOperators, selectedOperatorIds)
selectedOperatorIds.value != currEnabledOperatorIds && enabled -> SetOperatorsButton(true, onboarding, serverOperators, selectedOperatorIds, close)
else -> ContinueButton(enabled, onboarding, close)
}
if (onboarding && reviewForOperators.isEmpty()) {
TextButtonBelowOnboardingButton(stringResource(MR.strings.operator_conditions_of_use)) {
ModalManager.fullscreen.showModalCloseable { close ->
UsageConditionsView(
currUserServers = listOf(),
userServers = listOf(),
close = close
)
}
}
} else if (onboarding || reviewForOperators.isEmpty()) {
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
if (!onboarding && reviewForOperators.isNotEmpty()) {
ReviewLaterButton(canReviewLater, close)
SectionTextFooter(
annotatedStringResource(MR.strings.onboarding_network_operators_conditions_will_be_accepted) +
AnnotatedString(" ") +
annotatedStringResource(MR.strings.onboarding_network_operators_conditions_you_can_configure),
textAlign = TextAlign.Center
)
SectionBottomSpacer()
}
}
}
}
}
}
@Composable
private fun OperatorCheckView(serverOperator: ServerOperator, selectedOperatorIds: MutableState<Set<Long>>) {
val checked = selectedOperatorIds.value.contains(serverOperator.operatorId)
TextButton({
if (checked) {
selectedOperatorIds.value -= serverOperator.operatorId
} else {
selectedOperatorIds.value += serverOperator.operatorId
}
},
border = BorderStroke(1.dp, color = if (checked) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)),
shape = RoundedCornerShape(18.dp)
) {
Row(Modifier.padding(DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically) {
Icon(painterResource(serverOperator.largeLogo), null, Modifier.height(48.dp))
Spacer(Modifier.width(DEFAULT_PADDING_HALF).weight(1f))
CircleCheckbox(checked)
}
}
}
@Composable
private fun CircleCheckbox(checked: Boolean) {
if (checked) {
Box(contentAlignment = Alignment.Center) {
Icon(
painterResource(MR.images.ic_circle_filled),
null,
Modifier.size(26.dp),
tint = MaterialTheme.colors.primary
)
Icon(
painterResource(MR.images.ic_check_filled),
null,
Modifier.size(20.dp), tint = MaterialTheme.colors.background
)
}
} else {
Icon(
painterResource(MR.images.ic_circle),
null,
Modifier.size(26.dp),
tint = MaterialTheme.colors.secondary.copy(alpha = 0.5f)
)
}
}
@Composable
private fun ReviewConditionsButton(
enabled: Boolean,
onboarding: Boolean,
selectedOperators: State<List<ServerOperator>>,
selectedOperatorIds: State<Set<Long>>
) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.operator_review_conditions,
onboarding = null,
enabled = enabled,
onclick = {
ModalManager.fullscreen.showModalCloseable { close ->
ReviewConditionsView(onboarding, selectedOperators, selectedOperatorIds, close)
}
}
)
}
@Composable
private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOperators: State<List<ServerOperator>>, selectedOperatorIds: State<Set<Long>>, close: () -> Unit) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.onboarding_network_operators_update,
onboarding = null,
enabled = enabled,
onclick = {
withBGApi {
val enabledOperators = enabledOperators(serverOperators.value, selectedOperatorIds.value)
if (enabledOperators != null) {
val r = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators)
if (r != null) {
chatModel.conditions.value = r
}
continueToNextStep(onboarding, close)
}
}
}
)
}
@Composable
private fun ContinueButton(enabled: Boolean, onboarding: Boolean, close: () -> Unit) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.onboarding_network_operators_continue,
onboarding = null,
enabled = enabled,
onclick = {
continueToNextStep(onboarding, close)
}
)
}
@Composable
private fun ReviewLaterButton(enabled: Boolean, close: () -> Unit) {
TextButtonBelowOnboardingButton(
stringResource(MR.strings.onboarding_network_operators_review_later),
onClick = if (!enabled) null else {{ continueToNextStep(false, close) }}
)
}
@Composable
private fun ReviewConditionsView(
onboarding: Boolean,
selectedOperators: State<List<ServerOperator>>,
selectedOperatorIds: State<Set<Long>>,
close: () -> Unit
) {
val operatorsWithConditionsAccepted = chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted }
val acceptForOperators = selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted }
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) {
AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false)
if (operatorsWithConditionsAccepted.isNotEmpty()) {
ReadableText(MR.strings.operator_conditions_accepted_for_some, args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ })
ReadableText(MR.strings.operator_same_conditions_will_apply_to_operators, args = acceptForOperators.joinToString(", ") { it.legalName_ })
} else {
ReadableText(MR.strings.operator_conditions_will_be_accepted_for_some, args = acceptForOperators.joinToString(", ") { it.legalName_ })
}
Box(Modifier.weight(1f)) {
ConditionsTextView(chatModel.remoteHostId())
}
Column(Modifier.padding(top = DEFAULT_PADDING * 2).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
AcceptConditionsButton(onboarding, selectedOperators, selectedOperatorIds, close)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
}
}
@Composable
private fun AcceptConditionsButton(
onboarding: Boolean,
selectedOperators: State<List<ServerOperator>>,
selectedOperatorIds: State<Set<Long>>,
close: () -> Unit
) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.accept_conditions,
onboarding = null,
onclick = {
withBGApi {
val conditionsId = chatModel.conditions.value.currentConditions.conditionsId
val acceptForOperators = selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted }
val operatorIds = acceptForOperators.map { it.operatorId }
val r = chatController.acceptConditions(chatModel.remoteHostId(), conditionsId = conditionsId, operatorIds = operatorIds)
if (r != null) {
chatModel.conditions.value = r
val enabledOperators = enabledOperators(r.serverOperators, selectedOperatorIds.value)
if (enabledOperators != null) {
val r2 = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators)
if (r2 != null) {
chatModel.conditions.value = r2
if (onboarding) { close() }
continueToNextStep(onboarding, close)
}
} else {
if (onboarding) { close() }
continueToNextStep(onboarding, close)
}
}
}
}
)
}
private fun continueToNextStep(onboarding: Boolean, close: () -> Unit) {
if (onboarding) {
appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode)
} else {
close()
}
}
private fun enabledOperators(operators: List<ServerOperator>, selectedOperatorIds: Set<Long>): List<ServerOperator>? {
val ops = ArrayList(operators)
if (ops.isNotEmpty()) {
for (i in ops.indices) {
val op = ops[i]
ops[i] = op.copy(enabled = selectedOperatorIds.contains(op.operatorId))
}
val haveSMPStorage = ops.any { it.enabled && it.smpRoles.storage }
val haveSMPProxy = ops.any { it.enabled && it.smpRoles.proxy }
val haveXFTPStorage = ops.any { it.enabled && it.xftpRoles.storage }
val haveXFTPProxy = ops.any { it.enabled && it.xftpRoles.proxy }
val firstEnabledIndex = ops.indexOfFirst { it.enabled }
if (haveSMPStorage && haveSMPProxy && haveXFTPStorage && haveXFTPProxy) {
return ops
} else if (firstEnabledIndex != -1) {
var op = ops[firstEnabledIndex]
if (!haveSMPStorage) op = op.copy(smpRoles = op.smpRoles.copy(storage = true))
if (!haveSMPProxy) op = op.copy(smpRoles = op.smpRoles.copy(proxy = true))
if (!haveXFTPStorage) op = op.copy(xftpRoles = op.xftpRoles.copy(storage = true))
if (!haveXFTPProxy) op = op.copy(xftpRoles = op.xftpRoles.copy(proxy = true))
ops[firstEnabledIndex] = op
return ops
} else { // Shouldn't happen - view doesn't let to proceed if no operators are enabled
return null
}
} else {
return null
}
}
@Composable
private fun ChooseServerOperatorsInfoView() {
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) {
AppBarTitle(stringResource(MR.strings.onboarding_network_operators), withPadding = false)
ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_different_operators))
ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_for_routing))
SectionBottomSpacer()
}
}
@@ -5,6 +5,7 @@ enum class OnboardingStage {
Step2_CreateProfile,
LinkAMobile,
Step2_5_SetupDatabasePassphrase,
Step3_ChooseServerOperators,
Step3_CreateSimpleXAddress,
Step4_SetNotificationsMode,
OnboardingComplete
@@ -17,7 +17,6 @@ import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.database.*
@@ -36,7 +35,7 @@ fun SetupDatabasePassphrase(m: ChatModel) {
val confirmNewKey = rememberSaveable { mutableStateOf("") }
fun nextStep() {
if (appPlatform.isAndroid || chatModel.currentUser.value != null) {
m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode)
m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_ChooseServerOperators)
} else {
m.controller.appPrefs.onboardingStage.set(OnboardingStage.LinkAMobile)
}
@@ -164,14 +164,15 @@ fun OnboardingActionButton(
@Composable
fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) {
val state = getKeyboardState()
val enabled = onClick != null
val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING)
val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING * 2)
if ((appPlatform.isAndroid && state.value == KeyboardState.Closed) || topPadding > 0.dp) {
TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = onClick != null) {
TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = enabled) {
Text(
text,
Modifier.padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF, bottom = 5.dp),
color = MaterialTheme.colors.primary,
color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center
)
@@ -40,13 +40,12 @@ fun WhatsNewView(showWhatsNew: MutableState<Boolean> = mutableStateOf(true), sho
Log.d(TAG, "WhatsNewView setConditionsNotified error: ${e.message}")
}
}
ModalManager.fullscreen.showModalCloseable(showClose = false) { close ->
ChooseServerOperators(onboarding = false, close)
}
}
}
if (showOperatorsNotice) {
return ChooseServerOperators(onboarding = false)
}
@Composable
fun featureDescription(icon: ImageResource?, titleId: StringResource, descrId: StringResource?, link: String?, subfeatures: List<Pair<ImageResource, StringResource>>) {
@Composable
@@ -673,6 +673,13 @@ private fun ModalData.UsageConditionsView(conditionsAction: UsageConditionsActio
}
}
@Composable
fun ModalData.UsageConditionsView(currUserServers: List<UserOperatorServers>, userServers: List<UserOperatorServers>, close: () -> Unit) {
ModalView(close = close) {
Text("Hello")
}
}
@Composable
private fun ConditionsButton(conditionsAction: UsageConditionsAction) {
SectionItemView(
@@ -737,16 +744,12 @@ fun showUpdateNetworkSettingsDialog(
}
fun updateOperatorsConditionsAcceptance(usvs: MutableState<List<UserOperatorServers>>, updatedOperators: List<ServerOperator>) {
for (i in usvs.value.indices) {
val updatedOperator = updatedOperators.firstOrNull { it.operatorId == usvs.value[i].operator?.operatorId } ?: continue
usvs.value = usvs.value.toMutableList().apply {
this[i] = this[i].copy(
operator = this[i].operator?.copy(
conditionsAcceptance = updatedOperator.conditionsAcceptance
)
)
}
val modified = ArrayList(usvs.value)
for (i in modified.indices) {
val updatedOperator = updatedOperators.firstOrNull { it.operatorId == modified[i].operator?.operatorId } ?: continue
modified[i] = modified[i].copy(operator = modified[i].operator?.copy(conditionsAcceptance = updatedOperator.conditionsAcceptance))
}
usvs.value = modified
}
suspend fun validateServers(
@@ -1048,6 +1048,19 @@
<string name="you_can_change_it_later">Random passphrase is stored in settings as plaintext.\nYou can change it later.</string>
<string name="use_random_passphrase">Use random passphrase</string>
<!-- ChooseServerOperators.kt -->
<string name="onboarding_choose_server_operators">Choose operators</string>
<string name="onboarding_network_operators">Network operators</string>
<string name="onboarding_network_operators_app_will_use_different_operators">When more than one network operator is enabled, the app will use the servers of different operators for each conversation.</string>
<string name="onboarding_network_operators_app_will_use_for_routing">For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing.</string>
<string name="onboarding_select_network_operators_to_use">Select network operators to use.</string>
<string name="onboarding_network_operators_configure_via_settings">You can configure servers via settings.</string>
<string name="onboarding_network_operators_conditions_will_be_accepted">Conditions will be accepted for enabled operators after 30 days.</string>
<string name="onboarding_network_operators_conditions_you_can_configure">You can configure operators in Network &amp; servers settings.</string>
<string name="onboarding_network_operators_review_later">Review later</string>
<string name="onboarding_network_operators_update">Update</string>
<string name="onboarding_network_operators_continue">Continue</string>
<!-- Call -->
<string name="incoming_video_call">Incoming video call</string>
<string name="incoming_audio_call">Incoming audio call</string>
@@ -1697,8 +1710,13 @@
<string name="use_operator_x">Use operator %s</string>
<string name="operator_conditions_failed_to_load">Current conditions text couldn\'t be loaded, you can review conditions via this link:</string>
<string name="operator_conditions_accepted_for_some"><![CDATA[Conditions are already accepted for following operator(s): <b>%s</b>.]]></string>
<string name="operator_same_conditions_will_be_applied"><![CDATA[Same conditions will apply to operator <b>%s</b>.]]></string>
<string name="operator_same_conditions_will_apply_to_operators"><![CDATA[Same conditions will apply to operator(s): <b>%s</b>.]]></string>
<string name="operator_conditions_accepted_for_some"><![CDATA[Conditions are already accepted for following operator(s): <b>%s</b>.]]></string>
<string name="operator_conditions_will_be_applied"><![CDATA[These conditions will also apply for: <b>%s</b>.]]></string>
<string name="operator_same_conditions_will_be_applied"><![CDATA[Same conditions will apply to operator: <b>%s</b>.]]></string>
<string name="operator_conditions_will_be_accepted_for_some"><![CDATA[Conditions will be accepted for operator(s): <b>%s</b>.]]></string>
<string name="operators_conditions_will_also_apply"><![CDATA[Conditions will also apply for following operator(s) you use: <b>%s</b>.]]></string>
<string name="view_conditions">View conditions</string>
<string name="accept_conditions">Accept conditions</string>
<string name="operator_conditions_of_use">Conditions of use</string>