mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Merge pull request #1425 from vector-im/feature/integration_manager
Feature/integration manager
This commit is contained in:
commit
55bd346cb2
176 changed files with 5918 additions and 413 deletions
|
@ -1,6 +1,9 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
|
|
|
@ -2,7 +2,8 @@ Changes in RiotX 0.22.0 (2020-XX-XX)
|
|||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
- Integration Manager and Widget support (#48)
|
||||
- Send stickers (#51)
|
||||
|
||||
Improvements 🙌:
|
||||
- New wording for notice when current user is the sender
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
|
@ -61,7 +62,7 @@ class RxRoom(private val room: Room) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||
fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||
.startWithCallable {
|
||||
room.getStateEvent(eventType, stateKey).toOptional()
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
|
@ -35,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
|||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
|
@ -151,6 +153,18 @@ class RxSession(private val session: Session) {
|
|||
session.getAccountDataEvents(types)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveRoomWidgets(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): Observable<List<Widget>> {
|
||||
return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asObservable()
|
||||
.startWithCallable {
|
||||
session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
|
|
54
matrix-sdk-android/src/main/assets/postMessageAPI.js
Executable file
54
matrix-sdk-android/src/main/assets/postMessageAPI.js
Executable file
|
@ -0,0 +1,54 @@
|
|||
var android_widget_events = {};
|
||||
|
||||
var sendObjectMessageToRiotAndroid = function(parameters) {
|
||||
Android.onWidgetEvent(JSON.stringify(parameters));
|
||||
};
|
||||
|
||||
var onWidgetMessageToRiotAndroid = function(event) {
|
||||
/* Use an internal "_id" field for matching onMessage events and requests
|
||||
_id was originally used by the Modular API. Keep it */
|
||||
if (!event.data._id) {
|
||||
/* The Matrix Widget API v2 spec says:
|
||||
"The requestId field should be unique and included in all requests" */
|
||||
event.data._id = event.data.requestId;
|
||||
}
|
||||
/* Make sure to have one id */
|
||||
if (!event.data._id) {
|
||||
event.data._id = Date.now() + "-" + Math.random().toString(36);
|
||||
}
|
||||
|
||||
console.log("onWidgetMessageToRiotAndroid " + event.data._id);
|
||||
|
||||
if (android_widget_events[event.data._id]) {
|
||||
console.log("onWidgetMessageToRiotAndroid : already managed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.origin) {
|
||||
event.origin = event.originalEvent.origin;
|
||||
}
|
||||
|
||||
android_widget_events[event.data._id] = event;
|
||||
|
||||
console.log("onWidgetMessageToRiotAndroid : manage " + event.data);
|
||||
sendObjectMessageToRiotAndroid({'event.data': event.data});
|
||||
};
|
||||
|
||||
var sendResponseFromRiotAndroid = function(eventId, res) {
|
||||
var event = android_widget_events[eventId];
|
||||
|
||||
console.log("sendResponseFromRiotAndroid to " + event.data.action + " for "+ eventId + ": " + JSON.stringify(res));
|
||||
|
||||
var data = JSON.parse(JSON.stringify(event.data));
|
||||
|
||||
data.response = res;
|
||||
|
||||
console.log("sendResponseFromRiotAndroid ---> " + data);
|
||||
|
||||
event.source.postMessage(data, event.origin);
|
||||
android_widget_events[eventId] = true;
|
||||
|
||||
console.log("sendResponseFromRiotAndroid to done");
|
||||
};
|
||||
|
||||
window.addEventListener('message', onWidgetMessageToRiotAndroid, false);
|
|
@ -22,6 +22,15 @@ import java.net.Proxy
|
|||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
||||
val integrationUIUrl: String = "https://scalar.vector.im/",
|
||||
val integrationRestUrl: String = "https://scalar.vector.im/api",
|
||||
val integrationWidgetUrls: List<String> = listOf(
|
||||
"https://scalar.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar.vector.im/api",
|
||||
"https://scalar-staging.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
),
|
||||
/**
|
||||
* Optional proxy to connect to the matrix servers
|
||||
* You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port)
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.net.Uri
|
|||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig.Builder
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import okhttp3.CipherSuite
|
||||
import okhttp3.TlsVersion
|
||||
|
||||
|
@ -71,15 +72,11 @@ data class HomeServerConnectionConfig(
|
|||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
||||
}
|
||||
// ensure trailing /
|
||||
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = hsUri.toString()
|
||||
Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
} else {
|
||||
hsUri
|
||||
val hsString = hsUri.toString().ensureTrailingSlash()
|
||||
homeServerUri = try {
|
||||
Uri.parse(hsString)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@ -97,15 +94,11 @@ data class HomeServerConnectionConfig(
|
|||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
// ensure trailing /
|
||||
if (!identityServerUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = identityServerUri.toString()
|
||||
this.identityServerUri = Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
} else {
|
||||
this.identityServerUri = identityServerUri
|
||||
val isString = identityServerUri.toString().ensureTrailingSlash()
|
||||
this.identityServerUri = try {
|
||||
Uri.parse(isString)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
|
|
@ -54,30 +54,4 @@ data class WellKnown(
|
|||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: JsonDict? = null
|
||||
) {
|
||||
/**
|
||||
* Returns the list of integration managers proposed
|
||||
*/
|
||||
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
|
||||
val managers = ArrayList<WellKnownManagerConfig>()
|
||||
integrations?.get("managers")?.let {
|
||||
(it as? ArrayList<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
managers.add(WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return managers
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -25,8 +25,8 @@ sealed class QueryStringValue {
|
|||
object IsNotNull : QueryStringValue()
|
||||
object IsEmpty : QueryStringValue()
|
||||
object IsNotEmpty : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case) : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
|
||||
enum class Case {
|
||||
SENSITIVE,
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.file.FileService
|
|||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.identity.IdentityService
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
|
@ -42,6 +43,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
|
|||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
|
||||
/**
|
||||
* This interface defines interactions with a session.
|
||||
|
@ -153,6 +155,16 @@ interface Session :
|
|||
*/
|
||||
fun identityService(): IdentityService
|
||||
|
||||
/**
|
||||
* Returns the widget service associated with the session
|
||||
*/
|
||||
fun widgetService(): WidgetService
|
||||
|
||||
/**
|
||||
* Returns the integration manager service associated with the session
|
||||
*/
|
||||
fun integrationManagerService(): IntegrationManagerService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
|
|
@ -98,7 +98,7 @@ data class Event(
|
|||
* @return true if event is state event.
|
||||
*/
|
||||
fun isStateEvent(): Boolean {
|
||||
return EventType.isStateEvent(getClearType())
|
||||
return stateKey != null
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
|
|
|
@ -38,6 +38,8 @@ object EventType {
|
|||
|
||||
// State Events
|
||||
|
||||
const val STATE_ROOM_WIDGET_LEGACY = "im.vector.modular.widgets"
|
||||
const val STATE_ROOM_WIDGET = "m.widget"
|
||||
const val STATE_ROOM_NAME = "m.room.name"
|
||||
const val STATE_ROOM_TOPIC = "m.room.topic"
|
||||
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
||||
|
@ -84,29 +86,6 @@ object EventType {
|
|||
// Unwedging
|
||||
internal const val DUMMY = "m.dummy"
|
||||
|
||||
private val STATE_EVENTS = listOf(
|
||||
STATE_ROOM_NAME,
|
||||
STATE_ROOM_TOPIC,
|
||||
STATE_ROOM_AVATAR,
|
||||
STATE_ROOM_MEMBER,
|
||||
STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
STATE_ROOM_CREATE,
|
||||
STATE_ROOM_JOIN_RULES,
|
||||
STATE_ROOM_GUEST_ACCESS,
|
||||
STATE_ROOM_POWER_LEVELS,
|
||||
STATE_ROOM_ALIASES,
|
||||
STATE_ROOM_TOMBSTONE,
|
||||
STATE_ROOM_CANONICAL_ALIAS,
|
||||
STATE_ROOM_HISTORY_VISIBILITY,
|
||||
STATE_ROOM_RELATED_GROUPS,
|
||||
STATE_ROOM_PINNED_EVENT,
|
||||
STATE_ROOM_ENCRYPTION
|
||||
)
|
||||
|
||||
fun isStateEvent(type: String): Boolean {
|
||||
return STATE_EVENTS.contains(type)
|
||||
}
|
||||
|
||||
fun isCallEvent(type: String): Boolean {
|
||||
return type == CALL_INVITE
|
||||
|| type == CALL_CANDIDATES
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
sealed class IdentityServiceError : Throwable() {
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
||||
sealed class IdentityServiceError : Failure.FeatureFailure() {
|
||||
object OutdatedIdentityServer : IdentityServiceError()
|
||||
object OutdatedHomeServer : IdentityServiceError()
|
||||
object NoIdentityServerConfigured : IdentityServiceError()
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.integrationmanager
|
||||
|
||||
/**
|
||||
* This class holds configuration of integration manager.
|
||||
*/
|
||||
data class IntegrationManagerConfig(
|
||||
val uiUrl: String,
|
||||
val restUrl: String,
|
||||
val kind: Kind
|
||||
) {
|
||||
|
||||
// Order matters, first is preferred
|
||||
/**
|
||||
* The kind of config, it will reflect where the data is coming from.
|
||||
*/
|
||||
enum class Kind {
|
||||
/**
|
||||
* Defined in UserAccountData
|
||||
*/
|
||||
ACCOUNT,
|
||||
/**
|
||||
* Defined in Wellknown
|
||||
*/
|
||||
HOMESERVER,
|
||||
/**
|
||||
* Fallback value, hardcoded by the SDK
|
||||
*/
|
||||
DEFAULT
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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 {
|
||||
|
||||
/**
|
||||
* This listener allow you to observe change related to integrations.
|
||||
*/
|
||||
interface Listener {
|
||||
/**
|
||||
* Is called whenever integration is enabled or disabled, comes from user account data.
|
||||
*/
|
||||
fun onIsEnabledChanged(enabled: Boolean) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called whenever configs from user account data or wellknown are updated.
|
||||
*/
|
||||
fun onConfigurationChanged(configs: List<IntegrationManagerConfig>) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called whenever widget permissions from user account data are updated.
|
||||
*/
|
||||
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to observe changes.
|
||||
*/
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Removes a previously added 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>
|
||||
|
||||
/**
|
||||
* Return the preferred current configuration.
|
||||
* See [IntegrationManagerConfig.Kind]
|
||||
*/
|
||||
fun getPreferredConfig(): IntegrationManagerConfig
|
||||
|
||||
/**
|
||||
* Returns true if integration is enabled, false otherwise.
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.room.powerlevels
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
|
||||
/**
|
||||
|
@ -44,15 +43,15 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
* @param userId the user id
|
||||
* @return true if the user can send this type of event
|
||||
*/
|
||||
fun isAllowedToSend(eventType: String, userId: String): Boolean {
|
||||
return if (eventType.isNotEmpty() && userId.isNotEmpty()) {
|
||||
fun isAllowedToSend(isState: Boolean, eventType: String?, userId: String): Boolean {
|
||||
return if (userId.isNotEmpty()) {
|
||||
val powerLevel = getUserPowerLevel(userId)
|
||||
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
||||
?: if (EventType.isStateEvent(eventType)) {
|
||||
powerLevelsContent.stateDefault
|
||||
} else {
|
||||
powerLevelsContent.eventsDefault
|
||||
}
|
||||
?: if (isState) {
|
||||
powerLevelsContent.stateDefault
|
||||
} else {
|
||||
powerLevelsContent.eventsDefault
|
||||
}
|
||||
powerLevel >= minimumPowerLevel
|
||||
} else false
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.api.session.room.send
|
||||
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
|
@ -28,6 +29,14 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
*/
|
||||
interface SendService {
|
||||
|
||||
/**
|
||||
* Method to send a generic event asynchronously. If you want to send a state event, please use [StateService] instead.
|
||||
* @param eventType the type of the event
|
||||
* @param content the optional body as a json dict.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendEvent(eventType: String, content: Content?): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a text message asynchronously.
|
||||
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||
|
|
|
@ -18,7 +18,10 @@ package im.vector.matrix.android.api.session.room.state
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
||||
interface StateService {
|
||||
|
@ -26,9 +29,15 @@ interface StateService {
|
|||
/**
|
||||
* Update the topic of the room
|
||||
*/
|
||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun getStateEvent(eventType: String, stateKey: String): Event?
|
||||
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
|
||||
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
||||
|
||||
fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>>
|
||||
|
||||
fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event>
|
||||
|
||||
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets
|
||||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
||||
sealed class WidgetManagementFailure : Failure.FeatureFailure() {
|
||||
object NotEnoughPower : WidgetManagementFailure()
|
||||
object CreationFailed : WidgetManagementFailure()
|
||||
data class TermsNotSignedException(val baseUrl: String, val token: String) : WidgetManagementFailure()
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets
|
||||
|
||||
import android.webkit.WebView
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import java.lang.reflect.Type
|
||||
|
||||
interface WidgetPostAPIMediator {
|
||||
|
||||
/**
|
||||
* This initialize the webview to handle.
|
||||
* It will add a JavaScript Interface.
|
||||
* Please call [clearWebView] method when finished to clean the provided webview
|
||||
*/
|
||||
fun setWebView(webView: WebView)
|
||||
|
||||
/**
|
||||
* Set handler to communicate with the widgetPostAPIMediator.
|
||||
* Please remove the reference by passing null when finished.
|
||||
*/
|
||||
fun setHandler(handler: Handler?)
|
||||
|
||||
/**
|
||||
* This clear the mediator by removing the JavaScript Interface and cleaning references.
|
||||
*/
|
||||
fun clearWebView()
|
||||
|
||||
/**
|
||||
* Inject the necessary javascript into the configured WebView.
|
||||
* Should be called after a web page has been loaded.
|
||||
*/
|
||||
fun injectAPI()
|
||||
|
||||
/**
|
||||
* Send a boolean response
|
||||
*
|
||||
* @param response the response
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
fun sendBoolResponse(response: Boolean, eventData: JsonDict)
|
||||
|
||||
/**
|
||||
* Send an integer response
|
||||
*
|
||||
* @param response the response
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
fun sendIntegerResponse(response: Int, eventData: JsonDict)
|
||||
|
||||
/**
|
||||
* Send an object response
|
||||
*
|
||||
* @param klass the class of the response
|
||||
* @param response the response
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
fun <T> sendObjectResponse(type: Type, response: T?, eventData: JsonDict)
|
||||
|
||||
/**
|
||||
* Send success
|
||||
*
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
fun sendSuccess(eventData: JsonDict)
|
||||
|
||||
/**
|
||||
* Send an error
|
||||
*
|
||||
* @param message the error message
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
fun sendError(message: String, eventData: JsonDict)
|
||||
|
||||
interface Handler {
|
||||
/**
|
||||
* Triggered when a widget is posting
|
||||
*/
|
||||
fun handleWidgetRequest(eventData: JsonDict): Boolean
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Returns an instance of [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
|
||||
|
||||
/**
|
||||
* 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(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue = QueryStringValue.NoCondition,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): 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(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue = QueryStringValue.NoCondition,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): 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(
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): 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(
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets
|
||||
|
||||
interface WidgetURLFormatter {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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(
|
||||
baseUrl: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
forceFetchScalarToken: Boolean = false,
|
||||
bypassWhitelist: Boolean
|
||||
): String
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets.model
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.sender.SenderInfo
|
||||
|
||||
data class Widget(
|
||||
val widgetContent: WidgetContent,
|
||||
val event: Event,
|
||||
val widgetId: String,
|
||||
val senderInfo: SenderInfo?,
|
||||
val isAddedByMe: Boolean,
|
||||
val computedUrl: String?,
|
||||
val type: WidgetType
|
||||
) {
|
||||
|
||||
val isActive = widgetContent.isActive()
|
||||
|
||||
val name = widgetContent.getHumanName()
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets.model
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WidgetContent(
|
||||
@Json(name = "creatorUserId") val creatorUserId: String? = null,
|
||||
@Json(name = "id") val id: String? = null,
|
||||
@Json(name = "type") val type: String? = null,
|
||||
@Json(name = "url") val url: String? = null,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
@Json(name = "data") val data: JsonDict = emptyMap(),
|
||||
@Json(name = "waitForIframeLoad") val waitForIframeLoad: Boolean = false
|
||||
) {
|
||||
|
||||
fun isActive() = type != null && url != null
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun getHumanName(): String {
|
||||
return (name ?: type ?: "").capitalize()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.widgets.model
|
||||
|
||||
sealed class WidgetType(open val preferred: String, open val legacy: String = preferred) {
|
||||
object Jitsi : WidgetType("m.jitsi", "jitsi")
|
||||
object TradingView : WidgetType("m.tradingview")
|
||||
object Spotify : WidgetType("m.spotify")
|
||||
object Video : WidgetType("m.video")
|
||||
object GoogleDoc : WidgetType("m.googledoc")
|
||||
object GoogleCalendar : WidgetType("m.googlecalendar")
|
||||
object Etherpad : WidgetType("m.etherpad")
|
||||
object StickerPicker : WidgetType("m.stickerpicker")
|
||||
object Grafana : WidgetType("m.grafana")
|
||||
object Custom : WidgetType("m.custom")
|
||||
object IntegrationManager : WidgetType("m.integration_manager")
|
||||
data class Fallback(override val preferred: String) : WidgetType(preferred)
|
||||
|
||||
fun matches(type: String?): Boolean {
|
||||
return type == preferred || type == legacy
|
||||
}
|
||||
|
||||
fun values(): Set<String> {
|
||||
return setOf(preferred, legacy)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return matchingType ?: Fallback(type)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.identity.todelete
|
||||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
|||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
// There will be a duplicated class when Integration manager will be merged, so delete this one
|
||||
internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
|
||||
|
||||
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class ScalarTokenEntity(
|
||||
@PrimaryKey var serverUrl: String = "",
|
||||
var token: String = ""
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
}
|
|
@ -55,6 +55,8 @@ import io.realm.annotations.RealmModule
|
|||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberSummaryEntity::class,
|
||||
CurrentStateEventEntity::class,
|
||||
UserAccountDataEntity::class
|
||||
UserAccountDataEntity::class,
|
||||
ScalarTokenEntity::class,
|
||||
WellknownIntegrationManagerConfigEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class WellknownIntegrationManagerConfigEntity(
|
||||
@PrimaryKey var id: Long = 0,
|
||||
var apiUrl: String = "",
|
||||
var uiUrl: String = ""
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
}
|
|
@ -23,7 +23,7 @@ import io.realm.Realm
|
|||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.createObject
|
||||
|
||||
internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: String, type: String): RealmQuery<CurrentStateEventEntity> {
|
||||
internal fun CurrentStateEventEntity.Companion.whereType(realm: Realm, roomId: String, type: String): RealmQuery<CurrentStateEventEntity> {
|
||||
return realm.where(CurrentStateEventEntity::class.java)
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
||||
|
@ -31,7 +31,7 @@ internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: Strin
|
|||
|
||||
internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, roomId: String, type: String, stateKey: String)
|
||||
: RealmQuery<CurrentStateEventEntity> {
|
||||
return where(realm = realm, roomId = roomId, type = type)
|
||||
return whereType(realm = realm, roomId = roomId, type = type)
|
||||
.equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.ScalarTokenEntity
|
||||
import im.vector.matrix.android.internal.database.model.ScalarTokenEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun ScalarTokenEntity.Companion.where(realm: Realm, serverUrl: String): RealmQuery<ScalarTokenEntity> {
|
||||
return realm
|
||||
.where<ScalarTokenEntity>()
|
||||
.equalTo(ScalarTokenEntityFields.SERVER_URL, serverUrl)
|
||||
}
|
|
@ -14,13 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.identity.todelete
|
||||
package im.vector.matrix.android.internal.extensions
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
|
||||
// There will be a duplicated class when Integration manager will be merged, so delete this one
|
||||
inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
|
||||
this.observe(owner, Observer { observer(it) })
|
||||
}
|
|
@ -31,6 +31,5 @@ internal object NetworkConstants {
|
|||
const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
|
||||
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
|
||||
|
||||
// TODO Ganfra, use correct value
|
||||
const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
|
||||
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.network
|
|||
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import okhttp3.Call
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
@ -29,7 +30,7 @@ class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
|||
|
||||
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.baseUrl(baseUrl.ensureTrailingSlash())
|
||||
.callFactory(object : Call.Factory {
|
||||
override fun newCall(request: Request): Call {
|
||||
return okHttpClient.get().newCall(request)
|
||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
|||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
|
@ -45,6 +46,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
|
|||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||
|
@ -56,6 +58,7 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecr
|
|||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||
import im.vector.matrix.android.internal.session.widgets.WidgetDependenciesHolder
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -90,6 +93,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val fileService: Lazy<FileService>,
|
||||
private val secureStorageService: Lazy<SecureStorageService>,
|
||||
private val profileService: Lazy<ProfileService>,
|
||||
private val widgetService: Lazy<WidgetService>,
|
||||
private val syncThreadProvider: Provider<SyncThread>,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
private val syncTokenStore: SyncTokenStore,
|
||||
|
@ -101,11 +105,13 @@ internal class DefaultSession @Inject constructor(
|
|||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val accountService: Lazy<AccountService>,
|
||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val defaultIdentityService: DefaultIdentityService,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : Session,
|
||||
private val integrationManagerService: IntegrationManagerService,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val widgetDependenciesHolder: WidgetDependenciesHolder,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
GroupService by groupService.get(),
|
||||
|
@ -142,6 +148,7 @@ internal class DefaultSession @Inject constructor(
|
|||
timelineEventDecryptor.start()
|
||||
shieldTrustUpdater.start()
|
||||
defaultIdentityService.start()
|
||||
widgetDependenciesHolder.start()
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -187,6 +194,7 @@ internal class DefaultSession @Inject constructor(
|
|||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
// This has to be done on main thread
|
||||
defaultIdentityService.stop()
|
||||
widgetDependenciesHolder.stop()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,6 +241,10 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
override fun identityService() = defaultIdentityService
|
||||
|
||||
override fun widgetService(): WidgetService = widgetService.get()
|
||||
|
||||
override fun integrationManagerService() = integrationManagerService
|
||||
|
||||
override fun addListener(listener: Session.Listener) {
|
||||
sessionListeners.addListener(listener)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
|||
import im.vector.matrix.android.internal.session.group.GroupModule
|
||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityModule
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManagerModule
|
||||
import im.vector.matrix.android.internal.session.openid.OpenIdModule
|
||||
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
||||
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
||||
|
@ -55,6 +56,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
|||
import im.vector.matrix.android.internal.session.terms.TermsModule
|
||||
import im.vector.matrix.android.internal.session.user.UserModule
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
||||
import im.vector.matrix.android.internal.session.widgets.WidgetModule
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
|
||||
|
@ -74,6 +76,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||
CryptoModule::class,
|
||||
PushersModule::class,
|
||||
OpenIdModule::class,
|
||||
WidgetModule::class,
|
||||
IntegrationManagerModule::class,
|
||||
IdentityModule::class,
|
||||
TermsModule::class,
|
||||
AccountDataModule::class,
|
||||
|
|
|
@ -19,16 +19,16 @@ package im.vector.matrix.android.internal.session.content
|
|||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||
|
||||
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
||||
|
||||
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
|
||||
private val sep = if (baseUrl.endsWith("/")) "" else "/"
|
||||
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash()
|
||||
|
||||
override val uploadUrl = baseUrl + sep + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
||||
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
||||
|
||||
override fun resolveFullSize(contentUrl: String?): String? {
|
||||
return contentUrl
|
||||
|
@ -66,7 +66,7 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
|||
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
||||
}
|
||||
|
||||
return baseUrl + sep + prefix + serverAndMediaId + params + fragment
|
||||
return baseUrl + prefix + serverAndMediaId + params + fragment
|
||||
}
|
||||
|
||||
private fun String.isValidMatrixContentUrl(): Boolean {
|
||||
|
|
|
@ -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.wellknown.WellknownResult
|
||||
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.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
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.util.awaitTransaction
|
||||
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -39,6 +41,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
private val monarchy: Monarchy,
|
||||
private val eventBus: EventBus,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val configExtractor: IntegrationManagerConfigExtractor,
|
||||
@UserId
|
||||
private val userId: String
|
||||
) : GetHomeServerCapabilitiesTask {
|
||||
|
@ -102,8 +105,14 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
|
||||
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,16 +37,16 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.extensions.observeNotNull
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
|
||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||
import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
|
||||
import im.vector.matrix.android.internal.session.profile.UnbindThreePidsTask
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AllowedWidgetsContent(
|
||||
/**
|
||||
* Map of stateEventId to Allowed
|
||||
*/
|
||||
@Json(name = "widgets") val widgets: Map<String, Boolean> = emptyMap(),
|
||||
|
||||
/**
|
||||
* Map of native widgetType to a map of domain to Allowed
|
||||
* {
|
||||
* "jitsi" : {
|
||||
* "jitsi.domain.org" : true,
|
||||
* "jitsi.other.org" : false
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@Json(name = "native_widgets") val native: Map<String, Map<String, Boolean>> = emptyMap()
|
||||
)
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService {
|
||||
|
||||
override fun addListener(listener: IntegrationManagerService.Listener) {
|
||||
integrationManager.addListener(listener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: IntegrationManagerService.Listener) {
|
||||
integrationManager.removeListener(listener)
|
||||
}
|
||||
|
||||
override fun getOrderedConfigs(): List<IntegrationManagerConfig> {
|
||||
return integrationManager.getOrderedConfigs()
|
||||
}
|
||||
|
||||
override fun getPreferredConfig(): IntegrationManagerConfig {
|
||||
return integrationManager.getPreferredConfig()
|
||||
}
|
||||
|
||||
override fun isIntegrationEnabled(): Boolean {
|
||||
return integrationManager.isIntegrationEnabled()
|
||||
}
|
||||
|
||||
override fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return integrationManager.setIntegrationEnabled(enable, callback)
|
||||
}
|
||||
|
||||
override fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return integrationManager.setWidgetAllowed(stateEventId, allowed, callback)
|
||||
}
|
||||
|
||||
override fun isWidgetAllowed(stateEventId: String): Boolean {
|
||||
return integrationManager.isWidgetAllowed(stateEventId)
|
||||
}
|
||||
|
||||
override fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback)
|
||||
}
|
||||
|
||||
override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean {
|
||||
return integrationManager.isNativeWidgetDomainAllowed(widgetType, domain)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetType
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
import im.vector.matrix.android.internal.database.model.WellknownIntegrationManagerConfigEntity
|
||||
import im.vector.matrix.android.internal.extensions.observeNotNull
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.WidgetFactory
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.extractWidgetSequence
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* The integration manager allows to
|
||||
* - Get the Integration Manager that a user has explicitly set for its account (via account data)
|
||||
* - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown)
|
||||
* - Check if the user has disabled the integration manager feature
|
||||
* - Allow / Disallow Integration manager (propagated to other riot clients)
|
||||
*
|
||||
* The integration manager listen to account data, and can notify observer for changes.
|
||||
*
|
||||
* The wellknown is refreshed at each application fresh start
|
||||
*
|
||||
*/
|
||||
@SessionScope
|
||||
internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val widgetFactory: WidgetFactory) {
|
||||
|
||||
private val currentConfigs = ArrayList<IntegrationManagerConfig>()
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||
|
||||
private val listeners = HashSet<IntegrationManagerService.Listener>()
|
||||
fun addListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.add(listener) }
|
||||
fun removeListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.remove(listener) }
|
||||
|
||||
init {
|
||||
val defaultConfig = IntegrationManagerConfig(
|
||||
uiUrl = matrixConfiguration.integrationUIUrl,
|
||||
restUrl = matrixConfiguration.integrationRestUrl,
|
||||
kind = IntegrationManagerConfig.Kind.DEFAULT
|
||||
)
|
||||
currentConfigs.add(defaultConfig)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
observeWellknownConfig()
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val allowedWidgetsContent = it.getOrNull()?.content?.toModel<AllowedWidgetsContent>()
|
||||
if (allowedWidgetsContent != null) {
|
||||
notifyWidgetPermissionsChanged(allowedWidgetsContent)
|
||||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationProvisioningContent = it.getOrNull()?.content?.toModel<IntegrationProvisioningContent>()
|
||||
if (integrationProvisioningContent != null) {
|
||||
notifyIsEnabledChanged(integrationProvisioningContent)
|
||||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationManagerContent = it.getOrNull()?.asIntegrationManagerWidgetContent()
|
||||
val config = integrationManagerContent?.extractIntegrationManagerConfig()
|
||||
updateCurrentConfigs(IntegrationManagerConfig.Kind.ACCOUNT, config)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
fun hasConfig() = currentConfigs.isNotEmpty()
|
||||
|
||||
fun getOrderedConfigs(): List<IntegrationManagerConfig> {
|
||||
return currentConfigs.sortedBy {
|
||||
it.kind
|
||||
}
|
||||
}
|
||||
|
||||
fun getPreferredConfig(): IntegrationManagerConfig {
|
||||
// This can't be null as we should have at least the default one registered
|
||||
return getOrderedConfigs().first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the user as disabled integration manager feature
|
||||
*/
|
||||
fun isIntegrationEnabled(): Boolean {
|
||||
val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
|
||||
val integrationProvisioningContent = integrationProvisioningData?.content?.toModel<IntegrationProvisioningContent>()
|
||||
return integrationProvisioningContent?.enabled ?: false
|
||||
}
|
||||
|
||||
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val isIntegrationEnabled = isIntegrationEnabled()
|
||||
if (enable == isIntegrationEnabled) {
|
||||
callback.onSuccess(Unit)
|
||||
return NoOpCancellable
|
||||
}
|
||||
val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable)
|
||||
val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val allowedWidget = mapOf(stateEventId to allowed)
|
||||
AllowedWidgetsContent(widgets = allowedWidget, native = emptyMap())
|
||||
} else {
|
||||
val allowedWidgets = currentContent.widgets.toMutableMap().apply {
|
||||
put(stateEventId, allowed)
|
||||
}
|
||||
currentContent.copy(widgets = allowedWidgets)
|
||||
}
|
||||
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun isWidgetAllowed(stateEventId: String): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.widgets?.get(stateEventId) ?: false
|
||||
}
|
||||
|
||||
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed))
|
||||
AllowedWidgetsContent(widgets = emptyMap(), native = nativeAllowedWidgets)
|
||||
} else {
|
||||
val nativeAllowedWidgets = currentContent.native.toMutableMap().apply {
|
||||
(get(widgetType))?.let {
|
||||
set(widgetType, it.toMutableMap().apply { set(domain, allowed) })
|
||||
} ?: run {
|
||||
set(widgetType, mapOf(domain to allowed))
|
||||
}
|
||||
}
|
||||
currentContent.copy(native = nativeAllowedWidgets)
|
||||
}
|
||||
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.native?.get(widgetType)?.get(domain) ?: false
|
||||
}
|
||||
|
||||
private fun notifyConfigurationChanged() {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onConfigurationChanged(currentConfigs)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyWidgetPermissionsChanged(allowedWidgets: AllowedWidgetsContent) {
|
||||
Timber.v("On widget permissions changed: $allowedWidgets")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onWidgetPermissionsChanged(allowedWidgets.widgets)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyIsEnabledChanged(provisioningContent: IntegrationProvisioningContent) {
|
||||
Timber.v("On provisioningContent changed : $provisioningContent")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onIsEnabledChanged(provisioningContent.enabled)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WidgetContent.extractIntegrationManagerConfig(): IntegrationManagerConfig? {
|
||||
if (url.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
val integrationManagerData = data.toModel<IntegrationManagerWidgetData>()
|
||||
return IntegrationManagerConfig(
|
||||
uiUrl = url,
|
||||
restUrl = integrationManagerData?.apiUrl ?: url,
|
||||
kind = IntegrationManagerConfig.Kind.ACCOUNT
|
||||
)
|
||||
}
|
||||
|
||||
private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
|
||||
return extractWidgetSequence(widgetFactory)
|
||||
.filter {
|
||||
WidgetType.IntegrationManager == it.type
|
||||
}
|
||||
.firstOrNull()?.widgetContent
|
||||
}
|
||||
|
||||
private fun observeWellknownConfig() {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ it.where(WellknownIntegrationManagerConfigEntity::class.java) },
|
||||
{ IntegrationManagerConfig(it.uiUrl, it.apiUrl, IntegrationManagerConfig.Kind.HOMESERVER) }
|
||||
)
|
||||
liveData.observeNotNull(lifecycleOwner) {
|
||||
val config = it.firstOrNull()
|
||||
updateCurrentConfigs(IntegrationManagerConfig.Kind.HOMESERVER, config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCurrentConfigs(kind: IntegrationManagerConfig.Kind, config: IntegrationManagerConfig?) {
|
||||
val hasBeenRemoved = currentConfigs.removeAll { currentConfig ->
|
||||
currentConfig.kind == kind
|
||||
}
|
||||
if (config != null) {
|
||||
currentConfigs.add(config)
|
||||
}
|
||||
if (hasBeenRemoved || config != null) {
|
||||
notifyConfigurationChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||
import im.vector.matrix.android.internal.database.model.WellknownIntegrationManagerConfigEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class IntegrationManagerConfigExtractor @Inject constructor() {
|
||||
|
||||
fun extract(wellKnown: WellKnown): WellknownIntegrationManagerConfigEntity? {
|
||||
wellKnown.integrations?.get("managers")?.let {
|
||||
(it as? List<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
return WellknownIntegrationManagerConfigEntity(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
|
||||
@Module
|
||||
internal abstract class IntegrationManagerModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindIntegrationManagerService(service: DefaultIntegrationManagerService): IntegrationManagerService
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IntegrationManagerWidgetData(
|
||||
@Json(name = "api_url") val apiUrl: String? = null
|
||||
)
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IntegrationProvisioningContent(
|
||||
@Json(name = "enabled") val enabled: Boolean
|
||||
)
|
|
@ -48,7 +48,7 @@ internal class DefaultConditionResolver @Inject constructor(
|
|||
val roomId = event.roomId ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
|
||||
val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
?: PowerLevelsContent()
|
||||
|
|
|
@ -116,9 +116,11 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported"))
|
||||
}
|
||||
else -> {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_ENCRYPTION,
|
||||
mapOf(
|
||||
val params = SendStateTask.Params(
|
||||
roomId = roomId,
|
||||
stateKey = null,
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
body = mapOf(
|
||||
"algorithm" to algorithm
|
||||
))
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse
|
|||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
|
||||
|
@ -175,7 +176,7 @@ internal interface RoomAPI {
|
|||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
|
||||
fun sendStateEvent(@Path("roomId") roomId: String,
|
||||
@Path("state_event_type") stateEventType: String,
|
||||
@Body params: Map<String, String>): Call<Unit>
|
||||
@Body params: JsonDict): Call<Unit>
|
||||
|
||||
/**
|
||||
* Send a generic state events
|
||||
|
@ -189,7 +190,7 @@ internal interface RoomAPI {
|
|||
fun sendStateEvent(@Path("roomId") roomId: String,
|
||||
@Path("state_event_type") stateEventType: String,
|
||||
@Path("state_key") stateKey: String,
|
||||
@Body params: Map<String, String>): Call<Unit>
|
||||
@Body params: JsonDict): Call<Unit>
|
||||
|
||||
/**
|
||||
* Send a relation event to a room.
|
||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||
|
@ -67,6 +68,12 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
|
||||
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
override fun sendEvent(eventType: String, content: JsonDict?): Cancelable {
|
||||
return localEchoEventFactory.createEvent(roomId, eventType, content)
|
||||
.also { createLocalEcho(it) }
|
||||
.let { sendEvent(it) }
|
||||
}
|
||||
|
||||
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
|
||||
.also { createLocalEcho(it) }
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.exifinterface.media.ExifInterface
|
|||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
|
@ -95,7 +96,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
|
||||
}
|
||||
val content = MessageTextContent(msgType = msgType, body = text.toString())
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
|
||||
|
@ -129,7 +130,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
|
||||
|
||||
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event {
|
||||
return createEvent(roomId, textContent.toMessageTextContent(msgType))
|
||||
return createMessageEvent(roomId, textContent.toMessageTextContent(msgType))
|
||||
}
|
||||
|
||||
fun createReplaceTextEvent(roomId: String,
|
||||
|
@ -138,7 +139,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
newBodyAutoMarkdown: Boolean,
|
||||
msgType: String,
|
||||
compatibilityText: String): Event {
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessageTextContent(
|
||||
msgType = msgType,
|
||||
body = compatibilityText,
|
||||
|
@ -153,7 +154,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
pollEventId: String,
|
||||
optionIndex: Int,
|
||||
optionLabel: String): Event {
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessagePollResponseContent(
|
||||
body = optionLabel,
|
||||
relatesTo = RelationDefaultContent(
|
||||
|
@ -175,7 +176,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
append(it.value)
|
||||
}
|
||||
}
|
||||
return createEvent(
|
||||
return createMessageEvent(
|
||||
roomId,
|
||||
MessageOptionsContent(
|
||||
body = compatLabel,
|
||||
|
@ -211,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
//
|
||||
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
|
||||
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessageTextContent(
|
||||
msgType = msgType,
|
||||
body = compatibilityText,
|
||||
|
@ -280,7 +281,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -316,7 +317,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -329,7 +330,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -342,18 +343,22 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createEvent(roomId: String, content: Any? = null): Event {
|
||||
private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event {
|
||||
return createEvent(roomId, EventType.MESSAGE, content.toContent())
|
||||
}
|
||||
|
||||
fun createEvent(roomId: String, type: String, content: Content?): Event {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
content = content.toContent(),
|
||||
type = type,
|
||||
content = content,
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
)
|
||||
}
|
||||
|
@ -410,7 +415,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
formattedBody = replyFormatted,
|
||||
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
||||
|
|
|
@ -17,26 +17,21 @@
|
|||
package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.getOrNull
|
||||
import im.vector.matrix.android.internal.database.query.whereStateKey
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import io.realm.Realm
|
||||
|
||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendStateTask: SendStateTask
|
||||
) : StateService {
|
||||
|
@ -46,33 +41,47 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
|||
fun create(roomId: String): StateService
|
||||
}
|
||||
|
||||
override fun getStateEvent(eventType: String, stateKey: String): Event? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
CurrentStateEventEntity.getOrNull(realm, roomId, type = eventType, stateKey = stateKey)?.root?.asDomain()
|
||||
}
|
||||
override fun getStateEvent(eventType: String, stateKey: QueryStringValue): Event? {
|
||||
return stateEventDataSource.getStateEvent(roomId, eventType, stateKey)
|
||||
}
|
||||
|
||||
override fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm -> CurrentStateEventEntity.whereStateKey(realm, roomId, type = eventType, stateKey = "") },
|
||||
{ it.root?.asDomain() }
|
||||
override fun getStateEventLive(eventType: String, stateKey: QueryStringValue): LiveData<Optional<Event>> {
|
||||
return stateEventDataSource.getStateEventLive(roomId, eventType, stateKey)
|
||||
}
|
||||
|
||||
override fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> {
|
||||
return stateEventDataSource.getStateEvents(roomId, eventTypes, stateKey)
|
||||
}
|
||||
|
||||
override fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue): LiveData<List<Event>> {
|
||||
return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey)
|
||||
}
|
||||
|
||||
override fun sendStateEvent(
|
||||
eventType: String,
|
||||
stateKey: String?,
|
||||
body: JsonDict,
|
||||
callback: MatrixCallback<Unit>
|
||||
): Cancelable {
|
||||
val params = SendStateTask.Params(
|
||||
roomId = roomId,
|
||||
stateKey = stateKey,
|
||||
eventType = eventType,
|
||||
body = body
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
mapOf(
|
||||
"topic" to topic
|
||||
))
|
||||
|
||||
sendStateTask
|
||||
return sendStateTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_TOPIC,
|
||||
body = mapOf("topic" to topic),
|
||||
callback = callback,
|
||||
stateKey = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
|
@ -25,8 +26,9 @@ import javax.inject.Inject
|
|||
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val stateKey: String?,
|
||||
val eventType: String,
|
||||
val body: Map<String, String>
|
||||
val body: JsonDict
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -37,7 +39,20 @@ internal class DefaultSendStateTask @Inject constructor(
|
|||
|
||||
override suspend fun execute(params: SendStateTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body)
|
||||
apiCall = if (params.stateKey == null) {
|
||||
roomAPI.sendStateEvent(
|
||||
roomId = params.roomId,
|
||||
stateEventType = params.eventType,
|
||||
params = params.body
|
||||
)
|
||||
} else {
|
||||
roomAPI.sendStateEvent(
|
||||
roomId = params.roomId,
|
||||
stateEventType = params.eventType,
|
||||
stateKey = params.stateKey,
|
||||
params = params.body
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
|
||||
import im.vector.matrix.android.internal.query.process
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class StateEventDataSource @Inject constructor(private val monarchy: Monarchy) {
|
||||
|
||||
fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
buildStateEventQuery(realm, roomId, setOf(eventType), stateKey).findFirst()?.root?.asDomain()
|
||||
}
|
||||
}
|
||||
|
||||
fun getStateEventLive(roomId: String, eventType: String, stateKey: QueryStringValue): LiveData<Optional<Event>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey) },
|
||||
{ it.root?.asDomain() }
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun getStateEvents(roomId: String, eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
buildStateEventQuery(realm, roomId, eventTypes, stateKey)
|
||||
.findAll()
|
||||
.mapNotNull {
|
||||
it.root?.asDomain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getStateEventsLive(roomId: String, eventTypes: Set<String>, stateKey: QueryStringValue): LiveData<List<Event>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) },
|
||||
{ it.root?.asDomain() }
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.filterNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildStateEventQuery(realm: Realm,
|
||||
roomId: String,
|
||||
eventTypes: Set<String>,
|
||||
stateKey: QueryStringValue
|
||||
): RealmQuery<CurrentStateEventEntity> {
|
||||
return realm.where<CurrentStateEventEntity>()
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||
.`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
|
||||
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||
}
|
||||
}
|
|
@ -241,7 +241,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
eventIds.add(event.eventId)
|
||||
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
|
||||
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm)
|
||||
if (event.isStateEvent() && event.stateKey != null) {
|
||||
if (event.stateKey != null) {
|
||||
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
|
||||
eventId = event.eventId
|
||||
root = eventEntity
|
||||
|
|
|
@ -30,6 +30,8 @@ abstract class UserAccountData : AccountDataContent {
|
|||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||
const val TYPE_WIDGETS = "m.widgets"
|
||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||
const val TYPE_INTEGRATION_PROVISIONING = "im.vector.setting.integration_provisioning"
|
||||
const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
|
||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.AllowedWidgetsContent
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataAllowedWidgets(
|
||||
@Json(name = "type") override val type: String = TYPE_ALLOWED_WIDGETS,
|
||||
@Json(name = "content") val content: AllowedWidgetsContent
|
||||
) : UserAccountData()
|
|
@ -18,9 +18,10 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserAccountDataEvent(
|
||||
@Json(name = "type") override val type: String,
|
||||
@Json(name = "content") val content: Map<String, Any>
|
||||
@Json(name = "content") val content: JsonDict
|
||||
) : UserAccountData()
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationProvisioningContent
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataIntegrationProvisioning(
|
||||
@Json(name = "type") override val type: String = TYPE_INTEGRATION_PROVISIONING,
|
||||
@Json(name = "content") val content: IntegrationProvisioningContent
|
||||
) : UserAccountData()
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/*
|
||||
"m.widgets":{
|
||||
"stickerpicker_@rxl881:matrix.org_1514573757015":{
|
||||
"content":{
|
||||
"creatorUserId":"@rxl881:matrix.org",
|
||||
"data":{
|
||||
"..."
|
||||
},
|
||||
"id":"stickerpicker_@rxl881:matrix.org_1514573757015",
|
||||
"name":"Stickerpicker",
|
||||
"type":"m.stickerpicker",
|
||||
"url":"https://...",
|
||||
"waitForIframeLoad":true
|
||||
},
|
||||
"sender":"@rxl881:matrix.org"
|
||||
"state_key":"stickerpicker_@rxl881:matrix.org_1514573757015",
|
||||
"type":"m.widget"
|
||||
},
|
||||
{
|
||||
"..."
|
||||
}
|
||||
}
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataWidgets(
|
||||
@Json(name = "type") override val type: String = TYPE_WIDGETS,
|
||||
@Json(name = "content") val content: Map<String, Event>
|
||||
) : UserAccountData()
|
|
@ -28,14 +28,15 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
|
|||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityRegisterTask
|
||||
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -55,17 +56,10 @@ internal class DefaultTermsService @Inject constructor(
|
|||
baseUrl: String,
|
||||
callback: MatrixCallback<GetTermsResponse>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val sep = if (baseUrl.endsWith("/")) "" else "/"
|
||||
|
||||
val url = when (serviceType) {
|
||||
TermsService.ServiceType.IntegrationManager -> "$baseUrl$sep${NetworkConstants.URI_INTEGRATION_MANAGER_PATH}"
|
||||
TermsService.ServiceType.IdentityService -> "$baseUrl$sep${NetworkConstants.URI_IDENTITY_PATH_V2}"
|
||||
}
|
||||
|
||||
val url = buildUrl(baseUrl, serviceType)
|
||||
val termsResponse = executeRequest<TermsResponse>(null) {
|
||||
apiCall = termsAPI.getTerms("${url}terms")
|
||||
}
|
||||
|
||||
GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
|
||||
}
|
||||
}
|
||||
|
@ -76,13 +70,7 @@ internal class DefaultTermsService @Inject constructor(
|
|||
token: String?,
|
||||
callback: MatrixCallback<Unit>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val sep = if (baseUrl.endsWith("/")) "" else "/"
|
||||
|
||||
val url = when (serviceType) {
|
||||
TermsService.ServiceType.IntegrationManager -> "$baseUrl$sep${NetworkConstants.URI_INTEGRATION_MANAGER_PATH}"
|
||||
TermsService.ServiceType.IdentityService -> "$baseUrl$sep${NetworkConstants.URI_IDENTITY_PATH_V2}"
|
||||
}
|
||||
|
||||
val url = buildUrl(baseUrl, serviceType)
|
||||
val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
|
||||
|
||||
executeRequest<Unit>(null) {
|
||||
|
@ -112,6 +100,14 @@ internal class DefaultTermsService @Inject constructor(
|
|||
return token.token
|
||||
}
|
||||
|
||||
private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String {
|
||||
val servicePath = when (serviceType) {
|
||||
TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH
|
||||
TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2
|
||||
}
|
||||
return "${baseUrl.ensureTrailingSlash()}$servicePath"
|
||||
}
|
||||
|
||||
private fun getAlreadyAcceptedTermUrlsFromAccountData(): Set<String> {
|
||||
return accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ACCEPTED_TERMS)
|
||||
?.content
|
||||
|
|
|
@ -17,101 +17,41 @@
|
|||
package im.vector.matrix.android.internal.session.user
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
|
||||
import im.vector.matrix.android.internal.database.model.IgnoredUserEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
|
||||
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy,
|
||||
internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource,
|
||||
private val searchUserTask: SearchUserTask,
|
||||
private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask,
|
||||
private val taskExecutor: TaskExecutor) : UserService {
|
||||
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
|
||||
monarchy.createDataSourceFactory { realm ->
|
||||
realm.where(UserEntity::class.java)
|
||||
.isNotEmpty(UserEntityFields.USER_ID)
|
||||
.sort(UserEntityFields.DISPLAY_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
|
||||
realmDataSourceFactory.map {
|
||||
it.asDomain()
|
||||
}
|
||||
}
|
||||
|
||||
private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
|
||||
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
|
||||
}
|
||||
|
||||
override fun getUser(userId: String): User? {
|
||||
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
||||
?: return null
|
||||
|
||||
return userEntity.asDomain()
|
||||
return userDataSource.getUser(userId)
|
||||
}
|
||||
|
||||
override fun getUserLive(userId: String): LiveData<Optional<User>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ UserEntity.where(it, userId) },
|
||||
{ it.asDomain() }
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
return userDataSource.getUserLive(userId)
|
||||
}
|
||||
|
||||
override fun getUsersLive(): LiveData<List<User>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ realm ->
|
||||
realm.where(UserEntity::class.java)
|
||||
.isNotEmpty(UserEntityFields.USER_ID)
|
||||
.sort(UserEntityFields.DISPLAY_NAME)
|
||||
},
|
||||
{ it.asDomain() }
|
||||
)
|
||||
return userDataSource.getUsersLive()
|
||||
}
|
||||
|
||||
override fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
|
||||
realmDataSourceFactory.updateQuery { realm ->
|
||||
val query = realm.where(UserEntity::class.java)
|
||||
if (filter.isNullOrEmpty()) {
|
||||
query.isNotEmpty(UserEntityFields.USER_ID)
|
||||
} else {
|
||||
query
|
||||
.beginGroup()
|
||||
.contains(UserEntityFields.DISPLAY_NAME, filter)
|
||||
.or()
|
||||
.contains(UserEntityFields.USER_ID, filter)
|
||||
.endGroup()
|
||||
}
|
||||
excludedUserIds
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let {
|
||||
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
|
||||
}
|
||||
query.sort(UserEntityFields.DISPLAY_NAME)
|
||||
}
|
||||
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
||||
return userDataSource.getPagedUsersLive(filter, excludedUserIds)
|
||||
}
|
||||
|
||||
override fun getIgnoredUsersLive(): LiveData<List<User>> {
|
||||
return userDataSource.getIgnoredUsersLive()
|
||||
}
|
||||
|
||||
override fun searchUsersDirectory(search: String,
|
||||
|
@ -126,17 +66,6 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getIgnoredUsersLive(): LiveData<List<User>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ realm ->
|
||||
realm.where(IgnoredUserEntity::class.java)
|
||||
.isNotEmpty(IgnoredUserEntityFields.USER_ID)
|
||||
.sort(IgnoredUserEntityFields.USER_ID)
|
||||
},
|
||||
{ getUser(it.userId) ?: User(userId = it.userId) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList())
|
||||
return updateIgnoredUserIdsTask
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.user
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
|
||||
import im.vector.matrix.android.internal.database.model.IgnoredUserEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class UserDataSource @Inject constructor(private val monarchy: Monarchy) {
|
||||
|
||||
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
|
||||
monarchy.createDataSourceFactory { realm ->
|
||||
realm.where(UserEntity::class.java)
|
||||
.isNotEmpty(UserEntityFields.USER_ID)
|
||||
.sort(UserEntityFields.DISPLAY_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
|
||||
realmDataSourceFactory.map {
|
||||
it.asDomain()
|
||||
}
|
||||
}
|
||||
|
||||
private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
|
||||
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
|
||||
}
|
||||
|
||||
fun getUser(userId: String): User? {
|
||||
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
||||
?: return null
|
||||
|
||||
return userEntity.asDomain()
|
||||
}
|
||||
|
||||
fun getUserLive(userId: String): LiveData<Optional<User>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ UserEntity.where(it, userId) },
|
||||
{ it.asDomain() }
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun getUsersLive(): LiveData<List<User>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ realm ->
|
||||
realm.where(UserEntity::class.java)
|
||||
.isNotEmpty(UserEntityFields.USER_ID)
|
||||
.sort(UserEntityFields.DISPLAY_NAME)
|
||||
},
|
||||
{ it.asDomain() }
|
||||
)
|
||||
}
|
||||
|
||||
fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
|
||||
realmDataSourceFactory.updateQuery { realm ->
|
||||
val query = realm.where(UserEntity::class.java)
|
||||
if (filter.isNullOrEmpty()) {
|
||||
query.isNotEmpty(UserEntityFields.USER_ID)
|
||||
} else {
|
||||
query
|
||||
.beginGroup()
|
||||
.contains(UserEntityFields.DISPLAY_NAME, filter)
|
||||
.or()
|
||||
.contains(UserEntityFields.USER_ID, filter)
|
||||
.endGroup()
|
||||
}
|
||||
excludedUserIds
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let {
|
||||
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
|
||||
}
|
||||
query.sort(UserEntityFields.DISPLAY_NAME)
|
||||
}
|
||||
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
||||
}
|
||||
|
||||
fun getIgnoredUsersLive(): LiveData<List<User>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ realm ->
|
||||
realm.where(IgnoredUserEntity::class.java)
|
||||
.isNotEmpty(IgnoredUserEntityFields.USER_ID)
|
||||
.sort(IgnoredUserEntityFields.USER_ID)
|
||||
},
|
||||
{ getUser(it.userId) ?: User(userId = it.userId) }
|
||||
)
|
||||
}
|
||||
}
|
|
@ -14,13 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.identity.todelete
|
||||
package im.vector.matrix.android.internal.session.user.accountdata
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.AccountDataMapper
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
|
@ -28,7 +29,6 @@ import io.realm.Realm
|
|||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
||||
// There will be a duplicated class when Integration manager will be merged, so delete this one
|
||||
internal class AccountDataDataSource @Inject constructor(private val monarchy: Monarchy,
|
||||
private val accountDataMapper: AccountDataMapper) {
|
||||
|
|
@ -17,18 +17,12 @@
|
|||
package im.vector.matrix.android.internal.session.user.accountdata
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||
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.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
|
@ -39,54 +33,24 @@ internal class DefaultAccountDataService @Inject constructor(
|
|||
private val monarchy: Monarchy,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : AccountDataService {
|
||||
|
||||
private val moshi = MoshiProvider.providesMoshi()
|
||||
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
|
||||
override fun getAccountDataEvent(type: String): UserAccountDataEvent? {
|
||||
return getAccountDataEvents(setOf(type)).firstOrNull()
|
||||
return accountDataDataSource.getAccountDataEvent(type)
|
||||
}
|
||||
|
||||
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
|
||||
return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
|
||||
it.firstOrNull()?.toOptional()
|
||||
}
|
||||
return accountDataDataSource.getLiveAccountDataEvent(type)
|
||||
}
|
||||
|
||||
override fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
|
||||
return monarchy.fetchAllCopiedSync { realm ->
|
||||
realm.where(UserAccountDataEntity::class.java)
|
||||
.apply {
|
||||
if (types.isNotEmpty()) {
|
||||
`in`(UserAccountDataEntityFields.TYPE, types.toTypedArray())
|
||||
}
|
||||
}
|
||||
}.mapNotNull { entity ->
|
||||
entity.type?.let { type ->
|
||||
UserAccountDataEvent(
|
||||
type = type,
|
||||
content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
return accountDataDataSource.getAccountDataEvents(types)
|
||||
}
|
||||
|
||||
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
|
||||
return monarchy.findAllMappedWithChanges({ realm ->
|
||||
realm.where(UserAccountDataEntity::class.java)
|
||||
.apply {
|
||||
if (types.isNotEmpty()) {
|
||||
`in`(UserAccountDataEntityFields.TYPE, types.toTypedArray())
|
||||
}
|
||||
}
|
||||
}, { entity ->
|
||||
UserAccountDataEvent(
|
||||
type = entity.type ?: "",
|
||||
content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
|
||||
)
|
||||
})
|
||||
return accountDataDataSource.getLiveAccountDataEvents(types)
|
||||
}
|
||||
|
||||
override fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>?): Cancelable {
|
||||
|
|
|
@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
|||
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.AllowedWidgetsContent
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationProvisioningContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
|
@ -70,6 +72,22 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class AllowedWidgets(override val type: String = UserAccountData.TYPE_ALLOWED_WIDGETS,
|
||||
private val allowedWidgetsContent: AllowedWidgetsContent) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
return allowedWidgetsContent
|
||||
}
|
||||
}
|
||||
|
||||
data class IntegrationProvisioning(override val type: String = UserAccountData.TYPE_INTEGRATION_PROVISIONING,
|
||||
private val integrationProvisioningContent: IntegrationProvisioningContent) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
return integrationProvisioningContent
|
||||
}
|
||||
}
|
||||
|
||||
data class AnyParams(override val type: String,
|
||||
private val any: Any
|
||||
) : Params {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.whereStateKey
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> {
|
||||
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val widgetId: String,
|
||||
val content: Content
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultCreateWidgetTask @Inject constructor(private val monarchy: Monarchy,
|
||||
private val roomAPI: RoomAPI,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus) : CreateWidgetTask {
|
||||
|
||||
override suspend fun execute(params: CreateWidgetTask.Params) {
|
||||
executeRequest<Unit>(eventBus) {
|
||||
apiCall = roomAPI.sendStateEvent(
|
||||
roomId = params.roomId,
|
||||
stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||
stateKey = params.widgetId,
|
||||
params = params.content
|
||||
)
|
||||
}
|
||||
awaitNotEmptyResult(monarchy.realmConfiguration, 30_000L) {
|
||||
CurrentStateEventEntity
|
||||
.whereStateKey(it, params.roomId, type = EventType.STATE_ROOM_WIDGET_LEGACY, stateKey = params.widgetId)
|
||||
.and()
|
||||
.equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import android.os.Build
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.util.createUIHandler
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.Type
|
||||
import java.util.HashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultWidgetPostAPIMediator @Inject constructor(private val moshi: Moshi,
|
||||
private val widgetPostMessageAPIProvider: WidgetPostMessageAPIProvider)
|
||||
: WidgetPostAPIMediator {
|
||||
|
||||
private val jsonAdapter = moshi.adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
|
||||
private var handler: WidgetPostAPIMediator.Handler? = null
|
||||
private var webView: WebView? = null
|
||||
|
||||
private val uiHandler = createUIHandler()
|
||||
|
||||
override fun setWebView(webView: WebView) {
|
||||
this.webView = webView
|
||||
webView.addJavascriptInterface(this, "Android")
|
||||
}
|
||||
|
||||
override fun clearWebView() {
|
||||
webView?.removeJavascriptInterface("Android")
|
||||
webView = null
|
||||
}
|
||||
|
||||
override fun setHandler(handler: WidgetPostAPIMediator.Handler?) {
|
||||
this.handler = handler
|
||||
}
|
||||
|
||||
override fun injectAPI() {
|
||||
val js = widgetPostMessageAPIProvider.get()
|
||||
if (js != null) {
|
||||
uiHandler.post {
|
||||
webView?.loadUrl("javascript:$js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun onWidgetEvent(jsonEventData: String) {
|
||||
Timber.d("BRIDGE onWidgetEvent : $jsonEventData")
|
||||
try {
|
||||
val dataAsDict = jsonAdapter.fromJson(jsonEventData)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val eventData = (dataAsDict?.get("event.data") as? JsonDict) ?: return
|
||||
onWidgetMessage(eventData)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## onWidgetEvent() failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWidgetMessage(eventData: JsonDict) {
|
||||
try {
|
||||
if (handler?.handleWidgetRequest(eventData) == false) {
|
||||
sendError("", eventData)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## onWidgetMessage() : failed")
|
||||
sendError("", eventData)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* *********************************************************************************************
|
||||
* Message sending methods
|
||||
* *********************************************************************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a boolean response
|
||||
*
|
||||
* @param response the response
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
override fun sendBoolResponse(response: Boolean, eventData: JsonDict) {
|
||||
val jsString = if (response) "true" else "false"
|
||||
sendResponse(jsString, eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an integer response
|
||||
*
|
||||
* @param response the response
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
override fun sendIntegerResponse(response: Int, eventData: JsonDict) {
|
||||
sendResponse(response.toString() + "", eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an object response
|
||||
*
|
||||
* @param response the response
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
override fun <T> sendObjectResponse(type: Type, response: T?, eventData: JsonDict) {
|
||||
var jsString: String? = null
|
||||
if (response != null) {
|
||||
val objectAdapter = moshi.adapter<T>(type)
|
||||
try {
|
||||
jsString = "JSON.parse('${objectAdapter.toJson(response)}')"
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## sendObjectResponse() : toJson failed ")
|
||||
}
|
||||
}
|
||||
sendResponse(jsString ?: "null", eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send success
|
||||
*
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
override fun sendSuccess(eventData: JsonDict) {
|
||||
val successResponse = mapOf("success" to true)
|
||||
sendObjectResponse(Map::class.java, successResponse, eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error
|
||||
*
|
||||
* @param message the error message
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
override fun sendError(message: String, eventData: JsonDict) {
|
||||
Timber.e("## sendError() : eventData $eventData failed $message")
|
||||
|
||||
// TODO: JS has an additional optional parameter: nestedError
|
||||
val params = HashMap<String, Map<String, String>>()
|
||||
val subMap = HashMap<String, String>()
|
||||
subMap["message"] = message
|
||||
params["error"] = subMap
|
||||
sendObjectResponse(Map::class.java, params, eventData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the response to the javascript
|
||||
*
|
||||
* @param jsString the response data
|
||||
* @param eventData the modular data
|
||||
*/
|
||||
private fun sendResponse(jsString: String, eventData: JsonDict) = uiHandler.post {
|
||||
try {
|
||||
val functionLine = "sendResponseFromRiotAndroid('" + eventData["_id"] + "' , " + jsString + ");"
|
||||
Timber.v("BRIDGE sendResponse: $functionLine")
|
||||
// call the javascript method
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
webView?.loadUrl("javascript:$functionLine")
|
||||
} else {
|
||||
webView?.evaluateJavascript(functionLine, null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## sendResponse() failed ")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
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
|
||||
|
||||
internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
|
||||
private val widgetURLFormatter: WidgetURLFormatter,
|
||||
private val widgetPostAPIMediator: WidgetPostAPIMediator)
|
||||
: WidgetService {
|
||||
|
||||
override fun getWidgetURLFormatter(): WidgetURLFormatter {
|
||||
return widgetURLFormatter
|
||||
}
|
||||
|
||||
override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator {
|
||||
return widgetPostAPIMediator
|
||||
}
|
||||
|
||||
override fun getRoomWidgets(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue,
|
||||
widgetTypes: Set<String>?,
|
||||
excludedTypes: Set<String>?
|
||||
): List<Widget> {
|
||||
return widgetManager.getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
override fun getRoomWidgetsLive(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue,
|
||||
widgetTypes: Set<String>?,
|
||||
excludedTypes: Set<String>?
|
||||
): LiveData<List<Widget>> {
|
||||
return widgetManager.getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
override fun getUserWidgetsLive(
|
||||
widgetTypes: Set<String>?,
|
||||
excludedTypes: Set<String>?
|
||||
): LiveData<List<Widget>> {
|
||||
return widgetManager.getUserWidgetsLive(widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
override fun getUserWidgets(
|
||||
widgetTypes: Set<String>?,
|
||||
excludedTypes: Set<String>?
|
||||
): List<Widget> {
|
||||
return widgetManager.getUserWidgets(widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
override fun createRoomWidget(
|
||||
roomId: String,
|
||||
widgetId: String,
|
||||
content: Content,
|
||||
callback: MatrixCallback<Widget>
|
||||
): Cancelable {
|
||||
return widgetManager.createRoomWidget(roomId, widgetId, content, callback)
|
||||
}
|
||||
|
||||
override fun destroyRoomWidget(
|
||||
roomId: String,
|
||||
widgetId: String,
|
||||
callback: MatrixCallback<Unit>
|
||||
): Cancelable {
|
||||
return widgetManager.destroyRoomWidget(roomId, widgetId, callback)
|
||||
}
|
||||
|
||||
override fun hasPermissionsToHandleWidgets(roomId: String): Boolean {
|
||||
return widgetManager.hasPermissionsToHandleWidgets(roomId)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetURLFormatter
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
import im.vector.matrix.android.internal.session.widgets.token.GetScalarTokenTask
|
||||
import java.net.URLEncoder
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultWidgetURLFormatter @Inject constructor(private val integrationManager: IntegrationManager,
|
||||
private val getScalarTokenTask: GetScalarTokenTask,
|
||||
private val matrixConfiguration: MatrixConfiguration
|
||||
) : IntegrationManagerService.Listener, WidgetURLFormatter {
|
||||
|
||||
private lateinit var currentConfig: IntegrationManagerConfig
|
||||
private var whiteListedUrls: List<String> = emptyList()
|
||||
|
||||
fun start() {
|
||||
setupWithConfiguration()
|
||||
integrationManager.addListener(this)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
integrationManager.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(configs: List<IntegrationManagerConfig>) {
|
||||
setupWithConfiguration()
|
||||
}
|
||||
|
||||
private fun setupWithConfiguration() {
|
||||
val preferredConfig = integrationManager.getPreferredConfig()
|
||||
if (!this::currentConfig.isInitialized || preferredConfig != currentConfig) {
|
||||
currentConfig = preferredConfig
|
||||
whiteListedUrls = if (matrixConfiguration.integrationWidgetUrls.isEmpty()) {
|
||||
listOf(preferredConfig.restUrl)
|
||||
} else {
|
||||
matrixConfiguration.integrationWidgetUrls
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of fetching a scalar token if required and build the final url.
|
||||
*/
|
||||
override suspend fun format(baseUrl: String, params: Map<String, String>, forceFetchScalarToken: Boolean, bypassWhitelist: Boolean): String {
|
||||
return if (bypassWhitelist || isWhiteListed(baseUrl)) {
|
||||
val taskParams = GetScalarTokenTask.Params(currentConfig.restUrl, forceFetchScalarToken)
|
||||
val scalarToken = getScalarTokenTask.execute(taskParams)
|
||||
buildString {
|
||||
append(baseUrl)
|
||||
appendParamToUrl("scalar_token", scalarToken)
|
||||
appendParamsToUrl(params)
|
||||
}
|
||||
} else {
|
||||
buildString {
|
||||
append(baseUrl)
|
||||
appendParamsToUrl(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWhiteListed(url: String): Boolean {
|
||||
val allowed: List<String> = whiteListedUrls
|
||||
for (allowedUrl in allowed) {
|
||||
if (url.startsWith(allowedUrl)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendParamsToUrl(params: Map<String, String>): StringBuilder {
|
||||
params.forEach { (param, value) ->
|
||||
appendParamToUrl(param, value)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
|
||||
if (contains("?")) {
|
||||
append("&")
|
||||
} else {
|
||||
append("?")
|
||||
}
|
||||
|
||||
append(param)
|
||||
append("=")
|
||||
append(URLEncoder.encode(value, "utf-8"))
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class RegisterWidgetResponse(
|
||||
@Json(name = "scalar_token") val scalarToken: String?
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WidgetDependenciesHolder @Inject constructor(private val integrationManager: IntegrationManager,
|
||||
private val widgetManager: WidgetManager,
|
||||
private val widgetURLFormatter: DefaultWidgetURLFormatter) {
|
||||
|
||||
fun start() {
|
||||
integrationManager.start()
|
||||
widgetManager.start()
|
||||
widgetURLFormatter.start()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
widgetURLFormatter.stop()
|
||||
widgetManager.stop()
|
||||
integrationManager.stop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
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.room.model.PowerLevelsContent
|
||||
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.util.Cancelable
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
import im.vector.matrix.android.internal.session.room.state.StateEventDataSource
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.WidgetFactory
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.extractWidgetSequence
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import java.util.HashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val createWidgetTask: CreateWidgetTask,
|
||||
private val widgetFactory: WidgetFactory,
|
||||
@UserId private val userId: String) : IntegrationManagerService.Listener {
|
||||
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||
|
||||
fun start() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
integrationManager.addListener(this)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
integrationManager.removeListener(this)
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
fun getRoomWidgetsLive(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue = QueryStringValue.NoCondition,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): LiveData<List<Widget>> {
|
||||
// Get all im.vector.modular.widgets state events in the room
|
||||
val liveWidgetEvents = stateEventDataSource.getStateEventsLive(
|
||||
roomId = roomId,
|
||||
eventTypes = setOf(EventType.STATE_ROOM_WIDGET, EventType.STATE_ROOM_WIDGET_LEGACY),
|
||||
stateKey = widgetId
|
||||
)
|
||||
return Transformations.map(liveWidgetEvents) { widgetEvents ->
|
||||
widgetEvents.mapEventsToWidgets(widgetTypes, excludedTypes)
|
||||
}
|
||||
}
|
||||
|
||||
fun getRoomWidgets(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue = QueryStringValue.NoCondition,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): List<Widget> {
|
||||
// Get all im.vector.modular.widgets state events in the room
|
||||
val widgetEvents: List<Event> = stateEventDataSource.getStateEvents(
|
||||
roomId = roomId,
|
||||
eventTypes = setOf(EventType.STATE_ROOM_WIDGET, EventType.STATE_ROOM_WIDGET_LEGACY),
|
||||
stateKey = widgetId
|
||||
)
|
||||
return widgetEvents.mapEventsToWidgets(widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
private fun List<Event>.mapEventsToWidgets(widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null): List<Widget> {
|
||||
val widgetEvents = this
|
||||
// Widget id -> widget
|
||||
val widgets: MutableMap<String, Widget> = HashMap()
|
||||
// Order widgetEvents with the last event first
|
||||
// There can be several im.vector.modular.widgets state events for a same widget but
|
||||
// only the last one must be considered.
|
||||
val sortedWidgetEvents = widgetEvents.sortedByDescending {
|
||||
it.originServerTs
|
||||
}
|
||||
// Create each widget from its latest im.vector.modular.widgets state event
|
||||
for (widgetEvent in sortedWidgetEvents) { // Filter widget types if required
|
||||
val widget = widgetFactory.create(widgetEvent) ?: continue
|
||||
val widgetType = widget.widgetContent.type ?: continue
|
||||
if (widgetTypes != null && !widgetTypes.contains(widgetType)) {
|
||||
continue
|
||||
}
|
||||
if (excludedTypes != null && excludedTypes.contains(widgetType)) {
|
||||
continue
|
||||
}
|
||||
if (!widgets.containsKey(widget.widgetId)) {
|
||||
widgets[widget.widgetId] = widget
|
||||
}
|
||||
}
|
||||
return widgets.values.toList()
|
||||
}
|
||||
|
||||
fun getUserWidgetsLive(
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): LiveData<List<Widget>> {
|
||||
val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountData.TYPE_WIDGETS)
|
||||
return Transformations.map(widgetsAccountData) {
|
||||
it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes) ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun getUserWidgets(
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): List<Widget> {
|
||||
val widgetsAccountData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_WIDGETS) ?: return emptyList()
|
||||
return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null): List<Widget> {
|
||||
return extractWidgetSequence(widgetFactory)
|
||||
.filter {
|
||||
val widgetType = it.widgetContent.type ?: return@filter false
|
||||
(widgetTypes == null || widgetTypes.contains(widgetType))
|
||||
&& (excludedTypes == null || !excludedTypes.contains(widgetType))
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(callback = callback) {
|
||||
if (!hasPermissionsToHandleWidgets(roomId)) {
|
||||
throw WidgetManagementFailure.NotEnoughPower
|
||||
}
|
||||
val params = CreateWidgetTask.Params(
|
||||
roomId = roomId,
|
||||
widgetId = widgetId,
|
||||
content = content
|
||||
)
|
||||
createWidgetTask.execute(params)
|
||||
try {
|
||||
getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first()
|
||||
} catch (failure: Throwable) {
|
||||
throw WidgetManagementFailure.CreationFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(callback = callback) {
|
||||
if (!hasPermissionsToHandleWidgets(roomId)) {
|
||||
throw WidgetManagementFailure.NotEnoughPower
|
||||
}
|
||||
val params = CreateWidgetTask.Params(
|
||||
roomId = roomId,
|
||||
widgetId = widgetId,
|
||||
content = emptyMap()
|
||||
)
|
||||
createWidgetTask.execute(params)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasPermissionsToHandleWidgets(roomId: String): Boolean {
|
||||
val powerLevelsEvent = stateEventDataSource.getStateEvent(
|
||||
roomId = roomId,
|
||||
eventType = EventType.STATE_ROOM_POWER_LEVELS,
|
||||
stateKey = QueryStringValue.NoCondition
|
||||
)
|
||||
val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false
|
||||
return PowerLevelsHelper(powerLevelsContent).isAllowedToSend(true, null, userId)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetURLFormatter
|
||||
import im.vector.matrix.android.internal.session.widgets.token.DefaultGetScalarTokenTask
|
||||
import im.vector.matrix.android.internal.session.widgets.token.GetScalarTokenTask
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
internal abstract class WidgetModule {
|
||||
|
||||
@Module
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun providesWidgetsAPI(retrofit: Retrofit): WidgetsAPI {
|
||||
return retrofit.create(WidgetsAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindWidgetService(service: DefaultWidgetService): WidgetService
|
||||
|
||||
@Binds
|
||||
abstract fun bindWidgetURLBuilder(formatter: DefaultWidgetURLFormatter): WidgetURLFormatter
|
||||
|
||||
@Binds
|
||||
abstract fun bindWidgetPostAPIMediator(mediator: DefaultWidgetPostAPIMediator): WidgetPostAPIMediator
|
||||
|
||||
@Binds
|
||||
abstract fun bindCreateWidgetTask(task: DefaultCreateWidgetTask): CreateWidgetTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetScalarTokenTask(task: DefaultGetScalarTokenTask): GetScalarTokenTask
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import android.content.Context
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WidgetPostMessageAPIProvider @Inject constructor(private val context: Context) {
|
||||
|
||||
private var postMessageAPIString: String? = null
|
||||
|
||||
fun get(): String? {
|
||||
if (postMessageAPIString == null) {
|
||||
postMessageAPIString = readFromAsset(context)
|
||||
}
|
||||
return postMessageAPIString
|
||||
}
|
||||
|
||||
private fun readFromAsset(context: Context): String? {
|
||||
return try {
|
||||
context.assets.open("postMessageAPI.js").bufferedReader().use {
|
||||
it.readText()
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Reading postMessageAPI.js asset failed")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import im.vector.matrix.android.internal.session.openid.RequestOpenIdTokenResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
internal interface WidgetsAPI {
|
||||
|
||||
/**
|
||||
* register to the server
|
||||
*
|
||||
* @param requestOpenIdTokenResponse the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
|
||||
*/
|
||||
@POST("register")
|
||||
fun register(@Body body: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse>
|
||||
|
||||
@GET("account")
|
||||
fun validateToken(@Query("scalar_token") scalarToken: String?, @Query("v") version: String?): Call<Unit>
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets
|
||||
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class WidgetsAPIProvider @Inject constructor(@Unauthenticated private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory) {
|
||||
|
||||
// Map to keep one WidgetAPI instance by serverUrl
|
||||
private val widgetsAPIs = mutableMapOf<String, WidgetsAPI>()
|
||||
|
||||
fun get(serverUrl: String): WidgetsAPI {
|
||||
return widgetsAPIs.getOrPut(serverUrl) {
|
||||
retrofitFactory.create(okHttpClient, serverUrl).create(WidgetsAPI::class.java)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets.helper
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
|
||||
internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
|
||||
return content.asSequence()
|
||||
.mapNotNull {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(it.value as? JsonDict)?.toModel<Event>()
|
||||
}.mapNotNull { event ->
|
||||
widgetFactory.create(event)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets.helper
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.sender.SenderInfo
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetType
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.user.UserDataSource
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import java.net.URLEncoder
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WidgetFactory @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
||||
private val userDataSource: UserDataSource,
|
||||
@UserId private val userId: String) {
|
||||
|
||||
fun create(widgetEvent: Event): Widget? {
|
||||
val widgetContent = widgetEvent.content.toModel<WidgetContent>()
|
||||
if (widgetContent?.url == null) return null
|
||||
val widgetId = widgetEvent.stateKey ?: return null
|
||||
val type = widgetContent.type ?: return null
|
||||
val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) {
|
||||
null
|
||||
} else {
|
||||
Realm.getInstance(realmConfiguration).use {
|
||||
val roomMemberHelper = RoomMemberHelper(it, widgetEvent.roomId)
|
||||
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
|
||||
SenderInfo(
|
||||
userId = widgetEvent.senderId,
|
||||
displayName = roomMemberSummaryEntity?.displayName,
|
||||
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
|
||||
avatarUrl = roomMemberSummaryEntity?.avatarUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
val isAddedByMe = widgetEvent.senderId == userId
|
||||
val computedUrl = widgetContent.computeURL(widgetEvent.roomId)
|
||||
return Widget(
|
||||
widgetContent = widgetContent,
|
||||
event = widgetEvent,
|
||||
widgetId = widgetId,
|
||||
senderInfo = senderInfo,
|
||||
isAddedByMe = isAddedByMe,
|
||||
computedUrl = computedUrl,
|
||||
type = WidgetType.fromString(type)
|
||||
)
|
||||
}
|
||||
|
||||
private fun WidgetContent.computeURL(roomId: String?): String? {
|
||||
var computedUrl = url ?: return null
|
||||
val myUser = userDataSource.getUser(userId)
|
||||
computedUrl = computedUrl
|
||||
.replace("\$matrix_user_id", userId)
|
||||
.replace("\$matrix_display_name", myUser?.displayName ?: userId)
|
||||
.replace("\$matrix_avatar_url", myUser?.avatarUrl ?: "")
|
||||
|
||||
if (roomId != null) {
|
||||
computedUrl = computedUrl.replace("\$matrix_room_id", roomId)
|
||||
}
|
||||
for ((key, value) in data) {
|
||||
if (value is String) {
|
||||
computedUrl = computedUrl.replace("$$key", URLEncoder.encode(value, "utf-8"))
|
||||
}
|
||||
}
|
||||
return computedUrl
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets.token
|
||||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||
import im.vector.matrix.android.internal.session.widgets.RegisterWidgetResponse
|
||||
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 javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
internal interface GetScalarTokenTask : Task<GetScalarTokenTask.Params, String> {
|
||||
|
||||
data class Params(
|
||||
val serverUrl: String,
|
||||
val forceRefresh: Boolean = false
|
||||
)
|
||||
}
|
||||
|
||||
private const val WIDGET_API_VERSION = "1.1"
|
||||
|
||||
internal class DefaultGetScalarTokenTask @Inject constructor(private val widgetsAPIProvider: WidgetsAPIProvider,
|
||||
private val scalarTokenStore: ScalarTokenStore,
|
||||
private val getOpenIdTokenTask: GetOpenIdTokenTask) : GetScalarTokenTask {
|
||||
|
||||
override suspend fun execute(params: GetScalarTokenTask.Params): String {
|
||||
val widgetsAPI = widgetsAPIProvider.get(params.serverUrl)
|
||||
return if (params.forceRefresh) {
|
||||
scalarTokenStore.clearToken(params.serverUrl)
|
||||
getNewScalarToken(widgetsAPI, params.serverUrl)
|
||||
} else {
|
||||
val scalarToken = scalarTokenStore.getToken(params.serverUrl)
|
||||
if (scalarToken == null) {
|
||||
getNewScalarToken(widgetsAPI, params.serverUrl)
|
||||
} else {
|
||||
validateToken(widgetsAPI, params.serverUrl, scalarToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNewScalarToken(widgetsAPI: WidgetsAPI, serverUrl: String): String {
|
||||
val openId = getOpenIdTokenTask.execute(Unit)
|
||||
val registerWidgetResponse = executeRequest<RegisterWidgetResponse>(null) {
|
||||
apiCall = widgetsAPI.register(openId, WIDGET_API_VERSION)
|
||||
}
|
||||
if (registerWidgetResponse.scalarToken == null) {
|
||||
// Should not happen
|
||||
throw IllegalStateException("Scalar token is null")
|
||||
}
|
||||
scalarTokenStore.setToken(serverUrl, registerWidgetResponse.scalarToken)
|
||||
widgetsAPI.validateToken(registerWidgetResponse.scalarToken, WIDGET_API_VERSION)
|
||||
return registerWidgetResponse.scalarToken
|
||||
}
|
||||
|
||||
private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String {
|
||||
return try {
|
||||
executeRequest<Unit>(null) {
|
||||
apiCall = widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION)
|
||||
}
|
||||
scalarToken
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is Failure.ServerError && failure.httpCode == HttpsURLConnection.HTTP_FORBIDDEN) {
|
||||
if (failure.error.code == MatrixError.M_TERMS_NOT_SIGNED) {
|
||||
throw WidgetManagementFailure.TermsNotSignedException(serverUrl, scalarToken)
|
||||
} else {
|
||||
scalarTokenStore.clearToken(serverUrl)
|
||||
getNewScalarToken(widgetsAPI, serverUrl)
|
||||
}
|
||||
} else {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.widgets.token
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.internal.database.model.ScalarTokenEntity
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ScalarTokenStore @Inject constructor(private val monarchy: Monarchy) {
|
||||
|
||||
fun getToken(apiUrl: String): String? {
|
||||
return monarchy.fetchCopyMap({ realm ->
|
||||
ScalarTokenEntity.where(realm, apiUrl).findFirst()
|
||||
}, { scalarToken, _ ->
|
||||
scalarToken.token
|
||||
})
|
||||
}
|
||||
|
||||
suspend fun setToken(apiUrl: String, token: String) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val scalarTokenEntity = ScalarTokenEntity(apiUrl, token)
|
||||
realm.insertOrUpdate(scalarTokenEntity)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearToken(apiUrl: String) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
ScalarTokenEntity.where(realm, apiUrl).findFirst()?.deleteFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,9 @@ import im.vector.matrix.android.internal.extensions.foldToCallback
|
|||
import im.vector.matrix.android.internal.util.toCancelable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
|
@ -34,5 +36,7 @@ internal fun <T> CoroutineScope.launchToCallback(
|
|||
val result = runCatching {
|
||||
block()
|
||||
}
|
||||
result.foldToCallback(callback)
|
||||
withContext(Dispatchers.Main) {
|
||||
result.foldToCallback(callback)
|
||||
}
|
||||
}.toCancelable()
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.internal.util
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.ArrayRes
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.annotation.StringRes
|
||||
import dagger.Reusable
|
||||
|
@ -53,4 +54,9 @@ internal class StringProvider @Inject constructor(private val resources: Resourc
|
|||
fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||
return resources.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
@Throws(Resources.NotFoundException::class)
|
||||
fun getStringArray(@ArrayRes id: Int): Array<String> {
|
||||
return resources.getStringArray(id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,3 +37,14 @@ internal fun String.ensureProtocol(): String {
|
|||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure string has trailing /
|
||||
*/
|
||||
internal fun String.ensureTrailingSlash(): String {
|
||||
return when {
|
||||
isEmpty() -> this
|
||||
!endsWith("/") -> "$this/"
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,13 @@
|
|||
<string name="notice_room_third_party_registered_invite">%1$s accepted the invitation for %2$s</string>
|
||||
<string name="notice_room_third_party_registered_invite_by_you">You accepted the invitation for %1$s</string>
|
||||
|
||||
<string name="notice_widget_added">%1$s added %2$s widget</string>
|
||||
<string name="notice_widget_added_by_you">You added %1$s widget</string>
|
||||
<string name="notice_widget_removed">%1$s removed %2$s widget</string>
|
||||
<string name="notice_widget_removed_by_you">You removed %1$s widget</string>
|
||||
<string name="notice_widget_modified">%1$s modified %2$s widget</string>
|
||||
<string name="notice_widget_modified_by_you">You modified %1$s widget</string>
|
||||
|
||||
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
||||
<string name="notice_crypto_error_unkwown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
||||
|
||||
|
|
|
@ -162,6 +162,7 @@
|
|||
android:theme="@style/AppTheme.AttachmentsPreview" />
|
||||
|
||||
<activity android:name=".features.terms.ReviewTermsActivity" />
|
||||
<activity android:name=".features.widgets.WidgetActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
|||
import im.vector.riotx.features.terms.ReviewTermsFragment
|
||||
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||
import im.vector.riotx.features.widgets.WidgetFragment
|
||||
|
||||
@Module
|
||||
interface FragmentModule {
|
||||
|
@ -510,4 +511,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(ReviewTermsFragment::class)
|
||||
fun bindReviewTermsFragment(fragment: ReviewTermsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(WidgetFragment::class)
|
||||
fun bindWidgetFragment(fragment: WidgetFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceipt
|
|||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||
import im.vector.riotx.features.home.room.list.RoomListModule
|
||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
|
@ -63,6 +64,8 @@ import im.vector.riotx.features.share.IncomingShareActivity
|
|||
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||
import im.vector.riotx.features.ui.UiStateRepository
|
||||
import im.vector.riotx.features.widgets.WidgetActivity
|
||||
import im.vector.riotx.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
|
@ -120,6 +123,7 @@ interface ScreenComponent {
|
|||
fun inject(activity: BigImageViewerActivity)
|
||||
fun inject(activity: InviteUsersToRoomActivity)
|
||||
fun inject(activity: ReviewTermsActivity)
|
||||
fun inject(activity: WidgetActivity)
|
||||
|
||||
/* ==========================================================================================
|
||||
* BottomSheets
|
||||
|
@ -134,6 +138,8 @@ interface ScreenComponent {
|
|||
fun inject(bottomSheet: DeviceVerificationInfoBottomSheet)
|
||||
fun inject(bottomSheet: DeviceListBottomSheet)
|
||||
fun inject(bottomSheet: BootstrapBottomSheet)
|
||||
fun inject(bottomSheet: RoomWidgetPermissionBottomSheet)
|
||||
fun inject(bottomSheet: RoomWidgetsBottomSheet)
|
||||
|
||||
/* ==========================================================================================
|
||||
* Others
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail
|
|||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
@ -26,6 +27,7 @@ import im.vector.riotx.core.platform.VectorViewModelAction
|
|||
sealed class RoomDetailAction : VectorViewModelAction {
|
||||
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
||||
data class SaveDraft(val draft: String) : RoomDetailAction()
|
||||
data class SendSticker(val stickerContent: MessageStickerContent) : RoomDetailAction()
|
||||
data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : RoomDetailAction()
|
||||
data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction()
|
||||
data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailAction()
|
||||
|
@ -72,4 +74,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
data class RequestVerification(val userId: String) : RoomDetailAction()
|
||||
data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction()
|
||||
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
|
||||
|
||||
object SelectStickerAttachment : RoomDetailAction()
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.os.Bundle
|
|||
import android.os.Parcelable
|
||||
import android.text.Spannable
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
|
@ -65,6 +66,7 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
||||
|
@ -72,6 +74,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||
|
@ -80,6 +83,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetType
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||
|
@ -131,6 +135,7 @@ 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
|
||||
|
@ -143,6 +148,8 @@ import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformatio
|
|||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBannerView
|
||||
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||
import im.vector.riotx.features.html.PillImageSpan
|
||||
import im.vector.riotx.features.invite.VectorInviteView
|
||||
|
@ -155,6 +162,7 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
|||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import im.vector.riotx.features.widgets.WidgetActivity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
@ -194,7 +202,8 @@ class RoomDetailFragment @Inject constructor(
|
|||
VectorInviteView.Callback,
|
||||
JumpToReadMarkerView.Callback,
|
||||
AttachmentTypeSelectorView.Callback,
|
||||
AttachmentsHelper.Callback {
|
||||
AttachmentsHelper.Callback,
|
||||
RoomWidgetsBannerView.Callback {
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -259,6 +268,8 @@ class RoomDetailFragment @Inject constructor(
|
|||
setupNotificationView()
|
||||
setupJumpToReadMarkerView()
|
||||
setupJumpToBottomView()
|
||||
setupWidgetsBannerView()
|
||||
|
||||
roomToolbarContentView.debouncedClicks {
|
||||
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
|
||||
}
|
||||
|
@ -289,20 +300,49 @@ 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)
|
||||
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)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWidgetsBannerView() {
|
||||
roomWidgetsBannerView.callback = this
|
||||
}
|
||||
|
||||
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) {
|
||||
navigator.openStickerPicker(this, roomDetailArgs.roomId, event.widget)
|
||||
}
|
||||
|
||||
private fun displayPromptForIntegrationManager() {
|
||||
// The Sticker picker widget is not installed yet. Propose the user to install it
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
val v: View = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_no_sticker_pack, null)
|
||||
builder
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
// Open integration manager, to the sticker installation page
|
||||
navigator.openIntegrationManager(
|
||||
context = requireContext(),
|
||||
roomId = roomDetailArgs.roomId,
|
||||
integId = null,
|
||||
screen = WidgetType.StickerPicker.preferred
|
||||
)
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) {
|
||||
updateComposerText("")
|
||||
lockSendButton = false
|
||||
|
@ -428,18 +468,24 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.clear_message_queue) {
|
||||
// This a temporary option during dev as it is not super stable
|
||||
// Cancel all pending actions in room queue and post a dummy
|
||||
// Then mark all sending events as undelivered
|
||||
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
|
||||
return true
|
||||
return when (item.itemId) {
|
||||
R.id.clear_message_queue -> {
|
||||
// This a temporary option during dev as it is not super stable
|
||||
// Cancel all pending actions in room queue and post a dummy
|
||||
// Then mark all sending events as undelivered
|
||||
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
|
||||
true
|
||||
}
|
||||
R.id.resend_all -> {
|
||||
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
|
||||
true
|
||||
}
|
||||
R.id.open_matrix_apps -> {
|
||||
navigator.openIntegrationManager(requireContext(), roomDetailArgs.roomId, null, null)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
if (item.itemId == R.id.resend_all) {
|
||||
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun renderRegularMode(text: String) {
|
||||
|
@ -514,15 +560,19 @@ 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 -> {
|
||||
val content = WidgetActivity.getOutput(data).toModel<MessageStickerContent>() ?: return
|
||||
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -676,6 +726,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
val summary = state.asyncRoomSummary()
|
||||
val inviter = state.asyncInviter()
|
||||
if (summary?.membership == Membership.JOIN) {
|
||||
roomWidgetsBannerView.render(state.activeRoomWidgets())
|
||||
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
||||
timelineEventController.update(state)
|
||||
inviteView.visibility = View.GONE
|
||||
|
@ -1379,7 +1430,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
|
||||
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
|
||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
|
||||
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
|
||||
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
@ -1412,4 +1463,9 @@ class RoomDetailFragment @Inject constructor(
|
|||
val formattedContact = contactAttachment.toHumanReadable()
|
||||
roomDetailViewModel.handle(RoomDetailAction.SendMessage(formattedContact, false))
|
||||
}
|
||||
|
||||
override fun onViewWidgetsClicked() {
|
||||
RoomWidgetsBottomSheet.newInstance()
|
||||
.show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
import im.vector.riotx.features.command.Command
|
||||
import java.io.File
|
||||
|
@ -49,6 +50,10 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
|||
|
||||
abstract class SendMessageResult : RoomDetailViewEvents()
|
||||
|
||||
object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
|
||||
|
||||
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
|
||||
|
||||
object MessageSent : SendMessageResult()
|
||||
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
|
||||
class SlashCommandError(val command: Command) : SendMessageResult()
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
|
@ -34,6 +35,7 @@ import im.vector.matrix.android.api.session.Session
|
|||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||
|
@ -67,6 +69,7 @@ import im.vector.riotx.features.command.CommandParser
|
|||
import im.vector.riotx.features.command.ParsedCommand
|
||||
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.riotx.features.home.room.typing.TypingHelper
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
|
@ -74,6 +77,7 @@ import io.reactivex.Observable
|
|||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import timber.log.Timber
|
||||
|
@ -82,14 +86,15 @@ import java.util.concurrent.TimeUnit
|
|||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class RoomDetailViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: RoomDetailViewState,
|
||||
@Assisted private val initialState: RoomDetailViewState,
|
||||
userPreferencesProvider: UserPreferencesProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
private val session: Session,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||
private val stickerPickerActionHandler: StickerPickerActionHandler
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
|
@ -157,6 +162,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
observeDrafts()
|
||||
observeUnreadState()
|
||||
observeMyRoomMember()
|
||||
observeActiveRoomWidgets()
|
||||
room.getRoomSummaryLive()
|
||||
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback())
|
||||
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||
|
@ -164,6 +170,20 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
session.onRoomDisplayed(initialState.roomId)
|
||||
}
|
||||
|
||||
private fun observeActiveRoomWidgets() {
|
||||
session.rx()
|
||||
.liveRoomWidgets(
|
||||
roomId = initialState.roomId,
|
||||
widgetId = QueryStringValue.NoCondition
|
||||
)
|
||||
.map { widgets ->
|
||||
widgets.filter { it.isActive }
|
||||
}
|
||||
.execute {
|
||||
copy(activeRoomWidgets = it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeMyRoomMember() {
|
||||
val queryParams = roomMemberQueryParams {
|
||||
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
||||
|
@ -185,6 +205,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||
|
@ -216,6 +237,18 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
|
||||
room.sendEvent(EventType.STICKER, action.stickerContent.toContent())
|
||||
}
|
||||
|
||||
private fun handleSelectStickerAttachment() {
|
||||
viewModelScope.launch {
|
||||
val viewEvent = stickerPickerActionHandler.handle()
|
||||
_viewEvents.post(viewEvent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,6 +357,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
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()
|
||||
else -> false
|
||||
}
|
||||
|
||||
|
@ -1008,7 +1042,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
setState { copy(asyncInviter = Success(it)) }
|
||||
}
|
||||
}
|
||||
room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, "")?.also {
|
||||
room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE)?.also {
|
||||
setState { copy(tombstoneEvent = it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
|
||||
/**
|
||||
* Describes the current send mode:
|
||||
|
@ -55,6 +56,7 @@ data class RoomDetailViewState(
|
|||
val myRoomMember: Async<RoomMemberSummary> = Uninitialized,
|
||||
val asyncInviter: Async<User> = Uninitialized,
|
||||
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
||||
val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
|
||||
val typingRoomMembers: List<MatrixItem.UserItem>? = null,
|
||||
val typingMessage: String? = null,
|
||||
val sendMode: SendMode = SendMode.REGULAR(""),
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.room.detail.sticker
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetType
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewEvents
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class StickerPickerActionHandler @Inject constructor(private val session: Session) {
|
||||
|
||||
suspend fun handle(): RoomDetailViewEvents = withContext(Dispatchers.Default) {
|
||||
// Search for the sticker picker widget in the user account
|
||||
val stickerWidget = session.widgetService().getUserWidgets(WidgetType.StickerPicker.values()).firstOrNull { it.isActive }
|
||||
if (stickerWidget == null || stickerWidget.computedUrl.isNullOrBlank()) {
|
||||
RoomDetailViewEvents.DisplayPromptForIntegrationManager
|
||||
} else {
|
||||
RoomDetailViewEvents.OpenStickerPicker(
|
||||
widget = stickerWidget
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,9 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
data class WellKnownManagerConfig(
|
||||
val apiUrl: String,
|
||||
val uiUrl: String
|
||||
)
|
||||
package im.vector.riotx.features.home.room.detail.sticker
|
||||
|
||||
object StickerPickerConstants {
|
||||
const val STICKER_PICKER_REQUEST_CODE = 16000
|
||||
}
|
|
@ -54,6 +54,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||
EventType.STATE_ROOM_JOIN_RULES,
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
EventType.STATE_ROOM_GUEST_ACCESS,
|
||||
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||
EventType.STATE_ROOM_WIDGET,
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_ANSWER,
|
||||
|
|
|
@ -48,6 +48,9 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
||||
|
||||
when (timelineEvent.root.getClearType()) {
|
||||
EventType.STICKER -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
||||
when (messageContent.msgType) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.home.room.detail.timeline.format
|
||||
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
|
@ -34,6 +35,7 @@ import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
|||
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||
import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
||||
import im.vector.riotx.R
|
||||
|
@ -59,6 +61,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
|||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_WIDGET,
|
||||
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_HANGUP,
|
||||
|
@ -80,6 +84,34 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
|||
}
|
||||
}
|
||||
|
||||
private fun formatWidgetEvent(event: Event, disambiguatedDisplayName: String): CharSequence? {
|
||||
val widgetContent: WidgetContent = event.getClearContent().toModel() ?: return null
|
||||
val previousWidgetContent: WidgetContent? = event.prevContent.toModel()
|
||||
return if (widgetContent.isActive()) {
|
||||
val widgetName = widgetContent.getHumanName()
|
||||
if (previousWidgetContent?.isActive().orFalse()) {
|
||||
if (event.isSentByCurrentUser()) {
|
||||
sp.getString(R.string.notice_widget_modified_by_you, widgetName)
|
||||
} else {
|
||||
sp.getString(R.string.notice_widget_modified, disambiguatedDisplayName, widgetName)
|
||||
}
|
||||
} else {
|
||||
if (event.isSentByCurrentUser()) {
|
||||
sp.getString(R.string.notice_widget_added_by_you, widgetName)
|
||||
} else {
|
||||
sp.getString(R.string.notice_widget_added, disambiguatedDisplayName, widgetName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val widgetName = previousWidgetContent?.getHumanName()
|
||||
if (event.isSentByCurrentUser()) {
|
||||
sp.getString(R.string.notice_widget_removed_by_you, widgetName)
|
||||
} else {
|
||||
sp.getString(R.string.notice_widget_removed, disambiguatedDisplayName, widgetName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun format(event: Event, senderName: String?): CharSequence? {
|
||||
return when (val type = event.getClearType()) {
|
||||
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName)
|
||||
|
|
|
@ -158,7 +158,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (EventType.isStateEvent(event.root.type)) {
|
||||
if (event.root.isStateEvent()) {
|
||||
// Do not warn for state event, they are always in clear
|
||||
E2EDecoration.NONE
|
||||
} else {
|
||||
|
|
|
@ -24,6 +24,8 @@ object TimelineDisplayableEvents {
|
|||
|
||||
val DISPLAYABLE_TYPES = listOf(
|
||||
EventType.MESSAGE,
|
||||
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||
EventType.STATE_ROOM_WIDGET,
|
||||
EventType.STATE_ROOM_NAME,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
EventType.STATE_ROOM_MEMBER,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue