diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index 9d601bff14..5c27da044e 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -14,6 +14,7 @@
gplay
hmac
homeserver
+ jitsi
ktlint
linkified
linkify
diff --git a/build.gradle b/build.gradle
index 12c5181ea4..3a9bc2a991 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,6 +52,10 @@ allprojects {
}
}
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
+ // Jitsi repo
+ maven {
+ url "https://github.com/vector-im/jitsi_libre_maven/raw/master/releases"
+ }
google()
jcenter()
}
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index a96e0690dc..57a9dfaa87 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -132,8 +132,13 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version"
- implementation 'com.squareup.okhttp3:okhttp:4.2.2'
- implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
+
+
+ implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1"))
+ implementation 'com.squareup.okhttp3:okhttp'
+ implementation 'com.squareup.okhttp3:logging-interceptor'
+ implementation("com.squareup.okhttp3:okhttp-urlconnection")
+
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
@@ -175,7 +180,9 @@ dependencies {
// Web RTC
// TODO meant for development purposes only. See http://webrtc.github.io/webrtc-org/native-code/android/
- implementation 'org.webrtc:google-webrtc:1.0.+'
+ // implementation 'org.webrtc:google-webrtc:1.0.+'
+ // WebRTC
+ implementation('com.facebook.react:react-native-webrtc:1.69.2-jitsi-2062090@aar')
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
index 71961d02d3..5fff658a56 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
@@ -77,7 +77,11 @@ internal object NetworkModule {
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
- .addNetworkInterceptor(stethoInterceptor)
+ .apply {
+ if (BuildConfig.DEBUG) {
+ addNetworkInterceptor(stethoInterceptor)
+ }
+ }
.addInterceptor(timeoutInterceptor)
.addInterceptor(userAgentInterceptor)
.addInterceptor(httpLoggingInterceptor)
diff --git a/vector/build.gradle b/vector/build.gradle
index a1b0c006cf..c5d95f8d38 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -406,7 +406,10 @@ dependencies {
implementation 'com.github.BillCarsonFr:JsonViewer:0.5'
// TODO meant for development purposes only
- implementation 'org.webrtc:google-webrtc:1.0.+'
+// implementation 'org.webrtc:google-webrtc:1.0.+'
+ // WebRTC
+// implementation('com.facebook.react:react-native-webrtc:1.69.2-jitsi-2062090@aar')
+ implementation('org.jitsi.react:jitsi-meet-sdk:2.2.2') { transitive = true }
// QR-code
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
diff --git a/vector/proguard-rules.pro b/vector/proguard-rules.pro
index 7fcfce61b8..46fae8338c 100644
--- a/vector/proguard-rules.pro
+++ b/vector/proguard-rules.pro
@@ -24,3 +24,44 @@
## print all the rules in a file
# -printconfiguration ../proguard_files/full-r8-config.txt
+
+# WebRTC
+
+-keep class org.webrtc.** { *; }
+-dontwarn org.chromium.build.BuildHooksAndroid
+
+# Jitsi (else callbacks are not called)
+
+-keep class org.jitsi.meet.** { *; }
+-keep class org.jitsi.meet.sdk.** { *; }
+
+# React Native
+
+# Keep our interfaces so they can be used by other ProGuard rules.
+# See http://sourceforge.net/p/proguard/bugs/466/
+-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
+-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
+-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
+
+# Do not strip any method/class that is annotated with @DoNotStrip
+-keep @com.facebook.proguard.annotations.DoNotStrip class *
+-keep @com.facebook.common.internal.DoNotStrip class *
+-keepclassmembers class * {
+ @com.facebook.proguard.annotations.DoNotStrip *;
+ @com.facebook.common.internal.DoNotStrip *;
+}
+
+-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
+ void set*(***);
+ *** get*();
+}
+
+-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
+-keep class * extends com.facebook.react.bridge.NativeModule { *; }
+-keepclassmembers,includedescriptorclasses class * { native ; }
+-keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; }
+-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; }
+-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; }
+
+-dontwarn com.facebook.react.**
+-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
\ No newline at end of file
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 16fc952419..95baf304d0 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -202,6 +202,9 @@
android:name="im.vector.app.features.attachments.preview.AttachmentsPreviewActivity"
android:theme="@style/AppTheme.AttachmentsPreview" />
+
diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
index 265c0ec4fc..d337ec7977 100644
--- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
@@ -27,6 +27,7 @@ import im.vector.app.core.preference.UserAvatarPreference
import im.vector.app.features.MainActivity
import im.vector.app.features.call.CallControlsBottomSheet
import im.vector.app.features.call.VectorCallActivity
+import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
@@ -140,6 +141,7 @@ interface ScreenComponent {
fun inject(activity: WidgetActivity)
fun inject(activity: VectorCallActivity)
fun inject(activity: VectorAttachmentViewerActivity)
+ fun inject(activity: VectorJitsiActivity)
/* ==========================================================================================
* BottomSheets
diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt
index 69f0a693cb..d080cf90ed 100644
--- a/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericButtonItem.kt
@@ -57,7 +57,7 @@ abstract class GenericButtonItem : VectorEpoxyModel()
holder.button.icon = null
}
- itemClickAction?.let { holder.view.setOnClickListener(it) }
+ itemClickAction?.let { holder.button.setOnClickListener(it) }
}
class Holder : VectorEpoxyHolder() {
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt b/vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt
new file mode 100644
index 0000000000..d9870fac8b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020 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 im.vector.app.core.ui.views
+
+import android.content.Context
+import android.text.SpannableString
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.util.AttributeSet
+import android.view.View
+import android.widget.RelativeLayout
+import android.widget.TextView
+import im.vector.app.R
+import im.vector.app.core.utils.tappableMatchingText
+import im.vector.app.features.themes.ThemeUtils
+import org.matrix.android.sdk.api.session.widgets.model.Widget
+
+class ActiveConferenceView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
+
+ interface Callback {
+ fun onTapJoinAudio(jitsiWidget: Widget)
+ fun onTapJoinVideo(jitsiWidget: Widget)
+ }
+
+ var callback: Callback? = null
+ var jitsiWidget: Widget? = null
+
+ init {
+ setupView()
+ }
+
+ private fun setupView() {
+ inflate(context, R.layout.view_active_conference_view, this)
+ setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
+
+ // "voice" and "video" texts are underlined and clickable
+ val voiceString = context.getString(R.string.ongoing_conference_call_voice)
+ val videoString = context.getString(R.string.ongoing_conference_call_video)
+
+ val fullMessage = context.getString(R.string.ongoing_conference_call, voiceString, videoString)
+
+ val styledText = SpannableString(fullMessage)
+ styledText.tappableMatchingText(voiceString, object : ClickableSpan() {
+ override fun onClick(widget: View) {
+ jitsiWidget?.let {
+ callback?.onTapJoinAudio(it)
+ }
+ }
+ })
+ styledText.tappableMatchingText(videoString, object : ClickableSpan() {
+ override fun onClick(widget: View) {
+ jitsiWidget?.let {
+ callback?.onTapJoinVideo(it)
+ }
+ }
+ })
+
+ findViewById(R.id.activeConferenceInfo).apply {
+ text = styledText
+ movementMethod = LinkMovementMethod.getInstance()
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
new file mode 100644
index 0000000000..e5f031fbef
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2020 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 im.vector.app.features.call.conference
+
+import android.net.Uri
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.app.core.platform.VectorViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.features.call.WebRtcPeerConnectionManager
+import org.jitsi.meet.sdk.JitsiMeetUserInfo
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.widgets.model.WidgetType
+import org.matrix.android.sdk.api.util.toMatrixItem
+import org.matrix.android.sdk.rx.asObservable
+import java.net.URL
+
+sealed class JitsiCallViewActions : VectorViewModelAction
+
+sealed class JitsiCallViewEvents : VectorViewEvents
+
+class JitsiCallViewModel @AssistedInject constructor(
+ @Assisted initialState: JitsiCallViewState,
+ @Assisted val args: VectorJitsiActivity.Args,
+ val session: Session,
+ val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
+) : VectorViewModel(initialState) {
+
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(initialState: JitsiCallViewState, args: VectorJitsiActivity.Args): JitsiCallViewModel
+ }
+
+ init {
+ val me = session.getUser(session.myUserId)?.toMatrixItem()
+ val userInfo = JitsiMeetUserInfo().apply {
+ displayName = me?.displayName
+ avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) }
+ }
+ val roomName = session.getRoomSummary(args.roomId)?.displayName
+
+ setState {
+ copy(userInfo = userInfo)
+ }
+
+ session.widgetService().getRoomWidgetsLive(args.roomId, QueryStringValue.Equals(args.widgetId), WidgetType.Jitsi.values())
+ .asObservable()
+ .distinctUntilChanged()
+ .subscribe {
+ val jitsiWidget = it.firstOrNull()
+ if (jitsiWidget != null) {
+ val uri = Uri.parse(jitsiWidget.computedUrl)
+ val confId = uri.getQueryParameter("confId")
+ val ppt = jitsiWidget.computedUrl?.let { JitsiWidgetProperties(it) }
+ setState {
+ copy(
+ widget = Success(jitsiWidget),
+ jitsiUrl = "https://${ppt?.domain}",
+ confId = confId ?: "",
+ subject = roomName ?: ""
+ )
+ }
+ } else {
+ setState {
+ copy(
+ widget = Fail(IllegalArgumentException("Widget not found"))
+ )
+ }
+ }
+ }.disposeOnClear()
+ }
+
+ override fun handle(action: JitsiCallViewActions) {
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ const val ENABLE_VIDEO_OPTION = "ENABLE_VIDEO_OPTION"
+
+ @JvmStatic
+ override fun create(viewModelContext: ViewModelContext, state: JitsiCallViewState): JitsiCallViewModel? {
+ val callActivity: VectorJitsiActivity = viewModelContext.activity()
+ val callArgs: VectorJitsiActivity.Args = viewModelContext.args()
+ return callActivity.viewModelFactory.create(state, callArgs)
+ }
+
+ override fun initialState(viewModelContext: ViewModelContext): JitsiCallViewState? {
+ val args: VectorJitsiActivity.Args = viewModelContext.args()
+// val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
+
+ return JitsiCallViewState(
+ roomId = args.roomId,
+ widgetId = args.widgetId,
+ enableVideo = args.enableVideo
+ )
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt
new file mode 100644
index 0000000000..72b87e7f1b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 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 im.vector.app.features.call.conference
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+import org.jitsi.meet.sdk.JitsiMeetUserInfo
+import org.matrix.android.sdk.api.session.widgets.model.Widget
+
+data class JitsiCallViewState(
+ val roomId: String = "",
+ val widgetId: String = "",
+ val enableVideo: Boolean = true,
+ val jitsiUrl: String = "",
+ val subject: String = "",
+ val confId: String = "",
+ val userInfo: JitsiMeetUserInfo = JitsiMeetUserInfo(),
+ val widget: Async = Uninitialized
+) : MvRxState
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiWidgetProperties.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiWidgetProperties.kt
new file mode 100644
index 0000000000..c8d8f1f27f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiWidgetProperties.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 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 im.vector.app.features.call.conference
+
+import android.net.Uri
+
+class JitsiWidgetProperties(private val uriString: String) {
+ val domain: String by lazy { configs["conferenceDomain"] ?: DEFAULT_JITSI_DOMAIN }
+ val displayName: String? by lazy { configs["displayName"] }
+ val avatarUrl: String? by lazy { configs["avatarUrl"] }
+
+ private val configString: String? by lazy { Uri.parse(uriString).fragment }
+
+ private val configs: Map by lazy {
+ configString?.split("&")
+ ?.map { it.split("=") }
+ ?.map { (key, value) -> key to value }
+ ?.toMap()
+ ?: mapOf()
+ }
+}
+
+private const val DEFAULT_JITSI_DOMAIN = "jitsi.riot.im"
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
new file mode 100644
index 0000000000..eda284bce6
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2020 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 im.vector.app.features.call.conference
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcelable
+import android.widget.FrameLayout
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.MvRx
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.viewModel
+import com.facebook.react.modules.core.PermissionListener
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.platform.VectorBaseActivity
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.activity_jitsi.*
+import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
+import org.jitsi.meet.sdk.JitsiMeetActivityInterface
+import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
+import org.jitsi.meet.sdk.JitsiMeetView
+import org.jitsi.meet.sdk.JitsiMeetViewListener
+import org.matrix.android.sdk.api.extensions.tryThis
+import java.net.URL
+import javax.inject.Inject
+
+class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, JitsiMeetViewListener {
+
+ @Parcelize
+ data class Args(
+ val roomId: String,
+ val widgetId: String,
+ val enableVideo: Boolean
+ ) : Parcelable
+
+ override fun getLayoutRes() = R.layout.activity_jitsi
+
+ @Inject lateinit var viewModelFactory: JitsiCallViewModel.Factory
+
+ var jitsiMeetView: JitsiMeetView? = null
+
+ private val jitsiViewModel: JitsiCallViewModel by viewModel()
+
+ override fun injectWith(injector: ScreenComponent) {
+ super.injectWith(injector)
+ injector.inject(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ jitsiViewModel.subscribe(this) {
+ renderState(it)
+ }
+ }
+
+ override fun initUiAndData() {
+ super.initUiAndData()
+ jitsiMeetView = JitsiMeetView(this)
+ val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
+ jitsi_layout.addView(jitsiMeetView, params)
+ jitsiMeetView?.listener = this
+ }
+
+ private fun renderState(viewState: JitsiCallViewState) {
+ when (viewState.widget) {
+ is Fail -> finish()
+ is Success -> {
+// val widget = viewState.widget.invoke()
+ configureJitsiView(viewState)
+ }
+ }
+ }
+
+ private fun configureJitsiView(viewState: JitsiCallViewState) {
+ val jitsiMeetConferenceOptions = JitsiMeetConferenceOptions.Builder()
+ .setVideoMuted(!viewState.enableVideo)
+ .setUserInfo(viewState.userInfo)
+ .apply {
+ tryThis { URL(viewState.jitsiUrl) }?.let {
+ setServerURL(it)
+ }
+ }
+ // https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
+ .setFeatureFlag("chat.enabled", false)
+ .setFeatureFlag("invite.enabled", false)
+ .setFeatureFlag("add-people.enabled", false)
+ .setFeatureFlag("video-share.enabled", false)
+ .setRoom(viewState.confId)
+ .setSubject(viewState.subject)
+ .build()
+ jitsiMeetView?.join(jitsiMeetConferenceOptions)
+ }
+
+ override fun onPause() {
+ JitsiMeetActivityDelegate.onHostPause(this)
+ super.onPause()
+ }
+
+ override fun onResume() {
+ JitsiMeetActivityDelegate.onHostResume(this)
+ super.onResume()
+ }
+
+ override fun onBackPressed() {
+ JitsiMeetActivityDelegate.onBackPressed()
+ super.onBackPressed()
+ }
+
+ override fun onDestroy() {
+ JitsiMeetActivityDelegate.onHostDestroy(this)
+ super.onDestroy()
+ }
+
+// override fun onUserLeaveHint() {
+// super.onUserLeaveHint()
+// jitsiMeetView?.enterPictureInPicture()
+// }
+
+ override fun onNewIntent(intent: Intent?) {
+ JitsiMeetActivityDelegate.onNewIntent(intent)
+ super.onNewIntent(intent)
+ }
+
+ override fun requestPermissions(permissions: Array?, requestCode: Int, listener: PermissionListener?) {
+ JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener)
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ }
+
+ override fun onConferenceTerminated(p0: MutableMap?) {
+ finish()
+ }
+
+ override fun onConferenceJoined(p0: MutableMap?) {
+ }
+
+ override fun onConferenceWillJoin(p0: MutableMap?) {
+ }
+
+ companion object {
+ fun newIntent(context: Context, roomId: String, widgetId: String, enableVideo: Boolean): Intent {
+ return Intent(context, VectorJitsiActivity::class.java).apply {
+ putExtra(MvRx.KEY_ARG, Args(roomId, widgetId, enableVideo))
+ addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
index 0134fc310f..f815407816 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
@@ -80,4 +80,5 @@ sealed class RoomDetailAction : VectorViewModelAction {
object SelectStickerAttachment : RoomDetailAction()
object OpenIntegrationManager: RoomDetailAction()
+ object ManageIntegrations: RoomDetailAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 424731fdb0..c4514f6aaf 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -29,8 +29,11 @@ import android.text.Spannable
import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.Menu
+import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
@@ -77,6 +80,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.ui.views.ActiveCallView
import im.vector.app.core.ui.views.ActiveCallViewHolder
+import im.vector.app.core.ui.views.ActiveConferenceView
import im.vector.app.core.ui.views.JumpToReadMarkerView
import im.vector.app.core.ui.views.NotificationAreaView
import im.vector.app.core.utils.Debouncer
@@ -110,6 +114,7 @@ import im.vector.app.features.attachments.toGroupedContentAttachmentData
import im.vector.app.features.call.SharedActiveCallViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.WebRtcPeerConnectionManager
+import im.vector.app.features.call.conference.JitsiCallViewModel
import im.vector.app.features.command.Command
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.util.toImageRes
@@ -129,7 +134,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
-import im.vector.app.features.home.room.detail.widget.RoomWidgetsBannerView
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.html.EventHtmlRenderer
@@ -183,6 +187,7 @@ import kotlinx.android.synthetic.main.merge_composer_layout.view.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser
+import org.matrix.android.sdk.api.session.widgets.model.Widget
import timber.log.Timber
import java.io.File
import java.net.URL
@@ -217,7 +222,7 @@ class RoomDetailFragment @Inject constructor(
JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback,
AttachmentsHelper.Callback,
- RoomWidgetsBannerView.Callback,
+// RoomWidgetsBannerView.Callback,
ActiveCallView.Callback {
companion object {
@@ -292,7 +297,7 @@ class RoomDetailFragment @Inject constructor(
setupJumpToReadMarkerView()
setupActiveCallView()
setupJumpToBottomView()
- setupWidgetsBannerView()
+ setupConfBannerView()
roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
@@ -350,6 +355,7 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it)
+ RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
}.exhaustive
}
}
@@ -363,8 +369,16 @@ class RoomDetailFragment @Inject constructor(
)
}
- private fun setupWidgetsBannerView() {
- roomWidgetsBannerView.callback = this
+ private fun setupConfBannerView() {
+ activeConferenceView.callback = object : ActiveConferenceView.Callback {
+ override fun onTapJoinAudio(jitsiWidget: Widget) {
+ navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to false))
+ }
+
+ override fun onTapJoinVideo(jitsiWidget: Widget) {
+ navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to true))
+ }
+ }
}
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) {
@@ -529,10 +543,40 @@ class RoomDetailFragment @Inject constructor(
}
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ menu.findItem(R.id.open_matrix_apps).let { menuItem ->
+ menuItem.actionView.setOnClickListener {
+ onOptionsItemSelected(menuItem)
+ }
+ }
+ }
+
override fun onPrepareOptionsMenu(menu: Menu) {
menu.forEach {
it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
}
+ withState(roomDetailViewModel) { state ->
+ val findItem = menu.findItem(R.id.open_matrix_apps)
+ val widgetsCount = state.activeRoomWidgets.invoke()?.size
+ if (widgetsCount ?: 0 > 0) {
+ val actionView = findItem.actionView
+ actionView
+ .findViewById(R.id.action_view_icon_image)
+ .setColorFilter(ContextCompat.getColor(requireContext(), R.color.riotx_accent))
+ actionView.findViewById(R.id.cart_badge).isVisible = true
+ actionView.findViewById(R.id.cart_badge).text = "$widgetsCount"
+ findItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
+ } else {
+ // icon should be default color no badge
+ val actionView = findItem.actionView
+ actionView
+ .findViewById(R.id.action_view_icon_image)
+ .setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.riotx_text_secondary))
+ actionView.findViewById(R.id.cart_badge).isVisible = false
+ findItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ }
+ }
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -549,7 +593,7 @@ class RoomDetailFragment @Inject constructor(
true
}
R.id.open_matrix_apps -> {
- roomDetailViewModel.handle(RoomDetailAction.OpenIntegrationManager)
+ roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations)
true
}
R.id.voice_call,
@@ -873,7 +917,19 @@ class RoomDetailFragment @Inject constructor(
renderToolbar(summary, state.typingMessage)
val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) {
- roomWidgetsBannerView.render(state.activeRoomWidgets())
+ // We only display banner for 'live' widgets
+ val activeConf = // for now only jitsi?
+ state.activeRoomWidgets()?.firstOrNull {
+ // for now only jitsi?
+ it.type == WidgetType.Jitsi
+ }
+
+ if (activeConf == null) {
+ activeConferenceView.isVisible = false
+ } else {
+ activeConferenceView.isVisible = true
+ activeConferenceView.jitsiWidget = activeConf
+ }
jumpToBottomView.count = summary.notificationCount
jumpToBottomView.drawBadge = summary.hasUnreadMessages
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
@@ -1662,7 +1718,7 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.SendMessage(formattedContact, false))
}
- override fun onViewWidgetsClicked() {
+ private fun onViewWidgetsClicked() {
RoomWidgetsBottomSheet.newInstance()
.show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET")
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
index 0772da498e..89b42f4fc9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
@@ -66,6 +66,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
object OpenIntegrationManager: RoomDetailViewEvents()
+ object OpenActiveWidgetBottomSheet: RoomDetailViewEvents()
object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 5d1111d67d..de63fb04e1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -269,6 +269,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
is RoomDetailAction.StartCall -> handleStartCall(action)
is RoomDetailAction.EndCall -> handleEndCall()
+ is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
}.exhaustive
}
@@ -306,6 +307,16 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
+ private fun handleManageIntegrations() = withState { state ->
+ if (state.activeRoomWidgets().isNullOrEmpty()) {
+ // Directly open integration manager screen
+ handleOpenIntegrationManager()
+ } else {
+ // Display bottomsheet with widget list
+ _viewEvents.post(RoomDetailViewEvents.OpenActiveWidgetBottomSheet)
+ }
+ }
+
private fun startTrackingUnreadMessages() {
trackUnreadMessages.set(true)
setState { copy(canShowJumpToReadMarker = false) }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetController.kt
index c15dad77ad..77a179e2db 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetController.kt
@@ -16,28 +16,48 @@
package im.vector.app.features.home.room.detail.widget
+import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
+import im.vector.app.R
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.genericButtonItem
+import im.vector.app.core.ui.list.genericFooterItem
import org.matrix.android.sdk.api.session.widgets.model.Widget
import javax.inject.Inject
/**
* Epoxy controller for room widgets list
*/
-class RoomWidgetController @Inject constructor() : TypedEpoxyController>() {
+class RoomWidgetController @Inject constructor(val stringProvider: StringProvider, val colorProvider: ColorProvider) : TypedEpoxyController>() {
var listener: Listener? = null
- override fun buildModels(widget: List) {
- widget.forEach {
- RoomWidgetItem_()
- .id(it.widgetId)
- .widget(it)
- .widgetClicked { listener?.didSelectWidget(it) }
- .addTo(this)
+ override fun buildModels(widgets: List) {
+ if (widgets.isEmpty()) {
+ genericFooterItem {
+ id("empty")
+ text(stringProvider.getString(R.string.room_no_active_widgets))
+ }
+ } else {
+ widgets.forEach {
+ RoomWidgetItem_()
+ .id(it.widgetId)
+ .widget(it)
+ .widgetClicked { listener?.didSelectWidget(it) }
+ .addTo(this)
+ }
+ }
+ genericButtonItem {
+ id("addIntegration")
+ text(stringProvider.getString(R.string.room_manage_integrations))
+ textColor(colorProvider.getColor(R.color.riotx_accent))
+ itemClickAction(View.OnClickListener { listener?.didSelectManageWidgets() })
}
}
interface Listener {
fun didSelectWidget(widget: Widget)
+ fun didSelectManageWidgets()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt
index bc6c935102..a58b8291c7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt
@@ -16,7 +16,10 @@
package im.vector.app.features.home.room.detail.widget
+import android.widget.ImageView
import android.widget.TextView
+import androidx.annotation.DrawableRes
+import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
@@ -24,21 +27,34 @@ import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.onClick
+import org.matrix.android.sdk.api.extensions.tryThis
import org.matrix.android.sdk.api.session.widgets.model.Widget
+import java.net.URL
@EpoxyModelClass(layout = R.layout.item_room_widget)
abstract class RoomWidgetItem : EpoxyModelWithHolder() {
@EpoxyAttribute lateinit var widget: Widget
@EpoxyAttribute var widgetClicked: ClickListener? = null
+ @DrawableRes
+ @EpoxyAttribute var iconRes: Int? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.widgetName.text = widget.name
+ holder.widgetUrl.text = tryThis { URL(widget.computedUrl) }?.host ?: widget.computedUrl
+ if (iconRes != null) {
+ holder.iconImage.isVisible = true
+ holder.iconImage.setImageResource(iconRes!!)
+ } else {
+ holder.iconImage.isVisible = false
+ }
holder.view.onClick(widgetClicked)
}
class Holder : VectorEpoxyHolder() {
val widgetName by bind(R.id.roomWidgetName)
+ val widgetUrl by bind(R.id.roomWidgetUrl)
+ val iconImage by bind(R.id.roomWidgetAvatar)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
index 4a89c75b7f..32446bfcde 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
@@ -27,6 +27,7 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.resources.ColorProvider
+import im.vector.app.features.home.room.detail.RoomDetailAction
import im.vector.app.features.home.room.detail.RoomDetailViewModel
import im.vector.app.features.home.room.detail.RoomDetailViewState
import im.vector.app.features.navigation.Navigator
@@ -77,6 +78,11 @@ class RoomWidgetsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidget
dismiss()
}
+ override fun didSelectManageWidgets() {
+ roomDetailViewModel.handle(RoomDetailAction.OpenIntegrationManager)
+ dismiss()
+ }
+
companion object {
fun newInstance(): RoomWidgetsBottomSheet {
return RoomWidgetsBottomSheet()
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 7ec5c94e4b..3da6a7257a 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -31,6 +31,8 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
+import im.vector.app.features.call.conference.JitsiCallViewModel
+import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
@@ -66,6 +68,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget
+import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject
import javax.inject.Singleton
@@ -270,9 +273,14 @@ class DefaultNavigator @Inject constructor(
fragment.startActivityForResult(intent, WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE)
}
- override fun openRoomWidget(context: Context, roomId: String, widget: Widget) {
- val widgetArgs = widgetArgsBuilder.buildRoomWidgetArgs(roomId, widget)
- context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
+ override fun openRoomWidget(context: Context, roomId: String, widget: Widget, options: Map?) {
+ if (widget.type is WidgetType.Jitsi) {
+ val enableVideo = options?.get(JitsiCallViewModel.ENABLE_VIDEO_OPTION) == true
+ context.startActivity(VectorJitsiActivity.newIntent(context, roomId = roomId, widgetId = widget.widgetId, enableVideo = enableVideo))
+ } else {
+ val widgetArgs = widgetArgsBuilder.buildRoomWidgetArgs(roomId, widget)
+ context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
+ }
}
override fun openPinCode(fragment: Fragment, pinMode: PinMode, requestCode: Int) {
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index 28bb3e3c60..ee64c5fc75 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -97,7 +97,7 @@ interface Navigator {
fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?)
- fun openRoomWidget(context: Context, roomId: String, widget: Widget)
+ fun openRoomWidget(context: Context, roomId: String, widget: Widget, options: Map? = null)
fun openMediaViewer(activity: Activity,
roomId: String,
diff --git a/vector/src/main/res/drawable/ic_integrations.xml b/vector/src/main/res/drawable/ic_integrations.xml
new file mode 100644
index 0000000000..938b6f7d79
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_integrations.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/vector/src/main/res/layout/activity_jitsi.xml b/vector/src/main/res/layout/activity_jitsi.xml
new file mode 100644
index 0000000000..8928d298b3
--- /dev/null
+++ b/vector/src/main/res/layout/activity_jitsi.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/custom_action_item_layout_badge.xml b/vector/src/main/res/layout/custom_action_item_layout_badge.xml
new file mode 100644
index 0000000000..cfa2180219
--- /dev/null
+++ b/vector/src/main/res/layout/custom_action_item_layout_badge.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml
index e5246c43b7..d42020ad69 100644
--- a/vector/src/main/res/layout/fragment_room_detail.xml
+++ b/vector/src/main/res/layout/fragment_room_detail.xml
@@ -104,6 +104,14 @@
app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" />
+
+
-
+
+
+
+
+
+
+
+
+
-
-
\ No newline at end of file
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_active_conference_view.xml b/vector/src/main/res/layout/view_active_conference_view.xml
new file mode 100644
index 0000000000..4195c227a6
--- /dev/null
+++ b/vector/src/main/res/layout/view_active_conference_view.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
index d4eb923d50..6cdbbed424 100644
--- a/vector/src/main/res/menu/menu_timeline.xml
+++ b/vector/src/main/res/menu/menu_timeline.xml
@@ -12,11 +12,6 @@
app:showAsAction="always"
tools:visible="true" />
-
-
+
+
- A parameter is not valid.
No integration manager configured.
Add Matrix apps
+ Manage Integrations
+ No active widgets
Use native camera
Start the system camera instead of the custom camera screen.
Use keyboard enter key to send message
diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml
index b3430a59c8..fee95ab5e2 100644
--- a/vector/src/main/res/values/styles_riot.xml
+++ b/vector/src/main/res/values/styles_riot.xml
@@ -331,6 +331,17 @@
- 16sp
+
+