Widget: show room widgets in bottom sheet and fix some widget actions

This commit is contained in:
ganfra 2020-05-28 19:39:07 +02:00
parent cb80d8d349
commit 31c82b4ba6
21 changed files with 411 additions and 101 deletions

View File

@ -17,101 +17,41 @@
package im.vector.matrix.android.internal.session.user
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.database.model.IgnoredUserEntityFields
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.model.UserEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchCopied
import javax.inject.Inject
internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy,
internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource,
private val searchUserTask: SearchUserTask,
private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask,
private val taskExecutor: TaskExecutor) : UserService {
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
monarchy.createDataSourceFactory { realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
}
}
private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
realmDataSourceFactory.map {
it.asDomain()
}
}
private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
}
override fun getUser(userId: String): User? {
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
?: return null
return userEntity.asDomain()
return userDataSource.getUser(userId)
}
override fun getUserLive(userId: String): LiveData<Optional<User>> {
val liveData = monarchy.findAllMappedWithChanges(
{ UserEntity.where(it, userId) },
{ it.asDomain() }
)
return Transformations.map(liveData) { results ->
results.firstOrNull().toOptional()
}
return userDataSource.getUserLive(userId)
}
override fun getUsersLive(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
},
{ it.asDomain() }
)
return userDataSource.getUsersLive()
}
override fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
realmDataSourceFactory.updateQuery { realm ->
val query = realm.where(UserEntity::class.java)
if (filter.isNullOrEmpty()) {
query.isNotEmpty(UserEntityFields.USER_ID)
} else {
query
.beginGroup()
.contains(UserEntityFields.DISPLAY_NAME, filter)
.or()
.contains(UserEntityFields.USER_ID, filter)
.endGroup()
}
excludedUserIds
?.takeIf { it.isNotEmpty() }
?.let {
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
}
query.sort(UserEntityFields.DISPLAY_NAME)
}
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
return userDataSource.getPagedUsersLive(filter, excludedUserIds)
}
override fun getIgnoredUsersLive(): LiveData<List<User>> {
return userDataSource.getIgnoredUsersLive()
}
override fun searchUsersDirectory(search: String,
@ -126,17 +66,6 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
.executeBy(taskExecutor)
}
override fun getIgnoredUsersLive(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(IgnoredUserEntity::class.java)
.isNotEmpty(IgnoredUserEntityFields.USER_ID)
.sort(IgnoredUserEntityFields.USER_ID)
},
{ getUser(it.userId) ?: User(userId = it.userId) }
)
}
override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList())
return updateIgnoredUserIdsTask

View File

@ -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.matrix.android.internal.session.user
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.database.model.IgnoredUserEntityFields
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.model.UserEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.fetchCopied
import javax.inject.Inject
internal class UserDataSource @Inject constructor(private val monarchy: Monarchy) {
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
monarchy.createDataSourceFactory { realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
}
}
private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
realmDataSourceFactory.map {
it.asDomain()
}
}
private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
}
fun getUser(userId: String): User? {
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
?: return null
return userEntity.asDomain()
}
fun getUserLive(userId: String): LiveData<Optional<User>> {
val liveData = monarchy.findAllMappedWithChanges(
{ UserEntity.where(it, userId) },
{ it.asDomain() }
)
return Transformations.map(liveData) { results ->
results.firstOrNull().toOptional()
}
}
fun getUsersLive(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
},
{ it.asDomain() }
)
}
fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
realmDataSourceFactory.updateQuery { realm ->
val query = realm.where(UserEntity::class.java)
if (filter.isNullOrEmpty()) {
query.isNotEmpty(UserEntityFields.USER_ID)
} else {
query
.beginGroup()
.contains(UserEntityFields.DISPLAY_NAME, filter)
.or()
.contains(UserEntityFields.USER_ID, filter)
.endGroup()
}
excludedUserIds
?.takeIf { it.isNotEmpty() }
?.let {
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
}
query.sort(UserEntityFields.DISPLAY_NAME)
}
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
}
fun getIgnoredUsersLive(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(IgnoredUserEntity::class.java)
.isNotEmpty(IgnoredUserEntityFields.USER_ID)
.sort(IgnoredUserEntityFields.USER_ID)
},
{ getUser(it.userId) ?: User(userId = it.userId) }
)
}
}

