Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ import com.nextcloud.talk.location.LocationPickerActivity
import com.nextcloud.talk.models.ExternalSignalingServer
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationModel.Companion.checkIfVoiceRoom
import com.nextcloud.talk.models.domain.ConversationModel.Companion.isChannel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.models.json.conversations.ConversationEnums
Expand Down Expand Up @@ -1937,7 +1938,11 @@ class ChatActivity :
!isChatThread() &&
!ConversationUtils.isNoteToSelfConversation(conversation) &&
!isReadOnlyConversation() &&
!shouldShowLobby()
!shouldShowLobby() &&
!(
conversation.isChannel() &&
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.ANNOUNCEMENT_PRESET)
)

private fun isSearchAvailable(capabilitiesReady: Boolean, conversation: ConversationModel?): Boolean =
capabilitiesReady &&
Expand Down Expand Up @@ -2209,8 +2214,12 @@ class ChatActivity :
}

private fun checkShowMessageInputView() {
val permissions = participantPermissionsFlow.value
val isChannel = currentConversation.isChannel() &&
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.ANNOUNCEMENT_PRESET)

if (isReadOnlyConversation() ||
participantPermissionsFlow.value?.hasChatPermission() == false
(permissions?.hasChatPermission() == false && (!isChannel || permissions.hasReactPermission() == false))
) {
binding.fragmentContainerActivityChat.visibility = View.GONE
} else {
Expand Down Expand Up @@ -2783,7 +2792,7 @@ class ChatActivity :
)
}

public override fun onDestroy() {
override fun onDestroy() {
super.onDestroy()
logConversationInfos("onDestroy")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.databinding.FragmentMessageInputBinding
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.models.domain.ConversationModel.Companion.isChannel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatUtils
import com.nextcloud.talk.models.json.mention.Mention
Expand Down Expand Up @@ -185,6 +186,12 @@ class MessageInputFragment : Fragment() {
val threadTitle = text.toString()
chatActivity.chatViewModel.messageDraft.threadTitle = threadTitle
}

viewLifecycleOwner.lifecycleScope.launch {
chatActivity.participantPermissionsFlow.collect {
handleButtonsVisibility()
}
}
}

@Suppress("LongMethod")
Expand Down Expand Up @@ -331,7 +338,6 @@ class MessageInputFragment : Fragment() {

override fun onSuccess(result: Drawable) {
binding.fragmentCallStarted.callAuthorChip.chipIcon = result

}
})
.build()
Expand Down Expand Up @@ -718,6 +724,16 @@ class MessageInputFragment : Fragment() {
visibility = if (isVisible) View.VISIBLE else View.GONE
}

val isReactionOnly = isReactionOnlyMode()
if (isReactionOnly) {
binding.fragmentMessageInputView.setVisible(false)
binding.reactionOnlyAffordance.setVisible(true)
return
} else {
binding.fragmentMessageInputView.setVisible(true)
binding.reactionOnlyAffordance.setVisible(false)
}

val isEditModeActive = binding.fragmentEditView.editMessageView.isVisible
val isThreadCreateModeActive = binding.fragmentCreateThreadView.createThreadView.isVisible
val inputContainsText = binding.fragmentMessageInputView.messageInput.text.isNotEmpty()
Expand Down Expand Up @@ -1288,6 +1304,14 @@ class MessageInputFragment : Fragment() {
return jsonId != null
}

private fun isReactionOnlyMode(): Boolean {
val conversation = chatActivity.currentConversation
val permissions = chatActivity.participantPermissionsFlow.value
val isChannel = conversation.isChannel() &&
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.ANNOUNCEMENT_PRESET)
return isChannel && permissions?.hasChatPermission() == false && permissions.hasReactPermission() == true
}

