Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/__mocks__/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const mockedCapabilities: Capabilities = {
'dashboard-event-rooms',
'mutual-calendar-events',
'upcoming-reminders',
'sensitive-conversations',
// Conditional features
'message-expiration',
'reactions',
Expand Down
216 changes: 126 additions & 90 deletions src/components/ConversationSettings/NotificationsSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,102 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import { computed, reactive } from 'vue'

import IconAccount from 'vue-material-design-icons/Account.vue'
import IconVolumeHigh from 'vue-material-design-icons/VolumeHigh.vue'
import IconVolumeOff from 'vue-material-design-icons/VolumeOff.vue'

import { t } from '@nextcloud/l10n'

import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'

import { useStore } from '../../composables/useStore.js'
import { PARTICIPANT } from '../../constants.ts'
import { hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import type { Conversation } from '../../types/index.ts'

const supportImportantConversations = hasTalkFeature('local', 'important-conversations')
const supportSensitiveConversations = hasTalkFeature('local', 'sensitive-conversations')

const notificationLevels = [
{ value: PARTICIPANT.NOTIFY.ALWAYS, icon: IconVolumeHigh, label: t('spreed', 'All messages') },
{ value: PARTICIPANT.NOTIFY.MENTION, icon: IconAccount, label: t('spreed', '@-mentions only') },
{ value: PARTICIPANT.NOTIFY.NEVER, icon: IconVolumeOff, label: t('spreed', 'Off') },
]

const props = defineProps<{
conversation: Conversation,
}>()

const store = useStore()

const showCallNotificationSettings = computed(() => {
return !props.conversation.remoteServer || hasTalkFeature(props.conversation.token, 'federation-v2')
})

const loading = reactive({
level: false,
calls: false,
important: false,
sensitive: false,
})

const notificationLevel = computed(() => props.conversation.notificationLevel.toString())

/**
* Set the notification level for the conversation
* FIXME: should be a computed with type "number", but it doesn't work at Vue 2 TS
*
* @param value The notification level to set.
*/
async function setNotificationLevel(value: string) {
loading.level = true
await store.dispatch('setNotificationLevel', {
token: props.conversation.token,
notificationLevel: +value,
})
loading.level = false
}

const notifyCalls = computed({
get: () => props.conversation.notificationCalls === PARTICIPANT.NOTIFY_CALLS.ON,
set: async (value) => {
loading.calls = true
await store.dispatch('setNotificationCalls', {
token: props.conversation.token,
notificationCalls: value ? PARTICIPANT.NOTIFY_CALLS.ON : PARTICIPANT.NOTIFY_CALLS.OFF,
})
loading.calls = false
},
})

const isImportant = computed({
get: () => props.conversation.isImportant,
set: async (value) => {
loading.important = true
await store.dispatch('toggleImportant', {
token: props.conversation.token,
isImportant: value,
})
loading.important = false
},
})

const isSensitive = computed({
get: () => props.conversation.isSensitive,
set: async (value) => {
loading.sensitive = true
await store.dispatch('toggleSensitive', {
token: props.conversation.token,
isSensitive: value,
})
loading.sensitive = false
},
})
</script>

<template>
<div class="app-settings-subsection">
<h4 class="app-settings-section__subtitle">
Expand All @@ -11,113 +107,53 @@

<NcCheckboxRadioSwitch v-for="level in notificationLevels"
:key="level.value"
v-model="notificationLevel"
:model-value="notificationLevel"
:value="level.value.toString()"
:disabled="loading.level"
name="notification_level"
type="radio"
@update:model-value="setNotificationLevel">
<span class="radio-button">
<component :is="notificationLevelIcon(level.value)" />
<component :is="level.icon" />
{{ level.label }}
</span>
</NcCheckboxRadioSwitch>

<NcCheckboxRadioSwitch v-if="showCallNotificationSettings"
id="notification_calls"
v-model="notifyCalls"
type="switch"
@update:model-value="setNotificationCalls">
:disabled="loading.calls"
type="switch">
{{ t('spreed', 'Notify about calls in this conversation') }}
</NcCheckboxRadioSwitch>
</div>
</template>

<script>
import Account from 'vue-material-design-icons/Account.vue'
import VolumeHigh from 'vue-material-design-icons/VolumeHigh.vue'
import VolumeOff from 'vue-material-design-icons/VolumeOff.vue'

import { t } from '@nextcloud/l10n'

import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'

import { PARTICIPANT } from '../../constants.ts'
import { hasTalkFeature } from '../../services/CapabilitiesManager.ts'

const notificationLevels = [
{ value: PARTICIPANT.NOTIFY.ALWAYS, label: t('spreed', 'All messages') },
{ value: PARTICIPANT.NOTIFY.MENTION, label: t('spreed', '@-mentions only') },
{ value: PARTICIPANT.NOTIFY.NEVER, label: t('spreed', 'Off') },
]

export default {
name: 'NotificationsSettings',

components: {
NcCheckboxRadioSwitch,
},

props: {
conversation: {
type: Object,
required: true,
},
},

setup() {
return {
notificationLevels,
}
},

data() {
return {
notifyCalls: this.conversation.notificationCalls === PARTICIPANT.NOTIFY_CALLS.ON,
notificationLevel: this.conversation.notificationLevel.toString(),
}
},
<NcCheckboxRadioSwitch v-if="supportImportantConversations"
id="important"
v-model="isImportant"
:disabled="loading.important"
aria-describedby="important-hint"
type="switch">
{{ t('spreed', 'Important conversation') }}
</NcCheckboxRadioSwitch>

computed: {
showCallNotificationSettings() {
return !this.conversation.remoteServer || hasTalkFeature(this.conversation.token, 'federation-v2')
}
},
<p id="important-hint" class="app-settings-section__hint">
{{ t('spreed', '"Do not disturb" user status is ignored for important conversations') }}
</p>

<NcCheckboxRadioSwitch v-if="supportSensitiveConversations"
id="sensitive"
v-model="isSensitive"
:disabled="loading.sensitive"
aria-describedby="sensitive-hint"
type="switch">
{{ t('spreed', 'Sensitive conversation') }}
</NcCheckboxRadioSwitch>

methods: {
t,
notificationLevelIcon(value) {
switch (value) {
case PARTICIPANT.NOTIFY.ALWAYS:
return VolumeHigh
case PARTICIPANT.NOTIFY.MENTION:
return Account
case PARTICIPANT.NOTIFY.NEVER:
default:
return VolumeOff
}
},

/**
* Set the notification level for the conversation
*
* @param {number} notificationLevel The notification level to set.
*/
async setNotificationLevel(notificationLevel) {
await this.$store.dispatch('setNotificationLevel', { token: this.conversation.token, notificationLevel })
},

/**
* Set the call notification level for the conversation
*
* @param {boolean} isChecked Whether or not call notifications are enabled
*/
async setNotificationCalls(isChecked) {
const notificationCalls = isChecked ? PARTICIPANT.NOTIFY_CALLS.ON : PARTICIPANT.NOTIFY_CALLS.OFF
await this.$store.dispatch('setNotificationCalls', { token: this.conversation.token, notificationCalls })
},
},
}
</script>
<p id="sensitive-hint" class="app-settings-section__hint">
{{ t('spreed', 'Message preview will be disabled in conversation list and notifications') }}
</p>
</div>
</template>

<style lang="scss" scoped>
.radio-button {
Expand Down
76 changes: 76 additions & 0 deletions src/components/LeftSidebar/ConversationsList/Conversation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,33 @@
{{ t('spreed', 'Notify about calls') }}
</NcActionButton>
</template>

<template v-if="supportImportantConversations || supportSensitiveConversations">
<NcActionSeparator />

<NcActionButton v-if="supportImportantConversations"
type="checkbox"
class="conversation__action--with-subline"
:name="t('spreed', 'Important conversation')"
:model-value="item.isImportant"
@click="toggleImportant(!item.isImportant)">
<template #icon>
<IconMessageAlert :size="16" />
</template>
{{ labelImportantHint }}
</NcActionButton>
<NcActionButton v-if="supportSensitiveConversations"
type="checkbox"
class="conversation__action--with-subline"
:name="t('spreed', 'Sensitive conversation')"
:model-value="item.isSensitive"
@click="toggleSensitive(!item.isSensitive)">
<template #icon>
<IconShieldLock :size="16" />
</template>
{{ t('spreed', 'Hide message text') }}
</NcActionButton>
</template>
</template>
</template>

Expand Down Expand Up @@ -239,7 +266,9 @@ import IconDelete from 'vue-material-design-icons/Delete.vue'
import IconExitToApp from 'vue-material-design-icons/ExitToApp.vue'
import IconEye from 'vue-material-design-icons/Eye.vue'
import IconEyeOff from 'vue-material-design-icons/EyeOff.vue'
import IconMessageAlert from 'vue-material-design-icons/MessageAlert.vue'
import IconPhoneRing from 'vue-material-design-icons/PhoneRing.vue'
import IconShieldLock from 'vue-material-design-icons/ShieldLock.vue'
import IconStar from 'vue-material-design-icons/Star.vue'
import IconVideo from 'vue-material-design-icons/Video.vue'
import IconVolumeHigh from 'vue-material-design-icons/VolumeHigh.vue'
Expand All @@ -263,6 +292,8 @@ import { hasTalkFeature } from '../../../services/CapabilitiesManager.ts'
import { copyConversationLinkToClipboard } from '../../../utils/handleUrl.ts'

const supportsArchive = hasTalkFeature('local', 'archived-conversations-v2')
const supportImportantConversations = hasTalkFeature('local', 'important-conversations')
const supportSensitiveConversations = hasTalkFeature('local', 'sensitive-conversations')

const notificationLevels = [
{ value: PARTICIPANT.NOTIFY.ALWAYS, label: t('spreed', 'All messages') },
Expand All @@ -287,7 +318,9 @@ export default {
IconExitToApp,
IconEye,
IconEyeOff,
IconMessageAlert,
IconPhoneRing,
IconShieldLock,
IconStar,
IconVolumeHigh,
IconVolumeOff,
Expand Down Expand Up @@ -322,6 +355,7 @@ export default {
canDeleteConversation: false,
canLeaveConversation: false,
hasCall: false,
isImportant: false,
isSensitive: false,
}
},
Expand All @@ -345,6 +379,8 @@ export default {
return {
AVATAR,
supportsArchive,
supportImportantConversations,
supportSensitiveConversations,
submenu,
isLeaveDialogOpen,
isDeleteDialogOpen,
Expand Down Expand Up @@ -374,6 +410,10 @@ export default {
: t('spreed', 'Archive conversation')
},

labelImportantHint() {
return t('spreed', 'Ignore "Do not disturb"')
},

dialogLeaveMessage() {
return t('spreed', 'Do you really want to leave "{displayName}"?', this.item, undefined, {
escape: false,
Expand Down Expand Up @@ -529,6 +569,24 @@ export default {
})
},

/**
* Toggle the important flag for the conversation
*
* @param {boolean} isImportant The important flag to set.
*/
async toggleImportant(isImportant) {
await this.$store.dispatch('toggleImportant', { token: this.item.token, isImportant })
},

/**
* Toggle the sensitive flag for the conversation
*
* @param {boolean} isSensitive The sensitive flag to set.
*/
async toggleSensitive(isSensitive) {
await this.$store.dispatch('toggleSensitive', { token: this.item.token, isSensitive })
},

onClick() {
// add as temporary item that will refresh after the joining process is complete
if (this.isSearchResult) {
Expand Down Expand Up @@ -607,6 +665,24 @@ export default {
white-space: nowrap;
}
}

// FIXME: migrate to subline once it's ready
&__action--with-subline {
:deep(.action-button__longtext-wrapper) {
align-self: center;
padding-block: var(--default-grid-baseline);
line-height: 1;
}
:deep(.action-button__name) {
font-weight: 400;
}
:deep(.action-button__longtext) {
padding: 0;
color: var(--color-text-maxcontrast);
font-size: var(--font-size-small);
line-height: var(--default-font-size);
}
}
}

.text {
Expand Down
Loading