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,