diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2821fcbd04..839a5095dd 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -198,6 +198,7 @@ dependencies {
allLibrariesImpl()
allServicesImpl()
allFeaturesImpl(rootDir, logger)
+ implementation(projects.features.call)
implementation(projects.anvilannotations)
implementation(projects.appnav)
anvil(projects.anvilcodegen)
diff --git a/changelog.d/1300.feature b/changelog.d/1300.feature
new file mode 100644
index 0000000000..bfa40bfc3b
--- /dev/null
+++ b/changelog.d/1300.feature
@@ -0,0 +1 @@
+Integrate Element Call into EX by embedding a call in a WebView.
diff --git a/changelog.d/1332.feature b/changelog.d/1332.feature
new file mode 100644
index 0000000000..2a08ae9d25
--- /dev/null
+++ b/changelog.d/1332.feature
@@ -0,0 +1 @@
+[Rich text editor] Update design
\ No newline at end of file
diff --git a/features/call/build.gradle.kts b/features/call/build.gradle.kts
new file mode 100644
index 0000000000..69046e33b4
--- /dev/null
+++ b/features/call/build.gradle.kts
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id("io.element.android-compose-library")
+ alias(libs.plugins.anvil)
+ alias(libs.plugins.ksp)
+}
+
+android {
+ namespace = "io.element.android.features.call"
+}
+
+dependencies {
+ implementation(projects.libraries.architecture)
+ implementation(projects.libraries.designsystem)
+ implementation(projects.libraries.network)
+ implementation(libs.androidx.webkit)
+ ksp(libs.showkase.processor)
+
+ testImplementation(libs.test.junit)
+ testImplementation(libs.test.truth)
+ testImplementation(libs.test.robolectric)
+}
diff --git a/features/call/src/main/AndroidManifest.xml b/features/call/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..1aed77cd95
--- /dev/null
+++ b/features/call/src/main/AndroidManifest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt
new file mode 100644
index 0000000000..12355290e3
--- /dev/null
+++ b/features/call/src/main/kotlin/io/element/android/features/call/CallForegroundService.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationChannelCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.app.PendingIntentCompat
+import androidx.core.graphics.drawable.IconCompat
+import io.element.android.libraries.designsystem.utils.CommonDrawables
+
+class CallForegroundService : Service() {
+
+ companion object {
+ fun start(context: Context) {
+ val intent = Intent(context, CallForegroundService::class.java)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent)
+ } else {
+ context.startService(intent)
+ }
+ }
+
+ fun stop(context: Context) {
+ val intent = Intent(context, CallForegroundService::class.java)
+ context.stopService(intent)
+ }
+ }
+
+ private lateinit var notificationManagerCompat: NotificationManagerCompat
+
+ override fun onCreate() {
+ super.onCreate()
+
+ notificationManagerCompat = NotificationManagerCompat.from(this)
+
+ val foregroundServiceChannel = NotificationChannelCompat.Builder(
+ "call_foreground_service_channel",
+ NotificationManagerCompat.IMPORTANCE_LOW,
+ ).setName(
+ getString(R.string.call_foreground_service_channel_title_android).ifEmpty { "Ongoing call" }
+ ).build()
+ notificationManagerCompat.createNotificationChannel(foregroundServiceChannel)
+
+ val callActivityIntent = Intent(this, ElementCallActivity::class.java)
+ val pendingIntent = PendingIntentCompat.getActivity(this, 0, callActivityIntent, 0, false)
+ val notification = NotificationCompat.Builder(this, foregroundServiceChannel.id)
+ .setSmallIcon(IconCompat.createWithResource(this, CommonDrawables.ic_notification_small))
+ .setContentTitle(getString(R.string.call_foreground_service_title_android))
+ .setContentText(getString(R.string.call_foreground_service_message_android))
+ .setContentIntent(pendingIntent)
+ .build()
+ startForeground(1, notification)
+ }
+
+ @Suppress("DEPRECATION")
+ override fun onDestroy() {
+ super.onDestroy()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ } else {
+ stopForeground(true)
+ }
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+}
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt
new file mode 100644
index 0000000000..a664e562f3
--- /dev/null
+++ b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call
+
+import android.net.Uri
+import java.net.URLDecoder
+
+object CallIntentDataParser {
+
+ private val validHttpSchemes = sequenceOf("http", "https")
+
+ fun parse(data: String?): String? {
+ val parsedUrl = data?.let { Uri.parse(data) } ?: return null
+ val scheme = parsedUrl.scheme
+ return when {
+ scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> data
+ scheme == "element" && parsedUrl.host == "call" -> {
+ // We use this custom scheme to load arbitrary URLs for other instances of Element Call,
+ // so we can only verify it's an HTTP/HTTPs URL with a non-empty host
+ parsedUrl.getQueryParameter("url")
+ ?.let { URLDecoder.decode(it, "utf-8") }
+ ?.takeIf {
+ val internalUri = Uri.parse(it)
+ internalUri.scheme in validHttpSchemes && !internalUri.host.isNullOrBlank()
+ }
+ }
+ // This should never be possible, but we still need to take into account the possibility
+ else -> null
+ }
+ }
+}
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallScreenView.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallScreenView.kt
new file mode 100644
index 0000000000..08ad687f91
--- /dev/null
+++ b/features/call/src/main/kotlin/io/element/android/features/call/CallScreenView.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call
+
+import android.annotation.SuppressLint
+import android.view.ViewGroup
+import android.webkit.PermissionRequest
+import android.webkit.WebChromeClient
+import android.webkit.WebView
+import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.viewinterop.AndroidView
+import io.element.android.libraries.designsystem.components.button.BackButton
+import io.element.android.libraries.designsystem.preview.DayNightPreviews
+import io.element.android.libraries.designsystem.theme.components.Scaffold
+import io.element.android.libraries.designsystem.theme.components.Text
+import io.element.android.libraries.designsystem.theme.components.TopAppBar
+import io.element.android.libraries.theme.ElementTheme
+
+typealias RequestPermissionCallback = (Array) -> Unit
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CallScreenView(
+ url: String?,
+ userAgent: String,
+ requestPermissions: (Array, RequestPermissionCallback) -> Unit,
+ onClose: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ ElementTheme {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.element_call)) },
+ navigationIcon = {
+ BackButton(
+ imageVector = Icons.Default.Close,
+ onClick = onClose
+ )
+ }
+ )
+ }
+ ) { padding ->
+ CallWebView(
+ modifier = Modifier
+ .padding(padding)
+ .consumeWindowInsets(padding)
+ .fillMaxSize(),
+ url = url,
+ userAgent = userAgent,
+ onPermissionsRequested = { request ->
+ val androidPermissions = mapWebkitPermissions(request.resources)
+ val callback: RequestPermissionCallback = { request.grant(it) }
+ requestPermissions(androidPermissions.toTypedArray(), callback)
+ }
+ )
+ }
+ }
+}
+
+@Composable
+private fun CallWebView(
+ url: String?,
+ userAgent: String,
+ onPermissionsRequested: (PermissionRequest) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val isInpectionMode = LocalInspectionMode.current
+ AndroidView(
+ modifier = modifier,
+ factory = { context ->
+ WebView(context).apply {
+ if (!isInpectionMode) {
+ setup(userAgent, onPermissionsRequested)
+ if (url != null) {
+ loadUrl(url)
+ }
+ }
+ }
+ },
+ update = { webView ->
+ if (!isInpectionMode && url != null) {
+ webView.loadUrl(url)
+ }
+ },
+ onRelease = { webView ->
+ webView.destroy()
+ }
+ )
+}
+
+@SuppressLint("SetJavaScriptEnabled")
+private fun WebView.setup(userAgent: String, onPermissionsRequested: (PermissionRequest) -> Unit) {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+
+ with(settings) {
+ javaScriptEnabled = true
+ allowContentAccess = true
+ allowFileAccess = true
+ domStorageEnabled = true
+ mediaPlaybackRequiresUserGesture = false
+ databaseEnabled = true
+ loadsImagesAutomatically = true
+ userAgentString = userAgent
+ }
+
+ webChromeClient = object : WebChromeClient() {
+ override fun onPermissionRequest(request: PermissionRequest) {
+ onPermissionsRequested(request)
+ }
+ }
+}
+
+@DayNightPreviews
+@Composable
+internal fun CallScreenViewPreview() {
+ ElementTheme {
+ CallScreenView(
+ url = "https://call.element.io/some-actual-call?with=parameters",
+ userAgent = "",
+ requestPermissions = { _, _ -> },
+ onClose = { },
+ )
+ }
+}
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/ElementCallActivity.kt b/features/call/src/main/kotlin/io/element/android/features/call/ElementCallActivity.kt
new file mode 100644
index 0000000000..69ef3963cb
--- /dev/null
+++ b/features/call/src/main/kotlin/io/element/android/features/call/ElementCallActivity.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call
+
+import android.Manifest
+import android.content.Intent
+import android.content.res.Configuration
+import android.media.AudioAttributes
+import android.media.AudioFocusRequest
+import android.media.AudioManager
+import android.os.Build
+import android.os.Bundle
+import android.view.WindowManager
+import android.webkit.PermissionRequest
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.runtime.mutableStateOf
+import io.element.android.features.call.di.CallBindings
+import io.element.android.libraries.architecture.bindings
+import io.element.android.libraries.network.useragent.UserAgentProvider
+import javax.inject.Inject
+
+class ElementCallActivity : ComponentActivity() {
+
+ @Inject lateinit var userAgentProvider: UserAgentProvider
+
+ private lateinit var audioManager: AudioManager
+
+ private var requestPermissionCallback: RequestPermissionCallback? = null
+
+ private var audiofocusRequest: AudioFocusRequest? = null
+ private var audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
+
+ private val requestPermissionsLauncher = registerPermissionResultLauncher()
+
+ private var isDarkMode = false
+ private val urlState = mutableStateOf(null)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ applicationContext.bindings().inject(this)
+
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+
+ urlState.value = intent?.dataString?.let(::parseUrl) ?: run {
+ finish()
+ return
+ }
+
+ if (savedInstanceState == null) {
+ updateUiMode(resources.configuration)
+ }
+
+ audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
+ requestAudioFocus()
+
+ val userAgent = userAgentProvider.provide()
+
+ setContent {
+ CallScreenView(
+ url = urlState.value,
+ userAgent = userAgent,
+ onClose = this::finish,
+ requestPermissions = { permissions, callback ->
+ requestPermissionCallback = callback
+ requestPermissionsLauncher.launch(permissions)
+ }
+ )
+ }
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+
+ updateUiMode(newConfig)
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+
+ val intentUrl = intent?.dataString?.let(::parseUrl)
+ when {
+ // New URL, update it and reload the webview
+ intentUrl != null -> urlState.value = intentUrl
+ // Re-opened the activity but we have no url to load or a cached one, finish the activity
+ intent?.dataString == null && urlState.value == null -> finish()
+ // Coming back from notification, do nothing
+ else -> return
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ CallForegroundService.stop(this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (!isFinishing && !isChangingConfigurations) {
+ CallForegroundService.start(this)
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ releaseAudioFocus()
+ CallForegroundService.stop(this)
+ }
+
+ override fun finish() {
+ // Also remove the task from recents
+ finishAndRemoveTask()
+ }
+
+ private fun parseUrl(url: String?): String? = CallIntentDataParser.parse(url)
+
+ private fun registerPermissionResultLauncher(): ActivityResultLauncher> {
+ return registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ val callback = requestPermissionCallback ?: return@registerForActivityResult
+ val permissionsToGrant = mutableListOf()
+ permissions.forEach { (permission, granted) ->
+ if (granted) {
+ val webKitPermission = when (permission) {
+ Manifest.permission.CAMERA -> PermissionRequest.RESOURCE_VIDEO_CAPTURE
+ Manifest.permission.RECORD_AUDIO -> PermissionRequest.RESOURCE_AUDIO_CAPTURE
+ else -> return@forEach
+ }
+ permissionsToGrant.add(webKitPermission)
+ }
+ }
+ callback(permissionsToGrant.toTypedArray())
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun requestAudioFocus() {
+ val audioAttributes = AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .build()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(audioAttributes)
+ .build()
+ audioManager.requestAudioFocus(request)
+ audiofocusRequest = request
+ } else {
+ val listener = AudioManager.OnAudioFocusChangeListener { }
+ audioManager.requestAudioFocus(
+ listener,
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+ )
+
+ audioFocusChangeListener = listener
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun releaseAudioFocus() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ audiofocusRequest?.let { audioManager.abandonAudioFocusRequest(it) }
+ } else {
+ audioFocusChangeListener?.let { audioManager.abandonAudioFocus(it) }
+ }
+ }
+
+ private fun updateUiMode(configuration: Configuration) {
+ val prevDarkMode = isDarkMode
+ val currentNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_YES
+ isDarkMode = currentNightMode != 0
+ if (prevDarkMode != isDarkMode) {
+ if (isDarkMode) {
+ window.setBackgroundDrawableResource(android.R.drawable.screen_background_dark)
+ } else {
+ window.setBackgroundDrawableResource(android.R.drawable.screen_background_light)
+ }
+ }
+ }
+}
+
+internal fun mapWebkitPermissions(permissions: Array): List {
+ return permissions.mapNotNull { permission ->
+ when (permission) {
+ PermissionRequest.RESOURCE_AUDIO_CAPTURE -> Manifest.permission.RECORD_AUDIO
+ PermissionRequest.RESOURCE_VIDEO_CAPTURE -> Manifest.permission.CAMERA
+ else -> null
+ }
+ }
+}
diff --git a/features/call/src/main/kotlin/io/element/android/features/call/di/CallBindings.kt b/features/call/src/main/kotlin/io/element/android/features/call/di/CallBindings.kt
new file mode 100644
index 0000000000..1e261cc225
--- /dev/null
+++ b/features/call/src/main/kotlin/io/element/android/features/call/di/CallBindings.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call.di
+
+import com.squareup.anvil.annotations.ContributesTo
+import io.element.android.features.call.ElementCallActivity
+import io.element.android.libraries.di.AppScope
+
+@ContributesTo(AppScope::class)
+interface CallBindings {
+ fun inject(callActivity: ElementCallActivity)
+}
diff --git a/features/call/src/main/res/values-fr/translations.xml b/features/call/src/main/res/values-fr/translations.xml
new file mode 100644
index 0000000000..e35473b31b
--- /dev/null
+++ b/features/call/src/main/res/values-fr/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Appel en cours"
+ "Appuyez pour retourner à l\'appel."
+ "☎️ Appel en cours"
+
diff --git a/features/call/src/main/res/values/do_not_translate.xml b/features/call/src/main/res/values/do_not_translate.xml
new file mode 100644
index 0000000000..c1fe10cdfb
--- /dev/null
+++ b/features/call/src/main/res/values/do_not_translate.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ Element Call
+
diff --git a/features/call/src/main/res/values/localazy.xml b/features/call/src/main/res/values/localazy.xml
new file mode 100644
index 0000000000..cfe40526f4
--- /dev/null
+++ b/features/call/src/main/res/values/localazy.xml
@@ -0,0 +1,6 @@
+
+
+ "Ongoing call"
+ "Tap to return to the call"
+ "☎️ Call in progress"
+
diff --git a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt
new file mode 100644
index 0000000000..da41692b40
--- /dev/null
+++ b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import java.net.URLEncoder
+
+@RunWith(RobolectricTestRunner::class)
+class CallIntentDataParserTests {
+
+ @Test
+ fun `a null data returns null`() {
+ val url: String? = null
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+
+ @Test
+ fun `empty data returns null`() {
+ val url = ""
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+
+ @Test
+ fun `invalid data returns null`() {
+ val url = "!"
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+
+ @Test
+ fun `data with no scheme returns null`() {
+ val url = "test"
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+
+ @Test
+ fun `Element Call urls will be returned as is`() {
+ val httpBaseUrl = "http://call.element.io"
+ val httpCallUrl = "http://call.element.io/some-actual-call?with=parameters"
+ val httpsBaseUrl = "https://call.element.io"
+ val httpsCallUrl = "https://call.element.io/some-actual-call?with=parameters"
+ assertThat(CallIntentDataParser.parse(httpBaseUrl)).isEqualTo(httpBaseUrl)
+ assertThat(CallIntentDataParser.parse(httpCallUrl)).isEqualTo(httpCallUrl)
+ assertThat(CallIntentDataParser.parse(httpsBaseUrl)).isEqualTo(httpsBaseUrl)
+ assertThat(CallIntentDataParser.parse(httpsCallUrl)).isEqualTo(httpsCallUrl)
+ }
+
+ @Test
+ fun `HTTP and HTTPS urls that don't come from EC return null`() {
+ val httpBaseUrl = "http://app.element.io"
+ val httpsBaseUrl = "https://app.element.io"
+ val httpInvalidUrl = "http://"
+ val httpsInvalidUrl = "http://"
+ assertThat(CallIntentDataParser.parse(httpBaseUrl)).isNull()
+ assertThat(CallIntentDataParser.parse(httpsBaseUrl)).isNull()
+ assertThat(CallIntentDataParser.parse(httpInvalidUrl)).isNull()
+ assertThat(CallIntentDataParser.parse(httpsInvalidUrl)).isNull()
+ }
+
+ @Test
+ fun `element scheme with call host and url param gets url extracted`() {
+ val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters"
+ val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
+ val url = "element://call?url=$encodedUrl"
+ assertThat(CallIntentDataParser.parse(url)).isEqualTo(embeddedUrl)
+ }
+
+ @Test
+ fun `element scheme with call host and no url param returns null`() {
+ val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters"
+ val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
+ val url = "element://call?no-url=$encodedUrl"
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+
+ @Test
+ fun `element scheme with no call host returns null`() {
+ val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters"
+ val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
+ val url = "element://no-call?url=$encodedUrl"
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+
+ @Test
+ fun `element scheme with no data returns null`() {
+ val url = "element://call?url="
+ assertThat(CallIntentDataParser.parse(url)).isNull()
+ }
+}
diff --git a/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt b/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt
new file mode 100644
index 0000000000..f82e31c068
--- /dev/null
+++ b/features/call/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.call
+
+import android.Manifest
+import android.webkit.PermissionRequest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class MapWebkitPermissionsTest {
+
+ @Test
+ fun `given Webkit's RESOURCE_AUDIO_CAPTURE returns Android's RECORD_AUDIO permission`() {
+ val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE))
+ assertThat(permission).isEqualTo(listOf(Manifest.permission.RECORD_AUDIO))
+ }
+
+ @Test
+ fun `given Webkit's RESOURCE_VIDEO_CAPTURE returns Android's CAMERA permission`() {
+ val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
+ assertThat(permission).isEqualTo(listOf(Manifest.permission.CAMERA))
+ }
+
+ @Test
+ fun `given any other permission, it returns nothing`() {
+ val permission = mapWebkitPermissions(arrayOf(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID))
+ assertThat(permission).isEqualTo(emptyList())
+ }
+
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ebd4f4c888..b164dfbb95 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -91,6 +91,7 @@ androidx_activity_activity = { module = "androidx.activity:activity", version.re
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = "startup" }
androidx_preference = "androidx.preference:preference:1.2.1"
+androidx_webkit = "androidx.webkit:webkit:1.8.0"
androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" }
# Warning: issue on alpha07, make sure this is working when upgrading
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt
index d03bca751a..919c505439 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt
@@ -44,4 +44,6 @@ object VectorIcons {
val Mention = R.drawable.ic_mention
val Mute = R.drawable.ic_mute
val ThreadDecoration = R.drawable.ic_thread_decoration
+ val Plus = R.drawable.ic_plus
+ val Cancel = R.drawable.ic_cancel
}
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt
index 77ca4bd010..e7878f9bed 100644
--- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt
@@ -75,6 +75,16 @@ val SemanticColors.progressIndicatorTrackColor
Color(0x25F4F7FA)
}
+// This color is not present in Semantic color, so put hard-coded value for now
+val SemanticColors.iconSuccessPrimaryBackground
+ get() = if (isLight) {
+ // We want LightDesignTokens.colorGreen300
+ Color(0xffe3f7ed)
+ } else {
+ // We want DarkDesignTokens.colorGreen300
+ Color(0xff002513)
+ }
+
// Temporary color, which is not in the token right now
val SemanticColors.temporaryColorBgSpecial
get() = if (isLight) Color(0xFFE4E8F0) else Color(0xFF3A4048)
@@ -102,6 +112,7 @@ private fun ContentToPreview() {
"messageFromOtherBackground" to ElementTheme.colors.messageFromOtherBackground,
"progressIndicatorTrackColor" to ElementTheme.colors.progressIndicatorTrackColor,
"temporaryColorBgSpecial" to ElementTheme.colors.temporaryColorBgSpecial,
+ "iconSuccessPrimaryBackground" to ElementTheme.colors.iconSuccessPrimaryBackground,
)
)
}
diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonResources.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonResources.kt
new file mode 100644
index 0000000000..adcfd93af8
--- /dev/null
+++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonResources.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.designsystem.utils
+
+import io.element.android.libraries.designsystem.R
+
+typealias CommonDrawables = R.drawable
diff --git a/libraries/designsystem/src/main/res/drawable/ic_bold.xml b/libraries/designsystem/src/main/res/drawable/ic_bold.xml
index c361f85d3d..5a08fee2f3 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_bold.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_bold.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml b/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml
index 72d8324622..103d0b380d 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_bullet_list.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_cancel.xml b/libraries/designsystem/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000000..3e4ee21aee
--- /dev/null
+++ b/libraries/designsystem/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/libraries/designsystem/src/main/res/drawable/ic_code_block.xml b/libraries/designsystem/src/main/res/drawable/ic_code_block.xml
index 6e622f5b27..18279bd8b5 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_code_block.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_code_block.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml b/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml
index 5a0b284223..181f94c012 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_indent_decrease.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml b/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml
index 367686ceac..06a9ede8d5 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_indent_increase.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml b/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml
index c0dc504ed9..c15248f8ea 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_inline_code.xml
@@ -1,15 +1,15 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_italic.xml b/libraries/designsystem/src/main/res/drawable/ic_italic.xml
index f640c109f4..0a389dbf15 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_italic.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_italic.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_link.xml b/libraries/designsystem/src/main/res/drawable/ic_link.xml
index fd69ec4703..c8a37cdda2 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_link.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_link.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_notification_small.xml b/libraries/designsystem/src/main/res/drawable/ic_notification_small.xml
new file mode 100644
index 0000000000..cf84d679cd
--- /dev/null
+++ b/libraries/designsystem/src/main/res/drawable/ic_notification_small.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml b/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml
index f4f5862656..63e7269508 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_numbered_list.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_plus.xml b/libraries/designsystem/src/main/res/drawable/ic_plus.xml
new file mode 100644
index 0000000000..159ed32e1a
--- /dev/null
+++ b/libraries/designsystem/src/main/res/drawable/ic_plus.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/libraries/designsystem/src/main/res/drawable/ic_quote.xml b/libraries/designsystem/src/main/res/drawable/ic_quote.xml
index e17565a6cc..8f4768f818 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_quote.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_quote.xml
@@ -1,18 +1,18 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml b/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml
index d1994f8045..4469c5572d 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_strikethrough.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/designsystem/src/main/res/drawable/ic_underline.xml b/libraries/designsystem/src/main/res/drawable/ic_underline.xml
index 09f92f2104..9da2f2e0b4 100644
--- a/libraries/designsystem/src/main/res/drawable/ic_underline.xml
+++ b/libraries/designsystem/src/main/res/drawable/ic_underline.xml
@@ -1,9 +1,9 @@
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts
index b961146e78..c7e3251ffb 100644
--- a/libraries/push/impl/build.gradle.kts
+++ b/libraries/push/impl/build.gradle.kts
@@ -38,6 +38,7 @@ dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
+ implementation(projects.libraries.designsystem)
implementation(projects.libraries.di)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.network)
diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt
index b359f540f8..105b5789e4 100755
--- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt
+++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt
@@ -25,6 +25,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import io.element.android.libraries.core.meta.BuildMeta
+import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -67,7 +68,7 @@ class NotificationFactory @Inject constructor(
else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId)
}
- val smallIcon = R.drawable.ic_notification
+ val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(roomInfo.shouldBing)
return NotificationCompat.Builder(context, channelId)
@@ -141,7 +142,7 @@ class NotificationFactory @Inject constructor(
inviteNotifiableEvent: InviteNotifiableEvent
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
- val smallIcon = R.drawable.ic_notification
+ val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy)
return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true)
@@ -185,7 +186,7 @@ class NotificationFactory @Inject constructor(
simpleNotifiableEvent: SimpleNotifiableEvent,
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
- val smallIcon = R.drawable.ic_notification
+ val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy)
return NotificationCompat.Builder(context, channelId)
@@ -220,7 +221,7 @@ class NotificationFactory @Inject constructor(
fallbackNotifiableEvent: FallbackNotifiableEvent,
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
- val smallIcon = R.drawable.ic_notification
+ val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(false)
return NotificationCompat.Builder(context, channelId)
@@ -261,7 +262,7 @@ class NotificationFactory @Inject constructor(
lastMessageTimestamp: Long
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
- val smallIcon = R.drawable.ic_notification
+ val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(noisy)
return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true)
@@ -301,7 +302,7 @@ class NotificationFactory @Inject constructor(
return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest())
.setContentTitle(buildMeta.applicationName)
.setContentText(stringProvider.getString(R.string.notification_test_push_notification_content))
- .setSmallIcon(R.drawable.ic_notification)
+ .setSmallIcon(CommonDrawables.ic_notification_small)
.setLargeIcon(getBitmap(R.drawable.element_logo_green))
.setColor(ContextCompat.getColor(context, R.color.notification_accent_color))
.setPriority(NotificationCompat.PRIORITY_MAX)
diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_notification.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_notification.png
deleted file mode 100644
index a86508b71b..0000000000
Binary files a/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_notification.png and /dev/null differ
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
index b7c2b8ea40..d8906a49b0 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
@@ -114,7 +114,7 @@ fun TextComposer(
start = 3.dp,
end = 6.dp,
top = 8.dp,
- bottom = 5.dp,
+ bottom = 4.dp,
)
.fillMaxWidth(),
) {
@@ -137,7 +137,7 @@ fun TextComposer(
) {
Icon(
modifier = Modifier.size(30.dp.applyScaleUp()),
- resourceId = R.drawable.ic_plus, // TODO Replace with design system icon when available
+ resourceId = VectorIcons.Plus,
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
tint = ElementTheme.colors.iconPrimary,
)
@@ -146,7 +146,7 @@ fun TextComposer(
val roundCornerLarge = 28.dp.applyScaleUp()
val roundedCornerSize = remember(state.lineCount, composerMode) {
- if (state.lineCount > 1 || composerMode is MessageComposerMode.Special) {
+ if (composerMode is MessageComposerMode.Special) {
roundCornerSmall
} else {
roundCornerLarge
@@ -156,17 +156,13 @@ fun TextComposer(
targetValue = roundedCornerSize,
animationSpec = tween(
durationMillis = 100,
- )
+ ),
+ label = "roundedCornerSizeAnimation"
)
val roundedCorners = RoundedCornerShape(roundedCornerSizeState.value)
val colors = ElementTheme.colors
val bgColor = colors.bgSubtleSecondary
-
- val borderColor by remember(state.hasFocus, colors) {
- derivedStateOf {
- if (state.hasFocus) colors.borderDisabled else bgColor
- }
- }
+ val borderColor = colors.borderDisabled
Column(
modifier = Modifier
@@ -180,7 +176,7 @@ fun TextComposer(
.fillMaxWidth()
.clip(roundedCorners)
.background(color = bgColor)
- .border(1.dp, borderColor, roundedCorners)
+ .border(0.5.dp, borderColor, roundedCorners)
) {
if (composerMode is MessageComposerMode.Special) {
ComposerModeView(composerMode = composerMode, onResetComposerMode = onResetComposerMode)
@@ -272,7 +268,7 @@ private fun TextInput(
Text(
placeholder,
style = defaultTypography.copy(
- color = ElementTheme.colors.textDisabled,
+ color = ElementTheme.colors.textSecondary,
),
)
}
@@ -280,6 +276,7 @@ private fun TextInput(
RichTextEditor(
state = state,
modifier = Modifier
+ .padding(top = 6.dp, bottom = 6.dp)
.fillMaxWidth(),
style = RichTextEditorDefaults.style(
text = RichTextEditorDefaults.textStyle(
@@ -323,7 +320,7 @@ private fun TextFormatting(
) {
Icon(
modifier = Modifier.size(30.dp.applyScaleUp()),
- resourceId = R.drawable.ic_cancel, // TODO Replace with design system icon when available
+ resourceId = VectorIcons.Cancel,
contentDescription = stringResource(CommonStrings.action_close),
tint = ElementTheme.colors.iconPrimary,
)
@@ -335,8 +332,8 @@ private fun TextFormatting(
.constrainAs(formatting) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
- start.linkTo(close.end, margin = 3.dp)
- end.linkTo(send.start, margin = 20.dp)
+ start.linkTo(close.end, margin = 1.dp)
+ end.linkTo(send.start, margin = 14.dp)
width = fillToConstraints
}
.horizontalScroll(scrollState),
@@ -589,7 +586,7 @@ private fun SendButton(
) {
Icon(
modifier = Modifier
- .height(18.dp.applyScaleUp())
+ .height(24.dp.applyScaleUp())
.align(Alignment.Center),
resourceId = iconId,
contentDescription = contentDescription,
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt
index 7eb1d08293..a3635b28c3 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt
@@ -18,17 +18,26 @@ package io.element.android.libraries.textcomposer.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
+import io.element.android.libraries.designsystem.VectorIcons
+import io.element.android.libraries.designsystem.preview.DayNightPreviews
+import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.text.applyScaleUp
import io.element.android.libraries.designsystem.theme.components.Icon
+import io.element.android.libraries.designsystem.theme.iconSuccessPrimaryBackground
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.theme.compound.generated.SemanticColors
@@ -42,27 +51,65 @@ internal fun FormattingOption(
colors: SemanticColors = ElementTheme.colors,
) {
val backgroundColor = when (state) {
- FormattingOptionState.Selected -> colors.bgActionPrimaryRest
+ FormattingOptionState.Selected -> colors.iconSuccessPrimaryBackground
FormattingOptionState.Default,
FormattingOptionState.Disabled -> Color.Transparent
}
val foregroundColor = when (state) {
- FormattingOptionState.Selected -> colors.iconOnSolidPrimary
- FormattingOptionState.Default -> colors.iconPrimary
+ FormattingOptionState.Selected -> colors.iconSuccessPrimary
+ FormattingOptionState.Default -> colors.iconSecondary
FormattingOptionState.Disabled -> colors.iconDisabled
}
Box(
modifier = modifier
- .clickable { onClick() }
- .size(44.dp.applyScaleUp())
- .background(backgroundColor, shape = RoundedCornerShape(4.dp.applyScaleUp()))
+ .clickable(
+ onClick = onClick,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(
+ bounded = false,
+ radius = 20.dp,
+ ),
+ )
+ .size(48.dp.applyScaleUp())
) {
- Icon(
- modifier = Modifier.align(Alignment.Center),
- imageVector = imageVector,
- contentDescription = contentDescription,
- tint = foregroundColor,
+ Box(
+ modifier = Modifier
+ .size(36.dp.applyScaleUp())
+ .align(Alignment.Center)
+ .background(backgroundColor, shape = RoundedCornerShape(8.dp.applyScaleUp()))
+ ) {
+ Icon(
+ modifier = Modifier.align(Alignment.Center),
+ imageVector = imageVector,
+ contentDescription = contentDescription,
+ tint = foregroundColor,
+ )
+ }
+ }
+}
+
+@DayNightPreviews
+@Composable
+internal fun FormattingButtonPreview() = ElementPreview {
+ Row {
+ FormattingOption(
+ state = FormattingOptionState.Default,
+ onClick = { },
+ imageVector = ImageVector.vectorResource(VectorIcons.Bold),
+ contentDescription = "",
+ )
+ FormattingOption(
+ state = FormattingOptionState.Selected,
+ onClick = { },
+ imageVector = ImageVector.vectorResource(VectorIcons.Italic),
+ contentDescription = "",
+ )
+ FormattingOption(
+ state = FormattingOptionState.Disabled,
+ onClick = { },
+ imageVector = ImageVector.vectorResource(VectorIcons.Underline),
+ contentDescription = "",
)
}
}
diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_cancel.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_cancel.xml
deleted file mode 100644
index 5c27ba82d9..0000000000
--- a/libraries/textcomposer/impl/src/main/res/drawable/ic_cancel.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_plus.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_plus.xml
deleted file mode 100644
index ead38721dc..0000000000
--- a/libraries/textcomposer/impl/src/main/res/drawable/ic_plus.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml
index bf346b3a01..2ed6e6e53e 100644
--- a/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml
+++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_send.xml
@@ -1,9 +1,25 @@
+
+
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:pathData="M21.829,13.085 L6.259,20.867c-1.049,0.525 -2.141,-0.601 -1.628,-1.627 0,0 1.93,-3.897 2.461,-4.918 0.531,-1.021 1.138,-1.197 6.781,-1.927 0.209,-0.027 0.38,-0.185 0.38,-0.395 0,-0.21 -0.171,-0.368 -0.38,-0.395C8.23,10.876 7.622,10.699 7.091,9.678c-0.531,-1.02 -2.461,-4.918 -2.461,-4.918 -0.513,-1.026 0.579,-2.152 1.628,-1.627L21.829,10.915c0.894,0.446 0.894,1.723 0,2.17z"
+ android:fillColor="#ffffff"/>
diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_tick.xml b/libraries/textcomposer/impl/src/main/res/drawable/ic_tick.xml
index cf1d71a56f..dd7863bab8 100644
--- a/libraries/textcomposer/impl/src/main/res/drawable/ic_tick.xml
+++ b/libraries/textcomposer/impl/src/main/res/drawable/ic_tick.xml
@@ -1,9 +1,9 @@
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml
index 0ff43bceb7..271b614dc4 100644
--- a/libraries/ui-strings/src/main/res/values-cs/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml
@@ -195,9 +195,9 @@
"Další nastavení"
"Halsové a video hovory"
"Neshoda konfigurace"
- "Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností.
+ "Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností.
-Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní.
+Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní.
Pokud budete pokračovat, některá nastavení se mohou změnit."
"Přímé zprávy"
diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml
index 67b738cc21..4978cdb68c 100644
--- a/libraries/ui-strings/src/main/res/values-fr/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml
@@ -67,9 +67,6 @@
"Prendre une photo"
"Afficher la source"
"Oui"
- "Appel en cours"
- "Appuyez pour retourner à l\'appel."
- "☎️ Appel en cours"
"À propos"
"Politique d\'utilisation acceptable"
"Paramètres avancés"
diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml
index 798515fa6a..d37e28dc7a 100644
--- a/libraries/ui-strings/src/main/res/values-ru/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml
@@ -194,9 +194,9 @@
"Дополнительные параметры"
"Аудио и видео звонки"
"Несоответствие конфигурации"
- "Мы упростили настройки уведомлений, чтобы упростить поиск опций.
+ "Мы упростили настройки уведомлений, чтобы упростить поиск опций.
-Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны.
+Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны.
Если вы продолжите, некоторые настройки могут быть изменены."
"Прямые чаты"
diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml
index 2355e4b603..0cd0b7df0e 100644
--- a/libraries/ui-strings/src/main/res/values/localazy.xml
+++ b/libraries/ui-strings/src/main/res/values/localazy.xml
@@ -67,9 +67,6 @@
"Take photo"
"View Source"
"Yes"
- "Ongoing call"
- "Tap to return to the call"
- "☎️ Call in progress"
"About"
"Acceptable use policy"
"Advanced settings"
@@ -207,6 +204,11 @@
"This is the beginning of this conversation."
"New"
"Share analytics data"
+ "Display name"
+ "Your display name"
+ "An unknown error was encountered and the information couldn\'t be changed."
+ "Unable to update profile"
+ "Updating profile…"
"Failed selecting media, please try again."
"Failed processing media to upload, please try again."
"Failed uploading media, please try again."
diff --git a/tests/uitests/build.gradle.kts b/tests/uitests/build.gradle.kts
index ffa3e50aff..9556d653bf 100644
--- a/tests/uitests/build.gradle.kts
+++ b/tests/uitests/build.gradle.kts
@@ -52,6 +52,7 @@ dependencies {
// `testOptions { unitTests.isIncludeAndroidResources = true }` in the app build.gradle.kts file
// implementation(projects.app)
implementation(projects.appnav)
+ implementation(projects.features.call)
allLibrariesImpl()
allServicesImpl()
allFeaturesImpl(rootDir, logger)
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.call_null_CallScreenView-D-0_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.call_null_CallScreenView-D-0_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..9fdfd38cee
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.call_null_CallScreenView-D-0_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:26df292602cdf27ddd42b3b4c1dcc3fc7ae41e207af48d76c7b65bd66babf649
+size 10561
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.call_null_CallScreenView-N-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.call_null_CallScreenView-N-0_1_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..e524d75c96
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.call_null_CallScreenView-N-0_1_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f28b5214727111f39cef9a1d625469f30481fe2cbbe02df0efc53807a968d210
+size 9787
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png
index 67b7525ddc..c5fc8a7df4 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewDark_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6bf428927e9a3493284d9fa7ba307b51315ed52b317a60ac345e87ba70849d0f
-size 10523
+oid sha256:b237f45b9db8f1a7ac6dde6a2f513d28d697f6251e4a384a4b343daa417d3f15
+size 10441
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png
index 8007500f81..f88e231886 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_MessageComposerViewLight_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6eced1d7173c2d0100351f5bb9cd14c649a826b2e355e426b9c6d4add90015d9
-size 10833
+oid sha256:933d3530f5267dd015c1a80536c92428b4b5018a76952f5911782acc2d82b53a
+size 10813
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png
index a80b2ce36d..e01bc2261a 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2b55dbcb731dfc40a1e100e8caf26802f759b4bcd186dbbc4432644db08f8316
-size 52141
+oid sha256:ed4ba249dab6dc1e906bfb70886b531c4bf76e3acae8b8162496afe3b5a0f572
+size 52085
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png
index 03f50fe82f..bf8898fadc 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d5953ae4bb021c39157b4118ea628ca9bb2277f9672749cc6313f0b378d31cb8
-size 53312
+oid sha256:9cfd42ac1e4931d4fb01ac8cecffa635adc92d67f1107a4c4051d66eeaab954a
+size 53336
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png
index 8d7e2dcf95..78bffbdb73 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c329165fa341a2130b43a448b8bba465f1e5f458136efa6da80f2cdeee9d6caa
-size 52369
+oid sha256:7eac088a0d14a2b1b85f541d0df5a1d38bdca3f7612560770bbfcffe0c1b2389
+size 52442
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png
index 35d5965ff3..7a830c8020 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:dc662d180d1766706f249f1585fb100f5933206b65d4644fae65abc6a121a633
-size 51331
+oid sha256:9c65887eeeff0a815136ddfe9dbfae86d60e6abf4f409192d7950aad01c0bb0c
+size 51098
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png
index b2e621fc71..04a7cbfe71 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewDark_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:db0107e648e2acfc253d83f5e3b2f3e41de1abc9bdc4922dfd923ad4e50b8f5f
-size 49700
+oid sha256:eddb7e59898d7aaf644f958ae0b05578b7e1ef3802e352d7fb78651c2de97cf6
+size 49638
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png
index 65cdf717bb..a960aed41d 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3eb1479510a8cd04d133265e33ea15115158ac55c93387d1dafa93a3a3a858f0
-size 53620
+oid sha256:53bafb3688148dbeb3859aef1b8a9bf086340fc799f3d825ed246eba0cdda0f6
+size 53463
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png
index a5959f6d60..87c5b51154 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c0d5272b14b347f18b2d67d5fae6eaf507b8d6f7c9d361317bb6b8a062e63ceb
-size 54899
+oid sha256:22854689cac05b6ff1ec58a2b086c751516938161b844694b9300641de5e6ca9
+size 54871
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png
index ec09599aca..6d8c175733 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2b4e5e9d920ea2a7733453e030bb365c9e2af7263de8fe216ab56088e0861fa5
-size 53997
+oid sha256:ea6bebc608259c37a2b88c14e6b247696a2f9967152bcf2eb0c2a5d70f540ee4
+size 53944
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png
index 8212494315..7071e778c7 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:dff46ed537c81602be0dea8782e37bb78669ee798ec4d422c23ff29cbbdebc17
-size 55927
+oid sha256:885000bd08b05ca49eddbae17aa672fbf10a3acdcaf64d71fef77888229ae5c5
+size 55813
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png
index cbdda6b794..1bac3210f9 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_null_MessagesViewLight_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7e162503c27609f01e05bb2c634c2d3123abf6e786ecfe763c902d400ead050f
-size 51283
+oid sha256:f1fd5d62a43ccfca8e157371068dcf125a72cd22920df447689b50464d8d42e6
+size 51137
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png
index b1c6c996d8..014374fdb1 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d57924194a017902912825d4f43fcd290db11430d4d64abd89abe39b9e2ffc27
-size 48726
+oid sha256:e6625c102a88cc1096fea5425d044a96ef6e403317fa944cef8ec1ce5cd63a23
+size 52632
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png
index eaf130c209..c23cc8a0e2 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0eaa20c7c00c18cf08135aca70407d55ea9fc33a7f262aee3fa6ced272f7ab16
-size 48465
+oid sha256:dc7900cfc6a214acef16f642a2273088abef88f99cbf68b1aa99e43daab837ca
+size 52528
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_FormattingButton-D-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_FormattingButton-D-4_5_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..3c7c46d2da
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_FormattingButton-D-4_5_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d4a44b93e0791e4fdf7d9bd11e0d9a0fff44a271530d01ff0e6aa1e8789742ef
+size 6297
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_FormattingButton-N-4_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_FormattingButton-N-4_6_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..36ec55a50b
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_FormattingButton-N-4_6_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2236ffed5666f85ce3b1093437dc8c0d72ec630f6c707394d99ff4b19d7c3f47
+size 6158
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png
index 9d906ebe35..3fa65b5075 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-D-2_3_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:46683edccf9c7686a07d7098c4387f296556ad2678381aad35efe2787cf6ad0a
-size 14173
+oid sha256:192e86517a6add5eb3d58d9a3d4633afc455666fff4472f112c605dc441f24d1
+size 13843
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png
index 4b9d77d2b1..c11445bc3f 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerEdit-N-2_4_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6485de8235af05a9815a558b1af03b51e8c4b30ad0372aea0aa9fb2303525c9b
-size 13286
+oid sha256:ea17e3d7f26d1b3f233ee9db5875dcb363b22de12ed07d32693a2c0aa727fbb6
+size 13001
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png
index f9dc3f8b9a..4569825f69 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-D-1_2_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1f065f63fb37fa9a5441cfaeca04e867e4e8f6f744dd02a83c459fc128ee353d
-size 38384
+oid sha256:31013d59ca9755e316bfcb0f5bccf5206446223b1e4f5ce79060a442bb256292
+size 40936
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png
index ca3a85e52c..cc6aa467f3 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerFormatting-N-1_3_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a5ded1cbc536c544b0cb188ba0a333a541157eaf1772cd0e06c7338307549adc
-size 36483
+oid sha256:569a5c4b667e259ca895b41a1248234c339d16d7776a827ded5e151472fe2c5d
+size 38601
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png
index 0d9c311dcc..c244d01fb3 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-D-3_4_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:69e98a3521ae6545700e395bff211b06bb02095b01d9c254b3e0d2d0b8b88d26
-size 80484
+oid sha256:1b8da842d61ebcdede9a5337890279d740288c1a07b1b42505457ab65408cc76
+size 84240
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png
index ef035d6cd5..a50bba4ac8 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerReply-N-3_5_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:27dee9eaae6736a128107c9fd93048a677287a40d24c334223243b3c55f1cf69
-size 77686
+oid sha256:feae2409d9887cd752d2cff4be059a566c071094955b824faa72555e69269d91
+size 80648
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png
index 06fbfc6c7f..de4ca7c908 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-D-0_1_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:267f482ceddeadea4c3b580d6783fed1048015fa05a11e385dd547e60f72e1e8
-size 44206
+oid sha256:ed1aa73f94dac6839f91729bcea59b4b3b14b885fa32a90335faf483e9f886f8
+size 45176
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png
index a237bcce80..f5a53317ca 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerSimple-N-0_2_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9f5e7ab52469b406509964d71f12475df546973f1382944ce7f2e1a437e4f880
-size 41536
+oid sha256:76f54b7c7ab5c54b53a0fa9e410db942a2bce35fc0552569707ba2f3928ebd3b
+size 42306
diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml
index db1a20701c..ce49e50a7a 100644
--- a/tools/lint/lint.xml
+++ b/tools/lint/lint.xml
@@ -48,6 +48,8 @@
+
+
diff --git a/tools/localazy/config.json b/tools/localazy/config.json
index b92e02f670..c2cb5cef3e 100644
--- a/tools/localazy/config.json
+++ b/tools/localazy/config.json
@@ -128,6 +128,12 @@
"includeRegex": [
"screen_create_poll_.*"
]
+ },
+ {
+ "name": ":features:call",
+ "includeRegex": [
+ "call_.*"
+ ]
}
]
}