companion object {
fun newInstance() = MessageInputFragment()
private val TAG: String = MessageInputFragment::class.java.simpleName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.nextcloud.talk.conversationlist.DirectShareHelper
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.conversationlist.data.network.OfflineFirstConversationsRepository
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel.Companion.FOLLOWED_THREADS_EXIST
import com.nextcloud.talk.dagger.modules.ApplicationScope
import com.nextcloud.talk.data.database.mappers.toDomainModel
import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.data.user.model.User
Expand All @@ -45,6 +46,7 @@ import com.nextcloud.talk.logger.Logger
import com.nextcloud.talk.messagesearch.MessageSearchHelper
import com.nextcloud.talk.models.MessageDraft
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationModel.Companion.isChannel
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.domain.SearchMessageEntry
Expand Down Expand Up @@ -80,7 +82,6 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import com.nextcloud.talk.dagger.modules.ApplicationScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -510,8 +511,10 @@ class ChatViewModel @AssistedInject constructor(

@OptIn(ExperimentalCoroutinesApi::class)
private val messagesFlow: Flow<List<ChatMessage>> =
conversationAndUserFlow
.flatMapLatest { (conversation, user) ->
combine(conversationAndUserFlow, spreedCapabilities) { (conversation, user), capabilities ->
Triple(conversation, user, capabilities)
}
.flatMapLatest { (conversation, user, capabilities) ->
combine(chatMode, contextAnchorMessageId) { mode, anchorMessageId ->
mode to anchorMessageId
}
Expand All @@ -530,10 +533,10 @@ class ChatViewModel @AssistedInject constructor(
.distinctUntilChanged()
.mapToChatMessages(user.userId!!)
}
}
.map { messages ->
messages.let(::handleSystemMessages)
.let(::handleThreadMessages)
.map { messages ->
handleSystemMessages(messages, conversation, capabilities)
.let(::handleThreadMessages)
}
}
.distinctUntilChanged()

Expand Down Expand Up @@ -1340,7 +1343,15 @@ class ChatViewModel @AssistedInject constructor(
}
Log.d(TAG, "fetchNewMessagesWithRetry: no new messages after $POST_UPLOAD_FETCH_MAX_ATTEMPTS attempts")
}
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
private fun handleSystemMessages(
chatMessageList: List<ChatMessage>,
conversation: ConversationModel?,
capabilities: SpreedCapability?
): List<ChatMessage> {
if (conversation.isChannel() && hasSpreedFeatureCapability(capabilities, SpreedFeatures.ANNOUNCEMENT_PRESET)) {
return chatMessageList.filter { !it.isSystemMessage }
}

fun shouldRemoveMessage(currentMessage: MutableMap.MutableEntry<Int, ChatMessage>): Boolean =
isInfoMessageAboutDeletion(currentMessage) ||
isReactionsMessage(currentMessage) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Campaign
import androidx.compose.material.icons.outlined.Chat
import androidx.compose.material.icons.outlined.Podcasts
import androidx.compose.material.icons.outlined.VolumeUp
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
Expand Down Expand Up @@ -381,30 +383,62 @@ fun ConversationNameAndDescription(conversationCreationViewModel: ConversationCr
@Composable
fun ConversationPresets(conversationCreationViewModel: ConversationCreationViewModel) {
val preset by conversationCreationViewModel.conversationPreset
val hasAnnouncementPreset = CapabilitiesUtil.hasSpreedFeatureCapability(
conversationCreationViewModel.currentUser.capabilities?.spreedCapability,
SpreedFeatures.ANNOUNCEMENT_PRESET
)

Row(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
SelectableCard(
modifier = Modifier.weight(1f),
title = stringResource(R.string.default_room),
subtitle = stringResource(R.string.default_room_preset),
icon = Icons.Outlined.Chat,
isSelected = preset == "default",
onClick = { conversationCreationViewModel.conversationPreset.value = "default" }
)
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
SelectableCard(
modifier = Modifier.weight(1f),
title = stringResource(R.string.default_room),
subtitle = stringResource(R.string.default_room_preset),
icon = Icons.Outlined.Chat,
isSelected = preset == "default",
onClick = { conversationCreationViewModel.conversationPreset.value = "default" }
)

SelectableCard(
modifier = Modifier.weight(1f),
title = stringResource(R.string.voice_room),
subtitle = stringResource(R.string.voice_room_preset),
icon = Icons.Outlined.VolumeUp,
isSelected = preset == "voiceroom",
onClick = { conversationCreationViewModel.conversationPreset.value = "voiceroom" }
)
SelectableCard(
modifier = Modifier.weight(1f),
title = stringResource(R.string.voice_room),
subtitle = stringResource(R.string.voice_room_preset),
icon = Icons.Outlined.VolumeUp,
isSelected = preset == "voiceroom",
onClick = { conversationCreationViewModel.conversationPreset.value = "voiceroom" }
)
}

if (hasAnnouncementPreset) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
SelectableCard(
modifier = Modifier.weight(1f),
title = stringResource(R.string.nc_channel),
subtitle = stringResource(R.string.nc_channel_description),
icon = Icons.Outlined.Podcasts,
isSelected = preset == "channel",
onClick = { conversationCreationViewModel.conversationPreset.value = "channel" }
)

SelectableCard(
modifier = Modifier.weight(1f),
title = stringResource(R.string.nc_announcement),
subtitle = stringResource(R.string.nc_announcement_description),
icon = Icons.Outlined.Campaign,
isSelected = preset == "announcement",
onClick = { conversationCreationViewModel.conversationPreset.value = "announcement" }
)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.nextcloud.talk.conversationinfo.Participants
import com.nextcloud.talk.conversationinfo.model.ParticipantModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationModel.Companion.isChannel
import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
Expand Down Expand Up @@ -417,6 +418,10 @@ class ConversationInfoViewModel @Inject constructor(
val showMessageExpiration = isModerator &&
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)

val isChannel = conversationModel.isChannel() &&
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.ANNOUNCEMENT_PRESET)
val showParticipants = if (isChannel) isModerator else true

val credentials = ApiUtils.getCredentials(user.username, user.token) ?: ""

_uiState.update { state ->
Expand Down Expand Up @@ -466,12 +471,15 @@ class ConversationInfoViewModel @Inject constructor(
isArchived = isArchived,
canLeave = canLeave,
canDelete = canDelete,
showParticipants = showParticipants,
showClearHistory = showClearHistory,
showEditButton = showEditButton
)
}

loadParticipants(user, token)
if (showParticipants) {
loadParticipants(user, token)
}
}

@Suppress("Detekt.TooGenericExceptionCaught")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ data class ConversationModel(
it and ConversationEnums.Preset.VOICE_ROOM.ordinal != 0
} ?: false

fun ConversationModel?.isChannel(): Boolean =
this?.attributes?.let {
it and ConversationEnums.ATTRIBUTE_IS_CHANNEL != 0
} ?: false

fun ConversationModel?.isAnnouncement(): Boolean =
this?.attributes?.let {
it and ConversationEnums.ATTRIBUTE_IS_ANNOUNCEMENT != 0
} ?: false

@Suppress("LongMethod")
fun mapToConversationModel(conversation: Conversation, user: User): ConversationModel =
ConversationModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ class ConversationEnums {
DEFAULT,
VOICE_ROOM
}

companion object {
const val ATTRIBUTE_IS_CHANNEL = 8
const val ATTRIBUTE_IS_ANNOUNCEMENT = 16
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ enum class SpreedFeatures(val value: String) {
PINNED_MESSAGES("pinned-messages"),
SCHEDULED_MESSAGES("scheduled-messages"),
REACT_PERMISSION("react-permission"),
CONVERSATION_PRESETS("conversation-presets")
CONVERSATION_PRESETS("conversation-presets"),
ANNOUNCEMENT_PRESET("announcement-preset")
}

@Suppress("TooManyFunctions")
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/res/layout/fragment_message_input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@
android:animateLayoutChanges="true"
android:inputType="textLongMessage|textAutoComplete"
android:maxLength="1000" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/reactionOnlyAffordance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/standard_margin"
android:gravity="center"
android:text="@string/nc_channel_only_moderators_can_post"
android:textColor="@color/medium_emphasis_text"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ How to translate with transifex:

<string name="nc_search">Search</string>
<string name="nc_search_clear">Clear search</string>
<string name="nc_channel_only_moderators_can_post">Only moderators can post in this channel</string>
<string name="nc_channel">Channel</string>
<string name="nc_channel_description">Only moderators can post, everybody else can only react</string>
<string name="nc_announcement">Announcement</string>
<string name="nc_announcement_description">A channel where non-moderators can not leave</string>

<string name="nc_certificate_dialog_title">Check out the certificate</string>
<string name="nc_certificate_dialog_text">Do you trust the until now unknown SSL certificate, issued by %1$s for %2$s, valid from %3$s to %4$s?</string>
Expand Down
Loading