Show some more info in the about sheet.
This commit is contained in:
parent
47cdc50a81
commit
57ac7cb328
7 changed files with 251 additions and 58 deletions
|
@ -5,6 +5,7 @@
|
|||
|
||||
package org.thoughtcrime.securesms.recipients.ui.about
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -26,7 +27,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -38,6 +38,7 @@ import androidx.core.widget.TextViewCompat
|
|||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.thoughtcrime.securesms.AvatarPreviewActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.avatar.AvatarImage
|
||||
|
@ -81,11 +82,27 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
|||
override fun SheetContent() {
|
||||
val recipient by viewModel.recipient
|
||||
val groupsInCommonCount by viewModel.groupsInCommonCount
|
||||
val verified by viewModel.verified
|
||||
|
||||
if (recipient.isPresent) {
|
||||
AboutSheetContent(
|
||||
recipient = recipient.get(),
|
||||
groupsInCommonCount = groupsInCommonCount,
|
||||
Content(
|
||||
model = AboutModel(
|
||||
isSelf = recipient.get().isSelf,
|
||||
hasAvatar = recipient.get().profileAvatarFileDetails.hasFile(),
|
||||
displayName = recipient.get().getDisplayName(requireContext()),
|
||||
shortName = recipient.get().getShortDisplayName(requireContext()),
|
||||
about = recipient.get().about,
|
||||
verified = verified,
|
||||
recipientForAvatar = recipient.get(),
|
||||
formattedE164 = if (recipient.get().hasE164() && recipient.get().shouldShowE164()) {
|
||||
PhoneNumberFormatter.get(requireContext()).prettyPrintFormat(recipient.get().requireE164())
|
||||
} else {
|
||||
null
|
||||
},
|
||||
groupsInCommon = groupsInCommonCount,
|
||||
profileSharing = recipient.get().isProfileSharing,
|
||||
systemContact = recipient.get().isSystemContact
|
||||
),
|
||||
onClickSignalConnections = this::openSignalConnectionsSheet,
|
||||
onAvatarClicked = this::openProfilePhotoViewer
|
||||
)
|
||||
|
@ -102,25 +119,23 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AboutSheetContentPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
AboutSheetContent(
|
||||
recipient = Recipient.UNKNOWN,
|
||||
groupsInCommonCount = 0,
|
||||
onClickSignalConnections = {},
|
||||
onAvatarClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
private data class AboutModel(
|
||||
val isSelf: Boolean,
|
||||
val displayName: String,
|
||||
val shortName: String,
|
||||
val about: String?,
|
||||
val verified: Boolean,
|
||||
val hasAvatar: Boolean,
|
||||
val recipientForAvatar: Recipient,
|
||||
val formattedE164: String?,
|
||||
val profileSharing: Boolean,
|
||||
val systemContact: Boolean,
|
||||
val groupsInCommon: Int
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun AboutSheetContent(
|
||||
recipient: Recipient,
|
||||
groupsInCommonCount: Int,
|
||||
private fun Content(
|
||||
model: AboutModel,
|
||||
onClickSignalConnections: () -> Unit,
|
||||
onAvatarClicked: () -> Unit
|
||||
) {
|
||||
|
@ -131,8 +146,8 @@ private fun AboutSheetContent(
|
|||
BottomSheets.Handle(modifier = Modifier.padding(top = 6.dp))
|
||||
}
|
||||
|
||||
val avatarOnClick = remember(recipient.profileAvatarFileDetails.hasFile()) {
|
||||
if (recipient.profileAvatarFileDetails.hasFile()) {
|
||||
val avatarOnClick = remember(model.hasAvatar) {
|
||||
if (model.hasAvatar) {
|
||||
onAvatarClicked
|
||||
} else {
|
||||
{ }
|
||||
|
@ -141,7 +156,7 @@ private fun AboutSheetContent(
|
|||
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
AvatarImage(
|
||||
recipient = recipient,
|
||||
recipient = model.recipientForAvatar,
|
||||
modifier = Modifier
|
||||
.padding(top = 56.dp)
|
||||
.size(240.dp)
|
||||
|
@ -150,7 +165,7 @@ private fun AboutSheetContent(
|
|||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = if (recipient.isSelf) R.string.AboutSheet__you else R.string.AboutSheet__about),
|
||||
text = stringResource(id = if (model.isSelf) R.string.AboutSheet__you else R.string.AboutSheet__about),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -158,22 +173,19 @@ private fun AboutSheetContent(
|
|||
.padding(top = 20.dp, bottom = 14.dp)
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val displayName = remember(recipient) { recipient.getDisplayName(context) }
|
||||
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_person_24),
|
||||
text = displayName,
|
||||
text = model.displayName,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (!recipient.about.isNullOrBlank()) {
|
||||
if (model.about.isNotNullOrBlank()) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_edit_24),
|
||||
text = {
|
||||
Row {
|
||||
AndroidView(factory = ::EmojiTextView) {
|
||||
it.text = recipient.combinedAboutAndEmoji
|
||||
it.text = model.about
|
||||
|
||||
TextViewCompat.setTextAppearance(it, R.style.Signal_Text_BodyLarge)
|
||||
}
|
||||
|
@ -183,7 +195,16 @@ private fun AboutSheetContent(
|
|||
)
|
||||
}
|
||||
|
||||
if (recipient.isProfileSharing) {
|
||||
if (model.verified) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.check),
|
||||
text = stringResource(id = R.string.AboutSheet__verified),
|
||||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onClickSignalConnections
|
||||
)
|
||||
}
|
||||
|
||||
if (model.profileSharing || model.systemContact) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_connections_24),
|
||||
text = stringResource(id = R.string.AboutSheet__signal_connection),
|
||||
|
@ -191,36 +212,38 @@ private fun AboutSheetContent(
|
|||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onClickSignalConnections
|
||||
)
|
||||
} else {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.chat_x),
|
||||
text = stringResource(id = R.string.AboutSheet__no_direct_message, model.shortName),
|
||||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onClickSignalConnections
|
||||
)
|
||||
}
|
||||
|
||||
val shortName = remember(recipient) { recipient.getShortDisplayName(context) }
|
||||
if (recipient.isSystemContact) {
|
||||
if (model.systemContact) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_person_circle_24),
|
||||
text = stringResource(id = R.string.AboutSheet__s_is_in_your_system_contacts, shortName),
|
||||
text = stringResource(id = R.string.AboutSheet__s_is_in_your_system_contacts, model.shortName),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
if (recipient.e164.isPresent && recipient.shouldShowE164()) {
|
||||
val e164 = remember(recipient.e164.get()) {
|
||||
PhoneNumberFormatter.get(context).prettyPrintFormat(recipient.e164.get())
|
||||
}
|
||||
|
||||
if (model.formattedE164.isNotNullOrBlank()) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_phone_24),
|
||||
text = e164,
|
||||
text = model.formattedE164,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
val groupsInCommonText = if (recipient.hasGroupsInCommon()) {
|
||||
pluralStringResource(id = R.plurals.AboutSheet__d_groups_in, groupsInCommonCount, groupsInCommonCount)
|
||||
val groupsInCommonText = if (model.groupsInCommon > 0) {
|
||||
pluralStringResource(id = R.plurals.AboutSheet__d_groups_in, model.groupsInCommon, model.groupsInCommon)
|
||||
} else {
|
||||
stringResource(id = R.string.AboutSheet__you_have_no_groups_in_common)
|
||||
}
|
||||
|
||||
val groupsInCommonIcon = if (!recipient.isProfileSharing && groupsInCommonCount == 0) {
|
||||
val groupsInCommonIcon = if (!model.profileSharing && model.groupsInCommon == 0) {
|
||||
painterResource(R.drawable.symbol_error_circle_24)
|
||||
} else {
|
||||
painterResource(R.drawable.symbol_group_24)
|
||||
|
@ -236,20 +259,6 @@ private fun AboutSheetContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AboutRowPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_person_24),
|
||||
text = "Maya Johnson",
|
||||
endIcon = painterResource(id = R.drawable.symbol_chevron_right_compact_bold_16)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutRow(
|
||||
startIcon: Painter,
|
||||
|
@ -318,3 +327,126 @@ private fun AboutRow(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ContentPreviewDefault() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
Content(
|
||||
model = AboutModel(
|
||||
isSelf = false,
|
||||
hasAvatar = true,
|
||||
displayName = "Peter Parker",
|
||||
shortName = "Peter",
|
||||
about = "Photographer for the Daily Bugle.",
|
||||
verified = true,
|
||||
recipientForAvatar = Recipient.UNKNOWN,
|
||||
formattedE164 = "(123) 456-7890",
|
||||
profileSharing = true,
|
||||
systemContact = true,
|
||||
groupsInCommon = 0
|
||||
),
|
||||
onClickSignalConnections = {},
|
||||
onAvatarClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ContentPreviewInContactsNotProfileSharing() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
Content(
|
||||
model = AboutModel(
|
||||
isSelf = false,
|
||||
hasAvatar = true,
|
||||
displayName = "Peter Parker",
|
||||
shortName = "Peter",
|
||||
about = "Photographer for the Daily Bugle.",
|
||||
verified = false,
|
||||
recipientForAvatar = Recipient.UNKNOWN,
|
||||
formattedE164 = null,
|
||||
profileSharing = false,
|
||||
systemContact = true,
|
||||
groupsInCommon = 3
|
||||
),
|
||||
onClickSignalConnections = {},
|
||||
onAvatarClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ContentPreviewGroupsInCommonNoE164() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
Content(
|
||||
model = AboutModel(
|
||||
isSelf = false,
|
||||
hasAvatar = true,
|
||||
displayName = "Peter Parker",
|
||||
shortName = "Peter",
|
||||
about = "Photographer for the Daily Bugle.",
|
||||
verified = false,
|
||||
recipientForAvatar = Recipient.UNKNOWN,
|
||||
formattedE164 = null,
|
||||
profileSharing = true,
|
||||
systemContact = false,
|
||||
groupsInCommon = 3
|
||||
),
|
||||
onClickSignalConnections = {},
|
||||
onAvatarClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "content", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ContentPreviewNotAConnection() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
Content(
|
||||
model = AboutModel(
|
||||
isSelf = false,
|
||||
hasAvatar = true,
|
||||
displayName = "Peter Parker",
|
||||
shortName = "Peter",
|
||||
about = "Photographer for the Daily Bugle.",
|
||||
verified = false,
|
||||
recipientForAvatar = Recipient.UNKNOWN,
|
||||
formattedE164 = null,
|
||||
profileSharing = false,
|
||||
systemContact = false,
|
||||
groupsInCommon = 3
|
||||
),
|
||||
onClickSignalConnections = {},
|
||||
onAvatarClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "about row", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "about row", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AboutRowPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_person_24),
|
||||
text = "Maya Johnson",
|
||||
endIcon = painterResource(id = R.drawable.symbol_chevron_right_compact_bold_16)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ package org.thoughtcrime.securesms.recipients.ui.about
|
|||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class AboutSheetRepository {
|
||||
|
@ -16,4 +18,11 @@ class AboutSheetRepository {
|
|||
SignalDatabase.groups.getPushGroupsContainingMember(recipientId).size
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getVerified(recipientId: RecipientId): Single<Boolean> {
|
||||
return Single.fromCallable {
|
||||
val identityRecord = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(recipientId)
|
||||
identityRecord.isPresent && identityRecord.get().verifiedStatus == IdentityTable.VerifiedStatus.VERIFIED
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,9 @@ class AboutSheetViewModel(
|
|||
private val _groupsInCommonCount: MutableIntState = mutableIntStateOf(0)
|
||||
val groupsInCommonCount: IntState = _groupsInCommonCount
|
||||
|
||||
private val _verified: MutableState<Boolean> = mutableStateOf(false)
|
||||
val verified: State<Boolean> = _verified
|
||||
|
||||
private val recipientDisposable: Disposable = Recipient
|
||||
.observable(recipientId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -44,6 +47,13 @@ class AboutSheetViewModel(
|
|||
_groupsInCommonCount.intValue = it
|
||||
}
|
||||
|
||||
private val verifiedDisposable: Disposable = repository
|
||||
.getVerified(recipientId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy {
|
||||
_verified.value = it
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
recipientDisposable.dispose()
|
||||
groupsInCommonDisposable.dispose()
|
||||
|
|
17
app/src/main/res/drawable/chat_x.xml
Normal file
17
app/src/main/res/drawable/chat_x.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M8.26 8.26c0.34-0.35 0.9-0.35 1.23 0l2.51 2.5 2.5-2.5c0.35-0.35 0.9-0.35 1.24 0 0.35 0.34 0.35 0.9 0 1.23L13.24 12l2.5 2.5c0.35 0.35 0.35 0.9 0 1.24-0.34 0.35-0.9 0.35-1.23 0L12 13.24l-2.5 2.5c-0.35 0.35-0.9 0.35-1.24 0-0.35-0.34-0.35-0.9 0-1.23l2.5-2.51-2.5-2.5c-0.35-0.35-0.35-0.9 0-1.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M1.88 12C1.88 6.4 6.4 1.87 12 1.87c5.6 0 10.13 4.54 10.13 10.13 0 5.6-4.54 10.13-10.13 10.13-1.67 0-3.25-0.41-4.64-1.13L3.3 22.46c-1.1 0.4-2.16-0.66-1.76-1.76L3 16.64C2.28 15.25 1.88 13.67 1.88 12ZM12 3.62c-4.63 0-8.37 3.75-8.37 8.38 0 1.43 0.35 2.78 0.99 3.95 0.18 0.34 0.22 0.75 0.08 1.13l-1.25 3.47 3.47-1.25c0.38-0.14 0.79-0.1 1.13 0.09 1.17 0.63 2.52 0.98 3.95 0.98 4.63 0 8.38-3.74 8.38-8.37 0-4.63-3.75-8.38-8.38-8.38Z"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/check.xml
Normal file
14
app/src/main/res/drawable/check.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18.97 5.26c0.4 0.26 0.53 0.8 0.27 1.21l-8.32 13c-0.16 0.24-0.42 0.4-0.7 0.4-0.28 0.02-0.55-0.1-0.73-0.33l-4.68-5.98c-0.3-0.38-0.23-0.93 0.15-1.23 0.38-0.3 0.93-0.23 1.23 0.15l3.92 5 7.65-11.95c0.26-0.4 0.8-0.53 1.21-0.27Z"/>
|
||||
</vector>
|
|
@ -1965,6 +1965,10 @@
|
|||
<!-- AboutSheet -->
|
||||
<!-- Displayed in a sheet row and allows user to open signal connection explanation on tap -->
|
||||
<string name="AboutSheet__signal_connection">Signal connection</string>
|
||||
<!-- Displayed in a sheet row describing that the user has marked this contact as 'verified' from within the app -->
|
||||
<string name="AboutSheet__verified">Verified</string>
|
||||
<!-- Displayed in bottom sheet describing that the user has no direct messages with this person. The placeholder is a person's name. -->
|
||||
<string name="AboutSheet__no_direct_message">No direct messages with %1$s</string>
|
||||
<!-- Explains that the given user (placeholder is short name) is in the users system contact -->
|
||||
<string name="AboutSheet__s_is_in_your_system_contacts">%1$s is in your system contacts</string>
|
||||
<!-- Notice in a row when user has no groups in common -->
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.signal.core.util
|
||||
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* Treats the string as a serialized list of tokens and tells you if an item is present in the list.
|
||||
* In addition to exact matches, this handles wildcards at the end of an item.
|
||||
|
@ -55,6 +58,10 @@ fun String?.nullIfBlank(): String? {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun CharSequence?.isNotNullOrBlank(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isNotNullOrBlank != null)
|
||||
}
|
||||
return !this.isNullOrBlank()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue