diff --git a/docs/capabilities.md b/docs/capabilities.md index 87802a453c7..9de00305b65 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -171,3 +171,4 @@ * `config => conversations => force-passwords` - Whether passwords are enforced for public rooms * `conversation-creation-password` - Whether the endpoints for creating public conversations or making a conversation public support setting a password * `call-notification-state-api` (local) - Whether the endpoints exists for checking if a call notification should be dismissed +* `config => conversations => list-style` - Whether conversation list should appear in certain way diff --git a/docs/constants.md b/docs/constants.md index 91c2bfe421e..65086e0c35f 100644 --- a/docs/constants.md +++ b/docs/constants.md @@ -51,6 +51,10 @@ * `0` Everyone (default) - All participants can mention using `@all` * `1` Moderators - Only moderators can mention using `@all` +### Conversation list style +* `two-lines` Normal (default) - two-line elements (with display name and last message) +* `compact` Compact - one-line elements (with display name) + ## Participants ### Participant types diff --git a/docs/settings.md b/docs/settings.md index e95738b25a3..535d5cf13a7 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -32,6 +32,7 @@ Instead, the server API `POST /ocs/v2.php/apps/provisioning_api/api/v1/config/us | `play_sounds` | | `'yes'` | `'yes'` and `'no'` | | `calls_start_without_media` | `config => call => start-without-media` | `''` falling back to app config with the same name | `'yes'` and `'no'` | | `blur_virtual_background` | `config => call => blur-virtual-background` | `'no'` | `'yes'` and `'no'` | +| `conversations_list_style` | `config => conversations => list-style` | `'two-lines'` | One of the constants from the [constants list](constants.md#conversation-list-style) | ## Set SIP settings diff --git a/lib/Capabilities.php b/lib/Capabilities.php index b8817a629f2..9dce742b2f3 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -153,6 +153,7 @@ class Capabilities implements IPublicCapability { ], 'conversations' => [ 'can-create', + 'list-style', ], 'federation' => [ 'enabled', @@ -229,6 +230,7 @@ public function getCapabilities(): array { 'conversations' => [ 'can-create' => $user instanceof IUser && !$this->talkConfig->isNotAllowedToCreateConversations($user), 'force-passwords' => $this->talkConfig->isPasswordEnforced(), + 'list-style' => $this->talkConfig->getConversationsListStyle($user?->getUID()), ], 'federation' => [ 'enabled' => false, diff --git a/lib/Config.php b/lib/Config.php index 491d898def1..ceb86b005e9 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -695,6 +695,23 @@ public function getBlurVirtualBackground(?string $userId): bool { return false; } + /** + * User setting for conversations list style + * + * @param ?string $userId + * @return string + */ + public function getConversationsListStyle(?string $userId): string { + if ($userId !== null) { + $userSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CONVERSATIONS_LIST_STYLE); + if (!empty($userSetting)) { + return $userSetting; + } + return 'two-lines'; + } + return 'two-lines'; + } + /** * User setting falling back to admin defined app config */ diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 70b353c7b06..75b370b382c 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2539,6 +2539,9 @@ public function getCapabilities(): DataResponse { if (isset($data['config']['call']['blur-virtual-background'])) { $data['config']['call']['blur-virtual-background'] = $this->talkConfig->getBlurVirtualBackground($this->userId); } + if (isset($data['config']['conversations']['list-style'])) { + $data['config']['conversations']['list-style'] = $this->talkConfig->getConversationsListStyle($this->userId); + } if ($response->getHeaders()['X-Nextcloud-Talk-Hash']) { $headers['X-Nextcloud-Talk-Proxy-Hash'] = $response->getHeaders()['X-Nextcloud-Talk-Hash']; diff --git a/lib/Federation/Proxy/TalkV1/ProxyRequest.php b/lib/Federation/Proxy/TalkV1/ProxyRequest.php index 55ce4642c05..5a597dbd979 100644 --- a/lib/Federation/Proxy/TalkV1/ProxyRequest.php +++ b/lib/Federation/Proxy/TalkV1/ProxyRequest.php @@ -44,7 +44,10 @@ public function overwrittenRemoteTalkHash(string $hash): string { ], 'call' => [ 'blur-virtual-background', - ] + ], + 'conversations' => [ + 'list-style', + ], ], ] ])); diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 60da06cf6cb..fd5b7e14b8c 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -362,6 +362,7 @@ * conversations: array{ * can-create: bool, * force-passwords: bool, + * list-style: string, * }, * federation: array{ * enabled: bool, diff --git a/lib/Settings/BeforePreferenceSetEventListener.php b/lib/Settings/BeforePreferenceSetEventListener.php index 1fe87fdb1d5..275aea8c9df 100644 --- a/lib/Settings/BeforePreferenceSetEventListener.php +++ b/lib/Settings/BeforePreferenceSetEventListener.php @@ -76,6 +76,11 @@ public function validatePreference(string $userId, string $key, string|int|null return $valid; } + // "list-style" 'two-lines' / 'compact' + if ($key === UserPreference::CONVERSATIONS_LIST_STYLE) { + return $value === 'two-lines' || $value === 'compact'; + } + return false; } diff --git a/lib/Settings/UserPreference.php b/lib/Settings/UserPreference.php index 6c729ae0f11..2c9ca3d44d0 100644 --- a/lib/Settings/UserPreference.php +++ b/lib/Settings/UserPreference.php @@ -11,6 +11,7 @@ class UserPreference { public const BLUR_VIRTUAL_BACKGROUND = 'blur_virtual_background'; public const CALLS_START_WITHOUT_MEDIA = 'calls_start_without_media'; + public const CONVERSATIONS_LIST_STYLE = 'conversations_list_style'; public const PLAY_SOUNDS = 'play_sounds'; public const TYPING_PRIVACY = 'typing_privacy'; public const READ_STATUS_PRIVACY = 'read_status_privacy'; diff --git a/openapi-administration.json b/openapi-administration.json index 82336b43982..6031d10ff38 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -238,7 +238,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -246,6 +247,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi-backend-recording.json b/openapi-backend-recording.json index d89ba139d1c..8e743a7a3ee 100644 --- a/openapi-backend-recording.json +++ b/openapi-backend-recording.json @@ -171,7 +171,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -179,6 +180,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi-backend-signaling.json b/openapi-backend-signaling.json index 80d66c8f466..6bee639e592 100644 --- a/openapi-backend-signaling.json +++ b/openapi-backend-signaling.json @@ -171,7 +171,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -179,6 +180,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index 317475a72b4..c2b11991a9e 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -214,7 +214,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -222,6 +223,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi-bots.json b/openapi-bots.json index 2989979159d..cff5a51aa6f 100644 --- a/openapi-bots.json +++ b/openapi-bots.json @@ -171,7 +171,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -179,6 +180,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi-federation.json b/openapi-federation.json index 97f1f2ec13c..687b3728e5b 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -214,7 +214,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -222,6 +223,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi-full.json b/openapi-full.json index eccc60c132b..2438ce0c4a3 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -390,7 +390,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -398,6 +399,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/openapi.json b/openapi.json index e00793473bf..fdc3942dcd9 100644 --- a/openapi.json +++ b/openapi.json @@ -331,7 +331,8 @@ "type": "object", "required": [ "can-create", - "force-passwords" + "force-passwords", + "list-style" ], "properties": { "can-create": { @@ -339,6 +340,9 @@ }, "force-passwords": { "type": "boolean" + }, + "list-style": { + "type": "string" } } }, diff --git a/src/__mocks__/capabilities.ts b/src/__mocks__/capabilities.ts index 1646b320e7b..63b31f8d1a2 100644 --- a/src/__mocks__/capabilities.ts +++ b/src/__mocks__/capabilities.ts @@ -134,6 +134,7 @@ export const mockedCapabilities: Capabilities = { conversations: { 'can-create': true, 'force-passwords': false, + 'list-style': 'two-lines', }, federation: { enabled: false, @@ -167,6 +168,7 @@ export const mockedCapabilities: Capabilities = { ], conversations: [ 'can-create', + 'list-style', ], federation: [], previews: [ diff --git a/src/components/SettingsDialog/SettingsDialog.vue b/src/components/SettingsDialog/SettingsDialog.vue index 5512d8cb099..6b992a9b461 100644 --- a/src/components/SettingsDialog/SettingsDialog.vue +++ b/src/components/SettingsDialog/SettingsDialog.vue @@ -56,6 +56,19 @@ @close="showFilePicker = false" /> + + + {{ t('spreed', 'Show my conversations list in compact mode') }} + + { }) describe('media settings dialog', () => { + // FIXME: BrowserStorage.getItem('cachedConversations') is always called whenever capabilitiesManager.ts is imported + const EXTRA_CALLS = 3 it('shows correct stored values for conversations', () => { // Arrange settingsStore.showMediaSettings['token-1'] = true @@ -70,10 +72,7 @@ describe('settingsStore', () => { // Assert expect(results).toEqual([true, false]) - // It's always called at least once : BrowserStorage.getItem('cachedConversations') - // Whenever capabilitiesManager.ts is imported - // +2 - expect(BrowserStorage.getItem).toHaveBeenCalledTimes(2) + expect(BrowserStorage.getItem).toHaveBeenCalledTimes(EXTRA_CALLS) }) it('shows correct values received from BrowserStorage', () => { @@ -90,11 +89,10 @@ describe('settingsStore', () => { // Assert expect(results).toEqual([true, true, false]) - // It's always called at least once : BrowserStorage.getItem('cachedConversations') (+2) - expect(BrowserStorage.getItem).toHaveBeenCalledTimes(5) // 2 + 3 - expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(3, 'showMediaSettings_token-1') - expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(4, 'showMediaSettings_token-2') - expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(5, 'showMediaSettings_token-3') + expect(BrowserStorage.getItem).toHaveBeenCalledTimes(EXTRA_CALLS + 3) + expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(EXTRA_CALLS + 1, 'showMediaSettings_token-1') + expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(EXTRA_CALLS + 2, 'showMediaSettings_token-2') + expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(EXTRA_CALLS + 3, 'showMediaSettings_token-3') }) it('updates values correctly', async () => { @@ -110,8 +108,7 @@ describe('settingsStore', () => { // Assert expect(results).toEqual([false, true]) - // It's always called at least once : BrowserStorage.getItem('cachedConversations') (+2) - expect(BrowserStorage.getItem).toHaveBeenCalledTimes(2) + expect(BrowserStorage.getItem).toHaveBeenCalledTimes(EXTRA_CALLS) expect(BrowserStorage.setItem).toHaveBeenCalledTimes(2) expect(BrowserStorage.setItem).toHaveBeenNthCalledWith(1, 'showMediaSettings_token-1', 'false') expect(BrowserStorage.setItem).toHaveBeenNthCalledWith(2, 'showMediaSettings_token-2', 'true') diff --git a/src/stores/settings.js b/src/stores/settings.js index dc8e9e4ee0b..6d76824d2e1 100644 --- a/src/stores/settings.js +++ b/src/stores/settings.js @@ -16,6 +16,7 @@ import { setTypingStatusPrivacy, setStartWithoutMedia, setBlurVirtualBackground, + setConversationsListStyle, } from '../services/settingsService.ts' /** @@ -42,6 +43,7 @@ export const useSettingsStore = defineStore('settings', { showMediaSettings: {}, startWithoutMedia: getTalkConfig('local', 'call', 'start-without-media'), blurVirtualBackgroundEnabled: getTalkConfig('local', 'call', 'blur-virtual-background'), + conversationsListStyle: getTalkConfig('local', 'conversations', 'list-style'), }), getters: { @@ -114,5 +116,10 @@ export const useSettingsStore = defineStore('settings', { await setStartWithoutMedia(value) this.startWithoutMedia = value }, + + async setConversationsListStyle(value) { + await setConversationsListStyle(value) + this.conversationsListStyle = value + }, }, }) diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index 679689cb062..b4a2e48f0ef 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -247,6 +247,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi-backend-recording.ts b/src/types/openapi/openapi-backend-recording.ts index 4ca4a529557..c5acf1c36ba 100644 --- a/src/types/openapi/openapi-backend-recording.ts +++ b/src/types/openapi/openapi-backend-recording.ts @@ -81,6 +81,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi-backend-signaling.ts b/src/types/openapi/openapi-backend-signaling.ts index a7bd86951ca..510029a4360 100644 --- a/src/types/openapi/openapi-backend-signaling.ts +++ b/src/types/openapi/openapi-backend-signaling.ts @@ -67,6 +67,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index e85a92d06d5..0ce82a08644 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -162,6 +162,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi-bots.ts b/src/types/openapi/openapi-bots.ts index 389f124c257..bbf99a8ae0c 100644 --- a/src/types/openapi/openapi-bots.ts +++ b/src/types/openapi/openapi-bots.ts @@ -85,6 +85,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index ad4031ec8ce..bbe1060cca8 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -193,6 +193,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index eafe8a13943..64bbd06b7de 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -2013,6 +2013,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 6a208fd0f90..714818bfcb5 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1510,6 +1510,7 @@ export type components = { conversations: { "can-create": boolean; "force-passwords": boolean; + "list-style": string; }; federation: { enabled: boolean; diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php index 2669e433e88..35f795b985a 100644 --- a/tests/php/CapabilitiesTest.php +++ b/tests/php/CapabilitiesTest.php @@ -153,6 +153,7 @@ public function testGetCapabilitiesGuest(): void { 'conversations' => [ 'can-create' => false, 'force-passwords' => false, + 'list-style' => 'two-lines', ], 'federation' => [ 'enabled' => false, @@ -286,6 +287,7 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea 'conversations' => [ 'can-create' => $canCreate, 'force-passwords' => false, + 'list-style' => 'two-lines', ], 'federation' => [ 'enabled' => false,