Code review

This commit is contained in:
Benoit Marty 2021-02-15 14:50:26 +01:00
parent e5da026f1f
commit 59634753b3
20 changed files with 268 additions and 208 deletions

View file

@ -65,13 +65,30 @@ interface StateService {
*/
suspend fun deleteAvatar()
/**
* Send a state event to the room
*/
suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict)
/**
* Get a state event of the room
*/
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
/**
* Get a live state event of the room
*/
fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>>
/**
* Get state events of the room
* @param eventTypes Set of eventType. If empty, all state events will be returned
*/
fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event>
/**
* Get live state events of the room
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
*/
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2021 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.devtools
interface DevToolsInteractionListener {
fun processAction(action: RoomDevToolAction)
}

View file

@ -22,6 +22,6 @@ sealed class DevToolsViewEvents : VectorViewEvents {
object Dismiss : DevToolsViewEvents()
// object ShowStateList : DevToolsViewEvents()
data class showAlertMessage(val message: String) : DevToolsViewEvents()
data class showSnackMessage(val message: String) : DevToolsViewEvents()
data class ShowAlertMessage(val message: String) : DevToolsViewEvents()
data class ShowSnackMessage(val message: String) : DevToolsViewEvents()
}

View file

@ -35,6 +35,7 @@ import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.SimpleFragmentActivity
@ -57,7 +58,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
override fun getMenuRes() = R.menu.menu_devtools
var currentDisplayMode: RoomDevToolViewState.Mode? = null
private var currentDisplayMode: RoomDevToolViewState.Mode? = null
@Parcelize
data class Args(
@ -81,15 +82,16 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
viewModel.observeViewEvents {
when (it) {
DevToolsViewEvents.Dismiss -> finish()
is DevToolsViewEvents.showAlertMessage -> {
DevToolsViewEvents.Dismiss -> finish()
is DevToolsViewEvents.ShowAlertMessage -> {
AlertDialog.Builder(this)
.setMessage(it.message)
.setPositiveButton(R.string.ok, null)
.show()
Unit
}
is DevToolsViewEvents.showSnackMessage -> showSnackbar(it.message)
}
is DevToolsViewEvents.ShowSnackMessage -> showSnackbar(it.message)
}.exhaustive
}
supportFragmentManager.addOnBackStackChangedListener(this)
}
@ -97,7 +99,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
private fun renderState(it: RoomDevToolViewState) {
if (it.displayMode != currentDisplayMode) {
when (it.displayMode) {
RoomDevToolViewState.Mode.Root -> {
RoomDevToolViewState.Mode.Root -> {
val classJava = RoomDevToolFragment::class.java
val tag = classJava.name
if (supportFragmentManager.findFragmentByTag(tag) == null) {
@ -106,8 +108,13 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
supportFragmentManager.popBackStack()
}
}
RoomDevToolViewState.Mode.StateEventDetail -> {
val frag = JSonViewerFragment.newInstance(it.selectedEventJson ?: "", -1, true, createJSonViewerStyleProvider(colorProvider))
RoomDevToolViewState.Mode.StateEventDetail -> {
val frag = JSonViewerFragment.newInstance(
jsonString = it.selectedEventJson ?: "",
initialOpenDepth = -1,
wrap = true,
styleProvider = createJSonViewerStyleProvider(colorProvider)
)
navigateTo(frag)
}
RoomDevToolViewState.Mode.StateEventList,
@ -115,11 +122,11 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
val frag = createFragment(RoomDevToolStateEventListFragment::class.java, Bundle().toMvRxBundle())
navigateTo(frag)
}
RoomDevToolViewState.Mode.EditEventContent -> {
RoomDevToolViewState.Mode.EditEventContent -> {
val frag = createFragment(RoomDevToolEditFragment::class.java, Bundle().toMvRxBundle())
navigateTo(frag)
}
is RoomDevToolViewState.Mode.SendEventForm -> {
is RoomDevToolViewState.Mode.SendEventForm -> {
val frag = createFragment(RoomDevToolSendFormFragment::class.java, Bundle().toMvRxBundle())
navigateTo(frag)
}
@ -129,9 +136,9 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
}
when (it.modalLoading) {
is Loading -> showWaitingView()
is Success -> hideWaitingView()
is Fail -> {
is Loading -> showWaitingView()
is Success -> hideWaitingView()
is Fail -> {
hideWaitingView()
}
Uninitialized -> {
@ -203,7 +210,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
companion object {
fun intent(roomId: String, context: Context): Intent {
fun intent(context: Context, roomId: String): Intent {
return Intent(context, RoomDevToolActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, Args(roomId))
}
@ -216,24 +223,26 @@ class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Facto
private fun updateToolBar(state: RoomDevToolViewState) {
val title = when (state.displayMode) {
RoomDevToolViewState.Mode.Root -> {
RoomDevToolViewState.Mode.Root -> {
getString(getTitleRes())
}
RoomDevToolViewState.Mode.StateEventList -> {
"State Events"
RoomDevToolViewState.Mode.StateEventList -> {
getString(R.string.dev_tools_state_event)
}
RoomDevToolViewState.Mode.StateEventDetail -> {
RoomDevToolViewState.Mode.StateEventDetail -> {
state.selectedEvent?.type
}
RoomDevToolViewState.Mode.EditEventContent -> {
"Edit Content"
RoomDevToolViewState.Mode.EditEventContent -> {
getString(R.string.dev_tools_edit_content)
}
RoomDevToolViewState.Mode.StateEventListByType -> {
state.currentStateType ?: ""
}
is RoomDevToolViewState.Mode.SendEventForm -> {
if (state.displayMode.isState) "Send Custom State Event"
else "Send Custom Event"
is RoomDevToolViewState.Mode.SendEventForm -> {
getString(
if (state.displayMode.isState) R.string.dev_tools_send_custom_state_event
else R.string.dev_tools_send_custom_event
)
}
}

View file

@ -25,16 +25,13 @@ import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentDevtoolsEditorBinding
import javax.inject.Inject
class RoomDevToolEditFragment @Inject constructor(
val epoxyController: RoomDevToolRootController,
private val colorProvider: ColorProvider
) : VectorBaseFragment<FragmentDevtoolsEditorBinding>(), RoomDevToolRootController.InteractionListener {
class RoomDevToolEditFragment @Inject constructor()
: VectorBaseFragment<FragmentDevtoolsEditorBinding>() {
val sharedViewModel: RoomDevToolViewModel by activityViewModel()
private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDevtoolsEditorBinding {
return FragmentDevtoolsEditorBinding.inflate(inflater, container, false)
@ -53,13 +50,6 @@ class RoomDevToolEditFragment @Inject constructor(
.disposeOnDestroyView()
}
override fun invalidate() = withState(sharedViewModel) { _ ->
}
override fun processAction(action: RoomDevToolAction) {
sharedViewModel.handle(action)
}
override fun onResume() {
super.onResume()
views.editText.requestFocus()

View file

@ -21,20 +21,18 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
class RoomDevToolFragment @Inject constructor(
val epoxyController: RoomDevToolRootController,
private val colorProvider: ColorProvider
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), RoomDevToolRootController.InteractionListener {
private val epoxyController: RoomDevToolRootController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(),
DevToolsInteractionListener {
val sharedViewModel: RoomDevToolViewModel by activityViewModel()
private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
@ -62,10 +60,6 @@ class RoomDevToolFragment @Inject constructor(
super.onDestroyView()
}
override fun invalidate() = withState(sharedViewModel) { state ->
epoxyController.setData(state)
}
override fun processAction(action: RoomDevToolAction) {
sharedViewModel.handle(action)
}

View file

@ -17,44 +17,43 @@
package im.vector.app.features.devtools
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericButtonItem
import javax.inject.Inject
class RoomDevToolRootController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<RoomDevToolViewState>() {
) : EpoxyController() {
interface InteractionListener {
fun processAction(action: RoomDevToolAction)
init {
requestModelBuild()
}
var interactionListener: InteractionListener? = null
var interactionListener: DevToolsInteractionListener? = null
override fun buildModels(data: RoomDevToolViewState?) {
if (data?.displayMode == RoomDevToolViewState.Mode.Root) {
genericButtonItem {
id("explore")
text("Explore Room State")
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.ExploreRoomState)
})
}
genericButtonItem {
id("send")
text("Send Custom Event")
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.SendCustomEvent(false))
})
}
genericButtonItem {
id("send_state")
text("Send State Event")
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.SendCustomEvent(true))
})
}
override fun buildModels() {
genericButtonItem {
id("explore")
text(stringProvider.getString(R.string.dev_tools_explore_room_state))
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.ExploreRoomState)
})
}
genericButtonItem {
id("send")
text(stringProvider.getString(R.string.dev_tools_send_custom_event))
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.SendCustomEvent(false))
})
}
genericButtonItem {
id("send_state")
text(stringProvider.getString(R.string.dev_tools_send_state_event))
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.SendCustomEvent(true))
})
}
}
}

View file

@ -17,22 +17,21 @@
package im.vector.app.features.devtools
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formMultiLineEditTextItem
import javax.inject.Inject
class RoomDevToolSendFormController @Inject constructor() : TypedEpoxyController<RoomDevToolViewState>() {
class RoomDevToolSendFormController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<RoomDevToolViewState>() {
interface InteractionListener {
fun processAction(action: RoomDevToolAction)
}
var interactionListener: InteractionListener? = null
var interactionListener: DevToolsInteractionListener? = null
override fun buildModels(data: RoomDevToolViewState?) {
val sendMode = (data?.displayMode as? RoomDevToolViewState.Mode.SendEventForm)
?: return
val sendEventForm = (data?.displayMode as? RoomDevToolViewState.Mode.SendEventForm) ?: return
genericFooterItem {
id("topSpace")
@ -42,19 +41,19 @@ class RoomDevToolSendFormController @Inject constructor() : TypedEpoxyController
id("event_type")
enabled(true)
value(data.sendEventDraft?.type)
hint("Type")
hint(stringProvider.getString(R.string.dev_tools_form_hint_type))
showBottomSeparator(false)
onTextChange { text ->
interactionListener?.processAction(RoomDevToolAction.CustomEventTypeChange(text))
}
}
if (sendMode.isState) {
if (sendEventForm.isState) {
formEditTextItem {
id("state_key")
enabled(true)
value(data.sendEventDraft?.stateKey)
hint("State Key")
hint(stringProvider.getString(R.string.dev_tools_form_hint_state_key))
showBottomSeparator(false)
onTextChange { text ->
interactionListener?.processAction(RoomDevToolAction.CustomEventStateKeyChange(text))
@ -66,7 +65,7 @@ class RoomDevToolSendFormController @Inject constructor() : TypedEpoxyController
id("event_content")
enabled(true)
value(data.sendEventDraft?.content)
hint("Event Content")
hint(stringProvider.getString(R.string.dev_tools_form_hint_event_content))
showBottomSeparator(false)
onTextChange { text ->
interactionListener?.processAction(RoomDevToolAction.CustomEventContentChange(text))

View file

@ -25,14 +25,12 @@ import com.airbnb.mvrx.withState
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
class RoomDevToolSendFormFragment @Inject constructor(
val epoxyController: RoomDevToolSendFormController,
private val colorProvider: ColorProvider
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), RoomDevToolSendFormController.InteractionListener {
private val epoxyController: RoomDevToolSendFormController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener {
val sharedViewModel: RoomDevToolViewModel by activityViewModel()

View file

@ -25,14 +25,12 @@ import com.airbnb.mvrx.withState
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
class RoomDevToolStateEventListFragment @Inject constructor(
val epoxyController: RoomStateListController,
private val colorProvider: ColorProvider
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), RoomStateListController.InteractionListener {
private val epoxyController: RoomStateListController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener {
val sharedViewModel: RoomDevToolViewModel by activityViewModel()

View file

@ -28,12 +28,15 @@ import com.squareup.moshi.Types
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.launch
import org.json.JSONObject
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.util.JsonDict
@ -43,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
class RoomDevToolViewModel @AssistedInject constructor(
@Assisted val initialState: RoomDevToolViewState,
private val errorFormatter: ErrorFormatter,
private val stringProvider: StringProvider,
private val session: Session
) : VectorViewModel<RoomDevToolViewState, RoomDevToolAction, DevToolsViewEvents>(initialState) {
@ -64,8 +68,8 @@ class RoomDevToolViewModel @AssistedInject constructor(
}
init {
session.getRoom(initialState.roomId)?.rx()
session.getRoom(initialState.roomId)
?.rx()
?.liveStateEvents(emptySet())
?.execute { async ->
copy(stateEvents = async)
@ -74,7 +78,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
override fun handle(action: RoomDevToolAction) {
when (action) {
RoomDevToolAction.ExploreRoomState -> {
RoomDevToolAction.ExploreRoomState -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventList,
@ -82,7 +86,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
is RoomDevToolAction.ShowStateEvent -> {
is RoomDevToolAction.ShowStateEvent -> {
val jsonString = MoshiProvider.providesMoshi()
.adapter(Event::class.java)
.toJson(action.event)
@ -95,10 +99,10 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
RoomDevToolAction.OnBackPressed -> {
RoomDevToolAction.OnBackPressed -> {
handleBack()
}
RoomDevToolAction.MenuEdit -> {
RoomDevToolAction.MenuEdit -> {
withState {
if (it.displayMode == RoomDevToolViewState.Mode.StateEventDetail) {
// we want to edit it
@ -112,7 +116,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
}
}
}
is RoomDevToolAction.ShowStateEventType -> {
is RoomDevToolAction.ShowStateEventType -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventListByType,
@ -120,23 +124,23 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
RoomDevToolAction.MenuItemSend -> {
RoomDevToolAction.MenuItemSend -> {
handleMenuItemSend()
}
is RoomDevToolAction.UpdateContentText -> {
is RoomDevToolAction.UpdateContentText -> {
setState {
copy(editedContent = action.contentJson)
}
}
is RoomDevToolAction.SendCustomEvent -> {
is RoomDevToolAction.SendCustomEvent -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.SendEventForm(action.isStateEvent),
sendEventDraft = RoomDevToolViewState.SendEventDraft("m.room.message", null, "{\n}")
sendEventDraft = RoomDevToolViewState.SendEventDraft(EventType.MESSAGE, null, "{\n}")
)
}
}
is RoomDevToolAction.CustomEventTypeChange -> {
is RoomDevToolAction.CustomEventTypeChange -> {
setState {
copy(
sendEventDraft = sendEventDraft?.copy(type = action.type)
@ -150,7 +154,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
is RoomDevToolAction.CustomEventContentChange -> {
is RoomDevToolAction.CustomEventContentChange -> {
setState {
copy(
sendEventDraft = sendEventDraft?.copy(content = action.content)
@ -160,95 +164,102 @@ class RoomDevToolViewModel @AssistedInject constructor(
}
}
private fun handleMenuItemSend() = withState {
when (it.displayMode) {
RoomDevToolViewState.Mode.EditEventContent -> {
setState { copy(modalLoading = Loading()) }
viewModelScope.launch {
try {
val room = session.getRoom(initialState.roomId)
?: throw IllegalArgumentException("Room not found")
private fun handleMenuItemSend() = withState { state ->
when (state.displayMode) {
RoomDevToolViewState.Mode.EditEventContent -> editEventContent(state)
is RoomDevToolViewState.Mode.SendEventForm -> sendEventContent(state, state.displayMode.isState)
else -> Unit
}
}
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val json = adapter.fromJson(it.editedContent ?: "")
?: throw IllegalArgumentException("No content")
private fun editEventContent(state: RoomDevToolViewState) {
setState { copy(modalLoading = Loading()) }
room.sendStateEvent(
it.selectedEvent?.type ?: "",
it.selectedEvent?.stateKey,
json
viewModelScope.launch {
try {
val room = session.getRoom(initialState.roomId)
?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
)
_viewEvents.post(DevToolsViewEvents.showSnackMessage("State event sent!"))
setState {
copy(
modalLoading = Success(Unit),
selectedEventJson = null,
editedContent = null,
displayMode = RoomDevToolViewState.Mode.StateEventListByType
)
}
} catch (failure: Throwable) {
_viewEvents.post(DevToolsViewEvents.showAlertMessage(errorFormatter.toHumanReadable(failure)))
setState { copy(modalLoading = Fail(failure)) }
}
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val json = adapter.fromJson(state.editedContent ?: "")
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
room.sendStateEvent(
state.selectedEvent?.type ?: "",
state.selectedEvent?.stateKey,
json
)
_viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_state_event)))
setState {
copy(
modalLoading = Success(Unit),
selectedEventJson = null,
editedContent = null,
displayMode = RoomDevToolViewState.Mode.StateEventListByType
)
}
} catch (failure: Throwable) {
_viewEvents.post(DevToolsViewEvents.ShowAlertMessage(errorFormatter.toHumanReadable(failure)))
setState { copy(modalLoading = Fail(failure)) }
}
is RoomDevToolViewState.Mode.SendEventForm -> {
setState { copy(modalLoading = Loading()) }
viewModelScope.launch {
try {
val room = session.getRoom(initialState.roomId)
?: throw IllegalArgumentException("Room not found")
}
}
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val json = adapter.fromJson(it.sendEventDraft?.content ?: "")
?: throw IllegalArgumentException("No content")
private fun sendEventContent(state: RoomDevToolViewState, isState: Boolean) {
setState { copy(modalLoading = Loading()) }
viewModelScope.launch {
try {
val room = session.getRoom(initialState.roomId)
?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
val eventType = it.sendEventDraft?.type ?: throw IllegalArgumentException("Missing message type")
if (it.displayMode.isState) {
room.sendStateEvent(
eventType,
it.sendEventDraft.stateKey,
json
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val json = adapter.fromJson(state.sendEventDraft?.content ?: "")
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
)
} else {
// can we try to do some validation??
// val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "")
json.toModel<MessageContent>(catchError = false)
?: throw IllegalArgumentException("Malformed event")
room.sendEvent(
eventType,
json
)
}
val eventType = state.sendEventDraft?.type
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type))
_viewEvents.post(DevToolsViewEvents.showSnackMessage("Event sent!"))
setState {
copy(
modalLoading = Success(Unit),
sendEventDraft = null,
displayMode = RoomDevToolViewState.Mode.Root
)
}
} catch (failure: Throwable) {
_viewEvents.post(DevToolsViewEvents.showAlertMessage(errorFormatter.toHumanReadable(failure)))
setState { copy(modalLoading = Fail(failure)) }
}
if (isState) {
room.sendStateEvent(
eventType,
state.sendEventDraft.stateKey,
json
)
} else {
// can we try to do some validation??
// val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "")
json.toModel<MessageContent>(catchError = false)
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_malformed_event))
room.sendEvent(
eventType,
json
)
}
_viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_event)))
setState {
copy(
modalLoading = Success(Unit),
sendEventDraft = null,
displayMode = RoomDevToolViewState.Mode.Root
)
}
} catch (failure: Throwable) {
_viewEvents.post(DevToolsViewEvents.ShowAlertMessage(errorFormatter.toHumanReadable(failure)))
setState { copy(modalLoading = Fail(failure)) }
}
}
}
private fun handleBack() = withState {
when (it.displayMode) {
RoomDevToolViewState.Mode.Root -> {
RoomDevToolViewState.Mode.Root -> {
_viewEvents.post(DevToolsViewEvents.Dismiss)
}
RoomDevToolViewState.Mode.StateEventList -> {
RoomDevToolViewState.Mode.StateEventList -> {
setState {
copy(
selectedEvent = null,
@ -257,7 +268,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
RoomDevToolViewState.Mode.StateEventDetail -> {
RoomDevToolViewState.Mode.StateEventDetail -> {
setState {
copy(
selectedEvent = null,
@ -266,7 +277,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
RoomDevToolViewState.Mode.EditEventContent -> {
RoomDevToolViewState.Mode.EditEventContent -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventDetail
@ -281,7 +292,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
)
}
}
is RoomDevToolViewState.Mode.SendEventForm -> {
is RoomDevToolViewState.Mode.SendEventForm -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.Root

View file

@ -32,18 +32,12 @@ class RoomStateListController @Inject constructor(
private val colorProvider: ColorProvider
) : TypedEpoxyController<RoomDevToolViewState>() {
interface InteractionListener {
fun processAction(action: RoomDevToolAction)
}
var interactionListener: InteractionListener? = null
var interactionListener: DevToolsInteractionListener? = null
override fun buildModels(data: RoomDevToolViewState?) {
when (data?.displayMode) {
RoomDevToolViewState.Mode.StateEventList -> {
val stateEventsGroups = (data.stateEvents.invoke() ?: emptyList()).groupBy {
it.type
}
val stateEventsGroups = data.stateEvents.invoke().orEmpty().groupBy { it.type }
if (stateEventsGroups.isEmpty()) {
noResultItem {
@ -55,7 +49,7 @@ class RoomStateListController @Inject constructor(
genericItem {
id(entry.key)
title(entry.key)
description("${entry.value.size} entries")
description(stringProvider.getQuantityString(R.plurals.entries, entry.value.size, entry.value.size))
itemClickAction(GenericItem.Action("view").apply {
perform = Runnable {
interactionListener?.processAction(RoomDevToolAction.ShowStateEventType(entry.key))
@ -66,7 +60,7 @@ class RoomStateListController @Inject constructor(
}
}
RoomDevToolViewState.Mode.StateEventListByType -> {
val stateEvents = (data.stateEvents.invoke() ?: emptyList()).filter { it.type == data.currentStateType }
val stateEvents = data.stateEvents.invoke().orEmpty().filter { it.type == data.currentStateType }
if (stateEvents.isEmpty()) {
noResultItem {
id("no state events")
@ -74,10 +68,12 @@ class RoomStateListController @Inject constructor(
}
} else {
stateEvents.forEach { stateEvent ->
val contentMap = JSONObject(stateEvent.content ?: emptyMap<String, Any>()).toString().let {
val contentJson = JSONObject(stateEvent.content.orEmpty()).toString().let {
if (it.length > 140) {
it.take(140) + Typography.ellipsis
} else it.take(140)
} else {
it.take(140)
}
}
genericItem {
id(stateEvent.eventId)
@ -95,7 +91,7 @@ class RoomStateListController @Inject constructor(
textStyle = "normal"
}
})
description(contentMap)
description(contentJson)
itemClickAction(GenericItem.Action("view").apply {
perform = Runnable {
interactionListener?.processAction(RoomDevToolAction.ShowStateEvent(stateEvent))

View file

@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright (c) 2021 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.

View file

@ -128,7 +128,6 @@ import im.vector.app.features.command.Command
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.util.toImageRes
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.composer.TextComposerView
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
@ -769,7 +768,7 @@ class RoomDetailFragment @Inject constructor(
true
}
R.id.dev_tools -> {
startActivity(RoomDevToolActivity.intent(roomDetailArgs.roomId, requireContext()))
navigator.openDevTools(requireContext(), roomDetailArgs.roomId)
true
}
else -> super.onOptionsItemSelected(item)

View file

@ -42,6 +42,7 @@ import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
import im.vector.app.features.home.room.detail.search.SearchActivity
@ -345,6 +346,10 @@ class DefaultNavigator @Inject constructor(
context.startActivity(intent)
}
override fun openDevTools(context: Context, roomId: String) {
context.startActivity(RoomDevToolActivity.intent(context, roomId))
}
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
if (buildTask) {
val stackBuilder = TaskStackBuilder.create(context)

View file

@ -117,4 +117,6 @@ interface Navigator {
options: ((MutableList<Pair<View, String>>) -> Unit)?)
fun openSearch(context: Context, roomId: String)
fun openDevTools(context: Context, roomId: String)
}

View file

@ -63,8 +63,7 @@ class RoomMemberListController @Inject constructor(
?.filter { event ->
event.content.toModel<RoomThirdPartyInviteContent>()
?.takeIf {
it.displayName != null
&& (data.filter.isEmpty() || it.displayName!!.contains(data.filter, ignoreCase = true))
data.filter.isEmpty() || it.displayName?.contains(data.filter, ignoreCase = true) == true
} != null
}
.orEmpty()

View file

@ -1,16 +1,20 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|start"
android:textSize="12sp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:gravity="top|start"
android:inputType="textMultiLine"
android:scrollHorizontally="true"
android:inputType="textMultiLine" />
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -25,9 +25,9 @@
android:id="@+id/formMultiLineEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:imeOptions="actionDone"
android:gravity="top|start"
android:imeOptions="actionDone"
android:inputType="textMultiLine"
android:minLines="4"
tools:hint="@string/create_room_name_hint" />

View file

@ -2745,6 +2745,10 @@
<item quantity="one">Wrong code, %d remaining attempt</item>
<item quantity="other">Wrong code, %d remaining attempts</item>
</plurals>
<plurals name="entries">
<item quantity="one">%d entry"</item>
<item quantity="other">%d entries"</item>
</plurals>
<string name="wrong_pin_message_last_remaining_attempt">Warning! Last remaining attempt before logout!</string>
<string name="too_many_pin_failures">Too many errors, you\'ve been logged out</string>
<string name="create_pin_title">Choose a PIN for security</string>
@ -2793,4 +2797,19 @@
<string name="re_authentication_default_confirm_text">Element requires you to enter your credentials to perform this action.</string>
<string name="authentication_error">Failed to authenticate</string>
<string name="dev_tools_menu_name">Dev Tools</string>
<string name="dev_tools_explore_room_state">Explore Room State</string>
<string name="dev_tools_send_custom_event">Send Custom Event</string>
<string name="dev_tools_send_state_event">Send State Event</string>
<string name="dev_tools_state_event">State Events</string>
<string name="dev_tools_edit_content">Edit Content</string>
<string name="dev_tools_send_custom_state_event">Send Custom State Event</string>
<string name="dev_tools_form_hint_type">Type</string>
<string name="dev_tools_form_hint_state_key">State Key</string>
<string name="dev_tools_form_hint_event_content">Event Content</string>
<string name="dev_tools_error_no_content">No content</string>
<string name="dev_tools_error_no_message_type">Missing message type</string>
<string name="dev_tools_error_malformed_event">Malformed event</string>
<string name="dev_tools_success_event">Event sent!</string>
<string name="dev_tools_success_state_event">State event sent!</string>
</resources>