Clean after Benoit's review

This commit is contained in:
ganfra 2020-06-02 19:02:21 +02:00
parent 82b4415f7d
commit 06cc2f527e
35 changed files with 225 additions and 98 deletions

View File

@ -15,6 +15,9 @@
*/ */
package im.vector.matrix.android.api.session.integrationmanager package im.vector.matrix.android.api.session.integrationmanager
/**
* This class holds configuration of integration manager.
*/
data class IntegrationManagerConfig( data class IntegrationManagerConfig(
val uiUrl: String, val uiUrl: String,
val apiUrl: String, val apiUrl: String,
@ -22,9 +25,21 @@ data class IntegrationManagerConfig(
) { ) {
// Order matters, first is preferred // Order matters, first is preferred
/**
* The kind of config, it will reflect where the data is coming from.
*/
enum class Kind { enum class Kind {
/**
* Defined in UserAccountData
*/
ACCOUNT, ACCOUNT,
/**
* Defined in Wellknown
*/
HOMESERVER, HOMESERVER,
/**
* Fallback value, hardcoded by the SDK
*/
DEFAULT DEFAULT
} }
} }

View File

@ -19,39 +19,98 @@ package im.vector.matrix.android.api.session.integrationmanager
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
/**
* This is the entry point to manage integration. You can grab an instance of this service through an active session.
*/
interface IntegrationManagerService { interface IntegrationManagerService {
/**
* This listener allow you to observe change related to integrations.
*/
interface Listener { interface Listener {
/**
* Is called whenever integration is enabled or disabled, comes from user account data.
*/
fun onIsEnabledChanged(enabled: Boolean) { fun onIsEnabledChanged(enabled: Boolean) {
// No-op // No-op
} }
/**
* Is called whenever configs from user account data or wellknown are updated.
*/
fun onConfigurationChanged(configs: List<IntegrationManagerConfig>) { fun onConfigurationChanged(configs: List<IntegrationManagerConfig>) {
// No-op // No-op
} }
/**
* Is called whenever widget permissions from user account data are updated.
*/
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) { fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
// No-op // No-op
} }
} }
/**
* Adds a listener to observe changes.
*/
fun addListener(listener: Listener) fun addListener(listener: Listener)
/**
* Removes a previously added listener.
*/
fun removeListener(listener: Listener) fun removeListener(listener: Listener)
/**
* Return the list of current configurations, sorted by kind. First one is preferred.
* See [IntegrationManagerConfig.Kind]
*/
fun getOrderedConfigs(): List<IntegrationManagerConfig> fun getOrderedConfigs(): List<IntegrationManagerConfig>
/**
* Return the preferred current configuration.
* See [IntegrationManagerConfig.Kind]
*/
fun getPreferredConfig(): IntegrationManagerConfig fun getPreferredConfig(): IntegrationManagerConfig
/**
* Returns true if integration is enabled, false otherwise.
*/
fun isIntegrationEnabled(): Boolean fun isIntegrationEnabled(): Boolean
/**
* Offers to enable or disable the integration.
* @param enable the param to change
* @param callback the matrix callback to listen for result.
* @return Cancelable
*/
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable
/**
* Offers to allow or disallow a widget.
* @param stateEventId the eventId of the state event defining the widget.
* @param allowed the param to change
* @param callback the matrix callback to listen for result.
* @return Cancelable
*/
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable
/**
* Returns true if the widget is allowed, false otherwise.
* @param stateEventId the eventId of the state event defining the widget.
*/
fun isWidgetAllowed(stateEventId: String): Boolean fun isWidgetAllowed(stateEventId: String): Boolean
/**
* Offers to allow or disallow a native widget domain.
* @param widgetType the widget type to check for
* @param domain the domain to check for
*/
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable
fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean /**
* Returns true if the widget domain is allowed, false otherwise.
* @param widgetType the widget type to check for
* @param domain the domain to check for
*/
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.session.widgets package im.vector.matrix.android.api.session.widgets
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure

View File

@ -23,12 +23,32 @@ import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.session.widgets.model.Widget import im.vector.matrix.android.api.session.widgets.model.Widget
/**
* This is the entry point to manage widgets. You can grab an instance of this service through an active session.
*/
interface WidgetService { interface WidgetService {
/**
* Returns an instance of [WidgetURLFormatter].
*/
fun getWidgetURLFormatter(): WidgetURLFormatter fun getWidgetURLFormatter(): WidgetURLFormatter
/**
* Returns an instance of [WidgetPostAPIMediator].
* This is to be used for "admin" widgets so you can interact through JS.
*/
fun getWidgetPostAPIMediator(): WidgetPostAPIMediator fun getWidgetPostAPIMediator(): WidgetPostAPIMediator
/**
* Returns the current room widgets defined through state events.
* Some widgets can be deactivated, so be sure to check for isActive if needed.
*
* @param roomId the room where you want to fetch widgets
* @param widgetId if you want to fetch for some particular widget
* @param widgetTypes if you want to filter some widget type.
* @param excludedTypes if you want to exclude some widget type.
*/
fun getRoomWidgets( fun getRoomWidgets(
roomId: String, roomId: String,
widgetId: QueryStringValue = QueryStringValue.NoCondition, widgetId: QueryStringValue = QueryStringValue.NoCondition,
@ -36,6 +56,15 @@ interface WidgetService {
excludedTypes: Set<String>? = null excludedTypes: Set<String>? = null
): List<Widget> ): List<Widget>
/**
* Returns the live room widgets so you can listen to them.
* Some widgets can be deactivated, so be sure to check for isActive.
*
* @param roomId the room where you want to fetch widgets
* @param widgetId if you want to fetch for some particular widget
* @param widgetTypes if you want to filter some widget type.
* @param excludedTypes if you want to exclude some widget type.
*/
fun getRoomWidgetsLive( fun getRoomWidgetsLive(
roomId: String, roomId: String,
widgetId: QueryStringValue = QueryStringValue.NoCondition, widgetId: QueryStringValue = QueryStringValue.NoCondition,
@ -43,19 +72,53 @@ interface WidgetService {
excludedTypes: Set<String>? = null excludedTypes: Set<String>? = null
): LiveData<List<Widget>> ): LiveData<List<Widget>>
/**
* Returns the current user widgets.
* Some widgets can be deactivated, so be sure to check for isActive.
*
* @param widgetTypes if you want to filter some widget type.
* @param excludedTypes if you want to exclude some widget type.
*/
fun getUserWidgets( fun getUserWidgets(
widgetTypes: Set<String>? = null, widgetTypes: Set<String>? = null,
excludedTypes: Set<String>? = null excludedTypes: Set<String>? = null
): List<Widget> ): List<Widget>
/**
* Returns the live user widgets so you can listen to them.
* Some widgets can be deactivated, so be sure to check for isActive.
*
* @param widgetTypes if you want to filter some widget type.
* @param excludedTypes if you want to exclude some widget type.
*/
fun getUserWidgetsLive( fun getUserWidgetsLive(
widgetTypes: Set<String>? = null, widgetTypes: Set<String>? = null,
excludedTypes: Set<String>? = null excludedTypes: Set<String>? = null
): LiveData<List<Widget>> ): LiveData<List<Widget>>
/**
* Creates a new widget in a room. It makes sure you have the rights to handle this.
*
* @param roomId: the room where you want to deactivate the widget.
* @param widgetId: the widget to deactivate.
* @param callback the matrix callback to listen for result.
* @return Cancelable
*/
fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable
/**
* Deactivate a widget in a room. It makes sure you have the rights to handle this.
*
* @param roomId: the room where you want to deactivate the widget.
* @param widgetId: the widget to deactivate.
* @param callback the matrix callback to listen for result.
* @return Cancelable
*/
fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Returns true if you can add/remove widgets. It goes through
* @param roomId the room where you want to administrate widgets.
*/
fun hasPermissionsToHandleWidgets(roomId: String): Boolean fun hasPermissionsToHandleWidgets(roomId: String): Boolean
} }

View File

@ -20,6 +20,12 @@ interface WidgetURLFormatter {
/** /**
* Takes care of fetching a scalar token if required and build the final url. * Takes care of fetching a scalar token if required and build the final url.
* This methods can throw, you should take care of handling failure. * This methods can throw, you should take care of handling failure.
*
* @param baseUrl the baseUrl which will be checked for scalar token
* @param params additional params you want to append to the base url.
* @param forceFetchScalarToken if true, you will force to fetch a new scalar token
* from the server (only if the base url is whitelisted)
* @param bypassWhitelist if true, the base url will be considered as whitelisted
*/ */
suspend fun format( suspend fun format(
baseUrl: String, baseUrl: String,

View File

@ -16,19 +16,19 @@
package im.vector.matrix.android.api.session.widgets.model package im.vector.matrix.android.api.session.widgets.model
sealed class WidgetType(open val preferred: String, open val legacy: String) { sealed class WidgetType(open val preferred: String, open val legacy: String = preferred) {
object Jitsi : WidgetType("m.jitsi", "jitsi") object Jitsi : WidgetType("m.jitsi", "jitsi")
object TradingView : WidgetType("m.tradingview", "m.tradingview") object TradingView : WidgetType("m.tradingview")
object Spotify : WidgetType("m.spotify", "m.spotify") object Spotify : WidgetType("m.spotify")
object Video : WidgetType("m.video", "m.video") object Video : WidgetType("m.video")
object GoogleDoc : WidgetType("m.googledoc", "m.googledoc") object GoogleDoc : WidgetType("m.googledoc")
object GoogleCalendar : WidgetType("m.googlecalendar", "m.googlecalendar") object GoogleCalendar : WidgetType("m.googlecalendar")
object Etherpad : WidgetType("m.etherpad", "m.etherpad") object Etherpad : WidgetType("m.etherpad")
object StickerPicker : WidgetType("m.stickerpicker", "m.stickerpicker") object StickerPicker : WidgetType("m.stickerpicker")
object Grafana : WidgetType("m.grafana", "m.grafana") object Grafana : WidgetType("m.grafana")
object Custom : WidgetType("m.custom", "m.custom") object Custom : WidgetType("m.custom")
object IntegrationManager : WidgetType("m.integration_manager", "m.integration_manager") object IntegrationManager : WidgetType("m.integration_manager")
data class Fallback(override val preferred: String, override val legacy: String) : WidgetType(preferred, legacy) data class Fallback(override val preferred: String) : WidgetType(preferred)
fun matches(type: String?): Boolean { fun matches(type: String?): Boolean {
return type == preferred || type == legacy return type == preferred || type == legacy
@ -58,7 +58,7 @@ sealed class WidgetType(open val preferred: String, open val legacy: String) {
val matchingType = DEFINED_TYPES.firstOrNull { val matchingType = DEFINED_TYPES.firstOrNull {
it.matches(type) it.matches(type)
} }
return matchingType ?: Fallback(type, type) return matchingType ?: Fallback(type)
} }
} }
} }

View File

@ -29,7 +29,7 @@ internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
fun map(entity: UserAccountDataEntity): UserAccountDataEvent { fun map(entity: UserAccountDataEntity): UserAccountDataEvent {
return UserAccountDataEvent( return UserAccountDataEvent(
type = entity.type ?: "", type = entity.type ?: "",
content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
) )
} }
} }

View File

@ -21,14 +21,16 @@ import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.auth.wellknown.WellknownResult
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManagerConfigExtractor
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction import im.vector.matrix.android.internal.util.awaitTransaction
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
@ -39,6 +41,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val eventBus: EventBus, private val eventBus: EventBus,
private val getWellknownTask: GetWellknownTask, private val getWellknownTask: GetWellknownTask,
private val configExtractor: IntegrationManagerConfigExtractor,
@UserId @UserId
private val userId: String private val userId: String
) : GetHomeServerCapabilitiesTask { ) : GetHomeServerCapabilitiesTask {
@ -102,8 +105,14 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
}
// We are also checking for integration manager configurations
val config = configExtractor.extract(getWellknownResult.wellKnown)
if (config != null) {
Timber.v("Extracted integration config : $config")
realm.insertOrUpdate(config)
}
}
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
} }
} }

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class AllowedWidgetsContent( internal data class AllowedWidgetsContent(
/** /**
* Map of stateEventId to Allowed * Map of stateEventId to Allowed
*/ */

View File

@ -60,7 +60,7 @@ internal class DefaultIntegrationManagerService @Inject constructor(private val
return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback) return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback)
} }
override fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean { override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean {
return integrationManager.isNativeWidgetAllowed(widgetType, domain) return integrationManager.isNativeWidgetDomainAllowed(widgetType, domain)
} }
} }

