mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Add ability to edit member label from the about you sheet.
This commit is contained in:
committed by
Cody Henthorne
parent
bd121e47c8
commit
28c37cb3ac
@@ -1,8 +1,10 @@
|
||||
package org.thoughtcrime.securesms.groups;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
|
||||
public enum GroupAccessControl {
|
||||
ALL_MEMBERS(R.string.GroupManagement_access_level_all_members),
|
||||
@@ -18,4 +20,15 @@ public enum GroupAccessControl {
|
||||
public @StringRes int getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given [memberLevel] meets this access requirement.
|
||||
*/
|
||||
public boolean allows(@NonNull GroupTable.MemberLevel memberLevel) {
|
||||
return switch (this) {
|
||||
case ALL_MEMBERS -> memberLevel.isInGroup();
|
||||
case ONLY_ADMINS -> memberLevel == GroupTable.MemberLevel.ADMINISTRATOR;
|
||||
case NO_ONE -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,12 +243,7 @@ public final class LiveGroup {
|
||||
}
|
||||
|
||||
private static boolean applyAccessControl(@NonNull GroupTable.MemberLevel memberLevel, @NonNull GroupAccessControl rights) {
|
||||
switch (rights) {
|
||||
case ALL_MEMBERS: return memberLevel.isInGroup();
|
||||
case ONLY_ADMINS: return memberLevel == GroupTable.MemberLevel.ADMINISTRATOR;
|
||||
case NO_ONE : return false;
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
return rights.allows(memberLevel);
|
||||
}
|
||||
|
||||
public LiveData<GroupLinkUrlAndStatus> getGroupLink() {
|
||||
|
||||
@@ -80,6 +80,15 @@ class MemberLabelRepository private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the [Recipient] has permission to set their member label in the given group.
|
||||
*/
|
||||
suspend fun canSetLabel(groupId: GroupId.V2, recipient: Recipient): Boolean = withContext(Dispatchers.IO) {
|
||||
if (!RemoteConfig.sendMemberLabels) return@withContext false
|
||||
val groupRecord = groupsTable.getGroup(groupId).orNull() ?: return@withContext false
|
||||
groupRecord.attributesAccessControl.allows(groupRecord.memberLevel(recipient))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the group member label for the current user.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package org.thoughtcrime.securesms.recipients.ui.about
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -39,6 +40,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.navigation.Navigation
|
||||
import org.signal.core.ui.compose.BottomSheets
|
||||
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
@@ -50,7 +52,10 @@ import org.thoughtcrime.securesms.AvatarPreviewActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.avatar.AvatarImage
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsFragmentDirections
|
||||
import org.thoughtcrime.securesms.conversation.v2.UnverifiedProfileNameBottomSheet
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
|
||||
import org.thoughtcrime.securesms.groups.ui.incommon.GroupsInCommonActivity
|
||||
import org.thoughtcrime.securesms.nicknames.ViewNoteSheet
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -66,14 +71,15 @@ import org.signal.core.ui.R as CoreUiR
|
||||
class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val RECIPIENT_ID = "recipient_id"
|
||||
private const val VIEWING_FROM_GROUP_ID = "viewing_from_group_id"
|
||||
|
||||
@JvmStatic
|
||||
fun create(recipient: Recipient): AboutSheet {
|
||||
fun create(recipient: Recipient, viewingFromGroupId: GroupId.V2? = null): AboutSheet {
|
||||
return AboutSheet().apply {
|
||||
arguments = bundleOf(
|
||||
RECIPIENT_ID to recipient.id
|
||||
RECIPIENT_ID to recipient.id,
|
||||
VIEWING_FROM_GROUP_ID to viewingFromGroupId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -81,11 +87,11 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
private val recipientId: RecipientId
|
||||
get() = requireArguments().getParcelableCompat(RECIPIENT_ID, RecipientId::class.java)!!
|
||||
private val recipientId: RecipientId by lazy { requireArguments().getParcelableCompat(RECIPIENT_ID, RecipientId::class.java)!! }
|
||||
private val viewingFromGroupId: GroupId.V2? by lazy { requireArguments().getParcelableCompat(VIEWING_FROM_GROUP_ID, GroupId.V2::class.java) }
|
||||
|
||||
private val viewModel by viewModel {
|
||||
AboutSheetViewModel(recipientId)
|
||||
AboutSheetViewModel(recipientId, viewingFromGroupId)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -93,6 +99,8 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
val recipient by viewModel.recipient
|
||||
val groupsInCommonCount by viewModel.groupsInCommonCount
|
||||
val verified by viewModel.verified
|
||||
val memberLabel by viewModel.memberLabel
|
||||
val canEditMemberLabel by viewModel.canEditMemberLabel
|
||||
|
||||
if (recipient.isPresent) {
|
||||
Content(
|
||||
@@ -113,13 +121,16 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
profileSharing = recipient.get().isProfileSharing,
|
||||
systemContact = recipient.get().isSystemContact,
|
||||
groupsInCommon = groupsInCommonCount,
|
||||
note = recipient.get().note ?: ""
|
||||
note = recipient.get().note ?: "",
|
||||
memberLabel = memberLabel,
|
||||
canEditMemberLabel = canEditMemberLabel
|
||||
),
|
||||
onClickSignalConnections = this::openSignalConnectionsSheet,
|
||||
onAvatarClicked = this::openProfilePhotoViewer,
|
||||
onNoteClicked = this::openNoteSheet,
|
||||
onUnverifiedProfileClicked = this::openUnverifiedProfileSheet,
|
||||
onGroupsInCommonClicked = this::openGroupsInCommon
|
||||
onGroupsInCommonClicked = this::openGroupsInCommon,
|
||||
onMemberLabelClicked = this::openMemberLabelScreen
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -147,6 +158,14 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
dismiss()
|
||||
startActivity(GroupsInCommonActivity.createIntent(requireContext(), recipientId))
|
||||
}
|
||||
|
||||
private fun openMemberLabelScreen() {
|
||||
viewingFromGroupId?.let { groupId ->
|
||||
val navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
|
||||
navController.navigate(ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToMemberLabelFragment(groupId))
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class AboutModel(
|
||||
@@ -162,7 +181,9 @@ private data class AboutModel(
|
||||
val profileSharing: Boolean,
|
||||
val systemContact: Boolean,
|
||||
val groupsInCommon: Int,
|
||||
val note: String
|
||||
val note: String,
|
||||
val memberLabel: MemberLabel? = null,
|
||||
val canEditMemberLabel: Boolean = false
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -172,7 +193,8 @@ private fun Content(
|
||||
onAvatarClicked: () -> Unit,
|
||||
onNoteClicked: () -> Unit,
|
||||
onUnverifiedProfileClicked: () -> Unit = {},
|
||||
onGroupsInCommonClicked: () -> Unit = {}
|
||||
onGroupsInCommonClicked: () -> Unit = {},
|
||||
onMemberLabelClicked: () -> Unit = {}
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
@@ -218,6 +240,15 @@ private fun Content(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (model.isSelf && (model.memberLabel != null || model.canEditMemberLabel)) {
|
||||
MemberLabelRow(
|
||||
memberLabel = model.memberLabel,
|
||||
canEdit = model.canEditMemberLabel,
|
||||
onClick = onMemberLabelClicked,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
if (model.about.isNotNullOrBlank()) {
|
||||
val textColor = LocalContentColor.current
|
||||
|
||||
@@ -333,6 +364,48 @@ private fun Content(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MemberLabelRow(
|
||||
memberLabel: MemberLabel?,
|
||||
canEdit: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AboutRow(
|
||||
startIcon = ImageVector.vectorResource(R.drawable.symbol_tag_24),
|
||||
text = {
|
||||
if (memberLabel != null) {
|
||||
if (!memberLabel.emoji.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = memberLabel.emoji,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = memberLabel.text,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, false)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(id = R.string.AboutSheet__add_group_member_label),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, false)
|
||||
)
|
||||
}
|
||||
},
|
||||
endIcon = if (canEdit) ImageVector.vectorResource(id = R.drawable.symbol_chevron_right_compact_bold_16) else null,
|
||||
onClick = if (canEdit) onClick else null,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutRow(
|
||||
startIcon: ImageVector,
|
||||
@@ -485,6 +558,8 @@ private fun ContentPreviewForSelf() {
|
||||
profileSharing = true,
|
||||
systemContact = true,
|
||||
groupsInCommon = 0,
|
||||
memberLabel = MemberLabel("🕷️", "Superhero"),
|
||||
canEditMemberLabel = true,
|
||||
note = "Weird Things Happen To Me All The Time."
|
||||
),
|
||||
onClickSignalConnections = {},
|
||||
@@ -628,3 +703,27 @@ private fun AboutRowPreview() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MemberLabelRowPreviews() = Previews.Preview {
|
||||
val headerModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.padding(vertical = 4.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
|
||||
Column {
|
||||
Text("no label, can't edit:", style = MaterialTheme.typography.labelSmall, modifier = headerModifier)
|
||||
MemberLabelRow(memberLabel = null, canEdit = false, onClick = {})
|
||||
|
||||
Text("no label, editable:", style = MaterialTheme.typography.labelSmall, modifier = headerModifier)
|
||||
MemberLabelRow(memberLabel = null, canEdit = true, onClick = {})
|
||||
|
||||
Text("has label, can't edit:", style = MaterialTheme.typography.labelSmall, modifier = headerModifier)
|
||||
MemberLabelRow(memberLabel = MemberLabel("🕷️", "Superhero"), canEdit = false, onClick = {})
|
||||
|
||||
Text("has label, editable:", style = MaterialTheme.typography.labelSmall, modifier = headerModifier)
|
||||
MemberLabelRow(memberLabel = MemberLabel("🕷️", "Superhero"), canEdit = true, onClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,13 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.rx3.rxSingle
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupsInCommonRepository
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelRepository
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import java.util.Optional
|
||||
|
||||
class AboutSheetRepository {
|
||||
|
||||
@@ -25,4 +30,12 @@ class AboutSheetRepository {
|
||||
identityRecord.isPresent && identityRecord.get().verifiedStatus == IdentityTable.VerifiedStatus.VERIFIED
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getMemberLabel(groupId: GroupId.V2): Single<Optional<MemberLabel>> = rxSingle {
|
||||
Optional.ofNullable(MemberLabelRepository.instance.getLabel(groupId, Recipient.self()))
|
||||
}
|
||||
|
||||
fun canEditMemberLabel(groupId: GroupId.V2): Single<Boolean> = rxSingle {
|
||||
MemberLabelRepository.instance.canSetLabel(groupId, Recipient.self())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,20 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import java.util.Optional
|
||||
|
||||
class AboutSheetViewModel(
|
||||
recipientId: RecipientId,
|
||||
repository: AboutSheetRepository = AboutSheetRepository()
|
||||
groupId: GroupId.V2? = null,
|
||||
private val repository: AboutSheetRepository = AboutSheetRepository()
|
||||
) : ViewModel() {
|
||||
|
||||
private val _recipient: MutableState<Optional<Recipient>> = mutableStateOf(Optional.empty())
|
||||
@@ -33,6 +38,14 @@ class AboutSheetViewModel(
|
||||
private val _verified: MutableState<Boolean> = mutableStateOf(false)
|
||||
val verified: State<Boolean> = _verified
|
||||
|
||||
private val _memberLabel: MutableState<MemberLabel?> = mutableStateOf(null)
|
||||
val memberLabel: State<MemberLabel?> = _memberLabel
|
||||
|
||||
private val _canEditMemberLabel: MutableState<Boolean> = mutableStateOf(false)
|
||||
val canEditMemberLabel: State<Boolean> = _canEditMemberLabel
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private val recipientDisposable: Disposable = Recipient
|
||||
.observable(recipientId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
@@ -54,8 +67,29 @@ class AboutSheetViewModel(
|
||||
_verified.value = it
|
||||
}
|
||||
|
||||
init {
|
||||
disposables.addAll(recipientDisposable, groupsInCommonDisposable, verifiedDisposable)
|
||||
|
||||
if (groupId != null && RemoteConfig.sendMemberLabels) {
|
||||
observeMemberLabel(groupId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeMemberLabel(groupId: GroupId.V2) {
|
||||
disposables.add(
|
||||
repository.getMemberLabel(groupId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy { _memberLabel.value = it.orElse(null) }
|
||||
)
|
||||
|
||||
disposables.add(
|
||||
repository.canEditMemberLabel(groupId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy { _canEditMemberLabel.value = it }
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
recipientDisposable.dispose()
|
||||
groupsInCommonDisposable.dispose()
|
||||
disposables.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ class RecipientBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
|
||||
fun show(fragmentManager: FragmentManager, recipientId: RecipientId, groupId: GroupId?) {
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
if (recipient.isSelf) {
|
||||
AboutSheet.create(recipient).show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
AboutSheet.create(recipient, groupId as? GroupId.V2)
|
||||
.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
} else {
|
||||
val args = Bundle()
|
||||
val fragment = RecipientBottomSheetDialogFragment()
|
||||
|
||||
@@ -2683,6 +2683,8 @@
|
||||
<string name="AboutSheet__you">You</string>
|
||||
<!-- Displays the name of a contact. The first placeholder is the name the user has assigned to that contact, the second name is the name the contact assigned to themselves -->
|
||||
<string name="AboutSheet__user_set_display_name_and_profile_name">%1$s (%2$s)</string>
|
||||
<!-- Placeholder text in the group member label row when the user has no label set. -->
|
||||
<string name="AboutSheet__add_group_member_label">Add a member label</string>
|
||||
|
||||
<!-- Title of the screen showing which groups they have in common with another user. -->
|
||||
<plurals name="GroupsInCommon__n_groups_in_common_title">
|
||||
|
||||
Reference in New Issue
Block a user