Add support for announcement groups.

This commit is contained in:
Greyson Parrelli
2021-07-23 16:22:08 -04:00
parent 1a56924a56
commit 25234496bf
74 changed files with 1109 additions and 208 deletions

View File

@@ -93,6 +93,10 @@ public class InputPanel extends LinearLayout
private @Nullable Listener listener;
private boolean emojiVisible;
private boolean hideForGroupState;
private boolean hideForBlockedState;
private boolean hideForSearch;
private ConversationStickerSuggestionAdapter stickerSuggestionAdapter;
public InputPanel(Context context) {
@@ -317,6 +321,21 @@ public class InputPanel extends LinearLayout
}
}
public void setHideForGroupState(boolean hideForGroupState) {
this.hideForGroupState = hideForGroupState;
updateVisibility();
}
public void setHideForBlockedState(boolean hideForBlockedState) {
this.hideForBlockedState = hideForBlockedState;
updateVisibility();
}
public void setHideForSearch(boolean hideForSearch) {
this.hideForSearch = hideForSearch;
updateVisibility();
}
@Override
public void onRecordPermissionRequired() {
if (listener != null) listener.onRecorderPermissionRequired();
@@ -495,6 +514,14 @@ public class InputPanel extends LinearLayout
buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start();
}
private void updateVisibility() {
if (hideForGroupState || hideForBlockedState || hideForSearch) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
}
}
public interface Listener extends VoiceNoteDraftView.Listener {
void onRecorderStarted();
void onRecorderLocked();

View File

@@ -15,6 +15,7 @@ sealed class ConversationSettingsEvent {
val groupId: GroupId,
val selectionWarning: Int,
val selectionLimit: Int,
val isAnnouncementGroup: Boolean,
val groupMembersWithoutSelf: List<RecipientId>
) : ConversationSettingsEvent()

View File

@@ -317,7 +317,15 @@ class ConversationSettingsFragment : DSLSettingsFragment(
ButtonStripPreference.Model(
state = state.buttonStripState,
onVideoClick = {
CommunicationActions.startVideoCall(requireActivity(), state.recipient)
if (state.recipient.isPushV2Group && state.requireGroupSettingsState().isAnnouncementGroup && !state.requireGroupSettingsState().isSelfAdmin) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.ConversationActivity_cant_start_group_call)
.setMessage(R.string.ConversationActivity_only_admins_of_this_group_can_start_a_call)
.setPositiveButton(android.R.string.ok) { d, _ -> d.dismiss() }
.show()
} else {
CommunicationActions.startVideoCall(requireActivity(), state.recipient)
}
},
onAudioClick = {
CommunicationActions.startVoiceCall(requireActivity(), state.recipient)
@@ -666,6 +674,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
ContactsCursorLoader.DisplayMode.FLAG_PUSH,
addMembersToGroup.selectionWarning,
addMembersToGroup.selectionLimit,
addMembersToGroup.isAnnouncementGroup,
addMembersToGroup.groupMembersWithoutSelf
),
REQUEST_CODE_ADD_MEMBERS_TO_GROUP

View File

@@ -13,11 +13,13 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.database.MediaDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.groups.GroupProtoUtil
import org.thoughtcrime.securesms.groups.LiveGroup
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -26,6 +28,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.libsignal.util.guava.Preconditions
import java.io.IOException
import java.util.concurrent.TimeUnit
private val TAG = Log.tag(ConversationSettingsRepository::class.java)
@@ -130,9 +133,9 @@ class ConversationSettingsRepository(
members.addAll(groupRecord.members)
members.addAll(pendingMembers)
GroupCapacityResult(Recipient.self().id, members, FeatureFlags.groupLimits())
GroupCapacityResult(Recipient.self().id, members, FeatureFlags.groupLimits(), groupRecord.isAnnouncementGroup)
} else {
GroupCapacityResult(Recipient.self().id, groupRecord.members, FeatureFlags.groupLimits())
GroupCapacityResult(Recipient.self().id, groupRecord.members, FeatureFlags.groupLimits(), false)
}
)
}
@@ -140,6 +143,25 @@ class ConversationSettingsRepository(
fun addMembers(groupId: GroupId, selected: List<RecipientId>, consumer: (GroupAddMembersResult) -> Unit) {
SignalExecutors.BOUNDED.execute {
val record: GroupDatabase.GroupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId).get()
if (record.isAnnouncementGroup) {
val needsResolve = selected
.map { Recipient.resolved(it) }
.filter { it.announcementGroupCapability != Recipient.Capability.SUPPORTED && !it.isSelf }
.map { it.id }
.toSet()
ApplicationDependencies.getJobManager().runSynchronously(RetrieveProfileJob(needsResolve), TimeUnit.SECONDS.toMillis(10))
val updatedWithCapabilities = needsResolve.map { Recipient.resolved(it) }
if (updatedWithCapabilities.any { it.announcementGroupCapability != Recipient.Capability.SUPPORTED }) {
consumer(GroupAddMembersResult.Failure(GroupChangeFailureReason.NOT_ANNOUNCEMENT_CAPABLE))
return@execute
}
}
consumer(
try {
val groupActionResult = GroupManager.addMembers(context, groupId.requirePush(), selected)

View File

@@ -75,7 +75,8 @@ sealed class SpecificSettingsState {
private val groupDescriptionLoaded: Boolean = false,
val groupLinkEnabled: Boolean = false,
val membershipCountDescription: String = "",
val legacyGroupState: LegacyGroupPreference.State = LegacyGroupPreference.State.NONE
val legacyGroupState: LegacyGroupPreference.State = LegacyGroupPreference.State.NONE,
val isAnnouncementGroup: Boolean = false
) : SpecificSettingsState() {
override val isLoaded: Boolean = groupTitleLoaded && groupDescriptionLoaded

View File

@@ -322,6 +322,14 @@ sealed class ConversationSettingsViewModel(
)
}
store.update(liveGroup.isAnnouncementGroup) { announcementGroup, state ->
state.copy(
specificSettingsState = state.requireGroupSettingsState().copy(
isAnnouncementGroup = announcementGroup
)
)
}
val isMessageRequestAccepted: LiveData<Boolean> = LiveDataUtil.mapAsync(liveGroup.groupRecipient) { r -> repository.isMessageRequestAccepted(r) }
val descriptionState: LiveData<DescriptionState> = LiveDataUtil.combineLatest(liveGroup.description, isMessageRequestAccepted, ::DescriptionState)
@@ -386,11 +394,13 @@ sealed class ConversationSettingsViewModel(
override fun onAddToGroup() {
repository.getGroupCapacity(groupId) { capacityResult ->
if (capacityResult.getRemainingCapacity() > 0) {
internalEvents.postValue(
ConversationSettingsEvent.AddMembersToGroup(
groupId,
capacityResult.getSelectionWarning(),
capacityResult.getSelectionLimit(),
capacityResult.isAnnouncementGroup,
capacityResult.getMembersWithoutSelf()
)
)

View File

@@ -7,7 +7,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class GroupCapacityResult(
private val selfId: RecipientId,
private val members: List<RecipientId>,
private val selectionLimits: SelectionLimits
private val selectionLimits: SelectionLimits,
val isAnnouncementGroup: Boolean
) {
fun getMembers(): List<RecipientId?> {
return members

View File

@@ -72,6 +72,20 @@ class PermissionsSettingsFragment : DSLSettingsFragment(
viewModel.setNonAdminCanEditGroupInfo(it == 1)
}
)
if (state.announcementGroupPermissionEnabled) {
radioListPref(
title = DSLSettingsText.from(R.string.PermissionsSettingsFragment__send_messages),
isEnabled = state.selfCanEditSettings,
listItems = permissionsOptions,
dialogTitle = DSLSettingsText.from(R.string.PermissionsSettingsFragment__who_can_send_messages),
selected = getSelected(!state.announcementGroup),
confirmAction = true,
onSelected = {
viewModel.setAnnouncementGroup(it == 0)
}
)
}
}
}

View File

@@ -42,4 +42,18 @@ class PermissionsSettingsRepository(private val context: Context) {
}
}
}
fun applyAnnouncementGroupChange(groupId: GroupId, isAnnouncementGroup: Boolean, error: GroupChangeErrorCallback) {
SignalExecutors.UNBOUNDED.execute {
try {
GroupManager.applyAnnouncementGroupChange(context, groupId.requireV2(), isAnnouncementGroup)
} catch (e: GroupChangeException) {
Log.w(TAG, e)
error.onError(GroupChangeFailureReason.fromException(e))
} catch (e: IOException) {
Log.w(TAG, e)
error.onError(GroupChangeFailureReason.fromException(e))
}
}
}
}

View File

@@ -3,5 +3,7 @@ package org.thoughtcrime.securesms.components.settings.conversation.permissions
data class PermissionsSettingsState(
val selfCanEditSettings: Boolean = false,
val nonAdminCanAddMembers: Boolean = false,
val nonAdminCanEditGroupInfo: Boolean = false
val nonAdminCanEditGroupInfo: Boolean = false,
val announcementGroupPermissionEnabled: Boolean = false,
val announcementGroup: Boolean = false
)

View File

@@ -6,6 +6,8 @@ import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.groups.GroupAccessControl
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.LiveGroup
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.SingleLiveEvent
import org.thoughtcrime.securesms.util.livedata.Store
@@ -33,6 +35,18 @@ class PermissionsSettingsViewModel(
store.update(liveGroup.attributesAccessControl) { attributesAccessControl, state ->
state.copy(nonAdminCanEditGroupInfo = attributesAccessControl == GroupAccessControl.ALL_MEMBERS)
}
store.update(liveGroup.isAnnouncementGroup) { isAnnouncementGroup, state ->
state.copy(
announcementGroup = isAnnouncementGroup,
announcementGroupPermissionEnabled = state.announcementGroupPermissionEnabled || isAnnouncementGroup
)
}
store.update(liveGroup.groupRecipient) { groupRecipient, state ->
val allHaveCapability = groupRecipient.participants.map { it.announcementGroupCapability }.all { it == Recipient.Capability.SUPPORTED }
state.copy(announcementGroupPermissionEnabled = (FeatureFlags.announcementGroups() && allHaveCapability) || state.announcementGroup)
}
}
fun setNonAdminCanAddMembers(nonAdminCanAddMembers: Boolean) {
@@ -47,6 +61,12 @@ class PermissionsSettingsViewModel(
}
}
fun setAnnouncementGroup(announcementGroup: Boolean) {
repository.applyAnnouncementGroupChange(groupId, announcementGroup) { reason ->
internalEvents.postValue(PermissionsSettingsEvents.GroupChangeError(reason))
}
}
private fun Boolean.asGroupAccessControl(): GroupAccessControl {
return if (this) {
GroupAccessControl.ALL_MEMBERS