View File

@ -90,7 +90,6 @@ internal class IntegrationManager @Inject constructor(@UserId private val userId
} }
fun start() { fun start() {
refreshWellknown()
lifecycleRegistry.currentState = Lifecycle.State.STARTED lifecycleRegistry.currentState = Lifecycle.State.STARTED
observeWellknownConfig() observeWellknownConfig()
accountDataDataSource accountDataDataSource
@ -209,7 +208,7 @@ internal class IntegrationManager @Inject constructor(@UserId private val userId
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean { fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
return currentContent?.native?.get(widgetType)?.get(domain) ?: false return currentContent?.native?.get(widgetType)?.get(domain) ?: false
@ -273,25 +272,6 @@ internal class IntegrationManager @Inject constructor(@UserId private val userId
.firstOrNull()?.widgetContent .firstOrNull()?.widgetContent
} }
private fun refreshWellknown() {
taskExecutor.executorScope.launch {
val params = GetWellknownTask.Params(matrixId = userId)
val wellknownResult = try {
getWellknownTask.execute(params)
} catch (failure: Throwable) {
Timber.v("Get wellknown failed: $failure")
null
}
if (wellknownResult != null && wellknownResult is WellknownResult.Prompt) {
val config = configExtractor.extract(wellknownResult.wellKnown) ?: return@launch
Timber.v("Extracted config: $config")
monarchy.awaitTransaction {
it.insertOrUpdate(config)
}
}
}
}
private fun observeWellknownConfig() { private fun observeWellknownConfig() {
val liveData = monarchy.findAllMappedWithChanges( val liveData = monarchy.findAllMappedWithChanges(
{ it.where(WellknownIntegrationManagerConfigEntity::class.java) }, { it.where(WellknownIntegrationManagerConfigEntity::class.java) },

View File

@ -24,5 +24,5 @@ import im.vector.matrix.android.api.session.integrationmanager.IntegrationManage
internal abstract class IntegrationManagerModule { internal abstract class IntegrationManagerModule {
@Binds @Binds
abstract fun bindIntegrationManagerService(integrationManagerService: DefaultIntegrationManagerService): IntegrationManagerService abstract fun bindIntegrationManagerService(service: DefaultIntegrationManagerService): IntegrationManagerService
} }

View File

@ -20,6 +20,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class IntegrationManagerWidgetData( internal data class IntegrationManagerWidgetData(
@Json(name = "api_url") val apiUrl: String? = null @Json(name = "api_url") val apiUrl: String? = null
) )

View File

@ -20,6 +20,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class IntegrationProvisioningContent( internal data class IntegrationProvisioningContent(
@Json(name = "enabled") val enabled: Boolean @Json(name = "enabled") val enabled: Boolean
) )

View File

@ -118,6 +118,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
else -> { else -> {
val params = SendStateTask.Params( val params = SendStateTask.Params(
roomId = roomId, roomId = roomId,
stateKey = null,
eventType = EventType.STATE_ROOM_ENCRYPTION, eventType = EventType.STATE_ROOM_ENCRYPTION,
body = mapOf( body = mapOf(
"algorithm" to algorithm "algorithm" to algorithm

View File

@ -346,7 +346,7 @@ internal class LocalEchoEventFactory @Inject constructor(
return createMessageEvent(roomId, content) return createMessageEvent(roomId, content)
} }
private fun createMessageEvent(roomId: String, content: Any? = null): Event { private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event {
return createEvent(roomId, EventType.MESSAGE, content.toContent()) return createEvent(roomId, EventType.MESSAGE, content.toContent())
} }

View File

@ -65,6 +65,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
): Cancelable { ): Cancelable {
val params = SendStateTask.Params( val params = SendStateTask.Params(
roomId = roomId, roomId = roomId,
stateKey = stateKey,
eventType = eventType, eventType = eventType,
body = body body = body
) )

View File

@ -26,7 +26,7 @@ import javax.inject.Inject
internal interface SendStateTask : Task<SendStateTask.Params, Unit> { internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
data class Params( data class Params(
val roomId: String, val roomId: String,
val stateKey: String? = null, val stateKey: String?,
val eventType: String, val eventType: String,
val body: JsonDict val body: JsonDict
) )

View File

@ -20,6 +20,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RegisterWidgetResponse( internal data class RegisterWidgetResponse(
@Json(name = "scalar_token") val scalarToken: String? @Json(name = "scalar_token") val scalarToken: String?
) )

View File

@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
import im.vector.matrix.android.api.session.widgets.WidgetManagementFailure
import im.vector.matrix.android.api.session.widgets.model.Widget import im.vector.matrix.android.api.session.widgets.model.Widget
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId

View File

@ -39,13 +39,13 @@ internal abstract class WidgetModule {
} }
@Binds @Binds
abstract fun bindWidgetService(widgetService: DefaultWidgetService): WidgetService abstract fun bindWidgetService(service: DefaultWidgetService): WidgetService
@Binds @Binds
abstract fun bindWidgetURLBuilder(widgetURLBuilder: DefaultWidgetURLFormatter): WidgetURLFormatter abstract fun bindWidgetURLBuilder(formatter: DefaultWidgetURLFormatter): WidgetURLFormatter
@Binds @Binds
abstract fun bindWidgetPostAPIMediator(widgetPostMessageAPIProvider: DefaultWidgetPostAPIMediator): WidgetPostAPIMediator abstract fun bindWidgetPostAPIMediator(mediator: DefaultWidgetPostAPIMediator): WidgetPostAPIMediator
@Binds @Binds
abstract fun bindCreateWidgetTask(task: DefaultCreateWidgetTask): CreateWidgetTask abstract fun bindCreateWidgetTask(task: DefaultCreateWidgetTask): CreateWidgetTask

View File

@ -20,7 +20,7 @@ import android.content.Context
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class WidgetPostMessageAPIProvider @Inject constructor(private val context: Context) { internal class WidgetPostMessageAPIProvider @Inject constructor(private val context: Context) {
private var postMessageAPIString: String? = null private var postMessageAPIString: String? = null

View File

@ -30,7 +30,7 @@ internal interface WidgetsAPI {
* @param requestOpenIdTokenResponse the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961) * @param requestOpenIdTokenResponse the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
*/ */
@POST("register") @POST("register")
fun register(@Body requestOpenIdTokenResponse: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse> fun register(@Body body: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse>
@GET("account") @GET("account")
fun validateToken(@Query("scalar_token") scalarToken: String?, @Query("v") version: String?): Call<Unit> fun validateToken(@Query("scalar_token") scalarToken: String?, @Query("v") version: String?): Call<Unit>

View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
import im.vector.matrix.android.internal.session.widgets.RegisterWidgetResponse import im.vector.matrix.android.internal.session.widgets.RegisterWidgetResponse
import im.vector.matrix.android.internal.session.widgets.WidgetManagementFailure 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.WidgetsAPI
import im.vector.matrix.android.internal.session.widgets.WidgetsAPIProvider import im.vector.matrix.android.internal.session.widgets.WidgetsAPIProvider
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task

View File

@ -123,7 +123,7 @@ interface ScreenComponent {
fun inject(activity: BigImageViewerActivity) fun inject(activity: BigImageViewerActivity)
fun inject(activity: InviteUsersToRoomActivity) fun inject(activity: InviteUsersToRoomActivity)
fun inject(activity: ReviewTermsActivity) fun inject(activity: ReviewTermsActivity)
fun inject(widgetActivity: WidgetActivity) fun inject(activity: WidgetActivity)
/* ========================================================================================== /* ==========================================================================================
* BottomSheets * BottomSheets

View File

@ -202,7 +202,8 @@ class RoomDetailFragment @Inject constructor(
VectorInviteView.Callback, VectorInviteView.Callback,
JumpToReadMarkerView.Callback, JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback, AttachmentTypeSelectorView.Callback,
AttachmentsHelper.Callback, RoomWidgetsBannerView.Callback { AttachmentsHelper.Callback,
RoomWidgetsBannerView.Callback {
companion object { companion object {
@ -327,8 +328,6 @@ class RoomDetailFragment @Inject constructor(
private fun displayPromptForIntegrationManager() { private fun displayPromptForIntegrationManager() {
// The Sticker picker widget is not installed yet. Propose the user to install it // The Sticker picker widget is not installed yet. Propose the user to install it
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
// Use the builder context
// Use the builder context
val v: View = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_no_sticker_pack, null) val v: View = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_no_sticker_pack, null)
builder builder
.setView(v) .setView(v)

View File

@ -66,7 +66,6 @@ data class RoomDetailViewState(
val syncState: SyncState = SyncState.Idle, val syncState: SyncState = SyncState.Idle,
val highlightedEventId: String? = null, val highlightedEventId: String? = null,
val unreadState: UnreadState = UnreadState.Unknown, val unreadState: UnreadState = UnreadState.Unknown,
val menuItemsVisibility: SparseArray<Boolean> = SparseArray(4),
val canShowJumpToReadMarker: Boolean = true val canShowJumpToReadMarker: Boolean = true
) : MvRxState { ) : MvRxState {

View File

@ -22,17 +22,19 @@ import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.matrix.android.api.session.widgets.model.Widget import im.vector.matrix.android.api.session.widgets.model.Widget
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.ClickListener
import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.onClick
@EpoxyModelClass(layout = R.layout.item_room_widget) @EpoxyModelClass(layout = R.layout.item_room_widget)
abstract class RoomWidgetItem : EpoxyModelWithHolder<RoomWidgetItem.Holder>() { abstract class RoomWidgetItem : EpoxyModelWithHolder<RoomWidgetItem.Holder>() {
@EpoxyAttribute lateinit var widget: Widget @EpoxyAttribute lateinit var widget: Widget
@EpoxyAttribute var widgetClicked: (() -> Unit)? = null @EpoxyAttribute var widgetClicked: ClickListener? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.widgetName.text = widget.name holder.widgetName.text = widget.name
holder.view.setOnClickListener { widgetClicked?.invoke() } holder.view.onClick(widgetClicked)
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View File

@ -27,12 +27,10 @@ class WidgetAPICallback(private val postAPIMediator: WidgetPostAPIMediator,
private val stringProvider: StringProvider) : MatrixCallback<Any> { private val stringProvider: StringProvider) : MatrixCallback<Any> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
super.onFailure(failure)
postAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) postAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData)
} }
override fun onSuccess(data: Any) { override fun onSuccess(data: Any) {
super.onSuccess(data)
postAPIMediator.sendSuccess(eventData) postAPIMediator.sendSuccess(eventData)
} }
} }

View File

@ -80,9 +80,11 @@ class WidgetActivity : VectorBaseActivity(), ToolbarConfigurable, WidgetViewMode
} }
override fun initUiAndData() { override fun initUiAndData() {
val widgetArgs: WidgetArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) val widgetArgs: WidgetArgs? = intent?.extras?.getParcelable(MvRx.KEY_ARG)
?: return if (widgetArgs == null) {
finish()
return
}
configure(toolbar) configure(toolbar)
toolbar.isVisible = widgetArgs.kind.nameRes != 0 toolbar.isVisible = widgetArgs.kind.nameRes != 0
viewModel.observeViewEvents { viewModel.observeViewEvents {

View File

@ -34,7 +34,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
import im.vector.matrix.android.internal.session.widgets.WidgetManagementFailure import im.vector.matrix.android.api.session.widgets.WidgetManagementFailure
import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.matrix.rx.mapOptional import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx

View File

@ -34,6 +34,7 @@ import im.vector.riotx.core.extensions.withArgs
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.widgets.WidgetArgs import im.vector.riotx.features.widgets.WidgetArgs
import kotlinx.android.synthetic.main.bottom_sheet_room_widget_permission.*
import javax.inject.Inject import javax.inject.Inject
class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() { class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
@ -42,18 +43,6 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
private val viewModel: RoomWidgetPermissionViewModel by activityViewModel() private val viewModel: RoomWidgetPermissionViewModel by activityViewModel()
@BindView(R.id.bottom_sheet_widget_permission_shared_info)
lateinit var sharedInfoTextView: TextView
@BindView(R.id.bottom_sheet_widget_permission_owner_id)
lateinit var authorIdText: TextView
@BindView(R.id.bottom_sheet_widget_permission_owner_display_name)
lateinit var authorNameText: TextView
@BindView(R.id.bottom_sheet_widget_permission_owner_avatar)
lateinit var authorAvatarView: ImageView
@Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var avatarRenderer: AvatarRenderer
override val showExpanded = true override val showExpanded = true
@ -65,10 +54,10 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
super.invalidate() super.invalidate()
val permissionData = state.permissionData() ?: return@withState val permissionData = state.permissionData() ?: return@withState
authorIdText.text = permissionData.widget.senderInfo?.userId ?: "" widgetPermissionOwnerId.text = permissionData.widget.senderInfo?.userId ?: ""
authorNameText.text = permissionData.widget.senderInfo?.disambiguatedDisplayName widgetPermissionOwnerDisplayName.text = permissionData.widget.senderInfo?.disambiguatedDisplayName
permissionData.widget.senderInfo?.toMatrixItem()?.also { permissionData.widget.senderInfo?.toMatrixItem()?.also {
avatarRenderer.render(it, authorAvatarView) avatarRenderer.render(it, widgetPermissionOwnerAvatar)
} }
val domain = permissionData.widgetDomain ?: "" val domain = permissionData.widgetDomain ?: ""
@ -96,18 +85,17 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
} }
infoBuilder.append("\n") infoBuilder.append("\n")
widgetPermissionSharedInfo.text = infoBuilder
sharedInfoTextView.text = infoBuilder
} }
@OnClick(R.id.bottom_sheet_widget_permission_decline_button) @OnClick(R.id.widgetPermissionDecline)
fun doDecline() { fun doDecline() {
viewModel.handle(RoomWidgetPermissionActions.BlockWidget) viewModel.handle(RoomWidgetPermissionActions.BlockWidget)
// optimistic dismiss // optimistic dismiss
dismiss() dismiss()
} }
@OnClick(R.id.bottom_sheet_widget_permission_continue_button) @OnClick(R.id.widgetPermissionContinue)
fun doAccept() { fun doAccept() {
viewModel.handle(RoomWidgetPermissionActions.AllowWidget) viewModel.handle(RoomWidgetPermissionActions.AllowWidget)
// optimistic dismiss // optimistic dismiss

View File

@ -17,17 +17,18 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:text="@string/room_widget_permission_title" android:text="@string/room_widget_permission_title"
android:textSize="20sp" android:textSize="20sp"
android:textColor="?riotx_text_primary"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/bottom_sheet_widget_permission_h2_text" android:id="@+id/widgetPermissionHeader2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin" android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:text="@string/room_widget_permission_added_by" android:text="@string/room_widget_permission_added_by"
android:textColor="?android:attr/textColorSecondary" android:textColor="?riotx_text_secondary"
android:textSize="16sp" /> android:textSize="16sp" />
<LinearLayout <LinearLayout
@ -39,7 +40,7 @@
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/bottom_sheet_widget_permission_owner_avatar" android:id="@+id/widgetPermissionOwnerAvatar"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_gravity="center" android:layout_gravity="center"
@ -54,33 +55,35 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/bottom_sheet_widget_permission_owner_display_name" android:id="@+id/widgetPermissionOwnerDisplayName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="center" android:textAlignment="center"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="?riotx_text_primary"
tools:text="User name" /> tools:text="User name" />
<TextView <TextView
android:id="@+id/bottom_sheet_widget_permission_owner_id" android:id="@+id/widgetPermissionOwnerId"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="center" android:textAlignment="center"
android:textSize="14sp" android:textSize="14sp"
android:textColor="?riotx_text_secondary"
tools:text="\@foo:matrix.org" /> tools:text="\@foo:matrix.org" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<TextView <TextView
android:id="@+id/bottom_sheet_widget_permission_shared_info" android:id="@+id/widgetPermissionSharedInfo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin" android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="@dimen/layout_vertical_margin_big" android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?riotx_text_secondary"
android:textSize="16sp" android:textSize="16sp"
tools:text="@string/room_widget_permission_shared_info_title" /> tools:text="@string/room_widget_permission_shared_info_title" />
@ -91,16 +94,16 @@
android:gravity="end" android:gravity="end"
android:orientation="horizontal"> android:orientation="horizontal">
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/bottom_sheet_widget_permission_decline_button" android:id="@+id/widgetPermissionDecline"
style="@style/VectorButtonStyleDestructive" style="@style/VectorButtonStyleDestructive"
android:layout_marginEnd="@dimen/layout_vertical_margin" android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin" android:layout_marginRight="@dimen/layout_vertical_margin"
android:text="@string/decline" android:text="@string/decline"
android:textAllCaps="true"/> android:textAllCaps="true"/>
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/bottom_sheet_widget_permission_continue_button" android:id="@+id/widgetPermissionContinue"
style="@style/VectorButtonStylePositive" style="@style/VectorButtonStylePositive"
android:layout_marginEnd="@dimen/layout_vertical_margin" android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin" android:layout_marginRight="@dimen/layout_vertical_margin"

View File

@ -44,7 +44,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginStart="@dimen/layout_horizontal_margin" android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:textColor="?android:textColorPrimary" android:textColor="?riotx_text_primary"
android:textStyle="bold" android:textStyle="bold"
tools:text="Fail to load widget " /> tools:text="Fail to load widget " />
</LinearLayout> </LinearLayout>

View File

@ -105,6 +105,7 @@
<im.vector.riotx.core.preference.VectorSwitchPreference <im.vector.riotx.core.preference.VectorSwitchPreference
android:key="SETTINGS_ALLOW_INTEGRATIONS_KEY" android:key="SETTINGS_ALLOW_INTEGRATIONS_KEY"
android:persistent="false"
android:title="@string/settings_integration_allow" /> android:title="@string/settings_integration_allow" />
<im.vector.riotx.core.preference.VectorPreference <im.vector.riotx.core.preference.VectorPreference