Merge branch 'dev' into just-prefs

This commit is contained in:
bemusementpark 2024-07-18 23:07:30 +09:30
commit a196a98f00
97 changed files with 1913 additions and 1714 deletions

View File

@ -381,7 +381,7 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling:$composeVersion"
implementation "androidx.compose.runtime:runtime-livedata:$composeVersion"
implementation "androidx.compose.foundation:foundation-layout:$composeVersion"
implementation "androidx.compose.material:material:$composeVersion"
implementation "androidx.compose.material3:material3:1.2.1"
androidTestImplementation "androidx.compose.ui:ui-test-junit4-android:$composeVersion"
debugImplementation "androidx.compose.ui:ui-test-manifest:$composeVersion"

View File

@ -114,14 +114,14 @@ class SessionDialogBuilder(val context: Context) {
options,
) { dialog, it -> onSelect(it); dialog.dismiss() }
fun destructiveButton(
fun dangerButton(
@StringRes text: Int,
@StringRes contentDescription: Int = text,
listener: () -> Unit = {}
) = button(
text,
contentDescription,
R.style.Widget_Session_Button_Dialog_DestructiveText,
R.style.Widget_Session_Button_Dialog_DangerText,
) { listener() }
fun okButton(listener: (() -> Unit) = {}) = button(android.R.string.ok) { listener() }

View File

@ -17,6 +17,7 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.prefs
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@ -30,7 +31,7 @@ class ProfilePictureView @JvmOverloads constructor(
private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this)
private val glide: GlideRequests = GlideApp.with(this)
private val prefs = TextSecurePreferences(context)
private val prefs = context.prefs
private val userPublicKey = prefs.getLocalNumber()
var publicKey: String? = null
var displayName: String? = null

View File

@ -1,64 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.net.Uri;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.conversation.v2.Util;
import java.util.LinkedList;
import java.util.List;
public class CompositeEmojiPageModel implements EmojiPageModel {
@AttrRes private final int iconAttr;
@NonNull private final List<EmojiPageModel> models;
public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull List<EmojiPageModel> models) {
this.iconAttr = iconAttr;
this.models = models;
}
@Override
public String getKey() {
return Util.hasItems(models) ? models.get(0).getKey() : "";
}
public int getIconAttr() {
return iconAttr;
}
@Override
public @NonNull List<String> getEmoji() {
List<String> emojis = new LinkedList<>();
for (EmojiPageModel model : models) {
emojis.addAll(model.getEmoji());
}
return emojis;
}
@Override
public @NonNull List<Emoji> getDisplayEmoji() {
List<Emoji> emojis = new LinkedList<>();
for (EmojiPageModel model : models) {
emojis.addAll(model.getDisplayEmoji());
}
return emojis;
}
@Override
public boolean hasSpriteMap() {
return false;
}
@Override
public @Nullable Uri getSpriteUri() {
return null;
}
@Override
public boolean isDynamic() {
return false;
}
}

View File

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.components.emoji
import android.net.Uri
import androidx.annotation.AttrRes
import java.util.LinkedList
class CompositeEmojiPageModel(
@field:AttrRes @param:AttrRes private val iconAttr: Int,
private val models: List<EmojiPageModel>
) : EmojiPageModel {
override fun getKey(): String {
return if (models.isEmpty()) "" else models[0].key
}
override fun getIconAttr(): Int { return iconAttr }
override fun getEmoji(): List<String> {
val emojis: MutableList<String> = LinkedList()
for (model in models) {
emojis.addAll(model.emoji)
}
return emojis
}
override fun getDisplayEmoji(): List<Emoji> {
val emojis: MutableList<Emoji> = LinkedList()
for (model in models) {
emojis.addAll(model.displayEmoji)
}
return emojis
}
override fun hasSpriteMap(): Boolean { return false }
override fun getSpriteUri(): Uri? { return null }
override fun isDynamic(): Boolean { return false }
}

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.components.menu
import android.content.Context
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.annotation.ColorInt
/**
* Represents an action to be rendered
@ -13,5 +13,5 @@ data class ActionItem(
val action: Runnable,
val contentDescription: Int? = null,
val subtitle: ((Context) -> CharSequence?)? = null,
@ColorRes val color: Int? = null,
@ColorInt val color: Int? = null,
)

View File

@ -78,7 +78,7 @@ class ContextMenuList(recyclerView: RecyclerView, onItemClick: () -> Unit) {
override fun bind(model: DisplayItem) {
val item = model.item
val color = item.color?.let { ContextCompat.getColor(context, it) }
val color = item.color
if (item.iconRes > 0) {
val typedValue = TypedValue()

View File

@ -14,7 +14,6 @@ import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getExpirationTypeDisplayValue
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.showSessionDialog
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
@ -57,7 +56,7 @@ class DisappearingMessages @Inject constructor(
context.getExpirationTypeDisplayValue(message.isNotDisappearAfterRead)
)
})
destructiveButton(
dangerButton(
text = if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set,
contentDescription = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_set_button
) {

View File

@ -13,8 +13,8 @@ import kotlin.time.Duration.Companion.seconds
fun State.toUiState() = UiState(
cards = listOfNotNull(
typeOptions()?.let { ExpiryOptionsCard(GetString(R.string.activity_disappearing_messages_delete_type), it) },
timeOptions()?.let { ExpiryOptionsCard(GetString(R.string.activity_disappearing_messages_timer), it) }
typeOptions()?.let { ExpiryOptionsCardData(GetString(R.string.activity_disappearing_messages_delete_type), it) },
timeOptions()?.let { ExpiryOptionsCardData(GetString(R.string.activity_disappearing_messages_timer), it) }
),
showGroupFooter = isGroup && isNewConfigEnabled,
showSetButton = isSelfAdmin

View File

@ -3,11 +3,13 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -18,15 +20,15 @@ import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.ui.Callbacks
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.NoOpCallbacks
import org.thoughtcrime.securesms.ui.OptionsCard
import org.thoughtcrime.securesms.ui.RadioOption
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.extraSmall
import org.thoughtcrime.securesms.ui.fadingEdges
import org.thoughtcrime.securesms.ui.theme.LocalType
typealias ExpiryCallbacks = Callbacks<ExpiryMode>
typealias ExpiryRadioOption = RadioOption<ExpiryMode>
@ -39,26 +41,32 @@ fun DisappearingMessages(
) {
val scrollState = rememberScrollState()
Column(modifier = modifier.padding(horizontal = LocalDimensions.current.margin)) {
Column(modifier = modifier.padding(horizontal = LocalDimensions.current.spacing)) {
Box(modifier = Modifier.weight(1f)) {
Column(
modifier = Modifier
.padding(bottom = 20.dp)
.padding(vertical = LocalDimensions.current.spacing)
.verticalScroll(scrollState)
.fadingEdges(scrollState),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing)
) {
state.cards.forEach {
OptionsCard(it, callbacks)
state.cards.forEachIndexed { index, option ->
OptionsCard(option, callbacks)
// add spacing if not the last item
if(index != state.cards.lastIndex){
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
}
}
if (state.showGroupFooter) Text(
text = stringResource(R.string.activity_disappearing_messages_group_footer),
style = extraSmall,
style = LocalType.current.extraSmall,
fontWeight = FontWeight(400),
color = LocalColors.current.textSecondary,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(top = LocalDimensions.current.xsSpacing)
)
}
}
@ -68,7 +76,7 @@ fun DisappearingMessages(
modifier = Modifier
.contentDescription(R.string.AccessibilityId_set_button)
.align(Alignment.CenterHorizontally)
.padding(bottom = 20.dp),
.padding(bottom = LocalDimensions.current.spacing),
onClick = callbacks::onSetClick
)
}

View File

@ -10,9 +10,9 @@ import androidx.compose.ui.unit.dp
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType
import org.thoughtcrime.securesms.conversation.disappearingmessages.State
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
@Preview(widthDp = 450, heightDp = 700)
@Composable
@ -51,7 +51,7 @@ class StatePreviewParameterProvider : PreviewParameterProvider<State> {
@Preview
@Composable
fun PreviewThemes(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
DisappearingMessages(

View File

@ -5,15 +5,15 @@ import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.RadioOption
typealias ExpiryOptionsCard = OptionsCard<ExpiryMode>
typealias ExpiryOptionsCardData = OptionsCardData<ExpiryMode>
data class UiState(
val cards: List<ExpiryOptionsCard> = emptyList(),
val cards: List<ExpiryOptionsCardData> = emptyList(),
val showGroupFooter: Boolean = false,
val showSetButton: Boolean = true
) {
constructor(
vararg cards: ExpiryOptionsCard,
vararg cards: ExpiryOptionsCardData,
showGroupFooter: Boolean = false,
showSetButton: Boolean = true,
): this(
@ -23,7 +23,7 @@ data class UiState(
)
}
data class OptionsCard<T>(
data class OptionsCardData<T>(
val title: GetString,
val options: List<RadioOption<T>>
) {

View File

@ -7,8 +7,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -21,23 +22,25 @@ import org.thoughtcrime.securesms.conversation.start.NullStartConversationDelega
import org.thoughtcrime.securesms.conversation.start.StartConversationDelegate
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.ItemButton
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
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.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.AppBar
import org.thoughtcrime.securesms.ui.components.QrImage
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.ui.xl
import org.thoughtcrime.securesms.ui.theme.LocalType
@Composable
internal fun StartConversationScreen(
accountId: String,
delegate: StartConversationDelegate
) {
Column(modifier = Modifier.background(LocalColors.current.backgroundSecondary)) {
Column(modifier = Modifier.background(
LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small
)) {
AppBar(stringResource(R.string.dialog_start_conversation_title), onClose = delegate::onDialogClosePressed)
Surface(
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()),
@ -74,18 +77,18 @@ internal fun StartConversationScreen(
)
Column(
modifier = Modifier
.padding(horizontal = LocalDimensions.current.margin)
.padding(top = LocalDimensions.current.itemSpacing)
.padding(bottom = LocalDimensions.current.margin)
.padding(horizontal = LocalDimensions.current.spacing)
.padding(top = LocalDimensions.current.spacing)
.padding(bottom = LocalDimensions.current.spacing)
) {
Text(stringResource(R.string.accountIdYours), style = xl)
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsItemSpacing))
Text(stringResource(R.string.accountIdYours), style = LocalType.current.xl)
Spacer(modifier = Modifier.height(LocalDimensions.current.xxsSpacing))
Text(
text = stringResource(R.string.qrYoursDescription),
color = LocalColors.current.textSecondary,
style = small
style = LocalType.current.small
)
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
QrImage(
string = accountId,
Modifier.contentDescription(R.string.AccessibilityId_qr_code),
@ -100,7 +103,7 @@ internal fun StartConversationScreen(
@Preview
@Composable
private fun PreviewStartConversationScreen(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
StartConversationScreen(

View File

@ -8,23 +8,23 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.AppBar
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
import org.thoughtcrime.securesms.ui.components.border
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.ui.theme.LocalType
@Composable
internal fun InviteFriend(
@ -34,10 +34,14 @@ internal fun InviteFriend(
copyPublicKey: () -> Unit = {},
sendInvitation: () -> Unit = {},
) {
Column(modifier = Modifier.background(LocalColors.current.backgroundSecondary)) {
Column(modifier = Modifier.background(
LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small
)) {
AppBar(stringResource(R.string.invite_a_friend), onBack = onBack, onClose = onClose)
Column(
modifier = Modifier.padding(horizontal = LocalDimensions.current.itemSpacing),
modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing)
.padding(top = LocalDimensions.current.spacing),
) {
Text(
accountId,
@ -45,24 +49,24 @@ internal fun InviteFriend(
.contentDescription(R.string.AccessibilityId_account_id)
.fillMaxWidth()
.border()
.padding(LocalDimensions.current.smallMargin),
.padding(LocalDimensions.current.spacing),
textAlign = TextAlign.Center,
style = base
style = LocalType.current.base
)
Spacer(modifier = Modifier.height(LocalDimensions.current.xsItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
Text(
stringResource(R.string.invite_your_friend_to_chat_with_you_on_session_by_sharing_your_account_id_with_them),
textAlign = TextAlign.Center,
style = small,
style = LocalType.current.small,
color = LocalColors.current.textSecondary,
modifier = Modifier.padding(horizontal = LocalDimensions.current.smallItemSpacing)
modifier = Modifier.padding(horizontal = LocalDimensions.current.smallSpacing)
)
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
Row(horizontalArrangement = spacedBy(LocalDimensions.current.smallItemSpacing)) {
Row(horizontalArrangement = spacedBy(LocalDimensions.current.smallSpacing)) {
SlimOutlineButton(
stringResource(R.string.share),
modifier = Modifier

View File

@ -3,17 +3,19 @@ package org.thoughtcrime.securesms.conversation.start.newmessage
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
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.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -23,12 +25,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import network.loki.messenger.R
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
import org.thoughtcrime.securesms.ui.LoadingArcOr
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
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.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.AppBar
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
@ -36,7 +39,7 @@ import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
import org.thoughtcrime.securesms.ui.components.SessionTabRow
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.ui.theme.LocalType
private val TITLES = listOf(R.string.enter_account_id, R.string.qrScan)
@ -52,7 +55,10 @@ internal fun NewMessage(
) {
val pagerState = rememberPagerState { TITLES.size }
Column(modifier = Modifier.background(LocalColors.current.backgroundSecondary)) {
Column(modifier = Modifier.background(
LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small
)) {
AppBar(stringResource(R.string.messageNew), onClose = onClose, onBack = onBack)
SessionTabRow(pagerState, TITLES)
HorizontalPager(pagerState) {
@ -70,7 +76,6 @@ private fun EnterAccountId(
callbacks: Callbacks,
onHelp: () -> Unit = {}
) {
Column(
modifier = Modifier
.fillMaxSize()
@ -78,14 +83,13 @@ private fun EnterAccountId(
.imePadding()
) {
Column(
modifier = Modifier.padding(horizontal = LocalDimensions.current.xxsMargin, vertical = LocalDimensions.current.xsMargin),
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.xsMargin)
) {
SessionOutlinedTextField(
text = state.newMessageIdOrOns,
modifier = Modifier
.padding(horizontal = LocalDimensions.current.smallMargin),
.padding(horizontal = LocalDimensions.current.spacing),
contentDescription = "Session id input box",
placeholder = stringResource(R.string.accountIdOrOnsEnter),
onChange = callbacks::onChange,
@ -94,31 +98,36 @@ private fun EnterAccountId(
isTextErrorColor = state.isTextErrorColor
)
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
BorderlessButtonWithIcon(
text = stringResource(R.string.messageNewDescription),
modifier = Modifier
.contentDescription(R.string.AccessibilityId_help_desk_link)
.padding(horizontal = LocalDimensions.current.margin)
.padding(horizontal = LocalDimensions.current.mediumSpacing)
.fillMaxWidth(),
style = small,
style = LocalType.current.small,
color = LocalColors.current.textSecondary,
iconRes = R.drawable.ic_circle_question_mark,
onClick = onHelp
)
}
AnimatedVisibility(state.isNextButtonVisible) {
PrimaryOutlineButton(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(horizontal = LocalDimensions.current.largeMargin)
.fillMaxWidth()
.contentDescription(R.string.next),
onClick = callbacks::onContinue
) {
LoadingArcOr(state.loading) {
Text(stringResource(R.string.next))
}
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
Spacer(Modifier.weight(2f))
PrimaryOutlineButton(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
.padding(bottom = LocalDimensions.current.smallSpacing)
.fillMaxWidth()
.contentDescription(R.string.next),
enabled = state.isNextButtonEnabled,
onClick = callbacks::onContinue
) {
LoadingArcOr(state.loading) {
Text(stringResource(R.string.next))
}
}
}
@ -127,7 +136,7 @@ private fun EnterAccountId(
@Preview
@Composable
private fun PreviewNewMessage(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
NewMessage(State("z"))

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -22,8 +21,6 @@ import org.session.libsignal.utilities.timeout
import org.thoughtcrime.securesms.ui.GetString
import java.util.concurrent.TimeoutException
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration.Companion.seconds
@HiltViewModel
internal class NewMessageViewModel @Inject constructor(
@ -112,7 +109,7 @@ internal data class State(
val error: GetString? = null,
val loading: Boolean = false
) {
val isNextButtonVisible: Boolean get() = newMessageIdOrOns.isNotBlank()
val isNextButtonEnabled: Boolean get() = newMessageIdOrOns.isNotBlank()
}
internal data class Success(val publicKey: String)

View File

@ -1120,7 +1120,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
showSessionDialog {
title(R.string.RecipientPreferenceActivity_block_this_contact_question)
text(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact)
destructiveButton(R.string.RecipientPreferenceActivity_block, R.string.AccessibilityId_block_confirm) {
dangerButton(R.string.RecipientPreferenceActivity_block, R.string.AccessibilityId_block_confirm) {
viewModel.block()
if (deleteThread) {
viewModel.deleteThread()
@ -1163,7 +1163,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
showSessionDialog {
title(R.string.ConversationActivity_unblock_this_contact_question)
text(R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact)
destructiveButton(
dangerButton(
R.string.ConversationActivity_unblock,
R.string.AccessibilityId_block_confirm
) { viewModel.unblock() }

View File

@ -546,7 +546,8 @@ class ConversationReactionOverlay : FrameLayout {
}
// Delete message
if (userCanDeleteSelectedItems(context, message, openGroup, userPublicKey, blindedPublicKey)) {
items += ActionItem(R.attr.menu_trash_icon, R.string.delete, { handleActionItemClicked(Action.DELETE) }, R.string.AccessibilityId_delete_message, message.subtitle, R.color.destructive)
items += ActionItem(R.attr.menu_trash_icon, R.string.delete, { handleActionItemClicked(Action.DELETE) },
R.string.AccessibilityId_delete_message, message.subtitle, ThemeUtil.getThemedColor(context, R.attr.danger))
}
// Ban user
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey)) {

View File

@ -27,9 +27,9 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -63,20 +63,19 @@ import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator
import org.thoughtcrime.securesms.ui.ItemButton
import org.thoughtcrime.securesms.ui.LargeItemButton
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
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.TitledText
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.baseBold
import org.thoughtcrime.securesms.ui.baseMonospace
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.blackAlpha40
import org.thoughtcrime.securesms.ui.color.destructiveButtonColors
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.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.monospace
import javax.inject.Inject
@AndroidEntryPoint
@ -152,12 +151,12 @@ fun MessageDetails(
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(vertical = LocalDimensions.current.smallItemSpacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing)
.padding(vertical = LocalDimensions.current.smallSpacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
state.record?.let { message ->
AndroidView(
modifier = Modifier.padding(horizontal = LocalDimensions.current.margin),
modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing),
factory = {
ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply {
bind(
@ -193,7 +192,7 @@ fun CellMetadata(
state.apply {
if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return
CellWithPaddingAndMargin {
Column(verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing)) {
Column(verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)) {
TitledText(sent)
TitledText(received)
TitledErrorText(error)
@ -237,7 +236,7 @@ fun CellButtons(
LargeItemButton(
R.string.delete,
R.drawable.ic_message_details__trash,
colors = destructiveButtonColors(),
colors = dangerButtonColors(),
onClick = onDelete
)
}
@ -251,7 +250,7 @@ fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
val pagerState = rememberPagerState { attachments.size }
Column(verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing)) {
Column(verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)) {
Row {
CarouselPrevButton(pagerState)
Box(modifier = Modifier.weight(1f)) {
@ -260,7 +259,7 @@ fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
ExpandButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(LocalDimensions.current.xxsItemSpacing)
.padding(LocalDimensions.current.xxsSpacing)
) { onClick(pagerState.currentPage) }
}
CarouselNextButton(pagerState)
@ -313,7 +312,7 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
@Preview
@Composable
fun PreviewMessageDetails(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
MessageDetails(
@ -340,8 +339,8 @@ fun FileDetails(fileDetails: List<TitledText>) {
Cell {
FlowRow(
modifier = Modifier.padding(horizontal = LocalDimensions.current.xsItemSpacing, vertical = LocalDimensions.current.itemSpacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing)
modifier = Modifier.padding(horizontal = LocalDimensions.current.xsSpacing, vertical = LocalDimensions.current.spacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
fileDetails.forEach {
BoxWithConstraints {
@ -349,7 +348,7 @@ fun FileDetails(fileDetails: List<TitledText>) {
it,
modifier = Modifier
.widthIn(min = maxWidth.div(2))
.padding(horizontal = LocalDimensions.current.xsItemSpacing)
.padding(horizontal = LocalDimensions.current.xsSpacing)
.width(IntrinsicSize.Max)
)
}
@ -362,7 +361,7 @@ fun FileDetails(fileDetails: List<TitledText>) {
fun TitledErrorText(titledText: TitledText?) {
TitledText(
titledText,
style = base,
style = LocalType.current.base,
color = LocalColors.current.danger
)
}
@ -371,7 +370,7 @@ fun TitledErrorText(titledText: TitledText?) {
fun TitledMonospaceText(titledText: TitledText?) {
TitledText(
titledText,
style = baseMonospace
style = LocalType.current.base.monospace()
)
}
@ -379,7 +378,7 @@ fun TitledMonospaceText(titledText: TitledText?) {
fun TitledText(
titledText: TitledText?,
modifier: Modifier = Modifier,
style: TextStyle = base,
style: TextStyle = LocalType.current.base,
color: Color = Color.Unspecified
) {
titledText?.apply {
@ -396,8 +395,8 @@ fun TitledText(
@Composable
fun TitledView(title: GetString, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxxsItemSpacing)) {
Text(title.string(), style = baseBold)
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxxsSpacing)) {
Text(title.string(), style = LocalType.current.base.bold())
content()
}
}

View File

@ -1,381 +0,0 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.conversation.v2;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.components.ComposeText;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import network.loki.messenger.R;
public class Util {
private static final String TAG = Log.tag(Util.class);
private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90);
public static <T> List<T> asList(T... elements) {
List<T> result = new LinkedList<>();
Collections.addAll(result, elements);
return result;
}
public static String join(String[] list, String delimiter) {
return join(Arrays.asList(list), delimiter);
}
public static <T> String join(Collection<T> list, String delimiter) {
StringBuilder result = new StringBuilder();
int i = 0;
for (T item : list) {
result.append(item);
if (++i < list.size())
result.append(delimiter);
}
return result.toString();
}
public static String join(long[] list, String delimeter) {
List<Long> boxed = new ArrayList<>(list.length);
for (int i = 0; i < list.length; i++) {
boxed.add(list[i]);
}
return join(boxed, delimeter);
}
@SafeVarargs
public static @NonNull <E> List<E> join(@NonNull List<E>... lists) {
int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size());
List<E> joined = new ArrayList<>(totalSize);
for (List<E> list : lists) {
joined.addAll(list);
}
return joined;
}
public static String join(List<Long> list, String delimeter) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < list.size(); j++) {
if (j != 0) sb.append(delimeter);
sb.append(list.get(j));
}
return sb.toString();
}
public static String rightPad(String value, int length) {
if (value.length() >= length) {
return value;
}
StringBuilder out = new StringBuilder(value);
while (out.length() < length) {
out.append(" ");
}
return out.toString();
}
public static boolean isEmpty(EncodedStringValue[] value) {
return value == null || value.length == 0;
}
public static boolean isEmpty(ComposeText value) {
return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed());
}
public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty();
}
public static boolean isEmpty(@Nullable CharSequence charSequence) {
return charSequence == null || charSequence.length() == 0;
}
public static boolean hasItems(@Nullable Collection<?> collection) {
return collection != null && !collection.isEmpty();
}
public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {
return map.containsKey(key) ? map.get(key) : defaultValue;
}
public static String getFirstNonEmpty(String... values) {
for (String value : values) {
if (!Util.isEmpty(value)) {
return value;
}
}
return "";
}
public static @NonNull String emptyIfNull(@Nullable String value) {
return value != null ? value : "";
}
public static @NonNull CharSequence emptyIfNull(@Nullable CharSequence value) {
return value != null ? value : "";
}
public static CharSequence getBoldedString(String value) {
SpannableString spanned = new SpannableString(value);
spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
spanned.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spanned;
}
public static @NonNull String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
throw new AssertionError("ISO_8859_1 must be supported!");
}
}
public static byte[] toIsoBytes(String isoString) {
try {
return isoString.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
throw new AssertionError("ISO_8859_1 must be supported!");
}
}
public static byte[] toUtf8Bytes(String utf8String) {
try {
return utf8String.getBytes(CharacterSets.MIMENAME_UTF_8);
} catch (UnsupportedEncodingException e) {
throw new AssertionError("UTF_8 must be supported!");
}
}
public static void wait(Object lock, long timeout) {
try {
lock.wait(timeout);
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
}
public static List<String> split(String source, String delimiter) {
List<String> results = new LinkedList<>();
if (TextUtils.isEmpty(source)) {
return results;
}
String[] elements = source.split(delimiter);
Collections.addAll(results, elements);
return results;
}
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
byte[][] parts = new byte[2][];
parts[0] = new byte[firstLength];
System.arraycopy(input, 0, parts[0], 0, firstLength);
parts[1] = new byte[secondLength];
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
return parts;
}
public static byte[] combine(byte[]... elements) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (byte[] element : elements) {
baos.write(element);
}
return baos.toByteArray();
} catch (IOException e) {
throw new AssertionError(e);
}
}
public static byte[] trim(byte[] input, int length) {
byte[] result = new byte[length];
System.arraycopy(input, 0, result, 0, result.length);
return result;
}
public static byte[] getSecretBytes(int size) {
return getSecretBytes(new SecureRandom(), size);
}
public static byte[] getSecretBytes(@NonNull SecureRandom secureRandom, int size) {
byte[] secret = new byte[size];
secureRandom.nextBytes(secret);
return secret;
}
public static <T> T getRandomElement(T[] elements) {
return elements[new SecureRandom().nextInt(elements.length)];
}
public static <T> T getRandomElement(List<T> elements) {
return elements.get(new SecureRandom().nextInt(elements.size()));
}
public static boolean equals(@Nullable Object a, @Nullable Object b) {
return a == b || (a != null && a.equals(b));
}
public static int hashCode(@Nullable Object... objects) {
return Arrays.hashCode(objects);
}
public static @Nullable Uri uri(@Nullable String uri) {
if (uri == null) return null;
else return Uri.parse(uri);
}
@TargetApi(VERSION_CODES.KITKAT)
public static boolean isLowMemory(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) ||
activityManager.getLargeMemoryClass() <= 64;
}
public static int clamp(int value, int min, int max) {
return Math.min(Math.max(value, min), max);
}
public static long clamp(long value, long min, long max) {
return Math.min(Math.max(value, min), max);
}
public static float clamp(float value, float min, float max) {
return Math.min(Math.max(value, min), max);
}
/**
* Returns half of the difference between the given length, and the length when scaled by the
* given scale.
*/
public static float halfOffsetFromScale(int length, float scale) {
float scaledLength = length * scale;
return (length - scaledLength) / 2;
}
public static @Nullable String readTextFromClipboard(@NonNull Context context) {
{
ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClip().getItemCount() > 0) {
return clipboardManager.getPrimaryClip().getItemAt(0).getText().toString();
} else {
return null;
}
}
}
public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) {
writeTextToClipboard(context, context.getString(R.string.app_name), text);
}
public static void writeTextToClipboard(@NonNull Context context, @NonNull String label, @NonNull String text) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(label, text);
clipboard.setPrimaryClip(clip);
}
public static int toIntExact(long value) {
if ((int)value != value) {
throw new ArithmeticException("integer overflow");
}
return (int)value;
}
public static boolean isEquals(@Nullable Long first, long second) {
return first != null && first == second;
}
@SafeVarargs
public static <T> List<T> concatenatedList(Collection <T>... items) {
final List<T> concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size()));
for (Collection<T> list : items) {
concat.addAll(list);
}
return concat;
}
public static boolean isLong(String value) {
try {
Long.parseLong(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
public static int parseInt(String integer, int defaultValue) {
try {
return Integer.parseInt(integer);
} catch (NumberFormatException e) {
return defaultValue;
}
}
}

View File

@ -0,0 +1,384 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.conversation.v2
import android.annotation.TargetApi
import android.app.ActivityManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.text.Spannable
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.StyleSpan
import android.view.View
import com.annimon.stream.Stream
import com.google.android.mms.pdu_alt.CharacterSets
import com.google.android.mms.pdu_alt.EncodedStringValue
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.UnsupportedEncodingException
import java.security.SecureRandom
import java.util.Arrays
import java.util.Collections
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.min
import network.loki.messenger.R
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.components.ComposeText
object Util {
private val TAG: String = Log.tag(Util::class.java)
private val BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90)
fun <T> asList(vararg elements: T): List<T> {
val result = mutableListOf<T>() // LinkedList()
Collections.addAll(result, *elements)
return result
}
fun join(list: Array<String?>, delimiter: String?): String {
return join(listOf(*list), delimiter)
}
fun <T> join(list: Collection<T>, delimiter: String?): String {
val result = StringBuilder()
var i = 0
for (item in list) {
result.append(item)
if (++i < list.size) result.append(delimiter)
}
return result.toString()
}
fun join(list: LongArray, delimeter: String?): String {
val boxed: MutableList<Long> = ArrayList(list.size)
for (i in list.indices) {
boxed.add(list[i])
}
return join(boxed, delimeter)
}
@SafeVarargs
fun <E> join(vararg lists: List<E>): List<E> {
val totalSize = Stream.of(*lists).reduce(0) { sum: Int, list: List<E> -> sum + list.size }
val joined: MutableList<E> = ArrayList(totalSize)
for (list in lists) {
joined.addAll(list)
}
return joined
}
fun join(list: List<Long>, delimeter: String?): String {
val sb = StringBuilder()
for (j in list.indices) {
if (j != 0) sb.append(delimeter)
sb.append(list[j])
}
return sb.toString()
}
fun rightPad(value: String, length: Int): String {
if (value.length >= length) {
return value
}
val out = StringBuilder(value)
while (out.length < length) {
out.append(" ")
}
return out.toString()
}
fun isEmpty(value: Array<EncodedStringValue?>?): Boolean {
return value == null || value.size == 0
}
fun isEmpty(value: ComposeText?): Boolean {
return value == null || value.text == null || TextUtils.isEmpty(value.textTrimmed)
}
fun isEmpty(collection: Collection<*>?): Boolean {
return collection == null || collection.isEmpty()
}
fun isEmpty(charSequence: CharSequence?): Boolean {
return charSequence == null || charSequence.length == 0
}
fun hasItems(collection: Collection<*>?): Boolean {
return collection != null && !collection.isEmpty()
}
fun <K, V> getOrDefault(map: Map<K, V>, key: K, defaultValue: V): V? {
return if (map.containsKey(key)) map[key] else defaultValue
}
fun getFirstNonEmpty(vararg values: String?): String {
for (value in values) {
if (!value.isNullOrEmpty()) { return value }
}
return ""
}
fun emptyIfNull(value: String?): String {
return value ?: ""
}
fun emptyIfNull(value: CharSequence?): CharSequence {
return value ?: ""
}
fun getBoldedString(value: String?): CharSequence {
val spanned = SpannableString(value)
spanned.setSpan(
StyleSpan(Typeface.BOLD), 0,
spanned.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return spanned
}
fun toIsoString(bytes: ByteArray?): String {
try {
return String(bytes!!, charset(CharacterSets.MIMENAME_ISO_8859_1))
} catch (e: UnsupportedEncodingException) {
throw AssertionError("ISO_8859_1 must be supported!")
}
}
fun toIsoBytes(isoString: String): ByteArray {
try {
return isoString.toByteArray(charset(CharacterSets.MIMENAME_ISO_8859_1))
} catch (e: UnsupportedEncodingException) {
throw AssertionError("ISO_8859_1 must be supported!")
}
}
fun toUtf8Bytes(utf8String: String): ByteArray {
try {
return utf8String.toByteArray(charset(CharacterSets.MIMENAME_UTF_8))
} catch (e: UnsupportedEncodingException) {
throw AssertionError("UTF_8 must be supported!")
}
}
fun wait(lock: Any, timeout: Long) {
try {
(lock as Object).wait(timeout)
} catch (ie: InterruptedException) {
throw AssertionError(ie)
}
}
fun split(source: String, delimiter: String): List<String> {
val results = mutableListOf<String>()
if (TextUtils.isEmpty(source)) {
return results
}
val elements =
source.split(delimiter.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
Collections.addAll(results, *elements)
return results
}
fun split(input: ByteArray?, firstLength: Int, secondLength: Int): Array<ByteArray?> {
val parts = arrayOfNulls<ByteArray>(2)
parts[0] = ByteArray(firstLength)
System.arraycopy(input, 0, parts[0], 0, firstLength)
parts[1] = ByteArray(secondLength)
System.arraycopy(input, firstLength, parts[1], 0, secondLength)
return parts
}
fun combine(vararg elements: ByteArray?): ByteArray {
try {
val baos = ByteArrayOutputStream()
for (element in elements) {
baos.write(element)
}
return baos.toByteArray()
} catch (e: IOException) {
throw AssertionError(e)
}
}
fun trim(input: ByteArray?, length: Int): ByteArray {
val result = ByteArray(length)
System.arraycopy(input, 0, result, 0, result.size)
return result
}
fun getSecretBytes(size: Int): ByteArray {
return getSecretBytes(SecureRandom(), size)
}
fun getSecretBytes(secureRandom: SecureRandom, size: Int): ByteArray {
val secret = ByteArray(size)
secureRandom.nextBytes(secret)
return secret
}
fun <T> getRandomElement(elements: Array<T>): T {
return elements[SecureRandom().nextInt(elements.size)]
}
fun <T> getRandomElement(elements: List<T>): T {
return elements[SecureRandom().nextInt(elements.size)]
}
fun equals(a: Any?, b: Any?): Boolean {
return a === b || (a != null && a == b)
}
fun hashCode(vararg objects: Any?): Int {
return objects.contentHashCode()
}
fun uri(uri: String?): Uri? {
return if (uri == null) null
else Uri.parse(uri)
}
@TargetApi(VERSION_CODES.KITKAT)
fun isLowMemory(context: Context): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice) ||
activityManager.largeMemoryClass <= 64
}
fun clamp(value: Int, min: Int, max: Int): Int {
return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toInt()
}
fun clamp(value: Long, min: Long, max: Long): Long {
return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toLong()
}
fun clamp(value: Float, min: Float, max: Float): Float {
return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toFloat()
}
/**
* Returns half of the difference between the given length, and the length when scaled by the
* given scale.
*/
fun halfOffsetFromScale(length: Int, scale: Float): Float {
val scaledLength = length * scale
return (length - scaledLength) / 2
}
fun readTextFromClipboard(context: Context): String? {
run {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
return if (clipboardManager.hasPrimaryClip() && clipboardManager.primaryClip!!.itemCount > 0) {
clipboardManager.primaryClip!!.getItemAt(0).text.toString()
} else {
null
}
}
}
fun writeTextToClipboard(context: Context, text: String) {
writeTextToClipboard(context, context.getString(R.string.app_name), text)
}
fun writeTextToClipboard(context: Context, label: String, text: String) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(label, text)
clipboard.setPrimaryClip(clip)
}
fun toIntExact(value: Long): Int {
if (value.toInt().toLong() != value) {
throw ArithmeticException("integer overflow")
}
return value.toInt()
}
fun isEquals(first: Long?, second: Long): Boolean {
return first != null && first == second
}
@SafeVarargs
fun <T> concatenatedList(vararg items: Collection<T>): List<T> {
val concat: MutableList<T> = ArrayList(
Stream.of(*items).reduce(0) { sum: Int, list: Collection<T> -> sum + list.size })
for (list in items) {
concat.addAll(list)
}
return concat
}
fun isLong(value: String): Boolean {
try {
value.toLong()
return true
} catch (e: NumberFormatException) {
return false
}
}
fun parseInt(integer: String, defaultValue: Int): Int {
return try {
integer.toInt()
} catch (e: NumberFormatException) {
defaultValue
}
}
// Method to determine if we're currently in a left-to-right or right-to-left language like Arabic
fun usingRightToLeftLanguage(context: Context): Boolean {
val config = context.resources.configuration
return config.layoutDirection == View.LAYOUT_DIRECTION_RTL
}
// Method to determine if we're currently in a left-to-right or right-to-left language like Arabic
fun usingLeftToRightLanguage(context: Context): Boolean {
val config = context.resources.configuration
return config.layoutDirection == View.LAYOUT_DIRECTION_LTR
}
}

View File

@ -36,6 +36,7 @@ import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ThemeUtil.getThemedColor
import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.modifyLayoutParams
@ -382,7 +383,7 @@ class VisibleMessageView : FrameLayout {
private fun getMessageStatusInfo(message: MessageRecord): MessageStatusInfo = when {
message.isFailed ->
MessageStatusInfo(R.drawable.ic_delivery_status_failed,
resources.getColor(R.color.destructive, context.theme),
getThemedColor(context, R.attr.danger),
R.string.delivery_status_failed
)
message.isSyncFailed ->

View File

@ -15,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewConversationBinding
import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions
import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL
@ -67,7 +68,7 @@ class ConversationView : LinearLayout {
}
val unreadCount = thread.unreadCount
if (thread.recipient.isBlocked) {
binding.accentView.setBackgroundResource(R.color.destructive)
binding.accentView.setBackgroundColor(ThemeUtil.getThemedColor(context, R.attr.danger))
binding.accentView.visibility = View.VISIBLE
} else {
val accentColor = context.getAccentColor()
@ -120,7 +121,7 @@ class ConversationView : LinearLayout {
!thread.isOutgoing -> binding.statusIndicatorImageView.visibility = View.GONE
thread.isFailed -> {
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_error)?.mutate()
drawable?.setTint(ContextCompat.getColor(context, R.color.destructive))
drawable?.setTint(ThemeUtil.getThemedColor(context, R.attr.danger))
binding.statusIndicatorImageView.setImageDrawable(drawable)
}
thread.isPending -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot)

View File

@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.home
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -14,24 +14,22 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.h4
import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.small
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.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalType
@Composable
internal fun EmptyView(newAccount: Boolean) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(horizontal = LocalDimensions.current.homeEmptyViewMargin)
.padding(horizontal = 50.dp)
) {
Spacer(modifier = Modifier.weight(1f))
Icon(
@ -42,27 +40,27 @@ internal fun EmptyView(newAccount: Boolean) {
if (newAccount) {
Text(
stringResource(R.string.onboardingAccountCreated),
style = h4,
style = LocalType.current.h4,
textAlign = TextAlign.Center
)
Text(
stringResource(R.string.welcome_to_session),
style = base,
style = LocalType.current.base,
color = LocalColors.current.primary,
textAlign = TextAlign.Center
)
}
Divider(modifier = Modifier.padding(vertical = LocalDimensions.current.xsMargin))
Divider(modifier = Modifier.padding(vertical = LocalDimensions.current.smallSpacing))
Text(
stringResource(R.string.conversationsNone),
style = h8,
style = LocalType.current.h8,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = LocalDimensions.current.xsItemSpacing))
modifier = Modifier.padding(bottom = LocalDimensions.current.xsSpacing))
Text(
stringResource(R.string.onboardingHitThePlusButton),
style = small,
style = LocalType.current.small,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.weight(2f))
@ -72,7 +70,7 @@ internal fun EmptyView(newAccount: Boolean) {
@Preview
@Composable
fun PreviewEmptyView(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
EmptyView(newAccount = false)
@ -82,7 +80,7 @@ fun PreviewEmptyView(
@Preview
@Composable
fun PreviewEmptyViewNew(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
EmptyView(newAccount = true)

View File

@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -18,16 +18,15 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
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.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.SlimPrimaryOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.ui.theme.LocalType
@Composable
internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) {
@ -43,25 +42,25 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) {
Modifier
.background(LocalColors.current.backgroundSecondary)
.padding(
horizontal = LocalDimensions.current.smallMargin,
vertical = LocalDimensions.current.xsMargin
horizontal = LocalDimensions.current.spacing,
vertical = LocalDimensions.current.smallSpacing
)
) {
Column(Modifier.weight(1f)) {
Row {
Text(
stringResource(R.string.save_your_recovery_password),
style = h8
style = LocalType.current.h8
)
Spacer(Modifier.requiredWidth(LocalDimensions.current.xxsItemSpacing))
Spacer(Modifier.requiredWidth(LocalDimensions.current.xxsSpacing))
SessionShieldIcon()
}
Text(
stringResource(R.string.save_your_recovery_password_to_make_sure_you_don_t_lose_access_to_your_account),
style = small
style = LocalType.current.small
)
}
Spacer(Modifier.width(LocalDimensions.current.xxsMargin))
Spacer(Modifier.width(LocalDimensions.current.xsSpacing))
SlimPrimaryOutlineButton(
text = stringResource(R.string.continue_2),
modifier = Modifier
@ -76,7 +75,7 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) {
@Preview
@Composable
private fun PreviewSeedReminder(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
SeedReminder {}

View File

@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R
import org.session.libsession.utilities.ThemeUtil
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@ -60,8 +61,9 @@ class MessageRequestsAdapter(
for (i in 0 until popupMenu.menu.size()) {
val item = popupMenu.menu.getItem(i)
val s = SpannableString(item.title)
s.setSpan(ForegroundColorSpan(context.getColor(R.color.destructive)), 0, s.length, 0)
item.iconTintList = ColorStateList.valueOf(context.getColor(R.color.destructive))
val danger = ThemeUtil.getThemedColor(context, R.attr.danger)
s.setSpan(ForegroundColorSpan(danger), 0, s.length, 0)
item.iconTintList = ColorStateList.valueOf(danger)
item.title = s
}
popupMenu.setForceShowIcon(true)

View File

@ -1,209 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import static org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY;
import android.content.Context;
import android.content.res.Resources.Theme;
import android.net.Uri;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.squareup.phrase.Phrase;
import java.security.SecureRandom;
import network.loki.messenger.R;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.util.MediaUtil;
public abstract class Slide {
protected final Attachment attachment;
protected final Context context;
public Slide(@NonNull Context context, @NonNull Attachment attachment) {
this.context = context;
this.attachment = attachment;
}
public String getContentType() {
return attachment.getContentType();
}
@Nullable
public Uri getUri() {
return attachment.getDataUri();
}
@Nullable
public Uri getThumbnailUri() {
return attachment.getThumbnailUri();
}
@NonNull
public Optional<String> getBody() {
String attachmentString = context.getString(R.string.attachment);
if (MediaUtil.isAudio(attachment)) {
// A missing file name is the legacy way to determine if an audio attachment is
// a voice note vs. other arbitrary audio attachments.
if (attachment.isVoiceNote() || attachment.getFileName() == null ||
attachment.getFileName().isEmpty()) {
attachmentString = context.getString(R.string.attachment_type_voice_message);
return Optional.fromNullable("🎤 " + attachmentString);
}
}
String txt = Phrase.from(context, R.string.attachmentsNotification)
.put(EMOJI_KEY, emojiForMimeType())
.format().toString();
return Optional.fromNullable(txt);
}
private String emojiForMimeType() {
if (MediaUtil.isImage(attachment)) {
return "📷";
} else if (MediaUtil.isVideo(attachment)) {
return "🎥";
} else if (MediaUtil.isAudio(attachment)) {
return "🎧";
} else if (MediaUtil.isFile(attachment)) {
return "📎";
} else {
return "🎡"; // `isGif`
}
}
@NonNull
public Optional<String> getCaption() {
return Optional.fromNullable(attachment.getCaption());
}
@NonNull
public Optional<String> getFileName() {
return Optional.fromNullable(attachment.getFileName());
}
@Nullable
public String getFastPreflightId() {
return attachment.getFastPreflightId();
}
public long getFileSize() {
return attachment.getSize();
}
public boolean hasImage() {
return false;
}
public boolean hasVideo() {
return false;
}
public boolean hasAudio() {
return false;
}
public boolean hasDocument() {
return false;
}
public @NonNull String getContentDescription() { return ""; }
public @NonNull Attachment asAttachment() {
return attachment;
}
public boolean isInProgress() {
return attachment.isInProgress();
}
public boolean isPendingDownload() {
return getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED ||
getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING;
}
public int getTransferState() {
return attachment.getTransferState();
}
public @DrawableRes int getPlaceholderRes(Theme theme) {
throw new AssertionError("getPlaceholderRes() called for non-drawable slide");
}
public boolean hasPlaceholder() {
return false;
}
public boolean hasPlayOverlay() {
return false;
}
protected static Attachment constructAttachmentFromUri(@NonNull Context context,
@NonNull Uri uri,
@NonNull String defaultMime,
long size,
int width,
int height,
boolean hasThumbnail,
@Nullable String fileName,
@Nullable String caption,
boolean voiceNote,
boolean quote)
{
String resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime);
String fastPreflightId = String.valueOf(new SecureRandom().nextLong());
return new UriAttachment(uri,
hasThumbnail ? uri : null,
resolvedType,
AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED,
size,
width,
height,
fileName,
fastPreflightId,
voiceNote,
quote,
caption);
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof Slide)) return false;
Slide that = (Slide)other;
return Util.equals(this.getContentType(), that.getContentType()) &&
this.hasAudio() == that.hasAudio() &&
this.hasImage() == that.hasImage() &&
this.hasVideo() == that.hasVideo() &&
this.getTransferState() == that.getTransferState() &&
Util.equals(this.getUri(), that.getUri()) &&
Util.equals(this.getThumbnailUri(), that.getThumbnailUri());
}
@Override
public int hashCode() {
return Util.hashCode(getContentType(), hasAudio(), hasImage(),
hasVideo(), getUri(), getThumbnailUri(), getTransferState());
}
}

View File

@ -0,0 +1,180 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http:></http:>//www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms
import android.content.Context
import android.content.res.Resources
import android.net.Uri
import androidx.annotation.DrawableRes
import com.squareup.phrase.Phrase
import java.security.SecureRandom
import network.loki.messenger.R
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment
import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY
import org.session.libsession.utilities.Util.equals
import org.session.libsession.utilities.Util.hashCode
import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.conversation.v2.Util
import org.thoughtcrime.securesms.util.MediaUtil
abstract class Slide(@JvmField protected val context: Context, protected val attachment: Attachment) {
val contentType: String
get() = attachment.contentType
val uri: Uri?
get() = attachment.dataUri
open val thumbnailUri: Uri?
get() = attachment.thumbnailUri
val body: Optional<String>
get() {
if (MediaUtil.isAudio(attachment)) {
// A missing file name is the legacy way to determine if an audio attachment is
// a voice note vs. other arbitrary audio attachments.
if (attachment.isVoiceNote || attachment.fileName.isNullOrEmpty()) {
val baseString = context.getString(R.string.attachment_type_voice_message)
val languageIsLTR = Util.usingLeftToRightLanguage(context)
val attachmentString = if (languageIsLTR) {
"🎙 $baseString"
} else {
"$baseString 🎙"
}
return Optional.fromNullable(attachmentString)
}
}
val txt = Phrase.from(context, R.string.attachmentsNotification)
.put(EMOJI_KEY, emojiForMimeType())
.format().toString()
return Optional.fromNullable(txt)
}
private fun emojiForMimeType(): String {
return if (MediaUtil.isGif(attachment)) {
"🎡"
} else if (MediaUtil.isImage(attachment)) {
"📷"
} else if (MediaUtil.isVideo(attachment)) {
"🎥"
} else if (MediaUtil.isAudio(attachment)) {
"🎧"
} else if (MediaUtil.isFile(attachment)) {
"📎"
} else {
// We don't provide emojis for other mime-types such as VCARD
""
}
}
val caption: Optional<String?>
get() = Optional.fromNullable(attachment.caption)
val fileName: Optional<String?>
get() = Optional.fromNullable(attachment.fileName)
val fastPreflightId: String?
get() = attachment.fastPreflightId
val fileSize: Long
get() = attachment.size
open fun hasImage(): Boolean { return false }
open fun hasVideo(): Boolean { return false }
open fun hasAudio(): Boolean { return false }
open fun hasDocument(): Boolean { return false }
open val contentDescription: String
get() = ""
fun asAttachment(): Attachment { return attachment }
val isInProgress: Boolean
get() = attachment.isInProgress
val isPendingDownload: Boolean
get() = transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED ||
transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING
val transferState: Int
get() = attachment.transferState
@DrawableRes
open fun getPlaceholderRes(theme: Resources.Theme?): Int {
throw AssertionError("getPlaceholderRes() called for non-drawable slide")
}
open fun hasPlaceholder(): Boolean { return false }
open fun hasPlayOverlay(): Boolean { return false }
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (other !is Slide) return false
return (equals(this.contentType, other.contentType) &&
hasAudio() == other.hasAudio() &&
hasImage() == other.hasImage() &&
hasVideo() == other.hasVideo()) &&
this.transferState == other.transferState &&
equals(this.uri, other.uri) &&
equals(this.thumbnailUri, other.thumbnailUri)
}
override fun hashCode(): Int {
return hashCode(contentType, hasAudio(), hasImage(), hasVideo(), uri, thumbnailUri, transferState)
}
companion object {
@JvmStatic
protected fun constructAttachmentFromUri(
context: Context,
uri: Uri,
defaultMime: String,
size: Long,
width: Int,
height: Int,
hasThumbnail: Boolean,
fileName: String?,
caption: String?,
voiceNote: Boolean,
quote: Boolean
): Attachment {
val resolvedType =
Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime)
val fastPreflightId = SecureRandom().nextLong().toString()
return UriAttachment(
uri,
if (hasThumbnail) uri else null,
resolvedType!!,
AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED,
size,
width,
height,
fileName,
fastPreflightId,
voiceNote,
quote,
caption
)
}
}
}

View File

@ -7,7 +7,7 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.AlertDialog
import org.thoughtcrime.securesms.ui.DialogButtonModel
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
@Composable
fun OnboardingBackPressAlertDialog(

View File

@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -16,10 +17,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -40,23 +39,22 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.AlertDialog
import org.thoughtcrime.securesms.ui.DialogButtonModel
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.components.BorderlessHtmlButton
import org.thoughtcrime.securesms.ui.components.PrimaryFillButton
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h4
import org.thoughtcrime.securesms.ui.large
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.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.ThemeColors
import kotlin.time.Duration.Companion.milliseconds
@Preview
@Composable
private fun PreviewLandingScreen(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
LandingScreen({}, {}, {}, {})
@ -80,6 +78,7 @@ internal fun LandingScreen(
onDismissRequest = { isUrlDialogVisible = false },
title = stringResource(R.string.urlOpen),
text = stringResource(R.string.urlOpenBrowser),
showCloseButton = true, // display the 'x' button
buttons = listOf(
DialogButtonModel(
text = GetString(R.string.activity_landing_terms_of_service),
@ -107,24 +106,24 @@ internal fun LandingScreen(
Column {
Column(modifier = Modifier
.weight(1f)
.padding(horizontal = LocalDimensions.current.onboardingMargin)
.padding(horizontal = LocalDimensions.current.mediumSpacing)
) {
Spacer(modifier = Modifier.weight(1f))
Text(
stringResource(R.string.onboardingBubblePrivacyInYourPocket),
modifier = Modifier.align(Alignment.CenterHorizontally),
style = h4,
style = LocalType.current.h4,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
LazyColumn(
state = listState,
modifier = Modifier
.heightIn(min = LocalDimensions.current.minScrollableViewHeight)
.heightIn(min = 200.dp)
.fillMaxWidth()
.weight(3f),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing)
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
items(
MESSAGES.take(count),
@ -140,7 +139,7 @@ internal fun LandingScreen(
Spacer(modifier = Modifier.weight(1f))
}
Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.largeMargin)) {
Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.xlargeSpacing)) {
PrimaryFillButton(
text = stringResource(R.string.onboardingAccountCreate),
modifier = Modifier
@ -149,7 +148,7 @@ internal fun LandingScreen(
.contentDescription(R.string.AccessibilityId_create_account_button),
onClick = createAccount
)
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
PrimaryOutlineButton(
stringResource(R.string.onboardingAccountExists),
modifier = Modifier
@ -166,7 +165,7 @@ internal fun LandingScreen(
.contentDescription(R.string.AccessibilityId_open_url),
onClick = { isUrlDialogVisible = true }
)
Spacer(modifier = Modifier.height(LocalDimensions.current.xxsItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.xxsSpacing))
}
}
}
@ -195,7 +194,7 @@ private fun MessageText(text: String, isOutgoing: Boolean, modifier: Modifier) {
Box(modifier = modifier then Modifier.fillMaxWidth()) {
MessageText(
text,
color = if (isOutgoing) LocalColors.current.backgroundBubbleSent else LocalColors.current.backgroundBubbleReceived,
color = if (isOutgoing) LocalColors.current.primary else LocalColors.current.backgroundBubbleReceived,
textColor = if (isOutgoing) LocalColors.current.textBubbleSent else LocalColors.current.textBubbleReceived,
modifier = Modifier.align(if (isOutgoing) Alignment.TopEnd else Alignment.TopStart)
)
@ -209,19 +208,17 @@ private fun MessageText(
modifier: Modifier = Modifier,
textColor: Color = Color.Unspecified
) {
Card(
modifier = modifier.fillMaxWidth(0.666f),
shape = MaterialTheme.shapes.small,
backgroundColor = color,
elevation = 0.dp
Box(
modifier = modifier.fillMaxWidth(0.666f)
.background(color = color, shape = MaterialTheme.shapes.small)
) {
Text(
text,
style = large,
style = LocalType.current.large,
color = textColor,
modifier = Modifier.padding(
horizontal = LocalDimensions.current.smallItemSpacing,
vertical = LocalDimensions.current.xsItemSpacing
horizontal = LocalDimensions.current.smallSpacing,
vertical = LocalDimensions.current.xsSpacing
)
)
}

View File

@ -13,8 +13,8 @@ import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -24,13 +24,12 @@ import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.flow.Flow
import network.loki.messenger.R
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
import org.thoughtcrime.securesms.ui.components.SessionTabRow
import org.thoughtcrime.securesms.ui.h4
import org.thoughtcrime.securesms.ui.theme.LocalType
private val TITLES = listOf(R.string.sessionRecoveryPassword, R.string.qrScan)
@ -75,29 +74,29 @@ private fun RecoveryPassword(state: State, onChange: (String) -> Unit = {}, onCo
.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.weight(1f))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
Column(
modifier = Modifier.padding(horizontal = LocalDimensions.current.largeMargin)
modifier = Modifier.padding(horizontal = LocalDimensions.current.mediumSpacing)
) {
Row {
Text(
text = stringResource(R.string.sessionRecoveryPassword),
style = h4
style = LocalType.current.h4
)
Spacer(Modifier.width(LocalDimensions.current.xxsItemSpacing))
Spacer(Modifier.width(LocalDimensions.current.xxsSpacing))
Icon(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.ic_shield_outline),
contentDescription = null,
)
}
Spacer(Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(Modifier.height(LocalDimensions.current.smallSpacing))
Text(
stringResource(R.string.activity_link_enter_your_recovery_password_to_load_your_account_if_you_haven_t_saved_it_you_can_find_it_in_your_app_settings),
style = base
style = LocalType.current.base
)
Spacer(Modifier.height(LocalDimensions.current.itemSpacing))
Spacer(Modifier.height(LocalDimensions.current.spacing))
SessionOutlinedTextField(
text = state.recoveryPhrase,
modifier = Modifier.fillMaxWidth(),
@ -110,7 +109,7 @@ private fun RecoveryPassword(state: State, onChange: (String) -> Unit = {}, onCo
)
}
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
Spacer(Modifier.weight(2f))
ContinuePrimaryOutlineButton(modifier = Modifier.align(Alignment.CenterHorizontally), onContinue)

View File

@ -3,17 +3,16 @@ package org.thoughtcrime.securesms.onboarding.loading
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.ProgressArc
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h7
import org.thoughtcrime.securesms.ui.theme.LocalType
@Composable
internal fun LoadingScreen(progress: Float) {
@ -25,12 +24,12 @@ internal fun LoadingScreen(progress: Float) {
)
Text(
stringResource(R.string.waitOneMoment),
style = h7
style = LocalType.current.h7
)
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
Text(
stringResource(R.string.loadAccountProgressMessage),
style = base
style = LocalType.current.base
)
Spacer(modifier = Modifier.weight(2f))
}

View File

@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -22,22 +22,15 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.onboarding.OnboardingBackPressAlertDialog
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsViewModel.UiState
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
import org.thoughtcrime.securesms.ui.AlertDialog
import org.thoughtcrime.securesms.ui.DialogButtonModel
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
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.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator
import org.thoughtcrime.securesms.ui.components.RadioButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h4
import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.h9
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.ui.theme.LocalType
@Composable
internal fun MessageNotificationsScreen(
@ -63,11 +56,11 @@ internal fun MessageNotificationsScreen(
Column {
Spacer(Modifier.weight(1f))
Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.onboardingMargin)) {
Text(stringResource(R.string.notificationsMessage), style = h4)
Spacer(Modifier.height(LocalDimensions.current.xsMargin))
Text(stringResource(R.string.onboardingMessageNotificationExplaination), style = base)
Spacer(Modifier.height(LocalDimensions.current.itemSpacing))
Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.mediumSpacing)) {
Text(stringResource(R.string.notificationsMessage), style = LocalType.current.h4)
Spacer(Modifier.height(LocalDimensions.current.smallSpacing))
Text(stringResource(R.string.onboardingMessageNotificationExplaination), style = LocalType.current.base)
Spacer(Modifier.height(LocalDimensions.current.spacing))
}
NotificationRadioButton(
@ -107,8 +100,8 @@ private fun NotificationRadioButton(
RadioButton(
onClick = onClick,
modifier = modifier,
checked = checked,
contentPadding = PaddingValues(horizontal = LocalDimensions.current.margin, vertical = 7.dp)
selected = checked,
contentPadding = PaddingValues(horizontal = LocalDimensions.current.mediumSpacing, vertical = 7.dp)
) {
Box(
modifier = Modifier
@ -120,17 +113,18 @@ private fun NotificationRadioButton(
),
) {
Column(modifier = Modifier
.padding(horizontal = 15.dp)
.padding(top = 10.dp, bottom = 11.dp)) {
Text(stringResource(title), style = h8)
.padding(horizontal = LocalDimensions.current.smallSpacing,
vertical = LocalDimensions.current.xsSpacing)
) {
Text(stringResource(title), style = LocalType.current.h8)
Text(stringResource(explanation), style = small, modifier = Modifier.padding(top = 7.dp))
Text(stringResource(explanation), style = LocalType.current.small, modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing))
tag?.let {
Text(
stringResource(it),
modifier = Modifier.padding(top = 6.dp),
modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing),
color = LocalColors.current.primary,
style = h9
style = LocalType.current.h9
)
}
}
@ -141,7 +135,7 @@ private fun NotificationRadioButton(
@Preview
@Composable
private fun MessageNotificationsScreenPreview(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
MessageNotificationsScreen()

View File

@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -17,11 +17,10 @@ import androidx.compose.ui.tooling.preview.Preview
import network.loki.messenger.R
import org.thoughtcrime.securesms.onboarding.OnboardingBackPressAlertDialog
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
import org.thoughtcrime.securesms.ui.h4
import org.thoughtcrime.securesms.ui.theme.LocalType
@Preview
@Composable
@ -52,18 +51,18 @@ internal fun PickDisplayName(
.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.weight(1f))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
Column(
modifier = Modifier.padding(horizontal = LocalDimensions.current.largeMargin)
modifier = Modifier.padding(horizontal = LocalDimensions.current.mediumSpacing)
) {
Text(stringResource(state.title), style = h4)
Spacer(Modifier.height(LocalDimensions.current.smallItemSpacing))
Text(stringResource(state.title), style = LocalType.current.h4)
Spacer(Modifier.height(LocalDimensions.current.smallSpacing))
Text(
stringResource(state.description),
style = base,
modifier = Modifier.padding(bottom = LocalDimensions.current.xsItemSpacing))
Spacer(Modifier.height(LocalDimensions.current.itemSpacing))
style = LocalType.current.base,
modifier = Modifier.padding(bottom = LocalDimensions.current.xsSpacing))
Spacer(Modifier.height(LocalDimensions.current.spacing))
SessionOutlinedTextField(
text = state.displayName,
modifier = Modifier.fillMaxWidth(),
@ -76,7 +75,7 @@ internal fun PickDisplayName(
)
}
Spacer(modifier = Modifier.height(LocalDimensions.current.smallItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
Spacer(Modifier.weight(2f))
ContinuePrimaryOutlineButton(modifier = Modifier.align(Alignment.CenterHorizontally), onContinue)

View File

@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription
@ -17,8 +17,8 @@ fun ContinuePrimaryOutlineButton(modifier: Modifier, onContinue: () -> Unit) {
modifier = modifier
.contentDescription(R.string.AccessibilityId_continue)
.fillMaxWidth()
.padding(horizontal = LocalDimensions.current.largeMargin)
.padding(bottom = LocalDimensions.current.xxsMargin),
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
.padding(bottom = LocalDimensions.current.smallSpacing),
onClick = onContinue,
)
}

View File

@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@ -27,14 +27,14 @@ import org.session.libsignal.utilities.PublicKeyValidation
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.threadDatabase
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
import org.thoughtcrime.securesms.ui.components.QrImage
import org.thoughtcrime.securesms.ui.components.SessionTabRow
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.setComposeContent
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.util.start
private val TITLES = listOf(R.string.view, R.string.scan)
@ -95,14 +95,14 @@ private fun Tabs(accountId: String, errors: Flow<String>, onScan: (String) -> Un
fun QrPage(string: String) {
Column(
modifier = Modifier
.background(LocalColors.current.backgroundSecondary)
.padding(horizontal = LocalDimensions.current.margin)
.background(LocalColors.current.background)
.padding(horizontal = LocalDimensions.current.mediumSpacing)
.fillMaxSize()
) {
QrImage(
string = string,
modifier = Modifier
.padding(top = LocalDimensions.current.margin, bottom = LocalDimensions.current.xxsMargin)
.padding(top = LocalDimensions.current.mediumSpacing, bottom = LocalDimensions.current.xsSpacing)
.contentDescription(R.string.AccessibilityId_qr_code),
icon = R.drawable.session
)
@ -111,7 +111,7 @@ fun QrPage(string: String) {
text = stringResource(R.string.this_is_your_account_id_other_users_can_scan_it_to_start_a_conversation_with_you),
color = LocalColors.current.textSecondary,
textAlign = TextAlign.Center,
style = small
style = LocalType.current.small
)
}
}

View File

@ -77,8 +77,8 @@ import org.thoughtcrime.securesms.ui.Cell
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.LargeItemButton
import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.destructiveButtonColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton
import org.thoughtcrime.securesms.ui.contentDescription
@ -448,9 +448,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
Column {
Row(
modifier = Modifier
.padding(horizontal = LocalDimensions.current.smallMargin)
.padding(top = LocalDimensions.current.xxxsMargin),
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing),
.padding(horizontal = LocalDimensions.current.spacing)
.padding(top = LocalDimensions.current.xxsSpacing),
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
) {
PrimaryOutlineButton(
stringResource(R.string.share),
@ -464,7 +464,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
)
}
Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
val hasPaths by hasPaths().collectAsState(initial = false)
@ -492,7 +492,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
LargeItemButton(R.string.activity_settings_help_button, R.drawable.ic_help, Modifier.contentDescription(R.string.AccessibilityId_help)) { show<HelpSettingsActivity>() }
Divider()
LargeItemButton(R.string.activity_settings_clear_all_data_button_title, R.drawable.ic_clear_data, Modifier.contentDescription(R.string.AccessibilityId_clear_data), destructiveButtonColors()) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") }
LargeItemButton(R.string.activity_settings_clear_all_data_button_title, R.drawable.ic_message_details__trash, Modifier.contentDescription(R.string.AccessibilityId_clear_data), dangerButtonColors()) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") }
}
}
}
@ -509,4 +509,4 @@ private fun LocalBroadcastManager.hasPaths(): Flow<Boolean> = callbackFlow {
registerReceiver(receiver, IntentFilter("pathsBuilt"))
awaitClose { unregisterReceiver(receiver) }
}.onStart { emit(Unit) }.map { OnionRequestAPI.paths.isNotEmpty() }
}.onStart { emit(Unit) }.map { OnionRequestAPI.paths.isNotEmpty() }

View File

@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ui.theme.selectedTheme
import org.thoughtcrime.securesms.util.ThemeState
import org.thoughtcrime.securesms.util.themeState
import javax.inject.Inject
@ -26,11 +27,17 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
prefs.setThemeStyle(newThemeStyle)
// update UI state
_uiState.value = prefs.themeState()
// force compose to refresh its style reference
selectedTheme = null
}
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
prefs.setFollowSystemSettings(followSystemSettings)
_uiState.value = prefs.themeState()
// force compose to refresh its style reference
selectedTheme = null
}
}

View File

@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -26,20 +26,19 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
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.base
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
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.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
import org.thoughtcrime.securesms.ui.components.border
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.extraSmallMonospace
import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.monospace
@Composable
internal fun RecoveryPasswordScreen(
@ -49,11 +48,11 @@ internal fun RecoveryPasswordScreen(
onHide:() -> Unit = {}
) {
Column(
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.xsMargin),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password)
.verticalScroll(rememberScrollState())
.padding(bottom = LocalDimensions.current.xsMargin)
.padding(bottom = LocalDimensions.current.smallSpacing)
) {
RecoveryPasswordCell(mnemonic, seed, copyMnemonic)
HideRecoveryPasswordCell(onHide)
@ -75,17 +74,17 @@ private fun RecoveryPasswordCell(
Row {
Text(
stringResource(R.string.sessionRecoveryPassword),
style = h8
style = LocalType.current.h8
)
Spacer(Modifier.width(LocalDimensions.current.xxsItemSpacing))
Spacer(Modifier.width(LocalDimensions.current.xxsSpacing))
SessionShieldIcon()
}
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsMargin))
Spacer(modifier = Modifier.height(LocalDimensions.current.xxsSpacing))
Text(
stringResource(R.string.recoveryPasswordDescription),
style = base
style = LocalType.current.base
)
AnimatedVisibility(!showQr) {
@ -99,7 +98,7 @@ private fun RecoveryPasswordCell(
QrImage(
seed,
modifier = Modifier
.padding(vertical = LocalDimensions.current.smallMargin)
.padding(vertical = LocalDimensions.current.spacing)
.contentDescription(R.string.AccessibilityId_qr_code),
contentPadding = 10.dp,
icon = R.drawable.session_shield
@ -108,7 +107,7 @@ private fun RecoveryPasswordCell(
AnimatedVisibility(!showQr) {
Row(
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing),
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
verticalAlignment = Alignment.CenterVertically
) {
SlimOutlineCopyButton(
@ -138,11 +137,11 @@ private fun RecoveryPassword(mnemonic: String) {
mnemonic,
modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password_container)
.padding(vertical = LocalDimensions.current.smallMargin)
.padding(vertical = LocalDimensions.current.spacing)
.border()
.padding(LocalDimensions.current.smallMargin),
.padding(LocalDimensions.current.spacing),
textAlign = TextAlign.Center,
style = extraSmallMonospace,
style = LocalType.current.extraSmall.monospace(),
color = LocalColors.current.run { if (isLight) text else primary },
)
}
@ -156,14 +155,14 @@ private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
) {
Text(
stringResource(R.string.recoveryPasswordHideRecoveryPassword),
style = h8
style = LocalType.current.h8
)
Text(
stringResource(R.string.recoveryPasswordHideRecoveryPasswordDescription),
style = base
style = LocalType.current.base
)
}
Spacer(modifier = Modifier.width(LocalDimensions.current.xxsMargin))
Spacer(modifier = Modifier.width(LocalDimensions.current.xsSpacing))
SlimOutlineButton(
text = stringResource(R.string.hide),
modifier = Modifier
@ -180,7 +179,7 @@ private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
@Preview
@Composable
private fun PreviewRecoveryPasswordScreen(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
RecoveryPasswordScreen(mnemonic = "voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane")

View File

@ -34,7 +34,7 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
showSessionDialog {
title(R.string.recoveryPasswordHidePermanently)
htmlText(R.string.recoveryPasswordHidePermanentlyDescription1)
destructiveButton(R.string.continue_2, R.string.AccessibilityId_continue) { onHideConfirm() }
dangerButton(R.string.continue_2, R.string.AccessibilityId_continue) { onHideConfirm() }
cancelButton()
}
}
@ -44,7 +44,7 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
title(R.string.recoveryPasswordHidePermanently)
text(R.string.recoveryPasswordHidePermanentlyDescription2)
cancelButton()
destructiveButton(
dangerButton(
R.string.yes,
contentDescription = R.string.AccessibilityId_confirm_button
) {

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
@ -8,11 +9,13 @@ 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.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -20,9 +23,16 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.color.LocalColors
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
class DialogButtonModel(
val text: GetString,
@ -32,29 +42,35 @@ class DialogButtonModel(
val onClick: () -> Unit = {},
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AlertDialog(
onDismissRequest: () -> Unit,
title: String? = null,
text: String? = null,
content: @Composable () -> Unit = {},
buttons: List<DialogButtonModel>? = null
buttons: List<DialogButtonModel>? = null,
showCloseButton: Boolean = false,
content: @Composable () -> Unit = {}
) {
androidx.compose.material.AlertDialog(
onDismissRequest,
shape = MaterialTheme.shapes.small,
backgroundColor = LocalColors.current.backgroundSecondary,
buttons = {
Box {
IconButton(
onClick = onDismissRequest,
modifier = Modifier.align(Alignment.TopEnd)
) {
Icon(
painter = painterResource(id = R.drawable.ic_dialog_x),
tint = LocalColors.current.text,
contentDescription = "back"
)
BasicAlertDialog(
onDismissRequest = onDismissRequest,
content = {
Box(
modifier = Modifier.background(color = LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small)
) {
// only show the 'x' button is required
if(showCloseButton) {
IconButton(
onClick = onDismissRequest,
modifier = Modifier.align(Alignment.TopEnd)
) {
Icon(
painter = painterResource(id = R.drawable.ic_dialog_x),
tint = LocalColors.current.text,
contentDescription = "back"
)
}
}
Column(modifier = Modifier.fillMaxWidth()) {
@ -62,23 +78,23 @@ fun AlertDialog(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(top = LocalDimensions.current.smallItemSpacing)
.padding(horizontal = LocalDimensions.current.smallItemSpacing)
.padding(top = LocalDimensions.current.smallSpacing)
.padding(horizontal = LocalDimensions.current.smallSpacing)
) {
title?.let {
Text(
it,
textAlign = TextAlign.Center,
style = h7,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsItemSpacing)
style = LocalType.current.h7,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing)
)
}
text?.let {
Text(
it,
textAlign = TextAlign.Center,
style = large,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsItemSpacing)
style = LocalType.current.large,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing)
)
}
content()
@ -116,12 +132,59 @@ fun DialogButton(text: String, modifier: Modifier, color: Color = Color.Unspecif
Text(
text,
color = color.takeOrElse { LocalColors.current.text },
style = largeBold,
style = LocalType.current.large.bold(),
textAlign = TextAlign.Center,
modifier = Modifier.padding(
top = LocalDimensions.current.smallItemSpacing,
bottom = LocalDimensions.current.itemSpacing
top = LocalDimensions.current.smallSpacing,
bottom = LocalDimensions.current.spacing
)
)
}
}
@Preview
@Composable
fun PreviewSimpleDialog(){
PreviewTheme {
AlertDialog(
onDismissRequest = {},
title = stringResource(R.string.warning),
text = stringResource(R.string.you_cannot_go_back_further_in_order_to_stop_loading_your_account_session_needs_to_quit),
buttons = listOf(
DialogButtonModel(
GetString(stringResource(R.string.quit)),
color = LocalColors.current.danger,
onClick = {}
),
DialogButtonModel(
GetString(stringResource(R.string.cancel))
)
)
)
}
}
@Preview
@Composable
fun PreviewXCloseDialog(){
PreviewTheme {
AlertDialog(
title = stringResource(R.string.urlOpen),
text = stringResource(R.string.urlOpenBrowser),
showCloseButton = true, // display the 'x' button
buttons = listOf(
DialogButtonModel(
text = GetString(R.string.activity_landing_terms_of_service),
contentDescription = GetString(R.string.AccessibilityId_terms_of_service_button),
onClick = {}
),
DialogButtonModel(
text = GetString(R.string.activity_landing_privacy_policy),
contentDescription = GetString(R.string.AccessibilityId_privacy_policy_button),
onClick = {}
)
),
onDismissRequest = {}
)
}
}

View File

@ -16,12 +16,8 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -37,26 +33,27 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.blackAlpha40
import org.thoughtcrime.securesms.ui.theme.pillShape
import kotlin.math.absoluteValue
import kotlin.math.sign
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) {
if (pagerState.pageCount >= 2) Card(
shape = pillShape,
backgroundColor = Color.Black.copy(alpha = 0.4f),
if (pagerState.pageCount >= 2) Box(
modifier = Modifier
.background(color = blackAlpha40, shape = pillShape)
.align(Alignment.BottomCenter)
.padding(8.dp)
.padding(LocalDimensions.current.xxsSpacing)
) {
Box(modifier = Modifier.padding(8.dp)) {
Box(modifier = Modifier.padding(LocalDimensions.current.xxsSpacing)) {
ClickableHorizontalPagerIndicator(
pagerState = pagerState,
pageCount = pagerState.pageCount,
activeColor = Color.White,
inactiveColor = LocalColors.current.textSecondary)
pageCount = pagerState.pageCount
)
}
}
}
@ -73,9 +70,9 @@ fun ClickableHorizontalPagerIndicator(
pageCount: Int,
modifier: Modifier = Modifier,
pageIndexMapping: (Int) -> Int = { it },
activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
indicatorWidth: Dp = 8.dp,
activeColor: Color = Color.White,
inactiveColor: Color = LocalColors.current.disabled,
indicatorWidth: Dp = LocalDimensions.current.xxsSpacing,
indicatorHeight: Dp = indicatorWidth,
spacing: Dp = indicatorWidth,
indicatorShape: Shape = CircleShape,
@ -115,9 +112,9 @@ private fun HorizontalPagerIndicator(
pageCount: Int,
modifier: Modifier = Modifier,
pageIndexMapping: (Int) -> Int = { it },
activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
indicatorWidth: Dp = 8.dp,
activeColor: Color = Color.White,
inactiveColor: Color = LocalColors.current.disabled,
indicatorWidth: Dp = LocalDimensions.current.xxsSpacing,
indicatorHeight: Dp = indicatorWidth,
spacing: Dp = indicatorWidth,
indicatorShape: Shape = CircleShape,

View File

@ -7,13 +7,13 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
@ -24,14 +24,14 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.ButtonColors
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Card
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
@ -52,6 +52,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
@ -61,14 +62,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.runIf
import org.thoughtcrime.securesms.components.ProfilePictureView
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCard
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.divider
import org.thoughtcrime.securesms.ui.color.radioButtonColors
import org.thoughtcrime.securesms.ui.color.transparentButtonColors
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCardData
import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator
import org.thoughtcrime.securesms.ui.components.TitledRadioButton
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.transparentButtonColors
import kotlin.math.min
import kotlin.math.roundToInt
@ -92,18 +94,25 @@ data class RadioOption<T>(
)
@Composable
fun <T> OptionsCard(card: OptionsCard<T>, callbacks: Callbacks<T>) {
Text(
card.title(),
style = base
)
CellNoMargin {
LazyColumn(
modifier = Modifier.heightIn(max = 5000.dp)
) {
itemsIndexed(card.options) { i, it ->
if (i != 0) Divider()
TitledRadioButton(it) { callbacks.setValue(it.value) }
fun <T> OptionsCard(card: OptionsCardData<T>, callbacks: Callbacks<T>) {
Column {
Text(
modifier = Modifier.padding(start = LocalDimensions.current.smallSpacing),
text = card.title(),
style = LocalType.current.base,
color = LocalColors.current.textSecondary
)
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
CellNoMargin {
LazyColumn(
modifier = Modifier.heightIn(max = 5000.dp)
) {
itemsIndexed(card.options) { i, it ->
if (i != 0) Divider()
TitledRadioButton(option = it) { callbacks.setValue(it.value) }
}
}
}
}
@ -117,7 +126,10 @@ fun LargeItemButtonWithDrawable(
colors: ButtonColors = transparentButtonColors(),
onClick: () -> Unit
) {
ItemButtonWithDrawable(textId, icon, modifier.heightIn(min = LocalDimensions.current.minLargeItemButtonHeight), h8, colors, onClick)
ItemButtonWithDrawable(
textId, icon, modifier.heightIn(min = LocalDimensions.current.minLargeItemButtonHeight),
LocalType.current.h8, colors, onClick
)
}
@Composable
@ -125,7 +137,7 @@ fun ItemButtonWithDrawable(
@StringRes textId: Int,
@DrawableRes icon: Int,
modifier: Modifier = Modifier,
textStyle: TextStyle = xl,
textStyle: TextStyle = LocalType.current.xl,
colors: ButtonColors = transparentButtonColors(),
onClick: () -> Unit
) {
@ -155,7 +167,10 @@ fun LargeItemButton(
colors: ButtonColors = transparentButtonColors(),
onClick: () -> Unit
) {
ItemButton(textId, icon, modifier.heightIn(min = LocalDimensions.current.minLargeItemButtonHeight), h8, colors, onClick)
ItemButton(
textId, icon, modifier.heightIn(min = LocalDimensions.current.minLargeItemButtonHeight),
LocalType.current.h8, colors, onClick
)
}
/**
@ -166,7 +181,7 @@ fun ItemButton(
@StringRes textId: Int,
@DrawableRes icon: Int,
modifier: Modifier = Modifier,
textStyle: TextStyle = xl,
textStyle: TextStyle = LocalType.current.xl,
colors: ButtonColors = transparentButtonColors(),
onClick: () -> Unit
) {
@ -196,7 +211,7 @@ fun ItemButton(
text: String,
icon: @Composable BoxScope.() -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = xl,
textStyle: TextStyle = LocalType.current.xl,
colors: ButtonColors = transparentButtonColors(),
onClick: () -> Unit
) {
@ -208,27 +223,42 @@ fun ItemButton(
) {
Box(
modifier = Modifier
.width(80.dp)
.width(50.dp)
.wrapContentHeight()
.align(Alignment.CenterVertically)
) {
icon()
}
Spacer(modifier = Modifier.width(LocalDimensions.current.smallSpacing))
Text(
text,
Modifier
.fillMaxWidth()
.padding(vertical = LocalDimensions.current.xsItemSpacing)
.padding(vertical = LocalDimensions.current.xsSpacing)
.align(Alignment.CenterVertically),
style = textStyle
)
}
}
@Preview
@Composable
fun PrewviewItemButton() {
PreviewTheme {
ItemButton(
textId = R.string.activity_create_group_title,
icon = R.drawable.ic_group,
onClick = {}
)
}
}
@Composable
fun Cell(
padding: Dp = 0.dp,
margin: Dp = LocalDimensions.current.margin,
margin: Dp = LocalDimensions.current.spacing,
content: @Composable () -> Unit
) {
CellWithPaddingAndMargin(padding, margin) { content() }
@ -240,64 +270,22 @@ fun CellNoMargin(content: @Composable () -> Unit) {
@Composable
fun CellWithPaddingAndMargin(
padding: Dp = LocalDimensions.current.smallMargin,
margin: Dp = LocalDimensions.current.margin,
padding: Dp = LocalDimensions.current.spacing,
margin: Dp = LocalDimensions.current.spacing,
content: @Composable () -> Unit
) {
Card(
backgroundColor = LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.medium,
elevation = 0.dp,
Box(
modifier = Modifier
.padding(horizontal = margin)
.background(color = LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small)
.wrapContentHeight()
.fillMaxWidth()
.padding(horizontal = margin),
.fillMaxWidth(),
) {
Box(Modifier.padding(padding)) { content() }
}
}
@Composable
fun <T> TitledRadioButton(option: RadioOption<T>, onClick: () -> Unit) {
val color = if (option.enabled) LocalColors.current.text else LocalColors.current.disabled
Row(
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallItemSpacing),
modifier = Modifier
.runIf(option.enabled) { clickable { if (!option.selected) onClick() } }
.heightIn(min = 60.dp)
.padding(horizontal = LocalDimensions.current.margin)
.contentDescription(option.contentDescription)
) {
Column(modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)) {
Column {
Text(
text = option.title(),
style = large,
color = color
)
option.subtitle?.let {
Text(
text = it(),
style = extraSmall,
color = color
)
}
}
}
RadioButton(
selected = option.selected,
onClick = null,
modifier = Modifier
.height(26.dp)
.align(Alignment.CenterVertically),
enabled = option.enabled,
colors = LocalColors.current.radioButtonColors()
)
}
}
@Composable
fun Modifier.contentDescription(text: GetString?): Modifier {
return text?.let {
@ -357,10 +345,10 @@ fun Modifier.fadingEdges(
@Composable
fun Divider(modifier: Modifier = Modifier, startIndent: Dp = 0.dp) {
androidx.compose.material.Divider(
modifier = modifier.padding(horizontal = LocalDimensions.current.xsMargin),
color = LocalColors.current.divider,
startIndent = startIndent
HorizontalDivider(
modifier = modifier.padding(horizontal = LocalDimensions.current.smallSpacing)
.padding(start = startIndent),
color = LocalColors.current.borders,
)
}
@ -392,7 +380,7 @@ fun ProgressArc(progress: Float, modifier: Modifier = Modifier) {
"${text}%",
color = Color.White,
modifier = Modifier.align(Alignment.Center),
style = h2
style = LocalType.current.h2
)
}
}

View File

@ -1,32 +0,0 @@
package org.thoughtcrime.securesms.ui
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalDimensions = staticCompositionLocalOf { Dimensions() }
data class Dimensions(
val xxxsItemSpacing: Dp = 4.dp,
val xxsItemSpacing: Dp = 8.dp,
val xsItemSpacing: Dp = 12.dp,
val smallItemSpacing: Dp = 16.dp,
val itemSpacing: Dp = 24.dp,
val xxxsMargin: Dp = 8.dp,
val xxsMargin: Dp = 12.dp,
val xsMargin: Dp = 16.dp,
val smallMargin: Dp = 24.dp,
val margin: Dp = 32.dp,
val onboardingMargin: Dp = 36.dp,
val largeMargin: Dp = 64.dp,
val homeEmptyViewMargin: Dp = 50.dp,
val dividerIndent: Dp = 80.dp,
val appBarHeight: Dp = 64.dp,
val minScrollableViewHeight: Dp = 200.dp,
val minLargeItemButtonHeight: Dp = 60.dp,
val indicatorHeight: Dp = 4.dp,
val borderStroke: Dp = 1.dp
)

View File

@ -1,59 +0,0 @@
package org.thoughtcrime.securesms.ui
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontFamily.Companion.Monospace
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
fun boldStyle(size: TextUnit) = TextStyle.Default.copy(
fontSize = size,
lineHeight = size * 1.2,
fontWeight = FontWeight.Bold,
)
fun defaultStyle(size: TextUnit, fontFamily: FontFamily? = TextStyle.Default.fontFamily) = TextStyle.Default.copy(
fontSize = size,
lineHeight = size * 1.2,
fontFamily = fontFamily
)
val xl = defaultStyle(18.sp)
val large = defaultStyle(16.sp)
val largeBold = boldStyle(16.sp)
val base = defaultStyle(14.sp)
val baseBold = boldStyle(14.sp)
val baseMonospace = defaultStyle(14.sp, fontFamily = Monospace)
val small = defaultStyle(12.sp)
val smallBold = boldStyle(12.sp)
val smallMonospace = defaultStyle(12.sp, fontFamily = Monospace)
val extraSmall = defaultStyle(11.sp)
val extraSmallBold = boldStyle(11.sp)
val extraSmallMonospace = defaultStyle(11.sp, fontFamily = Monospace)
val fine = defaultStyle(9.sp)
val h1 = boldStyle(36.sp)
val h2 = boldStyle(32.sp)
val h3 = boldStyle(29.sp)
val h4 = boldStyle(26.sp)
val h5 = boldStyle(23.sp)
val h6 = boldStyle(20.sp)
val h7 = boldStyle(18.sp)
val h8 = boldStyle(16.sp)
val h9 = boldStyle(14.sp)
val sessionTypography = Typography(
h1 = h1,
h2 = h2,
h3 = h3,
h4 = h4,
h5 = h5,
h6 = h6,
)

View File

@ -5,6 +5,7 @@ import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme
fun Activity.setComposeContent(content: @Composable () -> Unit) {
ComposeView(this)

View File

@ -1,233 +0,0 @@
package org.thoughtcrime.securesms.ui.color
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.RadioButtonDefaults
import androidx.compose.material.TabRowDefaults
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.base
val LocalColors = staticCompositionLocalOf<Colors> { ClassicDark() }
interface Colors {
val isLight: Boolean
val primary: Color
val danger: Color
val disabled: Color
val background: Color
val backgroundSecondary: Color
val text: Color
val textSecondary: Color
val borders: Color
val textBubbleSent: Color
val backgroundBubbleReceived: Color
val textBubbleReceived: Color
val backgroundBubbleSent: Color get() = primary
val qrCodeContent: Color
val qrCodeBackground: Color
val primaryButtonFill: Color
val primaryButtonFillText: Color
}
fun Colors.text(isError: Boolean): Color = if (isError) danger else text
fun Colors.textSecondary(isError: Boolean): Color = if (isError) danger else textSecondary
fun Colors.borders(isError: Boolean): Color = if (isError) danger else borders
val Colors.textSelectionColors get() = TextSelectionColors(
handleColor = primary,
backgroundColor = primary.copy(alpha = 0.5f)
)
data class ClassicDark(override val primary: Color = primaryGreen): Colors {
override val isLight = false
override val danger = dangerDark
override val disabled = disabledDark
override val background = classicDark0
override val backgroundSecondary = classicDark1
override val text = classicDark6
override val textSecondary = classicDark5
override val borders = classicDark3
override val textBubbleSent = Color.Black
override val backgroundBubbleReceived = classicDark2
override val textBubbleReceived = Color.White
override val qrCodeContent = background
override val qrCodeBackground = text
override val primaryButtonFill = primary
override val primaryButtonFillText = Color.Black
}
data class ClassicLight(override val primary: Color = primaryGreen): Colors {
override val isLight = true
override val danger = dangerLight
override val disabled = disabledLight
override val background = classicLight6
override val backgroundSecondary = classicLight5
override val text = classicLight0
override val textSecondary = classicLight1
override val borders = classicLight3
override val textBubbleSent = Color.Black
override val backgroundBubbleReceived = classicLight4
override val textBubbleReceived = classicLight4
override val qrCodeContent = text
override val qrCodeBackground = backgroundSecondary
override val primaryButtonFill = text
override val primaryButtonFillText = Color.White
}
data class OceanDark(override val primary: Color = primaryBlue): Colors {
override val isLight = false
override val danger = dangerDark
override val disabled = disabledDark
override val background = oceanDark2
override val backgroundSecondary = oceanDark1
override val text = oceanDark7
override val textSecondary = oceanDark5
override val borders = oceanDark4
override val textBubbleSent = Color.Black
override val backgroundBubbleReceived = oceanDark4
override val textBubbleReceived = oceanDark4
override val qrCodeContent = background
override val qrCodeBackground = text
override val primaryButtonFill = primary
override val primaryButtonFillText = Color.Black
}
data class OceanLight(override val primary: Color = primaryBlue): Colors {
override val isLight = true
override val danger = dangerLight
override val disabled = disabledLight
override val background = oceanLight7
override val backgroundSecondary = oceanLight6
override val text = oceanLight1
override val textSecondary = oceanLight2
override val borders = oceanLight3
override val textBubbleSent = oceanLight1
override val backgroundBubbleReceived = oceanLight4
override val textBubbleReceived = oceanLight1
override val qrCodeContent = text
override val qrCodeBackground = backgroundSecondary
override val primaryButtonFill = text
override val primaryButtonFillText = Color.White
}
@Composable
fun Colors(name: String, colors: List<Color>) {
Column {
colors.forEachIndexed { i, it ->
Box(Modifier.background(it)) {
Text("$name: $i")
}
}
}
}
@Preview
@Composable
fun PreviewThemeColors(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
) {
PreviewTheme(colors) { ThemeColors() }
}
@Composable
private fun ThemeColors() {
Column {
Box(Modifier.background(MaterialTheme.colors.primary)) {
Text("primary", style = base)
}
Box(Modifier.background(MaterialTheme.colors.primaryVariant)) {
Text("primaryVariant", style = base)
}
Box(Modifier.background(MaterialTheme.colors.secondary)) {
Text("secondary", style = base)
}
Box(Modifier.background(MaterialTheme.colors.secondaryVariant)) {
Text("secondaryVariant", style = base)
}
Box(Modifier.background(MaterialTheme.colors.surface)) {
Text("surface", style = base)
}
Box(Modifier.background(MaterialTheme.colors.primarySurface)) {
Text("primarySurface", style = base)
}
Box(Modifier.background(MaterialTheme.colors.background)) {
Text("background", style = base)
}
Box(Modifier.background(MaterialTheme.colors.error)) {
Text("error", style = base)
}
}
}
@Composable
fun Colors.outlinedTextFieldColors(
isError: Boolean
) = TextFieldDefaults.outlinedTextFieldColors(
textColor = if (isError) danger else text,
cursorColor = if (isError) danger else text,
focusedBorderColor = borders,
unfocusedBorderColor = borders,
placeholderColor = if (isError) danger else textSecondary
)
val Colors.divider get() = text.copy(alpha = TabRowDefaults.DividerOpacity)
@Composable
fun Colors.radioButtonColors() = RadioButtonDefaults.colors(
selectedColor = primary,
unselectedColor = text,
disabledColor = disabled
)
@Composable
fun transparentButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)
@Composable
fun destructiveButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent, contentColor = LocalColors.current.danger)
/**
* This class holds two instances of [Colors], [light] representing the [Colors] to use when the system is in a
* light theme, and [dark] representing the [Colors] to use when the system is in a dark theme.
*
* If the user has [followSystemSettings] turned on then [light] should be equal to [dark].
*/
data class LightDarkColors(
val light: Colors,
val dark: Colors
) {
@Composable
fun colors() = if (light == dark || isSystemInDarkTheme()) dark else light
}
/**
* Courtesy constructor that sets [light] and [dark] based on properties.
*/
fun LightDarkColors(isClassic: Boolean, isLight: Boolean, followSystemSettings: Boolean, primaryOrUnspecified: Color): LightDarkColors {
val primary = primaryOrUnspecified.takeOrElse { if (isClassic) primaryGreen else primaryBlue }
val light = when {
isLight || followSystemSettings -> if (isClassic) ClassicLight(primary) else OceanLight(primary)
else -> if (isClassic) ClassicDark(primary) else OceanDark(primary)
}
val dark = when {
isLight && !followSystemSettings -> if (isClassic) ClassicLight(primary) else OceanLight(primary)
else -> if (isClassic) ClassicDark(primary) else OceanDark(primary)
}
return LightDarkColors(light, dark)
}

View File

@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.ui.color
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences.Companion.BLUE_ACCENT
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.GREEN_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
import org.session.libsession.utilities.TextSecurePreferences.Companion.ORANGE_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.PINK_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.PURPLE_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.RED_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.YELLOW_ACCENT
/**
* Retrieve the current [Colors] from [TextSecurePreferences] and current system settings.
*/
@Composable
fun TextSecurePreferences.colors(): Colors = lightDarkColors().colors()
private fun TextSecurePreferences.lightDarkColors() = LightDarkColors(isClassic(), isLight(), getFollowSystemSettings(), primaryColor())
private fun TextSecurePreferences.isLight(): Boolean = getThemeStyle() in setOf(CLASSIC_LIGHT, OCEAN_LIGHT)
private fun TextSecurePreferences.isClassic(): Boolean = getThemeStyle() in setOf(CLASSIC_DARK, CLASSIC_LIGHT)
private fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
GREEN_ACCENT -> primaryGreen
BLUE_ACCENT -> primaryBlue
PURPLE_ACCENT -> primaryPurple
PINK_ACCENT -> primaryPink
RED_ACCENT -> primaryRed
ORANGE_ACCENT -> primaryOrange
YELLOW_ACCENT -> primaryYellow
else -> Color.Unspecified
}

View File

@ -5,9 +5,9 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -15,16 +15,16 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.h4
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.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.ThemeColors
@Preview
@Composable
fun AppBarPreview(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
AppBar(title = "Title", {}, {})
@ -42,7 +42,7 @@ fun AppBar(title: String, onClose: () -> Unit = {}, onBack: (() -> Unit)? = null
}
}
Spacer(modifier = Modifier.weight(1f))
Text(text = title, style = h4)
Text(text = title, style = LocalType.current.h4)
Spacer(modifier = Modifier.weight(1f))
Box(contentAlignment = Alignment.Center, modifier = Modifier.size(LocalDimensions.current.appBarHeight)) {
IconButton(onClick = onClose) {

View File

@ -1,12 +1,12 @@
package org.thoughtcrime.securesms.ui.components
import androidx.compose.foundation.border
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalColors
@Composable
fun Modifier.border() = this.border(

View File

@ -16,8 +16,8 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ButtonColors
import androidx.compose.material.Text
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -37,13 +37,14 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LaunchedEffectAsync
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.baseBold
import org.thoughtcrime.securesms.ui.buttonShape
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.contentDescription
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.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.ThemeColors
import org.thoughtcrime.securesms.ui.theme.bold
import org.thoughtcrime.securesms.ui.theme.buttonShape
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@ -65,16 +66,16 @@ fun Button(
content: @Composable RowScope.() -> Unit
) {
style.applyButtonConstraints {
androidx.compose.material.Button(
onClick,
modifier.heightIn(min = style.minHeight),
enabled,
interactionSource,
androidx.compose.material3.Button(
onClick = onClick,
modifier = modifier.heightIn(min = style.minHeight),
enabled = enabled,
interactionSource = interactionSource,
elevation = null,
shape,
border,
colors,
contentPadding
shape = shape,
border = border,
colors = colors,
contentPadding = contentPadding
) {
// Button sets LocalTextStyle, so text style is applied inside to override that.
style.applyTextConstraints {
@ -129,11 +130,11 @@ fun Button(
Button(text, onClick, ButtonType.Outline(LocalColors.current.primaryButtonFill), modifier, enabled)
}
@Composable fun PrimaryOutlineButton(onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, content: @Composable RowScope.() -> Unit) {
@Composable fun PrimaryOutlineButton(modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {
Button(onClick, ButtonType.Outline(LocalColors.current.primaryButtonFill), modifier, enabled, content = content)
}
@Composable fun SlimOutlineButton(onClick: () -> Unit, modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, content: @Composable RowScope.() -> Unit) {
@Composable fun SlimOutlineButton(modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {
Button(onClick, ButtonType.Outline(color), modifier, enabled, ButtonStyle.Slim, content = content)
}
@ -257,7 +258,7 @@ fun BorderlessButtonWithIcon(
text: String,
@DrawableRes iconRes: Int,
modifier: Modifier = Modifier,
style: TextStyle = baseBold,
style: TextStyle = LocalType.current.base.bold(),
color: Color = LocalColors.current.text,
onClick: () -> Unit
) {
@ -292,7 +293,7 @@ val MutableInteractionSource.releases
@Preview
@Composable
private fun VariousButtons(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
FlowRow(

View File

@ -1,21 +1,20 @@
package org.thoughtcrime.securesms.ui.components
import android.annotation.SuppressLint
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material.LocalTextStyle
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.thoughtcrime.securesms.ui.baseBold
import org.thoughtcrime.securesms.ui.extraSmall
import org.thoughtcrime.securesms.ui.extraSmallBold
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.bold
interface ButtonStyle {
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("ComposableNaming")
@Composable fun applyButtonConstraints(content: @Composable () -> Unit) {
CompositionLocalProvider(
@ -27,26 +26,34 @@ interface ButtonStyle {
@SuppressLint("ComposableNaming")
@Composable fun applyTextConstraints(content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalTextStyle provides textStyle,
LocalTextStyle provides textStyle(),
content = content
)
}
val textStyle: TextStyle
@Composable
fun textStyle() : TextStyle
val minHeight: Dp
object Large: ButtonStyle {
override val textStyle = baseBold.copy(textAlign = TextAlign.Center)
@Composable
override fun textStyle() = LocalType.current.base.bold()
.copy(textAlign = TextAlign.Center)
override val minHeight = 41.dp
}
object Slim: ButtonStyle {
override val textStyle = extraSmallBold.copy(textAlign = TextAlign.Center)
@Composable
override fun textStyle() = LocalType.current.extraSmall.bold()
.copy(textAlign = TextAlign.Center)
override val minHeight = 29.dp
}
object Borderless: ButtonStyle {
override val textStyle = extraSmall.copy(textAlign = TextAlign.Center)
@Composable
override fun textStyle() = LocalType.current.extraSmall
.copy(textAlign = TextAlign.Center)
override val minHeight = 37.dp
}
}

View File

@ -2,13 +2,13 @@ package org.thoughtcrime.securesms.ui.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
private val disabledBorder @Composable get() = BorderStroke(
width = LocalDimensions.current.borderStroke,
@ -35,9 +35,9 @@ interface ButtonType {
@Composable
override fun buttonColors() = ButtonDefaults.buttonColors(
contentColor = contentColor,
backgroundColor = Color.Unspecified,
containerColor = Color.Transparent,
disabledContentColor = LocalColors.current.disabled,
disabledBackgroundColor = Color.Unspecified
disabledContainerColor = Color.Transparent
)
}
@ -47,9 +47,9 @@ interface ButtonType {
@Composable
override fun buttonColors() = ButtonDefaults.buttonColors(
contentColor = LocalColors.current.background,
backgroundColor = LocalColors.current.text,
containerColor = LocalColors.current.text,
disabledContentColor = LocalColors.current.disabled,
disabledBackgroundColor = Color.Unspecified
disabledContainerColor = Color.Transparent
)
}
@ -59,9 +59,9 @@ interface ButtonType {
@Composable
override fun buttonColors() = ButtonDefaults.buttonColors(
contentColor = LocalColors.current.primaryButtonFillText,
backgroundColor = LocalColors.current.primaryButtonFill,
containerColor = LocalColors.current.primaryButtonFill,
disabledContentColor = LocalColors.current.disabled,
disabledBackgroundColor = Color.Unspecified
disabledContainerColor = Color.Transparent
)
}
@ -73,8 +73,9 @@ interface ButtonType {
@Composable
override fun buttonColors() = ButtonDefaults.outlinedButtonColors(
contentColor = color,
backgroundColor = Color.Transparent,
disabledContentColor = LocalColors.current.disabled
containerColor = Color.Transparent,
disabledContentColor = LocalColors.current.disabled,
disabledContainerColor = Color.Transparent
)
}
}

View File

@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.ui.components
import androidx.compose.foundation.layout.size
import androidx.compose.material.LocalContentColor
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp
@Composable
fun CircularProgressIndicator(color: Color = LocalContentColor.current) {
androidx.compose.material.CircularProgressIndicator(
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(40.dp),
color = color,
strokeWidth = 2.dp
@ -18,7 +18,7 @@ fun CircularProgressIndicator(color: Color = LocalContentColor.current) {
@Composable
fun SmallCircularProgressIndicator(color: Color = LocalContentColor.current) {
androidx.compose.material.CircularProgressIndicator(
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = color,
strokeWidth = 2.dp

View File

@ -22,11 +22,11 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Scaffold
import androidx.compose.material.Snackbar
import androidx.compose.material.SnackbarHost
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@ -56,10 +56,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.xl
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import java.util.concurrent.Executors
private const val TAG = "NewMessageFragment"
@ -78,7 +77,6 @@ fun MaybeScanQrCode(
Box(
modifier = Modifier
.fillMaxSize()
.background(LocalColors.current.background)
) {
LocalSoftwareKeyboardController.current?.hide()
@ -94,10 +92,10 @@ fun MaybeScanQrCode(
) {
Text(
stringResource(R.string.activity_link_camera_permission_permanently_denied_configure_in_settings),
style = base,
style = LocalType.current.base,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.size(LocalDimensions.current.itemSpacing))
Spacer(modifier = Modifier.size(LocalDimensions.current.spacing))
OutlineButton(
stringResource(R.string.sessionSettings),
modifier = Modifier.align(Alignment.CenterHorizontally),
@ -107,14 +105,14 @@ fun MaybeScanQrCode(
} else {
Column(
modifier = Modifier
.background(color = LocalColors.current.backgroundSecondary)
.fillMaxSize()
.padding(LocalDimensions.current.largeMargin),
.padding(LocalDimensions.current.xlargeSpacing),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.weight(1f))
Text(stringResource(R.string.fragment_scan_qr_code_camera_access_explanation), style = xl, textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacing))
Text(stringResource(R.string.fragment_scan_qr_code_camera_access_explanation),
style = LocalType.current.xl, textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
PrimaryOutlineButton(
stringResource(R.string.cameraGrantAccess),
modifier = Modifier.fillMaxWidth(),
@ -158,13 +156,12 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
}
}
val scaffoldState = rememberScaffoldState()
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
errors.collect { error ->
scaffoldState.snackbarHostState
snackbarHostState
.takeIf { it.currentSnackbarData == null }
?.run {
scope.launch {
@ -175,22 +172,21 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
// Don't use debounce() because many QR scans can come through each second,
// and each scan could restart the timer which could mean no scan gets
// through until the user stops scanning; quite perplexing.
showSnackbar(message = error)
snackbarHostState.showSnackbar(message = error)
}
}
}
}
Scaffold(
scaffoldState = scaffoldState,
snackbarHost = {
SnackbarHost(
hostState = scaffoldState.snackbarHostState,
modifier = Modifier.padding(LocalDimensions.current.smallItemSpacing)
hostState = snackbarHostState,
modifier = Modifier.padding(LocalDimensions.current.smallSpacing)
) { data ->
Snackbar(
snackbarData = data,
modifier = Modifier.padding(LocalDimensions.current.smallItemSpacing)
modifier = Modifier.padding(LocalDimensions.current.smallSpacing)
)
}
}
@ -204,7 +200,7 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
Box(
Modifier
.aspectRatio(1f)
.padding(LocalDimensions.current.itemSpacing)
.padding(LocalDimensions.current.spacing)
.clip(shape = RoundedCornerShape(26.dp))
.background(Color(0x33ffffff))
.align(Alignment.Center)

View File

@ -10,8 +10,8 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -32,15 +32,15 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.util.QRCodeUtilities
@Composable
fun QrImage(
string: String?,
modifier: Modifier = Modifier,
contentPadding: Dp = LocalDimensions.current.smallItemSpacing,
contentPadding: Dp = LocalDimensions.current.smallSpacing,
icon: Int = R.drawable.session_shield
) {
var bitmap: Bitmap? by remember {
@ -56,10 +56,9 @@ fun QrImage(
}
}
Card(
backgroundColor = LocalColors.current.qrCodeBackground,
elevation = 0.dp,
modifier = modifier
Box(
modifier = modifier.background(color = LocalColors.current.qrCodeBackground,
shape = MaterialTheme.shapes.small)
) { Content(bitmap, icon, Modifier.padding(contentPadding), backgroundColor = LocalColors.current.qrCodeBackground) }
}

View File

@ -1,40 +1,58 @@
package org.thoughtcrime.securesms.ui.components
import android.content.ContentProvider
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.TextButton
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.transparentButtonColors
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.RadioOption
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.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.sessionTypography
import org.thoughtcrime.securesms.ui.theme.textEnabled
import org.thoughtcrime.securesms.ui.theme.transparentButtonColors
import kotlin.time.Duration.Companion.days
@Composable
fun RadioButton(
onClick: () -> Unit = {},
modifier: Modifier = Modifier,
checked: Boolean = false,
selected: Boolean = false,
enabled: Boolean = true,
contentPadding: PaddingValues = PaddingValues(),
content: @Composable RowScope.() -> Unit = {}
) {
@ -42,20 +60,22 @@ fun RadioButton(
modifier = modifier
.fillMaxWidth()
.selectable(
selected = checked,
selected = selected,
enabled = true,
role = Role.RadioButton,
onClick = onClick
),
enabled = enabled,
colors = transparentButtonColors(),
onClick = onClick,
shape = RectangleShape,
contentPadding = contentPadding
) {
content()
Spacer(modifier = Modifier.width(20.dp))
RadioButtonIndicator(
checked = checked,
selected = selected && enabled, // disabled radio shouldn't be selected
modifier = Modifier
.size(22.dp)
.align(Alignment.CenterVertically)
@ -65,12 +85,12 @@ fun RadioButton(
@Composable
private fun RadioButtonIndicator(
checked: Boolean,
selected: Boolean,
modifier: Modifier
) {
Box(modifier = modifier) {
AnimatedVisibility(
checked,
selected,
modifier = Modifier
.padding(2.5.dp)
.clip(CircleShape),
@ -91,9 +111,93 @@ private fun RadioButtonIndicator(
.aspectRatio(1f)
.border(
width = LocalDimensions.current.borderStroke,
color = LocalColors.current.text,
color = LocalContentColor.current,
shape = CircleShape
)
) {}
}
}
@Composable
fun <T> TitledRadioButton(
modifier: Modifier = Modifier,
option: RadioOption<T>,
onClick: () -> Unit
) {
RadioButton(
modifier = modifier.heightIn(min = 60.dp)
.contentDescription(option.contentDescription),
onClick = onClick,
selected = option.selected,
enabled = option.enabled,
contentPadding = PaddingValues(horizontal = LocalDimensions.current.spacing),
content = {
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
) {
Column {
Text(
text = option.title(),
style = LocalType.current.large
)
option.subtitle?.let {
Text(
text = it(),
style = LocalType.current.extraSmall
)
}
}
}
}
)
}
@Preview
@Composable
fun PreviewTextRadioButton() {
PreviewTheme {
TitledRadioButton(
option = RadioOption<ExpiryMode>(
value = ExpiryType.AFTER_SEND.mode(7.days),
title = GetString(7.days),
subtitle = GetString("This is a subtitle"),
enabled = true,
selected = true
)
) {}
}
}
@Preview
@Composable
fun PreviewDisabledTextRadioButton() {
PreviewTheme {
TitledRadioButton(
option = RadioOption<ExpiryMode>(
value = ExpiryType.AFTER_SEND.mode(7.days),
title = GetString(7.days),
subtitle = GetString("This is a subtitle"),
enabled = false,
selected = true
)
) {}
}
}
@Preview
@Composable
fun PreviewDeselectedTextRadioButton() {
PreviewTheme {
TitledRadioButton(
option = RadioOption<ExpiryMode>(
value = ExpiryType.AFTER_SEND.mode(7.days),
title = GetString(7.days),
subtitle = GetString("This is a subtitle"),
enabled = true,
selected = false
)
) {}
}
}

View File

@ -1,15 +1,15 @@
package org.thoughtcrime.securesms.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material.Text
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@ -19,13 +19,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.color.divider
import org.thoughtcrime.securesms.ui.h8
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.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.ThemeColors
private val TITLES = listOf(R.string.sessionRecoveryPassword, R.string.qrScan)
@ -33,32 +32,30 @@ private val TITLES = listOf(R.string.sessionRecoveryPassword, R.string.qrScan)
@Composable
fun SessionTabRow(pagerState: PagerState, titles: List<Int>) {
TabRow(
backgroundColor = Color.Unspecified,
containerColor = Color.Unspecified,
selectedTabIndex = pagerState.currentPage,
contentColor = LocalColors.current.text,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
TabRowDefaults.SecondaryIndicator(
Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]),
color = LocalColors.current.primary,
height = LocalDimensions.current.indicatorHeight
)
},
divider = { TabRowDefaults.Divider(color = LocalColors.current.divider) },
modifier = Modifier
.height(48.dp)
.background(color = Color.Unspecified)
divider = { HorizontalDivider(color = LocalColors.current.borders) }
) {
val animationScope = rememberCoroutineScope()
titles.forEachIndexed { i, it ->
Tab(
i == pagerState.currentPage,
modifier = Modifier.heightIn(min = 48.dp),
selected = i == pagerState.currentPage,
onClick = { animationScope.launch { pagerState.animateScrollToPage(i) } },
selectedContentColor = LocalColors.current.text,
unselectedContentColor = LocalColors.current.text,
) {
Text(
stringResource(id = it),
style = h8
text = stringResource(id = it),
style = LocalType.current.h8
)
}
}
@ -69,7 +66,7 @@ fun SessionTabRow(pagerState: PagerState, titles: List<Int>) {
@androidx.compose.ui.tooling.preview.Preview
@Composable
fun PreviewSessionTabRow(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
val pagerState = rememberPagerState { TITLES.size }

View File

@ -17,9 +17,9 @@ import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -37,15 +37,15 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.baseBold
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.borders
import org.thoughtcrime.securesms.ui.color.text
import org.thoughtcrime.securesms.ui.color.textSecondary
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.borders
import org.thoughtcrime.securesms.ui.theme.text
import org.thoughtcrime.securesms.ui.theme.textSecondary
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.bold
@Preview
@Composable
@ -85,7 +85,7 @@ fun SessionOutlinedTextField(
modifier: Modifier = Modifier,
contentDescription: String? = null,
onChange: (String) -> Unit = {},
textStyle: TextStyle = base,
textStyle: TextStyle = LocalType.current.base,
placeholder: String = "",
onContinue: () -> Unit = {},
error: String? = null,
@ -106,7 +106,7 @@ fun SessionOutlinedTextField(
if (text.isEmpty()) {
Text(
text = placeholder,
style = base,
style = LocalType.current.base,
color = LocalColors.current.textSecondary(isTextErrorColor),
modifier = Modifier.wrapContentSize()
.align(Alignment.CenterStart)
@ -130,13 +130,13 @@ fun SessionOutlinedTextField(
)
}
error?.let {
Spacer(modifier = Modifier.height(LocalDimensions.current.xsItemSpacing))
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
Text(
it,
modifier = Modifier.fillMaxWidth()
.contentDescription(R.string.AccessibilityId_error_message),
textAlign = TextAlign.Center,
style = baseBold,
style = LocalType.current.base.bold(),
color = LocalColors.current.danger
)
}
@ -148,7 +148,7 @@ fun AnnotatedTextWithIcon(
text: String,
@DrawableRes iconRes: Int,
modifier: Modifier = Modifier,
style: TextStyle = base,
style: TextStyle = LocalType.current.base,
color: Color = Color.Unspecified,
iconSize: TextUnit = 12.sp
) {

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.ui.color
package org.thoughtcrime.securesms.ui.theme
import androidx.compose.ui.graphics.Color

View File

@ -0,0 +1,24 @@
package org.thoughtcrime.securesms.ui.theme
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalDimensions = staticCompositionLocalOf { Dimensions() }
data class Dimensions(
val xxxsSpacing: Dp = 4.dp,
val xxsSpacing: Dp = 8.dp,
val xsSpacing: Dp = 12.dp,
val smallSpacing: Dp = 16.dp,
val spacing: Dp = 24.dp,
val mediumSpacing: Dp = 36.dp,
val xlargeSpacing: Dp = 64.dp,
val dividerIndent: Dp = 60.dp,
val appBarHeight: Dp = 64.dp,
val minLargeItemButtonHeight: Dp = 60.dp,
val indicatorHeight: Dp = 4.dp,
val borderStroke: Dp = 1.dp
)

View File

@ -0,0 +1,145 @@
package org.thoughtcrime.securesms.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
fun TextStyle.bold() = TextStyle.Default.copy(
fontWeight = FontWeight.Bold
)
fun TextStyle.monospace() = TextStyle.Default.copy(
fontFamily = FontFamily.Monospace
)
val sessionTypography = SessionTypography()
data class SessionTypography(
// Body
val xl: TextStyle = TextStyle(
fontSize = 18.sp,
lineHeight = 21.6.sp,
fontWeight = FontWeight.Normal
),
val large: TextStyle = TextStyle(
fontSize = 16.sp,
lineHeight = 19.2.sp,
fontWeight = FontWeight.Normal
),
val base: TextStyle = TextStyle(
fontSize = 14.sp,
lineHeight = 16.8.sp,
fontWeight = FontWeight.Normal
),
val small: TextStyle = TextStyle(
fontSize = 12.sp,
lineHeight = 14.4.sp,
fontWeight = FontWeight.Normal
),
val extraSmall: TextStyle = TextStyle(
fontSize = 11.sp,
lineHeight = 13.2.sp,
fontWeight = FontWeight.Normal
),
val fine: TextStyle = TextStyle(
fontSize = 9.sp,
lineHeight = 10.8.sp,
fontWeight = FontWeight.Normal
),
// Headings
val h1: TextStyle = TextStyle(
fontSize = 36.sp,
lineHeight = 43.2.sp,
fontWeight = FontWeight.Bold
),
val h2: TextStyle = TextStyle(
fontSize = 32.sp,
lineHeight = 38.4.sp,
fontWeight = FontWeight.Bold
),
val h3: TextStyle = TextStyle(
fontSize = 29.sp,
lineHeight = 34.8.sp,
fontWeight = FontWeight.Bold
),
val h4: TextStyle = TextStyle(
fontSize = 26.sp,
lineHeight = 31.2.sp,
fontWeight = FontWeight.Bold
),
val h5: TextStyle = TextStyle(
fontSize = 23.sp,
lineHeight = 27.6.sp,
fontWeight = FontWeight.Bold
),
val h6: TextStyle = TextStyle(
fontSize = 20.sp,
lineHeight = 24.sp,
fontWeight = FontWeight.Bold
),
val h7: TextStyle = TextStyle(
fontSize = 18.sp,
lineHeight = 21.6.sp,
fontWeight = FontWeight.Bold
),
val h8: TextStyle = TextStyle(
fontSize = 16.sp,
lineHeight = 19.2.sp,
fontWeight = FontWeight.Bold
),
val h9: TextStyle = TextStyle(
fontSize = 14.sp,
lineHeight = 16.8.sp,
fontWeight = FontWeight.Bold
)
) {
// An opinionated override of Material's defaults
@Composable
fun asMaterialTypography() = Typography(
// Display
displayLarge = h1,
displayMedium = h1,
displaySmall = h1,
// Headline
headlineLarge = h2,
headlineMedium = h3,
headlineSmall = h4,
// Title
titleLarge = h5,
titleMedium = h6,
titleSmall = h7,
// Body
bodyLarge = large,
bodyMedium = base,
bodySmall = small,
// Label
labelLarge = extraSmall,
labelMedium = fine,
labelSmall = fine
)
}

View File

@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.ui.theme
/**
* This class holds two instances of [ThemeColors], [light] representing the [ThemeColors] to use when the system is in a
* light theme, and [dark] representing the [ThemeColors] to use when the system is in a dark theme.
*/
data class ThemeColorSet(
val light: ThemeColors,
val dark: ThemeColors
)

View File

@ -0,0 +1,206 @@
package org.thoughtcrime.securesms.ui.theme
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
interface ThemeColors {
// properties to override for each theme
val isLight: Boolean
val primary: Color
val danger: Color
val disabled: Color
val background: Color
val backgroundSecondary: Color
val text: Color
val textSecondary: Color
val borders: Color
val textBubbleSent: Color
val backgroundBubbleReceived: Color
val textBubbleReceived: Color
val qrCodeContent: Color
val qrCodeBackground: Color
val primaryButtonFill: Color
val primaryButtonFillText: Color
}
// extra functions and properties that work for all themes
val ThemeColors.textSelectionColors
get() = TextSelectionColors(
handleColor = primary,
backgroundColor = primary.copy(alpha = 0.5f)
)
fun ThemeColors.text(isError: Boolean): Color = if (isError) danger else text
fun ThemeColors.textSecondary(isError: Boolean): Color = if (isError) danger else textSecondary
fun ThemeColors.textEnabled(enabled: Boolean) = if (enabled) text else disabled
fun ThemeColors.borders(isError: Boolean): Color = if (isError) danger else borders
fun ThemeColors.toMaterialColors() = if (isLight) {
lightColorScheme(
primary = background,
secondary = backgroundSecondary,
tertiary = backgroundSecondary,
onPrimary = text,
onSecondary = text,
onTertiary = text,
background = background,
surface = background,
surfaceVariant = background,
onBackground = text,
onSurface = text,
scrim = blackAlpha40,
outline = text,
outlineVariant = text
)
} else {
darkColorScheme(
primary = background,
secondary = backgroundSecondary,
tertiary = backgroundSecondary,
onPrimary = text,
onSecondary = text,
onTertiary = text,
background = background,
surface = background,
surfaceVariant = background,
onBackground = text,
onSurface = text,
scrim = blackAlpha40,
outline = text,
outlineVariant = text
)
}
@Composable
fun transparentButtonColors() = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
disabledContentColor = LocalColors.current.disabled
)
@Composable
fun dangerButtonColors() = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = LocalColors.current.danger
)
// Our themes
data class ClassicDark(override val primary: Color = primaryGreen) : ThemeColors {
override val isLight = false
override val danger = dangerDark
override val disabled = disabledDark
override val background = classicDark0
override val backgroundSecondary = classicDark1
override val text = classicDark6
override val textSecondary = classicDark5
override val borders = classicDark3
override val textBubbleSent = Color.Black
override val backgroundBubbleReceived = classicDark2
override val textBubbleReceived = Color.White
override val qrCodeContent = background
override val qrCodeBackground = text
override val primaryButtonFill = primary
override val primaryButtonFillText = Color.Black
}
data class ClassicLight(override val primary: Color = primaryGreen) : ThemeColors {
override val isLight = true
override val danger = dangerLight
override val disabled = disabledLight
override val background = classicLight6
override val backgroundSecondary = classicLight5
override val text = classicLight0
override val textSecondary = classicLight1
override val borders = classicLight3
override val textBubbleSent = text
override val backgroundBubbleReceived = classicLight4
override val textBubbleReceived = classicLight4
override val qrCodeContent = text
override val qrCodeBackground = backgroundSecondary
override val primaryButtonFill = text
override val primaryButtonFillText = Color.White
}
data class OceanDark(override val primary: Color = primaryBlue) : ThemeColors {
override val isLight = false
override val danger = dangerDark
override val disabled = disabledDark
override val background = oceanDark2
override val backgroundSecondary = oceanDark1
override val text = oceanDark7
override val textSecondary = oceanDark5
override val borders = oceanDark4
override val textBubbleSent = Color.Black
override val backgroundBubbleReceived = oceanDark4
override val textBubbleReceived = oceanDark4
override val qrCodeContent = background
override val qrCodeBackground = text
override val primaryButtonFill = primary
override val primaryButtonFillText = Color.Black
}
data class OceanLight(override val primary: Color = primaryBlue) : ThemeColors {
override val isLight = true
override val danger = dangerLight
override val disabled = disabledLight
override val background = oceanLight7
override val backgroundSecondary = oceanLight6
override val text = oceanLight1
override val textSecondary = oceanLight2
override val borders = oceanLight3
override val textBubbleSent = text
override val backgroundBubbleReceived = oceanLight4
override val textBubbleReceived = oceanLight1
override val qrCodeContent = text
override val qrCodeBackground = backgroundSecondary
override val primaryButtonFill = text
override val primaryButtonFillText = Color.White
}
@Preview
@Composable
fun PreviewThemeColors(
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) { ThemeColors() }
}
@Composable
private fun ThemeColors() {
Column {
Box(Modifier.background(LocalColors.current.primary)) {
Text("primary", style = LocalType.current.base)
}
Box(Modifier.background(LocalColors.current.background)) {
Text("background", style = LocalType.current.base)
}
Box(Modifier.background(LocalColors.current.backgroundSecondary)) {
Text("backgroundSecondary", style = LocalType.current.base)
}
Box(Modifier.background(LocalColors.current.text)) {
Text("text", style = LocalType.current.base)
}
Box(Modifier.background(LocalColors.current.textSecondary)) {
Text("textSecondary", style = LocalType.current.base)
}
Box(Modifier.background(LocalColors.current.danger)) {
Text("danger", style = LocalType.current.base)
}
Box(Modifier.background(LocalColors.current.borders)) {
Text("border", style = LocalType.current.base)
}
}
}

View File

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences.Companion.BLUE_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.ORANGE_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.PINK_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.PURPLE_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.RED_ACCENT
import org.session.libsession.utilities.TextSecurePreferences.Companion.YELLOW_ACCENT
/**
* Returns the compose theme based on saved preferences
* Some behaviour is hardcoded to cater for legacy usage of people with themes already set
* But future themes will be picked and set directly from the "Appearance" screen
*/
@Composable
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
val selectedTheme = getThemeStyle()
// get the chosen primary color from the preferences
val selectedPrimary = primaryColor()
// create a theme set with the appropriate primary
val colorSet = when(selectedTheme){
TextSecurePreferences.OCEAN_DARK,
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
light = OceanLight(selectedPrimary),
dark = OceanDark(selectedPrimary)
)
else -> ThemeColorSet(
light = ClassicLight(selectedPrimary),
dark = ClassicDark(selectedPrimary)
)
}
// deliver the right set from the light/dark mode chosen
val theme = when{
getFollowSystemSettings() -> if(isSystemInDarkTheme()) colorSet.dark else colorSet.light
selectedTheme == TextSecurePreferences.CLASSIC_LIGHT ||
selectedTheme == TextSecurePreferences.OCEAN_LIGHT -> colorSet.light
else -> colorSet.dark
}
return theme
}
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
BLUE_ACCENT -> primaryBlue
PURPLE_ACCENT -> primaryPurple
PINK_ACCENT -> primaryPink
RED_ACCENT -> primaryRed
ORANGE_ACCENT -> primaryOrange
YELLOW_ACCENT -> primaryYellow
else -> primaryGreen
}

View File

@ -1,28 +1,27 @@
package org.thoughtcrime.securesms.ui
package org.thoughtcrime.securesms.ui.theme
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Shapes
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ui.color.ClassicDark
import org.thoughtcrime.securesms.ui.color.ClassicLight
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.OceanDark
import org.thoughtcrime.securesms.ui.color.OceanLight
import org.thoughtcrime.securesms.ui.color.colors
import org.thoughtcrime.securesms.ui.color.textSelectionColors
import org.session.libsession.utilities.prefs
// Globally accessible composition local objects
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
val LocalType = compositionLocalOf { sessionTypography }
var selectedTheme: ThemeColors? = null
/**
* Apply a Material2 compose theme based on user selections in SharedPreferences.
@ -31,24 +30,32 @@ import org.thoughtcrime.securesms.ui.color.textSelectionColors
fun SessionMaterialTheme(
content: @Composable () -> Unit
) {
SessionMaterialTheme(LocalContext.current.colors()) { content() }
// set the theme data if it hasn't been done yet
if(selectedTheme == null) {
// Some values can be set from the preferences, and if not should fallback to a default value
val context = LocalContext.current
selectedTheme = context.prefs.getComposeTheme()
}
SessionMaterialTheme(colors = selectedTheme ?: ClassicDark()) { content() }
}
/**
* Apply a given [Colors], and our typography and shapes as a Material 2 Compose Theme.
* Apply a given [ThemeColors], and our typography and shapes as a Material 2 Compose Theme.
**/
@Composable
fun SessionMaterialTheme(
colors: Colors,
colors: ThemeColors,
content: @Composable () -> Unit
) {
MaterialTheme(
colors = colors.toMaterialColors(),
typography = sessionTypography,
colorScheme = colors.toMaterialColors(),
typography = sessionTypography.asMaterialTypography(),
shapes = sessionShapes,
) {
CompositionLocalProvider(
LocalColors provides colors,
LocalType provides sessionTypography,
LocalContentColor provides colors.text,
LocalTextSelectionColors provides colors.textSelectionColors,
) {
@ -57,24 +64,6 @@ fun SessionMaterialTheme(
}
}
private fun Colors.toMaterialColors() = androidx.compose.material.Colors(
primary = background,
primaryVariant = backgroundSecondary,
secondary = background,
secondaryVariant = background,
background = background,
surface = background,
error = danger,
onPrimary = text,
onSecondary = text,
onBackground = text,
onSurface = text,
onError = text,
isLight = isLight
)
@Composable private fun Context.colors() = TextSecurePreferences(this).colors()
val pillShape = RoundedCornerShape(percent = 50)
val buttonShape = pillShape
@ -88,7 +77,7 @@ val sessionShapes = Shapes(
*/
@Composable
fun PreviewTheme(
colors: Colors = LocalColors.current,
colors: ThemeColors = LocalColors.current,
content: @Composable () -> Unit
) {
SessionMaterialTheme(colors) {
@ -98,6 +87,7 @@ fun PreviewTheme(
}
}
class SessionColorsParameterProvider : PreviewParameterProvider<Colors> {
// used for previews
class SessionColorsParameterProvider : PreviewParameterProvider<ThemeColors> {
override val values = sequenceOf(ClassicDark(), ClassicLight(), OceanDark(), OceanLight())
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="?android:textColorTertiary"/>
<item android:color="@color/destructive"/>
<item android:color="?danger"/>
</selector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/destructive" android:state_selected="true"/>
<item android:color="?danger" android:state_selected="true"/>
<item android:color="@color/call_action_button" android:state_selected="false"/>
</selector>

View File

@ -3,9 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/destructive" />
<solid android:color="?danger" />
<corners android:radius="@dimen/dialog_button_corner_radius" />
<stroke android:width="@dimen/border_thickness" android:color="@color/destructive" />
<stroke android:width="@dimen/border_thickness" android:color="?danger" />
</shape>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/button_destructive">
android:color="@color/button_danger">
<item>
<shape android:shape="rectangle">
<solid android:color="?colorPrimary"/>
<corners android:radius="@dimen/medium_button_corner_radius" />
<stroke
android:color="@color/button_destructive"
android:color="@color/button_danger"
android:width="@dimen/border_thickness" />
</shape>
</item>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="26dp"
android:viewportWidth="25"
android:viewportHeight="26">
<path
android:pathData="M19.907,7.674H19.907H4.54H4.54C4.317,7.674 4.095,7.719 3.888,7.806L3.888,7.806C3.681,7.893 3.491,8.023 3.334,8.189C3.176,8.355 3.054,8.554 2.978,8.775L3.922,9.097L2.978,8.775C2.903,8.996 2.877,9.231 2.904,9.465L2.904,9.465L2.904,9.469L4.555,23.412C4.555,23.413 4.555,23.413 4.555,23.414C4.603,23.823 4.807,24.189 5.111,24.447C5.415,24.705 5.798,24.84 6.187,24.84H6.188H18.26H18.26C18.649,24.84 19.032,24.705 19.336,24.447C19.64,24.189 19.844,23.823 19.892,23.414C19.892,23.413 19.892,23.413 19.892,23.412L21.543,9.469L21.544,9.465C21.57,9.231 21.544,8.996 21.469,8.775L21.469,8.775C21.393,8.554 21.271,8.355 21.113,8.189C20.956,8.023 20.766,7.893 20.559,7.806L20.17,8.728L20.559,7.806C20.352,7.719 20.13,7.674 19.907,7.674ZM21.412,1.84H3.031C2.045,1.84 1.149,2.609 1.149,3.674V5.828C1.149,6.893 2.045,7.662 3.031,7.662H21.412C22.398,7.662 23.294,6.893 23.294,5.828V3.674C23.294,2.609 22.398,1.84 21.412,1.84Z"
android:strokeWidth="2"
android:fillColor="#FF3A3A"
android:strokeColor="?backgroundSecondary"/>
</vector>

View File

@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/destructive">
android:tint="?danger">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?conversation_menu_background_color" />
<corners android:radius="12dp" />
</shape>

View File

@ -39,7 +39,7 @@
/>
<TextView
style="@style/Widget.Session.Button.Common.DestructiveOutline"
style="@style/Widget.Session.Button.Common.DangerOutline"
android:paddingHorizontal="@dimen/large_spacing"
android:paddingVertical="@dimen/small_spacing"
android:text="@string/ConversationActivity_unblock"

View File

@ -228,7 +228,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
android:background="@color/destructive"
android:background="?danger"
android:visibility="gone"
tools:visibility="visible">
@ -300,7 +300,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/AccessibilityId_block_message_request_button"
android:textColor="@color/destructive"
android:textColor="?danger"
android:paddingHorizontal="@dimen/massive_spacing"
android:paddingVertical="@dimen/small_spacing"
android:textSize="@dimen/text_size"
@ -334,7 +334,7 @@
<Button
android:id="@+id/declineMessageRequestButton"
style="@style/Widget.Session.Button.Common.DestructiveOutline"
style="@style/Widget.Session.Button.Common.DangerOutline"
android:contentDescription="@string/AccessibilityId_decline_message_request_button"
android:layout_width="0dp"
android:layout_height="@dimen/medium_button_height"

View File

@ -51,7 +51,7 @@
<Button
android:contentDescription="@string/AccessibilityId_clear_all_message_requests"
android:id="@+id/clearAllMessageRequestsButton"
style="@style/Widget.Session.Button.Common.DestructiveOutline"
style="@style/Widget.Session.Button.Common.DangerOutline"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:layout_alignParentBottom="true"

View File

@ -175,7 +175,7 @@
android:src="@drawable/ic_baseline_call_end_24"
android:padding="@dimen/medium_spacing"
android:foregroundTint="@color/call_action_foreground"
android:backgroundTint="@color/destructive"
android:backgroundTint="?danger"
android:layout_width="@dimen/large_button_height"
android:layout_height="@dimen/large_button_height"
app:layout_constraintBottom_toBottomOf="parent"
@ -265,7 +265,7 @@
android:src="@drawable/ic_baseline_call_end_24"
android:padding="@dimen/medium_spacing"
android:foregroundTint="@color/call_action_foreground"
android:backgroundTint="@color/destructive"
android:backgroundTint="?danger"
android:layout_width="@dimen/large_button_height"
android:layout_height="@dimen/large_button_height"
android:layout_marginBottom="@dimen/very_large_spacing"

View File

@ -9,7 +9,7 @@
android:layout_marginHorizontal="@dimen/large_spacing"
android:layout_marginVertical="@dimen/medium_spacing"
android:text="@string/blocked_contacts_title"
android:textColor="@color/destructive"
android:textColor="?danger"
android:textSize="16sp"
android:textStyle="bold"
/>

View File

@ -44,7 +44,7 @@
android:orientation="horizontal">
<Button
style="@style/Widget.Session.Button.Dialog.DestructiveText"
style="@style/Widget.Session.Button.Dialog.DangerText"
android:id="@+id/clearAllDataButton"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"

View File

@ -43,7 +43,7 @@
android:text="@string/cancel" />
<Button
style="@style/Widget.Session.Button.Dialog.DestructiveText"
style="@style/Widget.Session.Button.Dialog.DangerText"
android:id="@+id/sendSeedButton"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"

View File

@ -62,7 +62,7 @@
<TextView
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:backgroundTint="@color/destructive"
android:backgroundTint="?danger"
android:layout_marginHorizontal="@dimen/small_spacing"
android:id="@+id/declineButton"
android:layout_width="wrap_content"

View File

@ -5,7 +5,7 @@
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?conversation_menu_background_color">
android:background="@drawable/sheet_rounded_bg">
<TextView
android:id="@+id/titleText"

View File

@ -14,14 +14,14 @@
android:contentDescription="@string/AccessibilityId_delete_just_for_me"
style="@style/BottomSheetActionItem"
android:text="@string/delete_message_for_me"
android:textColor="@color/destructive"/>
android:textColor="?danger"/>
<TextView
android:id="@+id/deleteForEveryoneTextView"
android:contentDescription="@string/delete_message_for_everyone"
style="@style/BottomSheetActionItem"
android:text="@string/delete_message_for_everyone"
android:textColor="@color/destructive"/>
android:textColor="?danger"/>
<TextView
android:id="@+id/cancelTextView"

View File

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?conversation_menu_background_color">
android:background="@drawable/sheet_rounded_bg">
<TextView
android:id="@+id/titleText"

View File

@ -46,7 +46,7 @@
/>
<TextView
android:textColor="@color/destructive"
android:textColor="?danger"
android:id="@+id/cancelButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -4,7 +4,7 @@
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?conversation_menu_background_color">
android:background="@drawable/sheet_rounded_bg">
<TextView
android:id="@+id/titleText"

View File

@ -13,13 +13,13 @@
android:layout_width="13dp"
android:layout_height="13dp"
android:src="@drawable/ic_baseline_block_24"
app:tint="@color/destructive" />
app:tint="?danger" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="@color/destructive"
android:textColor="?danger"
android:textSize="16sp"
android:text="@string/recipient_preferences__block"
/>

View File

@ -31,7 +31,7 @@
android:layout_height="wrap_content"/>
<TextView
android:text="@string/message_requests_clear_all"
android:textColor="@color/destructive"
android:textColor="?danger"
android:visibility="gone"
android:id="@+id/header_view_clear_all"
android:layout_width="wrap_content"

View File

@ -62,6 +62,7 @@
android:textColor="?message_received_text_color"
android:textAlignment="center"
android:layout_gravity="center"
android:gravity="center"
tools:text="You missed a call"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -35,7 +35,7 @@
android:layout_width="16dp"
android:layout_height="16dp"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/destructive" />
android:backgroundTint="?danger" />
<TextView
android:id="@+id/recordingViewDurationTextView"
@ -121,7 +121,7 @@
android:alpha="0.5"
android:layout_centerInParent="true"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/destructive" />
android:backgroundTint="?danger" />
</RelativeLayout>
@ -136,7 +136,7 @@
android:layout_marginEnd="-8dp"
android:layout_marginBottom="0dp"
android:background="@drawable/circle_tintable"
android:backgroundTint="@color/destructive" >
android:backgroundTint="?danger" >
<ImageView
android:id="@+id/recordButtonOverlayImageView"

View File

@ -32,6 +32,7 @@
<attr name="onLightCell" format="reference|color"/>
<attr name="accentColor" format="reference|color"/>
<attr name="danger" format="reference|color"/>
<attr name="backgroundSecondary" format="reference|color"/>
<attr name="prominentButtonColor" format="reference|color"/>
<attr name="elementBorderColor" format="reference|color"/>

View File

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<color name="destructive">#FF453A</color>
<color name="unimportant">#D8D8D8</color>
<color name="profile_picture_background">#353535</color>
<color name="action_bar_background">#161616</color>

View File

@ -108,9 +108,9 @@
<item name="android:textColor">?android:textColorPrimary</item>
</style>
<style name="Widget.Session.Button.Common.DestructiveOutline">
<item name="android:background">@drawable/destructive_outline_button_medium_background</item>
<item name="android:textColor">@color/button_destructive</item>
<style name="Widget.Session.Button.Common.DangerOutline">
<item name="android:background">@drawable/danger_outline_button_medium_background</item>
<item name="android:textColor">@color/button_danger</item>
</style>
<style name="Widget.Session.Button.Dialog" parent="">
@ -125,9 +125,9 @@
<item name="android:background">@drawable/unimportant_dialog_text_button_background</item>
</style>
<style name="Widget.Session.Button.Dialog.DestructiveText">
<item name="android:background">@drawable/destructive_dialog_text_button_background</item>
<item name="android:textColor">@color/destructive</item>
<style name="Widget.Session.Button.Dialog.DangerText">
<item name="android:background">@drawable/danger_dialog_text_button_background</item>
<item name="android:textColor">?danger</item>
</style>
<style name="Widget.Session.EditText.Compose" parent="@style/Signal.Text.Body">

View File

@ -58,7 +58,7 @@
<item name="prominentButtonColor">?colorAccent</item>
<item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item>
<item name="attachment_document_icon_large">@drawable/ic_document_large_dark</item>
<item name="colorError">@color/destructive</item>
<item name="colorError">?danger</item>
</style>
<!-- This should be the default theme for the application. -->
@ -318,6 +318,7 @@
<item name="backgroundSecondary">@color/classic_dark_1</item>
<item name="colorControlNormal">?android:textColorPrimary</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="danger">@color/danger_dark</item>
<item name="android:textColorPrimary">@color/classic_dark_6</item>
<item name="android:textColorSecondary">?android:textColorPrimary</item>
<item name="android:textColorTertiary">@color/classic_dark_5</item>
@ -399,6 +400,7 @@
<item name="colorPrimaryDark">@color/classic_light_6</item>
<item name="colorControlNormal">?android:textColorPrimary</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="danger">@color/danger_light</item>
<item name="android:textColorPrimary">@color/classic_light_0</item>
<item name="android:textColorSecondary">@color/classic_light_1</item>
<item name="android:textColorTertiary">@color/classic_light_1</item>
@ -488,6 +490,7 @@
<item name="backgroundSecondary">@color/ocean_dark_1</item>
<item name="colorControlNormal">@color/ocean_dark_7</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="danger">@color/danger_dark</item>
<item name="android:textColorPrimary">@color/ocean_dark_7</item>
<item name="android:textColorSecondary">@color/ocean_dark_5</item>
<item name="android:textColorTertiary">@color/ocean_dark_5</item>
@ -574,6 +577,7 @@
<item name="backgroundSecondary">@color/ocean_light_6</item>
<item name="colorControlNormal">@color/ocean_light_1</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="danger">@color/danger_light</item>
<item name="android:textColorPrimary">@color/ocean_light_1</item>
<item name="android:textColorSecondary">@color/ocean_light_2</item>
<item name="android:textColorTertiary">@color/ocean_light_2</item>
@ -686,6 +690,7 @@
<item name="actionBarStyle">@style/Widget.Session.ActionBar</item>
<item name="prominentButtonColor">?colorAccent</item>
<item name="elementBorderColor">@color/classic_dark_3</item>
<item name="danger">@color/danger_dark</item>
<!-- Home screen -->
<item name="searchBackgroundColor">#1B1B1B</item>

View File

@ -25,7 +25,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.MainCoroutineRule
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.ExpiryRadioOption
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCard
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCardData
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.UiState
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.Storage
@ -87,7 +87,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, selected = true),
timeOption(ExpiryType.AFTER_SEND, 12.hours),
@ -126,7 +126,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, selected = false),
timeOption(ExpiryType.LEGACY, 12.hours),
@ -165,7 +165,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, selected = true),
timeOption(ExpiryType.AFTER_SEND, 12.hours),
@ -205,7 +205,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, enabled = false, selected = true),
timeOption(ExpiryType.AFTER_SEND, 12.hours, enabled = false),
@ -247,7 +247,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE, selected = true),
typeOption(12.hours, ExpiryType.AFTER_READ),
@ -286,13 +286,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(time, ExpiryType.AFTER_READ),
typeOption(time, ExpiryType.AFTER_SEND, selected = true)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_SEND, 12.hours, selected = true),
timeOption(ExpiryType.AFTER_SEND, 1.days),
@ -332,14 +332,14 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(time, ExpiryType.LEGACY, selected = true),
typeOption(12.hours, ExpiryType.AFTER_READ, enabled = false),
typeOption(1.days, ExpiryType.AFTER_SEND, enabled = false)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.LEGACY, 12.hours, selected = true),
timeOption(ExpiryType.LEGACY, 1.days),
@ -379,13 +379,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(12.hours, ExpiryType.AFTER_READ),
typeOption(time, ExpiryType.AFTER_SEND, selected = true)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_SEND, 12.hours),
timeOption(ExpiryType.AFTER_SEND, 1.days, selected = true),
@ -426,13 +426,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(1.days, ExpiryType.AFTER_READ, selected = true),
typeOption(time, ExpiryType.AFTER_SEND)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_READ, 5.minutes),
timeOption(ExpiryType.AFTER_READ, 1.hours),
@ -479,13 +479,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(12.hours, ExpiryType.AFTER_READ),
typeOption(1.days, ExpiryType.AFTER_SEND, selected = true)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_SEND, 12.hours),
timeOption(ExpiryType.AFTER_SEND, 1.days, selected = true),

View File

@ -1,6 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<color name="destructive">#FF453A</color>
<color name="unimportant">#D8D8D8</color>
<color name="profile_picture_background">#353535</color>
<color name="cell_background">#1B1B1B</color>