View File

@ -25,12 +25,12 @@ data class Widget(
val event: Event,
val widgetId: String,
val senderInfo: SenderInfo?,
val isAddedByMe: Boolean
val isAddedByMe: Boolean,
val computedUrl: String?
) {
val isActive = widgetContent.type != null && widgetContent.url != null
val isActive = widgetContent.type != null && computedUrl != null
val name = widgetContent.getHumanName()
}

View File

@ -23,12 +23,15 @@ import im.vector.matrix.android.api.session.widgets.model.WidgetContent
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.user.UserDataSource
import im.vector.matrix.android.internal.session.widgets.Widget
import io.realm.Realm
import io.realm.RealmConfiguration
import java.net.URLEncoder
import javax.inject.Inject
internal class WidgetFactory @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
private val userDataSource: UserDataSource,
@UserId private val userId: String) {
fun create(widgetEvent: Event): Widget? {
@ -50,6 +53,22 @@ internal class WidgetFactory @Inject constructor(@SessionDatabase private val re
}
}
val isAddedByMe = widgetEvent.senderId == userId
return Widget(widgetContent, widgetEvent, widgetId, senderInfo, isAddedByMe)
val computedUrl = widgetContent.computeURL()
return Widget(widgetContent, widgetEvent, widgetId, senderInfo, isAddedByMe, computedUrl)
}
private fun WidgetContent.computeURL(): String? {
var computedUrl = url ?: return null
val myUser = userDataSource.getUser(userId)
computedUrl = computedUrl
.replace("\$matrix_user_id", userId)
.replace("\$matrix_display_name", myUser?.displayName ?: userId)
.replace("\$matrix_avatar_url", myUser?.avatarUrl ?: "")
for ((key, value) in data) {
if (value is String) {
computedUrl = computedUrl.replace("$$key", URLEncoder.encode(value, "utf-8"))
}
}
return computedUrl
}
}

View File

@ -36,6 +36,7 @@ import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceipt
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.home.room.list.RoomListModule
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
@ -138,6 +139,7 @@ interface ScreenComponent {
fun inject(bottomSheet: DeviceListBottomSheet)
fun inject(bottomSheet: BootstrapBottomSheet)
fun inject(bottomSheet: RoomWidgetPermissionBottomSheet)
fun inject(bottomSheet: RoomWidgetsBottomSheet)
/* ==========================================================================================
* Others
@ -147,7 +149,6 @@ interface ScreenComponent {
fun inject(preference: UserAvatarPreference)
fun inject(button: ReactionButton)
/* ==========================================================================================
* Factory
* ========================================================================================== */

View File

@ -148,6 +148,7 @@ import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBannerView
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.PillImageSpan
import im.vector.riotx.features.invite.VectorInviteView
@ -1465,6 +1466,7 @@ class RoomDetailFragment @Inject constructor(
}
override fun onViewWidgetsClicked() {
Toast.makeText(requireContext(), "Show widgets", Toast.LENGTH_SHORT).show()
RoomWidgetsBottomSheet.newInstance()
.show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET")
}
}

View File

