mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Merge pull request #2805 from vector-im/feature/bca/devtools
Dev tools initial commit
This commit is contained in:
commit
b3a408a34c
34 changed files with 1475 additions and 11 deletions
|
@ -26,6 +26,7 @@ Test:
|
||||||
-
|
-
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
- New Dev Tools panel for developers
|
||||||
- Fix typos in CHANGES.md (#2811)
|
- Fix typos in CHANGES.md (#2811)
|
||||||
|
|
||||||
Changes in Element 1.0.17 (2021-02-09)
|
Changes in Element 1.0.17 (2021-02-09)
|
||||||
|
|
|
@ -30,24 +30,24 @@ data class RoomThirdPartyInviteContent(
|
||||||
* This should not contain the user's third party ID, as otherwise when the invite
|
* This should not contain the user's third party ID, as otherwise when the invite
|
||||||
* is accepted it would leak the association between the matrix ID and the third party ID.
|
* is accepted it would leak the association between the matrix ID and the third party ID.
|
||||||
*/
|
*/
|
||||||
@Json(name = "display_name") val displayName: String,
|
@Json(name = "display_name") val displayName: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A URL which can be fetched, with querystring public_key=public_key, to validate
|
* Required. A URL which can be fetched, with querystring public_key=public_key, to validate
|
||||||
* whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'.
|
* whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "key_validity_url") val keyValidityUrl: String,
|
@Json(name = "key_validity_url") val keyValidityUrl: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in
|
* Required. A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in
|
||||||
* public_keys is also sufficient). This exists for backwards compatibility.
|
* public_keys is also sufficient). This exists for backwards compatibility.
|
||||||
*/
|
*/
|
||||||
@Json(name = "public_key") val publicKey: String,
|
@Json(name = "public_key") val publicKey: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys with which the token may be signed.
|
* Keys with which the token may be signed.
|
||||||
*/
|
*/
|
||||||
@Json(name = "public_keys") val publicKeys: List<PublicKeys> = emptyList()
|
@Json(name = "public_keys") val publicKeys: List<PublicKeys>? = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
|
@ -65,13 +65,30 @@ interface StateService {
|
||||||
*/
|
*/
|
||||||
suspend fun deleteAvatar()
|
suspend fun deleteAvatar()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a state event to the room
|
||||||
|
*/
|
||||||
suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict)
|
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?
|
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>>
|
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>
|
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>>
|
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ internal class EventSenderProcessor @Inject constructor(
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("## SendThread retryLoop Un-Retryable error, try next task")
|
Timber.v("## SendThread retryLoop Un-Retryable error, try next task")
|
||||||
// this task is in error, check next one?
|
// this task is in error, check next one?
|
||||||
|
task.onTaskFailed()
|
||||||
break@retryLoop
|
break@retryLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,11 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private
|
||||||
): RealmQuery<CurrentStateEventEntity> {
|
): RealmQuery<CurrentStateEventEntity> {
|
||||||
return realm.where<CurrentStateEventEntity>()
|
return realm.where<CurrentStateEventEntity>()
|
||||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||||
.`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
|
.apply {
|
||||||
|
if (eventTypes.isNotEmpty()) {
|
||||||
|
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,6 +267,7 @@
|
||||||
<!-- </intent-filter>-->
|
<!-- </intent-filter>-->
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".features.devtools.RoomDevToolActivity"/>
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -45,6 +45,10 @@ import im.vector.app.features.crypto.verification.emoji.VerificationEmojiCodeFra
|
||||||
import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
|
import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
|
||||||
import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
|
import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
|
||||||
import im.vector.app.features.crypto.verification.request.VerificationRequestFragment
|
import im.vector.app.features.crypto.verification.request.VerificationRequestFragment
|
||||||
|
import im.vector.app.features.devtools.RoomDevToolEditFragment
|
||||||
|
import im.vector.app.features.devtools.RoomDevToolFragment
|
||||||
|
import im.vector.app.features.devtools.RoomDevToolSendFormFragment
|
||||||
|
import im.vector.app.features.devtools.RoomDevToolStateEventListFragment
|
||||||
import im.vector.app.features.discovery.DiscoverySettingsFragment
|
import im.vector.app.features.discovery.DiscoverySettingsFragment
|
||||||
import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
||||||
import im.vector.app.features.grouplist.GroupListFragment
|
import im.vector.app.features.grouplist.GroupListFragment
|
||||||
|
@ -594,4 +598,24 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(ShowUserCodeFragment::class)
|
@FragmentKey(ShowUserCodeFragment::class)
|
||||||
fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment
|
fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomDevToolFragment::class)
|
||||||
|
fun bindRoomDevToolFragment(fragment: RoomDevToolFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomDevToolStateEventListFragment::class)
|
||||||
|
fun bindRoomDevToolStateEventListFragment(fragment: RoomDevToolStateEventListFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomDevToolEditFragment::class)
|
||||||
|
fun bindRoomDevToolEditFragment(fragment: RoomDevToolEditFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomDevToolSendFormFragment::class)
|
||||||
|
fun bindRoomDevToolSendFormFragment(fragment: RoomDevToolSendFormFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
|
||||||
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
|
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.debug.DebugMenuActivity
|
import im.vector.app.features.debug.DebugMenuActivity
|
||||||
|
import im.vector.app.features.devtools.RoomDevToolActivity
|
||||||
import im.vector.app.features.home.HomeActivity
|
import im.vector.app.features.home.HomeActivity
|
||||||
import im.vector.app.features.home.HomeModule
|
import im.vector.app.features.home.HomeModule
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
|
@ -149,6 +150,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: UserCodeActivity)
|
fun inject(activity: UserCodeActivity)
|
||||||
fun inject(activity: CallTransferActivity)
|
fun inject(activity: CallTransferActivity)
|
||||||
fun inject(activity: ReAuthActivity)
|
fun inject(activity: ReAuthActivity)
|
||||||
|
fun inject(activity: RoomDevToolActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
|
|
@ -48,7 +48,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var title: String? = null
|
var title: CharSequence? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var description: CharSequence? = null
|
var description: CharSequence? = null
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class DevToolsViewEvents : VectorViewEvents {
|
||||||
|
object Dismiss : DevToolsViewEvents()
|
||||||
|
|
||||||
|
// object ShowStateList : DevToolsViewEvents()
|
||||||
|
data class ShowAlertMessage(val message: String) : DevToolsViewEvents()
|
||||||
|
data class ShowSnackMessage(val message: String) : DevToolsViewEvents()
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
|
sealed class RoomDevToolAction : VectorViewModelAction {
|
||||||
|
object ExploreRoomState : RoomDevToolAction()
|
||||||
|
object OnBackPressed : RoomDevToolAction()
|
||||||
|
object MenuEdit : RoomDevToolAction()
|
||||||
|
object MenuItemSend : RoomDevToolAction()
|
||||||
|
data class ShowStateEvent(val event: Event) : RoomDevToolAction()
|
||||||
|
data class ShowStateEventType(val stateEventType: String) : RoomDevToolAction()
|
||||||
|
data class UpdateContentText(val contentJson: String) : RoomDevToolAction()
|
||||||
|
data class SendCustomEvent(val isStateEvent: Boolean) : RoomDevToolAction()
|
||||||
|
data class CustomEventTypeChange(val type: String) : RoomDevToolAction()
|
||||||
|
data class CustomEventContentChange(val content: String) : RoomDevToolAction()
|
||||||
|
data class CustomEventStateKeyChange(val stateKey: String) : RoomDevToolAction()
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.view.forEach
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
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
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.utils.createJSonViewerStyleProvider
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.billcarsonfr.jsonviewer.JSonViewerFragment
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Factory,
|
||||||
|
FragmentManager.OnBackStackChangedListener {
|
||||||
|
|
||||||
|
@Inject lateinit var viewModelFactory: RoomDevToolViewModel.Factory
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
|
// private lateinit var viewModel: RoomDevToolViewModel
|
||||||
|
private val viewModel: RoomDevToolViewModel by viewModel()
|
||||||
|
|
||||||
|
override fun getTitleRes() = R.string.dev_tools_menu_name
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.menu_devtools
|
||||||
|
|
||||||
|
private var currentDisplayMode: RoomDevToolViewState.Mode? = null
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Args(
|
||||||
|
val roomId: String
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
super.injectWith(injector)
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(initialState: RoomDevToolViewState): RoomDevToolViewModel {
|
||||||
|
return viewModelFactory.create(initialState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initUiAndData() {
|
||||||
|
super.initUiAndData()
|
||||||
|
viewModel.subscribe(this) {
|
||||||
|
renderState(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
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)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
supportFragmentManager.addOnBackStackChangedListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderState(it: RoomDevToolViewState) {
|
||||||
|
if (it.displayMode != currentDisplayMode) {
|
||||||
|
when (it.displayMode) {
|
||||||
|
RoomDevToolViewState.Mode.Root -> {
|
||||||
|
val classJava = RoomDevToolFragment::class.java
|
||||||
|
val tag = classJava.name
|
||||||
|
if (supportFragmentManager.findFragmentByTag(tag) == null) {
|
||||||
|
replaceFragment(R.id.container, RoomDevToolFragment::class.java)
|
||||||
|
} else {
|
||||||
|
supportFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventDetail -> {
|
||||||
|
val frag = JSonViewerFragment.newInstance(
|
||||||
|
jsonString = it.selectedEventJson ?: "",
|
||||||
|
initialOpenDepth = -1,
|
||||||
|
wrap = true,
|
||||||
|
styleProvider = createJSonViewerStyleProvider(colorProvider)
|
||||||
|
)
|
||||||
|
navigateTo(frag)
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventList,
|
||||||
|
RoomDevToolViewState.Mode.StateEventListByType -> {
|
||||||
|
val frag = createFragment(RoomDevToolStateEventListFragment::class.java, Bundle().toMvRxBundle())
|
||||||
|
navigateTo(frag)
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.EditEventContent -> {
|
||||||
|
val frag = createFragment(RoomDevToolEditFragment::class.java, Bundle().toMvRxBundle())
|
||||||
|
navigateTo(frag)
|
||||||
|
}
|
||||||
|
is RoomDevToolViewState.Mode.SendEventForm -> {
|
||||||
|
val frag = createFragment(RoomDevToolSendFormFragment::class.java, Bundle().toMvRxBundle())
|
||||||
|
navigateTo(frag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentDisplayMode = it.displayMode
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
when (it.modalLoading) {
|
||||||
|
is Loading -> showWaitingView()
|
||||||
|
is Success -> hideWaitingView()
|
||||||
|
is Fail -> {
|
||||||
|
hideWaitingView()
|
||||||
|
}
|
||||||
|
Uninitialized -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == android.R.id.home) {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (item.itemId == R.id.menuItemEdit) {
|
||||||
|
viewModel.handle(RoomDevToolAction.MenuEdit)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (item.itemId == R.id.menuItemSend) {
|
||||||
|
viewModel.handle(RoomDevToolAction.MenuItemSend)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
viewModel.handle(RoomDevToolAction.OnBackPressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateTo(fragment: Fragment) {
|
||||||
|
val tag = fragment.javaClass.name
|
||||||
|
if (supportFragmentManager.findFragmentByTag(tag) == null) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
||||||
|
.replace(R.id.container, fragment, tag)
|
||||||
|
.addToBackStack(tag)
|
||||||
|
.commit()
|
||||||
|
} else {
|
||||||
|
if (!supportFragmentManager.popBackStackImmediate(tag, 0)) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
||||||
|
.replace(R.id.container, fragment, tag)
|
||||||
|
.addToBackStack(tag)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
supportFragmentManager.removeOnBackStackChangedListener(this)
|
||||||
|
currentDisplayMode = null
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu?): Boolean = withState(viewModel) { state ->
|
||||||
|
menu?.forEach {
|
||||||
|
val isVisible = when (it.itemId) {
|
||||||
|
R.id.menuItemEdit -> {
|
||||||
|
state.displayMode is RoomDevToolViewState.Mode.StateEventDetail
|
||||||
|
}
|
||||||
|
R.id.menuItemSend -> {
|
||||||
|
state.displayMode is RoomDevToolViewState.Mode.EditEventContent
|
||||||
|
|| state.displayMode is RoomDevToolViewState.Mode.SendEventForm
|
||||||
|
}
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
it.isVisible = isVisible
|
||||||
|
}
|
||||||
|
return@withState true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun intent(context: Context, roomId: String): Intent {
|
||||||
|
return Intent(context, RoomDevToolActivity::class.java).apply {
|
||||||
|
putExtra(MvRx.KEY_ARG, Args(roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackStackChanged() = withState(viewModel) { state ->
|
||||||
|
updateToolBar(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateToolBar(state: RoomDevToolViewState) {
|
||||||
|
val title = when (state.displayMode) {
|
||||||
|
RoomDevToolViewState.Mode.Root -> {
|
||||||
|
getString(getTitleRes())
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventList -> {
|
||||||
|
getString(R.string.dev_tools_state_event)
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventDetail -> {
|
||||||
|
state.selectedEvent?.type
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.EditEventContent -> {
|
||||||
|
getString(R.string.dev_tools_edit_content)
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventListByType -> {
|
||||||
|
state.currentStateType ?: ""
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supportActionBar?.let {
|
||||||
|
it.title = title
|
||||||
|
} ?: run {
|
||||||
|
setTitle(title)
|
||||||
|
}
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
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.databinding.FragmentDevtoolsEditorBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomDevToolEditFragment @Inject constructor()
|
||||||
|
: VectorBaseFragment<FragmentDevtoolsEditorBinding>() {
|
||||||
|
|
||||||
|
private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDevtoolsEditorBinding {
|
||||||
|
return FragmentDevtoolsEditorBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
withState(sharedViewModel) {
|
||||||
|
views.editText.setText(it.editedContent ?: "{}")
|
||||||
|
}
|
||||||
|
views.editText.textChanges()
|
||||||
|
.skipInitialValue()
|
||||||
|
.subscribe {
|
||||||
|
sharedViewModel.handle(RoomDevToolAction.UpdateContentText(it.toString()))
|
||||||
|
}
|
||||||
|
.disposeOnDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
views.editText.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
views.editText.hideKeyboard()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
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.databinding.FragmentGenericRecyclerBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomDevToolFragment @Inject constructor(
|
||||||
|
private val epoxyController: RoomDevToolRootController
|
||||||
|
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(),
|
||||||
|
DevToolsInteractionListener {
|
||||||
|
|
||||||
|
private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
|
||||||
|
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
views.genericRecyclerView.configureWith(epoxyController, showDivider = true)
|
||||||
|
epoxyController.interactionListener = this
|
||||||
|
|
||||||
|
// sharedViewModel.observeViewEvents {
|
||||||
|
// when (it) {
|
||||||
|
// is DevToolsViewEvents.showJson -> {
|
||||||
|
// JSonViewerDialog.newInstance(it.jsonString, -1, createJSonViewerStyleProvider(colorProvider))
|
||||||
|
// .show(childFragmentManager, "JSON_VIEWER")
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
views.genericRecyclerView.cleanup()
|
||||||
|
epoxyController.interactionListener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processAction(action: RoomDevToolAction) {
|
||||||
|
sharedViewModel.handle(action)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
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
|
||||||
|
) : EpoxyController() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionListener: DevToolsInteractionListener? = null
|
||||||
|
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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(
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) : TypedEpoxyController<RoomDevToolViewState>() {
|
||||||
|
|
||||||
|
var interactionListener: DevToolsInteractionListener? = null
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomDevToolViewState?) {
|
||||||
|
val sendEventForm = (data?.displayMode as? RoomDevToolViewState.Mode.SendEventForm) ?: return
|
||||||
|
|
||||||
|
genericFooterItem {
|
||||||
|
id("topSpace")
|
||||||
|
text("")
|
||||||
|
}
|
||||||
|
formEditTextItem {
|
||||||
|
id("event_type")
|
||||||
|
enabled(true)
|
||||||
|
value(data.sendEventDraft?.type)
|
||||||
|
hint(stringProvider.getString(R.string.dev_tools_form_hint_type))
|
||||||
|
showBottomSeparator(false)
|
||||||
|
onTextChange { text ->
|
||||||
|
interactionListener?.processAction(RoomDevToolAction.CustomEventTypeChange(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendEventForm.isState) {
|
||||||
|
formEditTextItem {
|
||||||
|
id("state_key")
|
||||||
|
enabled(true)
|
||||||
|
value(data.sendEventDraft?.stateKey)
|
||||||
|
hint(stringProvider.getString(R.string.dev_tools_form_hint_state_key))
|
||||||
|
showBottomSeparator(false)
|
||||||
|
onTextChange { text ->
|
||||||
|
interactionListener?.processAction(RoomDevToolAction.CustomEventStateKeyChange(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formMultiLineEditTextItem {
|
||||||
|
id("event_content")
|
||||||
|
enabled(true)
|
||||||
|
value(data.sendEventDraft?.content)
|
||||||
|
hint(stringProvider.getString(R.string.dev_tools_form_hint_event_content))
|
||||||
|
showBottomSeparator(false)
|
||||||
|
onTextChange { text ->
|
||||||
|
interactionListener?.processAction(RoomDevToolAction.CustomEventContentChange(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
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.databinding.FragmentGenericRecyclerBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomDevToolSendFormFragment @Inject constructor(
|
||||||
|
private val epoxyController: RoomDevToolSendFormController
|
||||||
|
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener {
|
||||||
|
|
||||||
|
val sharedViewModel: RoomDevToolViewModel by activityViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
|
||||||
|
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
views.genericRecyclerView.configureWith(epoxyController, showDivider = false)
|
||||||
|
epoxyController.interactionListener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
views.genericRecyclerView.cleanup()
|
||||||
|
epoxyController.interactionListener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||||
|
epoxyController.setData(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processAction(action: RoomDevToolAction) {
|
||||||
|
sharedViewModel.handle(action)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
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.databinding.FragmentGenericRecyclerBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomDevToolStateEventListFragment @Inject constructor(
|
||||||
|
private val epoxyController: RoomStateListController
|
||||||
|
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener {
|
||||||
|
|
||||||
|
val sharedViewModel: RoomDevToolViewModel by activityViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
|
||||||
|
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
views.genericRecyclerView.configureWith(epoxyController, showDivider = true)
|
||||||
|
epoxyController.interactionListener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
views.genericRecyclerView.cleanup()
|
||||||
|
epoxyController.interactionListener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||||
|
epoxyController.setData(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processAction(action: RoomDevToolAction) {
|
||||||
|
sharedViewModel.handle(action)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
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
|
||||||
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
|
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) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: RoomDevToolViewState): RoomDevToolViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<RoomDevToolViewModel, RoomDevToolViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: RoomDevToolViewState): RoomDevToolViewModel {
|
||||||
|
val factory = when (viewModelContext) {
|
||||||
|
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||||
|
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||||
|
}
|
||||||
|
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
session.getRoom(initialState.roomId)
|
||||||
|
?.rx()
|
||||||
|
?.liveStateEvents(emptySet())
|
||||||
|
?.execute { async ->
|
||||||
|
copy(stateEvents = async)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: RoomDevToolAction) {
|
||||||
|
when (action) {
|
||||||
|
RoomDevToolAction.ExploreRoomState -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
displayMode = RoomDevToolViewState.Mode.StateEventList,
|
||||||
|
selectedEvent = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.ShowStateEvent -> {
|
||||||
|
val jsonString = MoshiProvider.providesMoshi()
|
||||||
|
.adapter(Event::class.java)
|
||||||
|
.toJson(action.event)
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
displayMode = RoomDevToolViewState.Mode.StateEventDetail,
|
||||||
|
selectedEvent = action.event,
|
||||||
|
selectedEventJson = jsonString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolAction.OnBackPressed -> {
|
||||||
|
handleBack()
|
||||||
|
}
|
||||||
|
RoomDevToolAction.MenuEdit -> {
|
||||||
|
withState {
|
||||||
|
if (it.displayMode == RoomDevToolViewState.Mode.StateEventDetail) {
|
||||||
|
// we want to edit it
|
||||||
|
val content = it.selectedEvent?.content?.let { JSONObject(it).toString(4) } ?: "{\n\t\n}"
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
editedContent = content,
|
||||||
|
displayMode = RoomDevToolViewState.Mode.EditEventContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.ShowStateEventType -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
displayMode = RoomDevToolViewState.Mode.StateEventListByType,
|
||||||
|
currentStateType = action.stateEventType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolAction.MenuItemSend -> {
|
||||||
|
handleMenuItemSend()
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.UpdateContentText -> {
|
||||||
|
setState {
|
||||||
|
copy(editedContent = action.contentJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.SendCustomEvent -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
displayMode = RoomDevToolViewState.Mode.SendEventForm(action.isStateEvent),
|
||||||
|
sendEventDraft = RoomDevToolViewState.SendEventDraft(EventType.MESSAGE, null, "{\n}")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.CustomEventTypeChange -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
sendEventDraft = sendEventDraft?.copy(type = action.type)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.CustomEventStateKeyChange -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
sendEventDraft = sendEventDraft?.copy(stateKey = action.stateKey)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolAction.CustomEventContentChange -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
sendEventDraft = sendEventDraft?.copy(content = action.content)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMenuItemSend() = withState { state ->
|
||||||
|
when (state.displayMode) {
|
||||||
|
RoomDevToolViewState.Mode.EditEventContent -> editEventContent(state)
|
||||||
|
is RoomDevToolViewState.Mode.SendEventForm -> sendEventContent(state, state.displayMode.isState)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun editEventContent(state: RoomDevToolViewState) {
|
||||||
|
setState { copy(modalLoading = Loading()) }
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val room = session.getRoom(initialState.roomId)
|
||||||
|
?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
|
||||||
|
|
||||||
|
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)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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))
|
||||||
|
|
||||||
|
val eventType = state.sendEventDraft?.type
|
||||||
|
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type))
|
||||||
|
|
||||||
|
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 -> {
|
||||||
|
_viewEvents.post(DevToolsViewEvents.Dismiss)
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventList -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
selectedEvent = null,
|
||||||
|
selectedEventJson = null,
|
||||||
|
displayMode = RoomDevToolViewState.Mode.Root
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventDetail -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
selectedEvent = null,
|
||||||
|
selectedEventJson = null,
|
||||||
|
displayMode = RoomDevToolViewState.Mode.StateEventListByType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.EditEventContent -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
displayMode = RoomDevToolViewState.Mode.StateEventDetail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventListByType -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
currentStateType = null,
|
||||||
|
displayMode = RoomDevToolViewState.Mode.StateEventList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RoomDevToolViewState.Mode.SendEventForm -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
displayMode = RoomDevToolViewState.Mode.Root
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
|
data class RoomDevToolViewState(
|
||||||
|
val roomId: String = "",
|
||||||
|
val displayMode: Mode = Mode.Root,
|
||||||
|
val stateEvents: Async<List<Event>> = Uninitialized,
|
||||||
|
val currentStateType: String? = null,
|
||||||
|
val selectedEvent: Event? = null,
|
||||||
|
val selectedEventJson: String? = null,
|
||||||
|
val editedContent: String? = null,
|
||||||
|
val modalLoading: Async<Unit> = Uninitialized,
|
||||||
|
val sendEventDraft: SendEventDraft? = null
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: RoomDevToolActivity.Args) : this(roomId = args.roomId, displayMode = Mode.Root)
|
||||||
|
|
||||||
|
sealed class Mode {
|
||||||
|
object Root : Mode()
|
||||||
|
object StateEventList : Mode()
|
||||||
|
object StateEventListByType : Mode()
|
||||||
|
object StateEventDetail : Mode()
|
||||||
|
object EditEventContent : Mode()
|
||||||
|
data class SendEventForm(val isState: Boolean) : Mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SendEventDraft(
|
||||||
|
val type: String?,
|
||||||
|
val stateKey: String?,
|
||||||
|
val content: String?
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.noResultItem
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.ui.list.GenericItem
|
||||||
|
import im.vector.app.core.ui.list.genericItem
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import org.json.JSONObject
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomStateListController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val colorProvider: ColorProvider
|
||||||
|
) : TypedEpoxyController<RoomDevToolViewState>() {
|
||||||
|
|
||||||
|
var interactionListener: DevToolsInteractionListener? = null
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomDevToolViewState?) {
|
||||||
|
when (data?.displayMode) {
|
||||||
|
RoomDevToolViewState.Mode.StateEventList -> {
|
||||||
|
val stateEventsGroups = data.stateEvents.invoke().orEmpty().groupBy { it.type }
|
||||||
|
|
||||||
|
if (stateEventsGroups.isEmpty()) {
|
||||||
|
noResultItem {
|
||||||
|
id("no state events")
|
||||||
|
text(stringProvider.getString(R.string.no_result_placeholder))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stateEventsGroups.forEach { entry ->
|
||||||
|
genericItem {
|
||||||
|
id(entry.key)
|
||||||
|
title(entry.key)
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomDevToolViewState.Mode.StateEventListByType -> {
|
||||||
|
val stateEvents = data.stateEvents.invoke().orEmpty().filter { it.type == data.currentStateType }
|
||||||
|
if (stateEvents.isEmpty()) {
|
||||||
|
noResultItem {
|
||||||
|
id("no state events")
|
||||||
|
text(stringProvider.getString(R.string.no_result_placeholder))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stateEvents.forEach { stateEvent ->
|
||||||
|
val contentJson = JSONObject(stateEvent.content.orEmpty()).toString().let {
|
||||||
|
if (it.length > 140) {
|
||||||
|
it.take(140) + Typography.ellipsis
|
||||||
|
} else {
|
||||||
|
it.take(140)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
genericItem {
|
||||||
|
id(stateEvent.eventId)
|
||||||
|
title(span {
|
||||||
|
+"Type: "
|
||||||
|
span {
|
||||||
|
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||||
|
text = "\"${stateEvent.type}\""
|
||||||
|
textStyle = "normal"
|
||||||
|
}
|
||||||
|
+"\nState Key: "
|
||||||
|
span {
|
||||||
|
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||||
|
text = stateEvent.stateKey.let { "\"$it\"" }
|
||||||
|
textStyle = "normal"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
description(contentJson)
|
||||||
|
itemClickAction(GenericItem.Action("view").apply {
|
||||||
|
perform = Runnable {
|
||||||
|
interactionListener?.processAction(RoomDevToolAction.ShowStateEvent(stateEvent))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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.form
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.text.Editable
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.extensions.setTextSafe
|
||||||
|
import im.vector.app.core.platform.SimpleTextWatcher
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_form_multiline_text_input)
|
||||||
|
abstract class FormMultiLineEditTextItem : VectorEpoxyModel<FormMultiLineEditTextItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var hint: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var value: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var showBottomSeparator: Boolean = true
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var errorMessage: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var enabled: Boolean = true
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var textSizeSp: Int? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var minLines: Int = 3
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var typeFace: Typeface = Typeface.DEFAULT
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var onTextChange: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
private val onTextChangeListener = object : SimpleTextWatcher() {
|
||||||
|
override fun afterTextChanged(s: Editable) {
|
||||||
|
onTextChange?.invoke(s.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.textInputLayout.isEnabled = enabled
|
||||||
|
holder.textInputLayout.hint = hint
|
||||||
|
holder.textInputLayout.error = errorMessage
|
||||||
|
|
||||||
|
holder.textInputEditText.typeface = typeFace
|
||||||
|
holder.textInputEditText.textSize = textSizeSp?.toFloat() ?: 12f
|
||||||
|
holder.textInputEditText.minLines = minLines
|
||||||
|
|
||||||
|
// Update only if text is different and value is not null
|
||||||
|
holder.textInputEditText.setTextSafe(value)
|
||||||
|
holder.textInputEditText.isEnabled = enabled
|
||||||
|
|
||||||
|
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||||
|
holder.bottomSeparator.isVisible = showBottomSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldSaveViewState(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
super.unbind(holder)
|
||||||
|
holder.textInputEditText.removeTextChangedListener(onTextChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val textInputLayout by bind<TextInputLayout>(R.id.formMultiLineTextInputLayout)
|
||||||
|
val textInputEditText by bind<TextInputEditText>(R.id.formMultiLineEditText)
|
||||||
|
val bottomSeparator by bind<View>(R.id.formTextInputDivider)
|
||||||
|
}
|
||||||
|
}
|
|
@ -789,6 +789,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
handleSearchAction()
|
handleSearchAction()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.dev_tools -> {
|
||||||
|
navigator.openDevTools(requireContext(), roomDetailArgs.roomId)
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -625,10 +625,11 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||||
R.id.open_matrix_apps -> true
|
R.id.open_matrix_apps -> true
|
||||||
R.id.voice_call,
|
R.id.voice_call,
|
||||||
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
|
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
|
||||||
R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty()
|
R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty()
|
||||||
R.id.search -> true
|
R.id.search -> true
|
||||||
else -> false
|
R.id.dev_tools -> vectorPreferences.developerMode()
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ import im.vector.app.features.crypto.recover.SetupMode
|
||||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.debug.DebugMenuActivity
|
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.RoomDetailActivity
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||||
import im.vector.app.features.home.room.detail.search.SearchActivity
|
import im.vector.app.features.home.room.detail.search.SearchActivity
|
||||||
|
@ -357,6 +358,10 @@ class DefaultNavigator @Inject constructor(
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openDevTools(context: Context, roomId: String) {
|
||||||
|
context.startActivity(RoomDevToolActivity.intent(context, roomId))
|
||||||
|
}
|
||||||
|
|
||||||
override fun openCallTransfer(context: Context, callId: String) {
|
override fun openCallTransfer(context: Context, callId: String) {
|
||||||
val intent = CallTransferActivity.newIntent(context, callId)
|
val intent = CallTransferActivity.newIntent(context, callId)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
|
|
@ -118,5 +118,7 @@ interface Navigator {
|
||||||
|
|
||||||
fun openSearch(context: Context, roomId: String)
|
fun openSearch(context: Context, roomId: String)
|
||||||
|
|
||||||
|
fun openDevTools(context: Context, roomId: String)
|
||||||
|
|
||||||
fun openCallTransfer(context: Context, callId: String)
|
fun openCallTransfer(context: Context, callId: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ class RoomMemberListController @Inject constructor(
|
||||||
?.filter { event ->
|
?.filter { event ->
|
||||||
event.content.toModel<RoomThirdPartyInviteContent>()
|
event.content.toModel<RoomThirdPartyInviteContent>()
|
||||||
?.takeIf {
|
?.takeIf {
|
||||||
data.filter.isEmpty() || it.displayName.contains(data.filter, ignoreCase = true)
|
data.filter.isEmpty() || it.displayName?.contains(data.filter, ignoreCase = true) == true
|
||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
20
vector/src/main/res/layout/fragment_devtools_editor.xml
Normal file
20
vector/src/main/res/layout/fragment_devtools_editor.xml
Normal file
|
@ -0,0 +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="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:gravity="top|start"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:scrollHorizontally="true"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:minHeight="@dimen/item_form_min_height">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/formMultiLineTextInputLayout"
|
||||||
|
style="@style/VectorTextInputLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:errorEnabled="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/formTextInputDivider"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<!-- android:imeOptions="actionDone" to fix a crash -->
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/formMultiLineEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="top|start"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:minLines="4"
|
||||||
|
tools:hint="@string/create_room_name_hint" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/formTextInputDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="?riotx_header_panel_border_mobile"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -5,6 +5,7 @@
|
||||||
android:id="@+id/item_generic_root"
|
android:id="@+id/item_generic_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
android:minHeight="50dp">
|
android:minHeight="50dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
22
vector/src/main/res/menu/menu_devtools.xml
Normal file
22
vector/src/main/res/menu/menu_devtools.xml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menuItemEdit"
|
||||||
|
android:visible="false"
|
||||||
|
tools:visible="true"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:icon="@drawable/ic_edit"
|
||||||
|
android:title="@string/edit" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menuItemSend"
|
||||||
|
android:visible="false"
|
||||||
|
tools:visible="true"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:icon="@drawable/ic_send"
|
||||||
|
android:title="@string/send" />
|
||||||
|
|
||||||
|
</menu>
|
|
@ -68,4 +68,12 @@
|
||||||
app:showAsAction="never"
|
app:showAsAction="never"
|
||||||
tools:visible="true" />
|
tools:visible="true" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/dev_tools"
|
||||||
|
android:icon="@drawable/ic_settings_root_general"
|
||||||
|
android:title="@string/dev_tools_menu_name"
|
||||||
|
android:visible="false"
|
||||||
|
app:showAsAction="never"
|
||||||
|
tools:visible="true" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -2750,6 +2750,10 @@
|
||||||
<item quantity="one">Wrong code, %d remaining attempt</item>
|
<item quantity="one">Wrong code, %d remaining attempt</item>
|
||||||
<item quantity="other">Wrong code, %d remaining attempts</item>
|
<item quantity="other">Wrong code, %d remaining attempts</item>
|
||||||
</plurals>
|
</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="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="too_many_pin_failures">Too many errors, you\'ve been logged out</string>
|
||||||
<string name="create_pin_title">Choose a PIN for security</string>
|
<string name="create_pin_title">Choose a PIN for security</string>
|
||||||
|
@ -2827,4 +2831,20 @@
|
||||||
<string name="re_authentication_activity_title">Re-Authentication Needed</string>
|
<string name="re_authentication_activity_title">Re-Authentication Needed</string>
|
||||||
<string name="re_authentication_default_confirm_text">Element requires you to enter your credentials to perform this action.</string>
|
<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="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>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue