Widgets: fix some issues with navigation

This commit is contained in:
ganfra 2020-06-19 20:38:30 +02:00
parent ba0823f4d0
commit 234dfa18d3
21 changed files with 198 additions and 133 deletions

View file

@ -90,6 +90,6 @@ interface WidgetPostAPIMediator {
/**
* Triggered when a widget is posting
*/
fun handleWidgetRequest(eventData: JsonDict): Boolean
fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean
}
}

View file

@ -34,7 +34,8 @@ interface WidgetService {
fun getWidgetURLFormatter(): WidgetURLFormatter
/**
* Returns an instance of [WidgetPostAPIMediator].
* Returns a new instance of [WidgetPostAPIMediator].
* Be careful to call clearWebView method and setHandler to null to avoid memory leaks.
* This is to be used for "admin" widgets so you can interact through JS.
*/
fun getWidgetPostAPIMediator(): WidgetPostAPIMediator

View file

@ -16,6 +16,22 @@
package im.vector.matrix.android.api.session.widgets.model
private val DEFINED_TYPES by lazy {
listOf(
WidgetType.Jitsi,
WidgetType.TradingView,
WidgetType.Spotify,
WidgetType.Video,
WidgetType.GoogleDoc,
WidgetType.GoogleCalendar,
WidgetType.Etherpad,
WidgetType.StickerPicker,
WidgetType.Grafana,
WidgetType.Custom,
WidgetType.IntegrationManager
)
}
sealed class WidgetType(open val preferred: String, open val legacy: String = preferred) {
object Jitsi : WidgetType("m.jitsi", "jitsi")
object TradingView : WidgetType("m.tradingview")
@ -30,7 +46,7 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr
object IntegrationManager : WidgetType("m.integration_manager")
data class Fallback(override val preferred: String) : WidgetType(preferred)
fun matches(type: String?): Boolean {
fun matches(type: String): Boolean {
return type == preferred || type == legacy
}
@ -40,20 +56,6 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr
companion object {
private val DEFINED_TYPES = listOf(
Jitsi,
TradingView,
Spotify,
Video,
GoogleDoc,
GoogleCalendar,
Etherpad,
StickerPicker,
Grafana,
Custom,
IntegrationManager
)
fun fromString(type: String): WidgetType {
val matchingType = DEFINED_TYPES.firstOrNull {
it.matches(type)

View file

@ -78,7 +78,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
private fun onWidgetMessage(eventData: JsonDict) {
try {
if (handler?.handleWidgetRequest(eventData) == false) {
if (handler?.handleWidgetRequest(this, eventData) == false) {
sendError("", eventData)
}
} catch (e: Exception) {

View file

@ -26,10 +26,11 @@ import im.vector.matrix.android.api.session.widgets.WidgetURLFormatter
import im.vector.matrix.android.api.session.widgets.model.Widget
import im.vector.matrix.android.api.util.Cancelable
import javax.inject.Inject
import javax.inject.Provider
internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
private val widgetURLFormatter: WidgetURLFormatter,
private val widgetPostAPIMediator: WidgetPostAPIMediator)
private val widgetPostAPIMediator: Provider<WidgetPostAPIMediator>)
: WidgetService {
override fun getWidgetURLFormatter(): WidgetURLFormatter {
@ -37,7 +38,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
}
override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator {
return widgetPostAPIMediator
return widgetPostAPIMediator.get()
}
override fun getRoomWidgets(

View file

@ -25,7 +25,6 @@ import im.vector.matrix.android.api.session.widgets.WidgetManagementFailure
import im.vector.matrix.android.internal.session.widgets.WidgetsAPI
import im.vector.matrix.android.internal.session.widgets.WidgetsAPIProvider
import im.vector.matrix.android.internal.task.Task
import timber.log.Timber
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@ -68,7 +67,7 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets
throw IllegalStateException("Scalar token is null")
}
scalarTokenStore.setToken(serverUrl, registerWidgetResponse.scalarToken)
return validateToken(widgetsAPI,serverUrl, registerWidgetResponse.scalarToken)
return validateToken(widgetsAPI, serverUrl, registerWidgetResponse.scalarToken)
}
private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String {

View file

@ -76,4 +76,5 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
object SelectStickerAttachment : RoomDetailAction()
object OpenIntegrationManager: RoomDetailAction()
}

View file

@ -134,7 +134,6 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
@ -149,6 +148,7 @@ import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBannerView
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.PillImageSpan
import im.vector.riotx.features.invite.VectorInviteView
@ -159,6 +159,7 @@ import im.vector.riotx.features.permalink.NavigationInterceptor
import im.vector.riotx.features.permalink.PermalinkHandler
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.settings.VectorSettingsActivity
import im.vector.riotx.features.share.SharedData
import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.widgets.WidgetActivity
@ -302,22 +303,33 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.observeViewEvents {
when (it) {
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayEnableIntegrationsWarning()
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
}.exhaustive
}
}
private fun openIntegrationManager(screen: String? = null) {
navigator.openIntegrationManager(
fragment = this,
roomId = roomDetailArgs.roomId,
integId = null,
screen = screen
)
}
private fun setupWidgetsBannerView() {
roomWidgetsBannerView.callback = this
}
@ -334,10 +346,7 @@ class RoomDetailFragment @Inject constructor(
.setView(v)
.setPositiveButton(R.string.yes) { _, _ ->
// Open integration manager, to the sticker installation page
navigator.openIntegrationManager(
context = requireContext(),
roomId = roomDetailArgs.roomId,
integId = null,
openIntegrationManager(
screen = WidgetType.StickerPicker.preferred
)
}
@ -345,6 +354,17 @@ class RoomDetailFragment @Inject constructor(
.show()
}
private fun displayEnableIntegrationsWarning() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.integration_manager_not_enabled_title)
.setMessage(R.string.integration_manager_not_enabled_msg)
.setPositiveButton(R.string.open_settings) { _, _ ->
navigator.openSettings(requireContext(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_GENERAL)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) {
updateComposerText("")
lockSendButton = false
@ -469,7 +489,7 @@ class RoomDetailFragment @Inject constructor(
true
}
R.id.open_matrix_apps -> {
navigator.openIntegrationManager(requireContext(), roomDetailArgs.roomId, null, null)
roomDetailViewModel.handle(RoomDetailAction.OpenIntegrationManager)
true
}
else -> super.onOptionsItemSelected(item)
@ -548,16 +568,16 @@ class RoomDetailFragment @Inject constructor(
val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data)
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
when (requestCode) {
AttachmentsPreviewActivity.REQUEST_CODE -> {
AttachmentsPreviewActivity.REQUEST_CODE -> {
val sendData = AttachmentsPreviewActivity.getOutput(data)
val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
}
REACTION_SELECT_REQUEST_CODE -> {
REACTION_SELECT_REQUEST_CODE -> {
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
}
StickerPickerConstants.STICKER_PICKER_REQUEST_CODE -> {
WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE -> {
val content = WidgetActivity.getOutput(data).toModel<MessageStickerContent>() ?: return
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
}

View file

@ -52,8 +52,12 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
object DisplayEnableIntegrationsWarning: RoomDetailViewEvents()
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
object OpenIntegrationManager: RoomDetailViewEvents()
object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
class SlashCommandError(val command: Command) : SendMessageResult()

View file

@ -79,7 +79,9 @@ import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import timber.log.Timber
@ -252,6 +254,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
}
}
@ -266,6 +269,19 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
private fun handleOpenIntegrationManager() {
viewModelScope.launch {
val viewEvent = withContext(Dispatchers.Default) {
if (isIntegrationEnabled()) {
RoomDetailViewEvents.OpenIntegrationManager
} else {
RoomDetailViewEvents.DisplayEnableIntegrationsWarning
}
}
_viewEvents.post(viewEvent)
}
}
private fun startTrackingUnreadMessages() {
trackUnreadMessages.set(true)
setState { copy(canShowJumpToReadMarker = false) }
@ -365,13 +381,15 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled()
fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) {
R.id.clear_message_queue ->
/* For now always disable on production, worker cancellation is not working properly */
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
R.id.open_matrix_apps -> session.integrationManagerService().isIntegrationEnabled()
R.id.open_matrix_apps -> true
else -> false
}

View file

@ -27,6 +27,10 @@ class StickerPickerActionHandler @Inject constructor(private val session: Sessio
suspend fun handle(): RoomDetailViewEvents = withContext(Dispatchers.Default) {
// Search for the sticker picker widget in the user account
val integrationsEnabled = session.integrationManagerService().isIntegrationEnabled()
if (!integrationsEnabled) {
return@withContext RoomDetailViewEvents.DisplayEnableIntegrationsWarning
}
val stickerWidget = session.widgetService().getUserWidgets(WidgetType.StickerPicker.values()).firstOrNull { it.isActive }
if (stickerWidget == null || stickerWidget.computedUrl.isNullOrBlank()) {
RoomDetailViewEvents.DisplayPromptForIntegrationManager

View file

@ -14,8 +14,9 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.room.detail.sticker
package im.vector.riotx.features.home.room.detail.widget
object StickerPickerConstants {
object WidgetRequestCodes {
const val STICKER_PICKER_REQUEST_CODE = 16000
const val INTEGRATION_MANAGER_REQUEST_CODE = 16001
}

View file

@ -30,8 +30,8 @@ import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.session.widgets.model.Widget
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.error.fatalError
@ -46,7 +46,7 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.debug.DebugMenuActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
import im.vector.riotx.features.media.BigImageViewerActivity
@ -230,12 +230,13 @@ class DefaultNavigator @Inject constructor(
override fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, requestCode: Int) {
val widgetArgs = widgetArgsBuilder.buildStickerPickerArgs(roomId, widget)
val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
fragment.startActivityForResult(intent, StickerPickerConstants.STICKER_PICKER_REQUEST_CODE)
fragment.startActivityForResult(intent, WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
}
override fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?) {
override fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?) {
val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screen)
context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
fragment.startActivityForResult(intent, WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE)
}
override fun openRoomWidget(context: Context, roomId: String, widget: Widget) {

View file

@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.session.widgets.model.Widget
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer
import im.vector.riotx.features.settings.VectorSettingsActivity
@ -85,9 +85,9 @@ interface Navigator {
fun openStickerPicker(fragment: Fragment,
roomId: String,
widget: Widget,
requestCode: Int = StickerPickerConstants.STICKER_PICKER_REQUEST_CODE)
requestCode: Int = WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?)
fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?)
fun openRoomWidget(context: Context, roomId: String, widget: Widget)

View file

@ -59,6 +59,8 @@ class VectorSettingsActivity : VectorBaseActivity(),
if (isFirstCreation()) {
// display the fragment
when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) {
EXTRA_DIRECT_ACCESS_GENERAL ->
replaceFragment(R.id.vector_settings_page, VectorSettingsGeneralFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS ->
replaceFragment(R.id.vector_settings_page, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY ->
@ -137,6 +139,7 @@ class VectorSettingsActivity : VectorBaseActivity(),
const val EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS = 1
const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY = 2
const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS = 3
const val EXTRA_DIRECT_ACCESS_GENERAL = 4
private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
}

View file

@ -40,6 +40,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.openUrlInExternalBrowser
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.terms.ReviewTermsActivity
import im.vector.riotx.features.webview.WebViewEventListener
import im.vector.riotx.features.widgets.webview.clearAfterWidget
@ -77,7 +78,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
Timber.v("Observed view events: $it")
when (it) {
is WidgetViewEvents.DisplayTerms -> displayTerms(it)
is WidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it)
is WidgetViewEvents.OnURLFormatted -> loadFormattedUrl(it)
is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it)
is WidgetViewEvents.Failure -> displayErrorDialog(it.throwable)
}
@ -86,11 +87,17 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
viewModel.handle(WidgetAction.OnTermsReviewed)
} else {
vectorBaseActivity.finish()
when (requestCode) {
ReviewTermsActivity.TERMS_REQUEST_CODE -> {
Timber.v("On terms results")
if (resultCode == Activity.RESULT_OK) {
viewModel.handle(WidgetAction.OnTermsReviewed)
} else {
vectorBaseActivity.finish()
}
}
WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE -> {
viewModel.handle(WidgetAction.LoadFormattedUrl)
}
}
}
@ -139,7 +146,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { state ->
when (item.itemId) {
R.id.action_edit -> {
navigator.openIntegrationManager(requireContext(), state.roomId, state.widgetId, state.widgetKind.screenId)
navigator.openIntegrationManager(this, state.roomId, state.widgetId, state.widgetKind.screenId)
return@withState true
}
R.id.action_delete -> {
@ -261,9 +268,9 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
)
}
private fun loadFormattedUrl(loadFormattedUrl: WidgetViewEvents.LoadFormattedURL) {
private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) {
widgetWebView.clearHistory()
widgetWebView.loadUrl(loadFormattedUrl.formattedURL)
widgetWebView.loadUrl(event.formattedURL)
}
private fun setStateError(message: String?) {
@ -280,7 +287,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
private fun displayIntegrationManager(event: WidgetViewEvents.DisplayIntegrationManager) {
navigator.openIntegrationManager(
context = vectorBaseActivity,
fragment = this,
roomId = fragmentArgs.roomId,
integId = event.integId,
screen = event.integType

View file

@ -39,13 +39,12 @@ import java.util.ArrayList
import java.util.HashMap
class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roomId: String,
@Assisted private val navigationCallback: NavigationCallback,
private val stringProvider: StringProvider,
private val session: Session) : WidgetPostAPIMediator.Handler {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String, navigationCallback: NavigationCallback): WidgetPostAPIHandler
fun create(roomId: String): WidgetPostAPIHandler
}
interface NavigationCallback {
@ -54,31 +53,31 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
fun openIntegrationManager(integId: String?, integType: String?)
}
private val widgetPostAPIMediator = session.widgetService().getWidgetPostAPIMediator()
private val room = session.getRoom(roomId)!!
var navigationCallback: NavigationCallback? = null
override fun handleWidgetRequest(eventData: JsonDict): Boolean {
override fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean {
return when (eventData["action"] as String?) {
"integration_manager_open" -> handleIntegrationManagerOpenAction(eventData).run { true }
"bot_options" -> getBotOptions(eventData).run { true }
"can_send_event" -> canSendEvent(eventData).run { true }
"bot_options" -> getBotOptions(mediator, eventData).run { true }
"can_send_event" -> canSendEvent(mediator, eventData).run { true }
"close_scalar" -> handleCloseScalar().run { true }
"get_membership_count" -> getMembershipCount(eventData).run { true }
"get_widgets" -> getWidgets(eventData).run { true }
"invite" -> inviteUser(eventData).run { true }
"join_rules_state" -> getJoinRules(eventData).run { true }
"membership_state" -> getMembershipState(eventData).run { true }
"set_bot_options" -> setBotOptions(eventData).run { true }
"set_bot_power" -> setBotPower(eventData).run { true }
"set_plumbing_state" -> setPlumbingState(eventData).run { true }
"set_widget" -> setWidget(eventData).run { true }
"m.sticker" -> pickStickerData(eventData).run { true }
"get_membership_count" -> getMembershipCount(mediator, eventData).run { true }
"get_widgets" -> getWidgets(mediator, eventData).run { true }
"invite" -> inviteUser(mediator, eventData).run { true }
"join_rules_state" -> getJoinRules(mediator, eventData).run { true }
"membership_state" -> getMembershipState(mediator, eventData).run { true }
"set_bot_options" -> setBotOptions(mediator, eventData).run { true }
"set_bot_power" -> setBotPower(mediator, eventData).run { true }
"set_plumbing_state" -> setPlumbingState(mediator, eventData).run { true }
"set_widget" -> setWidget(mediator, eventData).run { true }
"m.sticker" -> pickStickerData(mediator, eventData).run { true }
else -> false
}
}
private fun handleCloseScalar() {
navigationCallback.close()
navigationCallback?.close()
}
private fun handleIntegrationManagerOpenAction(eventData: JsonDict) {
@ -101,7 +100,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
// Add "type_" as a prefix
integType?.let { integType = "type_$integType" }
}
navigationCallback.openIntegrationManager(integId, integType)
navigationCallback?.openIntegrationManager(integId, integType)
}
/**
@ -109,8 +108,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun getBotOptions(eventData: JsonDict) {
if (checkRoomId(eventData) || checkUserId(eventData)) {
private fun getBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@ -134,8 +133,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
}
}
private fun canSendEvent(eventData: JsonDict) {
if (checkRoomId(eventData)) {
private fun canSendEvent(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request canSendEvent in room $roomId")
@ -170,8 +169,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun getMembershipState(eventData: JsonDict) {
if (checkRoomId(eventData) || checkUserId(eventData)) {
private fun getMembershipState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@ -189,8 +188,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun getJoinRules(eventData: JsonDict) {
if (checkRoomId(eventData)) {
private fun getJoinRules(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request join rules in room $roomId")
@ -207,8 +206,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun getWidgets(eventData: JsonDict) {
if (checkRoomId(eventData)) {
private fun getWidgets(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request to get widget in room $roomId")
@ -227,12 +226,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun setWidget(eventData: JsonDict) {
private fun setWidget(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
val userWidget = eventData["userWidget"] as Boolean?
if (userWidget == true) {
Timber.d("Received request to set widget for user")
} else {
if (checkRoomId(eventData)) {
if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request to set widget in room $roomId")
@ -283,14 +282,14 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
session.updateAccountData(
type = UserAccountData.TYPE_WIDGETS,
content = addUserWidgetBody,
callback = createWidgetAPICallback(eventData)
callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
} else {
session.widgetService().createRoomWidget(
roomId = roomId,
widgetId = widgetId,
content = widgetEventContent,
callback = createWidgetAPICallback(eventData)
callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
}
}
@ -300,8 +299,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun setPlumbingState(eventData: JsonDict) {
if (checkRoomId(eventData)) {
private fun setPlumbingState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
val description = "Received request to set plumbing state to status " + eventData["status"] + " in room " + roomId + " requested"
@ -315,7 +314,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
eventType = EventType.PLUMBING,
stateKey = null,
body = params,
callback = createWidgetAPICallback(eventData)
callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
}
@ -325,8 +324,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
* @param eventData the modular data
*/
@Suppress("UNCHECKED_CAST")
private fun setBotOptions(eventData: JsonDict) {
if (checkRoomId(eventData) || checkUserId(eventData)) {
private fun setBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@ -338,7 +337,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
eventType = EventType.BOT_OPTIONS,
stateKey = stateKey,
body = content,
callback = createWidgetAPICallback(eventData)
callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
}
@ -347,8 +346,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun setBotPower(eventData: JsonDict) {
if (checkRoomId(eventData) || checkUserId(eventData)) {
private fun setBotPower(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@ -369,8 +368,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun inviteUser(eventData: JsonDict) {
if (checkRoomId(eventData) || checkUserId(eventData)) {
private fun inviteUser(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@ -380,7 +379,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
if (member != null && member.membership == Membership.JOIN) {
widgetPostAPIMediator.sendSuccess(eventData)
} else {
room.invite(userId = userId, callback = createWidgetAPICallback(eventData))
room.invite(userId = userId, callback = createWidgetAPICallback(widgetPostAPIMediator, eventData))
}
}
@ -389,8 +388,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
private fun getMembershipCount(eventData: JsonDict) {
if (checkRoomId(eventData)) {
private fun getMembershipCount(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
val numberOfJoinedMembers = room.getNumberOfJoinedMembers()
@ -398,7 +397,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
}
@Suppress("UNCHECKED_CAST")
private fun pickStickerData(eventData: JsonDict) {
private fun pickStickerData(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
Timber.d("Received request send sticker")
val data = eventData["data"]
if (data == null) {
@ -411,7 +410,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
return
}
widgetPostAPIMediator.sendSuccess(eventData)
navigationCallback.closeWithResult(content)
navigationCallback?.closeWithResult(content)
}
/**
@ -420,7 +419,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @return true in case of error
*/
private fun checkRoomId(eventData: JsonDict): Boolean {
private fun checkRoomId(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean {
val roomIdInEvent = eventData["room_id"] as String?
// Check if param is present
if (null == roomIdInEvent) {
@ -443,7 +442,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @return true in case of error
*/
private fun checkUserId(eventData: JsonDict): Boolean {
private fun checkUserId(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean {
val userIdInEvent = eventData["user_id"] as String?
// Check if param is present
if (null == userIdInEvent) {
@ -454,7 +453,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
return false
}
private fun createWidgetAPICallback(eventData: JsonDict): WidgetAPICallback {
private fun createWidgetAPICallback(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): WidgetAPICallback {
return WidgetAPICallback(widgetPostAPIMediator, eventData, stringProvider)
}
}

View file

@ -23,6 +23,6 @@ sealed class WidgetViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable): WidgetViewEvents()
data class Close(val content: Content? = null) : WidgetViewEvents()
data class DisplayIntegrationManager(val integId: String?, val integType: String?) : WidgetViewEvents()
data class LoadFormattedURL(val formattedURL: String) : WidgetViewEvents()
data class OnURLFormatted(val formattedURL: String) : WidgetViewEvents()
data class DisplayTerms(val url: String, val token: String) : WidgetViewEvents()
}

View file

@ -76,6 +76,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
private val integrationManagerService = session.integrationManagerService()
private val widgetURLFormatter = widgetService.getWidgetURLFormatter()
private val postAPIMediator = widgetService.getWidgetPostAPIMediator()
private var widgetPostAPIHandler: WidgetPostAPIHandler? = null
// Flag to avoid infinite loop
private var canRefreshToken = true
@ -83,9 +84,14 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
init {
integrationManagerService.addListener(this)
if (initialState.widgetKind.isAdmin()) {
val widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId, this)
widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId).apply {
navigationCallback = this@WidgetViewModel
}
postAPIMediator.setHandler(widgetPostAPIHandler)
}
if (!integrationManagerService.isIntegrationEnabled()) {
_viewEvents.post(WidgetViewEvents.Close(null))
}
setupName()
refreshPermissionStatus()
observePowerLevel()
@ -142,10 +148,10 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action)
is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action)
is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading()
WidgetAction.LoadFormattedUrl -> loadFormattedUrl()
WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false)
WidgetAction.DeleteWidget -> handleDeleteWidget()
WidgetAction.RevokeWidget -> handleRevokeWidget()
WidgetAction.OnTermsReviewed -> refreshPermissionStatus()
WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false)
}
}
@ -227,7 +233,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
)
setState { copy(formattedURL = Success(formattedUrl)) }
Timber.v("Post load formatted url event: $formattedUrl")
_viewEvents.post(WidgetViewEvents.LoadFormattedURL(formattedUrl))
_viewEvents.post(WidgetViewEvents.OnURLFormatted(formattedUrl))
} catch (failure: Throwable) {
if (failure is WidgetManagementFailure.TermsNotSignedException) {
_viewEvents.post(WidgetViewEvents.DisplayTerms(initialState.baseUrl, failure.token))
@ -265,17 +271,24 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
override fun onCleared() {
integrationManagerService.removeListener(this)
widgetPostAPIHandler?.navigationCallback = null
postAPIMediator.setHandler(null)
super.onCleared()
}
// IntegrationManagerService.Listener
// IntegrationManagerService.Listener
override fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
refreshPermissionStatus()
}
// WidgetPostAPIHandler.NavigationCallback
override fun onIsEnabledChanged(enabled: Boolean) {
if (!enabled) {
_viewEvents.post(WidgetViewEvents.Close(null))
}
}
// WidgetPostAPIHandler.NavigationCallback
override fun close() {
_viewEvents.post(WidgetViewEvents.Close(null))

View file

@ -81,22 +81,10 @@ fun WebView.clearAfterWidget() {
webChromeClient = null
webViewClient = null
clearHistory()
// NOTE: clears RAM cache, if you pass true, it will also clear the disk cache.
clearCache(true)
// Loading a blank page is optional, but will ensure that the WebView isn't doing anything when you destroy it.
loadUrl("about:blank")
onPause()
removeAllViews()
// NOTE: This pauses JavaScript execution for ALL WebViews,
// do not use if you have other WebViews still alive.
// If you create another WebView after calling this,
// make sure to call mWebView.resumeTimers().
pauseTimers()
// NOTE: This can occasionally cause a segfault below API 17 (4.2)
destroy()
}

View file

@ -1162,6 +1162,9 @@
<string name="room_widget_webview_read_protected_media">Read DRM protected Media</string>
<!-- Widget Integration Manager -->
<string name="integration_manager_not_enabled_title">Integrations are disabled</string>
<string name="integration_manager_not_enabled_msg">Enable integrations in settings to allow this action.</string>
<string name="widget_integration_unable_to_create">Unable to create widget.</string>
<string name="widget_integration_failed_to_send_request">Failed to send request.</string>
<string name="widget_integration_positive_power_level">Power level must be positive integer.</string>