@ -27,7 +27,7 @@ class StickerPickerActionHandler @Inject constructor(private val session: Sessio
suspend fun handle(): RoomDetailViewEvents = withContext(Dispatchers.Default) {
// Search for the sticker picker widget in the user account
val stickerWidget = session.widgetService().getUserWidgets(setOf(StickerPickerConstants.WIDGET_NAME)).firstOrNull()
if (stickerWidget == null || stickerWidget.widgetContent.url.isNullOrBlank()) {
if (stickerWidget == null || stickerWidget.computedUrl.isNullOrBlank()) {
RoomDetailViewEvents.DisplayPromptForIntegrationManager
} else {
RoomDetailViewEvents.OpenStickerPicker(

View File

@ -0,0 +1,43 @@
/*
* 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.riotx.features.home.room.detail.widget
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.internal.session.widgets.Widget
import javax.inject.Inject
/**
* Epoxy controller for room widgets list
*/
class RoomWidgetController @Inject constructor() : TypedEpoxyController<List<Widget>>() {
var listener: Listener? = null
override fun buildModels(widget: List<Widget>) {
widget.forEach {
RoomWidgetItem_()
.id(it.widgetId)
.widget(it)
.widgetClicked { listener?.didSelectWidget(it) }
.addTo(this)
}
}
interface Listener {
fun didSelectWidget(widget: Widget)
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.riotx.features.home.room.detail.widget
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.matrix.android.internal.session.widgets.Widget
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
@EpoxyModelClass(layout = R.layout.item_room_widget)
abstract class RoomWidgetItem : EpoxyModelWithHolder<RoomWidgetItem.Holder>() {
@EpoxyAttribute lateinit var widget: Widget
@EpoxyAttribute var widgetClicked: (() -> Unit)? = null
override fun bind(holder: Holder) {
holder.widgetName.text = widget.name
holder.view.setOnClickListener { widgetClicked?.invoke() }
}
class Holder : VectorEpoxyHolder() {
val widgetName by bind<TextView>(R.id.roomWidgetName)
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.riotx.features.home.room.detail.widget
import android.os.Bundle
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.internal.session.widgets.Widget
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
import im.vector.riotx.features.home.room.detail.RoomDetailViewState
import im.vector.riotx.features.navigation.Navigator
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
import javax.inject.Inject
/**
* Bottom sheet displaying active widgets in a room
*/
class RoomWidgetsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidgetController.Listener {
@Inject lateinit var epoxyController: RoomWidgetController
@Inject lateinit var colorProvider: ColorProvider
@Inject lateinit var navigator: Navigator
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
private val roomDetailViewModel: RoomDetailViewModel by parentFragmentViewModel()
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.active_widgets_title)
bottomSheetTitle.textSize = 20f
bottomSheetTitle.setTextColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
epoxyController.listener = this
roomDetailViewModel.asyncSubscribe(this, RoomDetailViewState::activeRoomWidgets) {
epoxyController.setData(it)
}
}
override fun onDestroyView() {
recyclerView.cleanup()
epoxyController.listener = null
super.onDestroyView()
}
override fun didSelectWidget(widget: Widget) = withState(roomDetailViewModel) {
navigator.openRoomWidget(requireContext(), it.roomId, widget)
dismiss()
}
companion object {
fun newInstance(): RoomWidgetsBottomSheet {
return RoomWidgetsBottomSheet()
}
}
}

View File

@ -238,6 +238,12 @@ class DefaultNavigator @Inject constructor(
context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
}
override fun openRoomWidget(context: Context, roomId: String, widget: Widget) {
val widgetArgs = widgetArgsBuilder.buildRoomWidgetArgs(roomId, widget)
context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
}
override fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?) {
val intent = ImageMediaViewerActivity.newIntent(activity, mediaData, ViewCompat.getTransitionName(view))
val pairs = ArrayList<Pair<View, String>>()

View File

@ -89,6 +89,8 @@ interface Navigator {
fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?)
fun openRoomWidget(context: Context, roomId: String, widget: Widget)
fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList<Pair<View, String>>) -> Unit)?)
fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data)

View File

@ -46,7 +46,7 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
@Suppress("UNCHECKED_CAST")
fun buildStickerPickerArgs(roomId: String, widget: Widget): WidgetArgs {
val widgetId = widget.widgetId
val baseUrl = widget.widgetContent.url ?: throw IllegalStateException()
val baseUrl = widget.computedUrl ?: throw IllegalStateException()
return WidgetArgs(
baseUrl = baseUrl,
kind = WidgetKind.STICKER_PICKER,
@ -59,6 +59,17 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
)
}
fun buildRoomWidgetArgs(roomId: String, widget: Widget): WidgetArgs {
val widgetId = widget.widgetId
val baseUrl = widget.computedUrl?: throw IllegalStateException()
return WidgetArgs(
baseUrl = baseUrl,
kind = WidgetKind.ROOM,
roomId = roomId,
widgetId = widgetId
)
}
@Suppress("UNCHECKED_CAST")
private fun Map<String, String?>.filterNotNull(): Map<String, String> {
return filterValues { it != null } as Map<String, String>

View File

@ -168,6 +168,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
val widgetId = initialState.widgetId ?: return@launch
awaitCallback<Unit> {
widgetService.destroyRoomWidget(initialState.roomId, widgetId, it)
_viewEvents.post(WidgetViewEvents.Close())
}
}
}

View File

@ -15,8 +15,8 @@
*/
package im.vector.riotx.features.widgets.permissions
import android.content.DialogInterface
import android.os.Build
import android.os.Parcelable
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.BulletSpan
@ -34,10 +34,9 @@ import im.vector.riotx.core.extensions.withArgs
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.widgets.WidgetArgs
import kotlinx.android.parcel.Parcelize
import javax.inject.Inject
class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidgetPermissionViewModel.Factory {
override fun getLayoutResId(): Int = R.layout.bottom_sheet_room_widget_permission
@ -56,13 +55,20 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
lateinit var authorAvatarView: ImageView
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var viewModelFactory: RoomWidgetPermissionViewModel.Factory
var onFinish: ((Boolean) -> Unit)? = null
override val showExpanded = true
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun create(initialState: RoomWidgetPermissionViewState): RoomWidgetPermissionViewModel {
return viewModelFactory.create(initialState)
}
override fun invalidate() = withState(viewModel) { state ->
super.invalidate()
val permissionData = state.permissionData() ?: return@withState
@ -117,6 +123,11 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
onFinish?.invoke(true)
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
onFinish?.invoke(false)
}
companion object {
fun newInstance(widgetArgs: WidgetArgs) = RoomWidgetPermissionBottomSheet().withArgs {

View File

@ -51,7 +51,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in
.map {
val widget = it.first()
val domain = try {
URL(widget.widgetContent.url).host
URL(widget.computedUrl).host
} catch (e: Throwable) {
null
}

View File

@ -20,19 +20,15 @@ import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
import im.vector.matrix.android.api.session.widgets.WidgetService
import im.vector.matrix.android.internal.util.awaitCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class WidgetPermissionsHelper(private val integrationManagerService: IntegrationManagerService,
private val widgetService: WidgetService) {
suspend fun changePermission(roomId: String, widgetId: String, allow: Boolean) {
val widget = withContext(Dispatchers.Default) {
widgetService.getRoomWidgets(
roomId = roomId,
widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.SENSITIVE)
).firstOrNull()
}
val widget = widgetService.getRoomWidgets(
roomId = roomId,
widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.SENSITIVE)
).firstOrNull()
val eventId = widget?.event?.eventId ?: return
awaitCallback<Unit> {
integrationManagerService.setWidgetAllowed(eventId, allow, it)

View File

@ -38,7 +38,7 @@
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:orientation="horizontal">
<im.vector.view.VectorCircularImageView
<ImageView
android:id="@+id/bottom_sheet_widget_permission_owner_avatar"
android:layout_width="40dp"
android:layout_height="40dp"

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rootConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RoomWidgetsFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="64dp"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingEnd="8dp">
<ImageView
android:id="@+id/roomWidgetAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/roomWidgetName"
style="@style/BottomSheetItemTextMain"
tools:text="@sample/matrix.json/data/displayName" />
</LinearLayout>

View File

@ -1117,6 +1117,7 @@
<item quantity="other">%d active widgets</item>
</plurals>
<string name="active_widget_view_action">"VIEW"</string>
<string name="active_widgets_title">"Active widgets"</string>
<string name="room_widget_activity_title">Widget</string>