mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 10:55:55 +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">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<option name="RIGHT_MARGIN" value="160" />
|
<option name="RIGHT_MARGIN" value="160" />
|
||||||
|
<AndroidXmlCodeStyleSettings>
|
||||||
|
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||||
|
</AndroidXmlCodeStyleSettings>
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
<value>
|
<value>
|
||||||
|
|
|
@ -2,7 +2,8 @@ Changes in RiotX 0.22.0 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
-
|
- Integration Manager and Widget support (#48)
|
||||||
|
- Send stickers (#51)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- New wording for notice when current user is the sender
|
- New wording for notice when current user is the sender
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.rx
|
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.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
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()
|
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
room.getStateEvent(eventType, stateKey).toOptional()
|
room.getStateEvent(eventType, stateKey).toOptional()
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
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.Session
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
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.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
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.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
|
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
@ -151,6 +153,18 @@ class RxSession(private val session: Session) {
|
||||||
session.getAccountDataEvents(types)
|
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 {
|
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(
|
data class MatrixConfiguration(
|
||||||
val applicationFlavor: String = "Default-application-flavor",
|
val applicationFlavor: String = "Default-application-flavor",
|
||||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
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
|
* Optional proxy to connect to the matrix servers
|
||||||
* You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port)
|
* 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 com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig.Builder
|
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.network.ssl.Fingerprint
|
||||||
|
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||||
import okhttp3.CipherSuite
|
import okhttp3.CipherSuite
|
||||||
import okhttp3.TlsVersion
|
import okhttp3.TlsVersion
|
||||||
|
|
||||||
|
@ -71,16 +72,12 @@ data class HomeServerConnectionConfig(
|
||||||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
throw RuntimeException("Invalid home server URI: " + hsUri)
|
||||||
}
|
}
|
||||||
// ensure trailing /
|
// ensure trailing /
|
||||||
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
val hsString = hsUri.toString().ensureTrailingSlash()
|
||||||
try {
|
homeServerUri = try {
|
||||||
val url = hsUri.toString()
|
Uri.parse(hsString)
|
||||||
Uri.parse("$url/")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
hsUri
|
|
||||||
}
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,16 +94,12 @@ data class HomeServerConnectionConfig(
|
||||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||||
}
|
}
|
||||||
// ensure trailing /
|
// ensure trailing /
|
||||||
if (!identityServerUri.toString().endsWith("/")) {
|
val isString = identityServerUri.toString().ensureTrailingSlash()
|
||||||
try {
|
this.identityServerUri = try {
|
||||||
val url = identityServerUri.toString()
|
Uri.parse(isString)
|
||||||
this.identityServerUri = Uri.parse("$url/")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.identityServerUri = identityServerUri
|
|
||||||
}
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,30 +54,4 @@ data class WellKnown(
|
||||||
|
|
||||||
@Json(name = "m.integrations")
|
@Json(name = "m.integrations")
|
||||||
val integrations: JsonDict? = null
|
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 IsNotNull : QueryStringValue()
|
||||||
object IsEmpty : QueryStringValue()
|
object IsEmpty : QueryStringValue()
|
||||||
object IsNotEmpty : QueryStringValue()
|
object IsNotEmpty : QueryStringValue()
|
||||||
data class Equals(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) : QueryStringValue()
|
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||||
|
|
||||||
enum class Case {
|
enum class Case {
|
||||||
SENSITIVE,
|
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.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
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.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.profile.ProfileService
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
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.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.terms.TermsService
|
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.user.UserService
|
||||||
|
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines interactions with a session.
|
* This interface defines interactions with a session.
|
||||||
|
@ -153,6 +155,16 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun identityService(): IdentityService
|
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.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
|
|
@ -98,7 +98,7 @@ data class Event(
|
||||||
* @return true if event is state event.
|
* @return true if event is state event.
|
||||||
*/
|
*/
|
||||||
fun isStateEvent(): Boolean {
|
fun isStateEvent(): Boolean {
|
||||||
return EventType.isStateEvent(getClearType())
|
return stateKey != null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
|
|
@ -38,6 +38,8 @@ object EventType {
|
||||||
|
|
||||||
// State Events
|
// 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_NAME = "m.room.name"
|
||||||
const val STATE_ROOM_TOPIC = "m.room.topic"
|
const val STATE_ROOM_TOPIC = "m.room.topic"
|
||||||
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
||||||
|
@ -84,29 +86,6 @@ object EventType {
|
||||||
// Unwedging
|
// Unwedging
|
||||||
internal const val DUMMY = "m.dummy"
|
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 {
|
fun isCallEvent(type: String): Boolean {
|
||||||
return type == CALL_INVITE
|
return type == CALL_INVITE
|
||||||
|| type == CALL_CANDIDATES
|
|| type == CALL_CANDIDATES
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.identity
|
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 OutdatedIdentityServer : IdentityServiceError()
|
||||||
object OutdatedHomeServer : IdentityServiceError()
|
object OutdatedHomeServer : IdentityServiceError()
|
||||||
object NoIdentityServerConfigured : 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
|
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
|
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,11 +43,11 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
* @return true if the user can send this type of event
|
* @return true if the user can send this type of event
|
||||||
*/
|
*/
|
||||||
fun isAllowedToSend(eventType: String, userId: String): Boolean {
|
fun isAllowedToSend(isState: Boolean, eventType: String?, userId: String): Boolean {
|
||||||
return if (eventType.isNotEmpty() && userId.isNotEmpty()) {
|
return if (userId.isNotEmpty()) {
|
||||||
val powerLevel = getUserPowerLevel(userId)
|
val powerLevel = getUserPowerLevel(userId)
|
||||||
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
||||||
?: if (EventType.isStateEvent(eventType)) {
|
?: if (isState) {
|
||||||
powerLevelsContent.stateDefault
|
powerLevelsContent.stateDefault
|
||||||
} else {
|
} else {
|
||||||
powerLevelsContent.eventsDefault
|
powerLevelsContent.eventsDefault
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.api.session.room.send
|
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.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.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
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 {
|
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.
|
* Method to send a text message asynchronously.
|
||||||
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
* 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 androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.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
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
|
||||||
interface StateService {
|
interface StateService {
|
||||||
|
@ -26,9 +29,15 @@ interface StateService {
|
||||||
/**
|
/**
|
||||||
* Update the topic of the room
|
* 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.
|
* 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 com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
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 im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
import javax.inject.Inject
|
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) {
|
internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
|
||||||
|
|
||||||
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
|
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,
|
HomeServerCapabilitiesEntity::class,
|
||||||
RoomMemberSummaryEntity::class,
|
RoomMemberSummaryEntity::class,
|
||||||
CurrentStateEventEntity::class,
|
CurrentStateEventEntity::class,
|
||||||
UserAccountDataEntity::class
|
UserAccountDataEntity::class,
|
||||||
|
ScalarTokenEntity::class,
|
||||||
|
WellknownIntegrationManagerConfigEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
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.RealmQuery
|
||||||
import io.realm.kotlin.createObject
|
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)
|
return realm.where(CurrentStateEventEntity::class.java)
|
||||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
.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)
|
internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, roomId: String, type: String, stateKey: String)
|
||||||
: RealmQuery<CurrentStateEventEntity> {
|
: RealmQuery<CurrentStateEventEntity> {
|
||||||
return where(realm = realm, roomId = roomId, type = type)
|
return whereType(realm = realm, roomId = roomId, type = type)
|
||||||
.equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
.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.
|
* 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.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
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) {
|
inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
|
||||||
this.observe(owner, Observer { observer(it) })
|
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_PREFIX_PATH = "_matrix/identity/v2"
|
||||||
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
|
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
|
||||||
|
|
||||||
// TODO Ganfra, use correct value
|
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
|
||||||
const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -29,7 +30,7 @@ class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
||||||
|
|
||||||
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
|
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl.ensureTrailingSlash())
|
||||||
.callFactory(object : Call.Factory {
|
.callFactory(object : Call.Factory {
|
||||||
override fun newCall(request: Request): Call {
|
override fun newCall(request: Request): Call {
|
||||||
return okHttpClient.get().newCall(request)
|
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.file.FileService
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
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.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.profile.ProfileService
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
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.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.terms.TermsService
|
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.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.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
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.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
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.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.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -90,6 +93,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val fileService: Lazy<FileService>,
|
private val fileService: Lazy<FileService>,
|
||||||
private val secureStorageService: Lazy<SecureStorageService>,
|
private val secureStorageService: Lazy<SecureStorageService>,
|
||||||
private val profileService: Lazy<ProfileService>,
|
private val profileService: Lazy<ProfileService>,
|
||||||
|
private val widgetService: Lazy<WidgetService>,
|
||||||
private val syncThreadProvider: Provider<SyncThread>,
|
private val syncThreadProvider: Provider<SyncThread>,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
|
@ -101,11 +105,13 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||||
private val accountService: Lazy<AccountService>,
|
private val accountService: Lazy<AccountService>,
|
||||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||||
private val shieldTrustUpdater: ShieldTrustUpdater,
|
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val defaultIdentityService: DefaultIdentityService,
|
private val defaultIdentityService: DefaultIdentityService,
|
||||||
private val taskExecutor: TaskExecutor
|
private val integrationManagerService: IntegrationManagerService,
|
||||||
) : Session,
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val widgetDependenciesHolder: WidgetDependenciesHolder,
|
||||||
|
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||||
|
: Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
GroupService by groupService.get(),
|
GroupService by groupService.get(),
|
||||||
|
@ -142,6 +148,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
timelineEventDecryptor.start()
|
timelineEventDecryptor.start()
|
||||||
shieldTrustUpdater.start()
|
shieldTrustUpdater.start()
|
||||||
defaultIdentityService.start()
|
defaultIdentityService.start()
|
||||||
|
widgetDependenciesHolder.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requireBackgroundSync() {
|
override fun requireBackgroundSync() {
|
||||||
|
@ -187,6 +194,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
// This has to be done on main thread
|
// This has to be done on main thread
|
||||||
defaultIdentityService.stop()
|
defaultIdentityService.stop()
|
||||||
|
widgetDependenciesHolder.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +241,10 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun identityService() = defaultIdentityService
|
override fun identityService() = defaultIdentityService
|
||||||
|
|
||||||
|
override fun widgetService(): WidgetService = widgetService.get()
|
||||||
|
|
||||||
|
override fun integrationManagerService() = integrationManagerService
|
||||||
|
|
||||||
override fun addListener(listener: Session.Listener) {
|
override fun addListener(listener: Session.Listener) {
|
||||||
sessionListeners.addListener(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.group.GroupModule
|
||||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
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.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.openid.OpenIdModule
|
||||||
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
||||||
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
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.terms.TermsModule
|
||||||
import im.vector.matrix.android.internal.session.user.UserModule
|
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.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.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
|
||||||
|
@ -74,6 +76,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
PushersModule::class,
|
PushersModule::class,
|
||||||
OpenIdModule::class,
|
OpenIdModule::class,
|
||||||
|
WidgetModule::class,
|
||||||
|
IntegrationManagerModule::class,
|
||||||
IdentityModule::class,
|
IdentityModule::class,
|
||||||
TermsModule::class,
|
TermsModule::class,
|
||||||
AccountDataModule::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.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||||
|
|
||||||
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
||||||
|
|
||||||
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
|
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash()
|
||||||
private val sep = if (baseUrl.endsWith("/")) "" else "/"
|
|
||||||
|
|
||||||
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? {
|
override fun resolveFullSize(contentUrl: String?): String? {
|
||||||
return contentUrl
|
return contentUrl
|
||||||
|
@ -66,7 +66,7 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||||
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseUrl + sep + prefix + serverAndMediaId + params + fragment
|
return baseUrl + prefix + serverAndMediaId + params + fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.isValidMatrixContentUrl(): Boolean {
|
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.data.isLoginAndRegistrationSupportedBySdk
|
||||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||||
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
|
||||||
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManagerConfigExtractor
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
|
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val eventBus: EventBus,
|
private val eventBus: EventBus,
|
||||||
private val getWellknownTask: GetWellknownTask,
|
private val getWellknownTask: GetWellknownTask,
|
||||||
|
private val configExtractor: IntegrationManagerConfigExtractor,
|
||||||
@UserId
|
@UserId
|
||||||
private val userId: String
|
private val userId: String
|
||||||
) : GetHomeServerCapabilitiesTask {
|
) : GetHomeServerCapabilitiesTask {
|
||||||
|
@ -102,8 +105,14 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
|
|
||||||
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||||
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
||||||
}
|
|
||||||
|
|
||||||
|
// We are also checking for integration manager configurations
|
||||||
|
val config = configExtractor.extract(getWellknownResult.wellKnown)
|
||||||
|
if (config != null) {
|
||||||
|
Timber.v("Extracted integration config : $config")
|
||||||
|
realm.insertOrUpdate(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.api.util.NoOpCancellable
|
||||||
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
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.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
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.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.openid.GetOpenIdTokenTask
|
||||||
import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
|
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.profile.UnbindThreePidsTask
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
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.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.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
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 roomId = event.roomId ?: return false
|
||||||
val room = roomGetter.getRoom(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
|
?.content
|
||||||
?.toModel<PowerLevelsContent>()
|
?.toModel<PowerLevelsContent>()
|
||||||
?: 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"))
|
callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported"))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val params = SendStateTask.Params(roomId,
|
val params = SendStateTask.Params(
|
||||||
EventType.STATE_ROOM_ENCRYPTION,
|
roomId = roomId,
|
||||||
mapOf(
|
stateKey = null,
|
||||||
|
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||||
|
body = mapOf(
|
||||||
"algorithm" to algorithm
|
"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.PublicRoomsParams
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
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.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.network.NetworkConstants
|
||||||
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
|
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}")
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
|
||||||
fun sendStateEvent(@Path("roomId") roomId: String,
|
fun sendStateEvent(@Path("roomId") roomId: String,
|
||||||
@Path("state_event_type") stateEventType: String,
|
@Path("state_event_type") stateEventType: String,
|
||||||
@Body params: Map<String, String>): Call<Unit>
|
@Body params: JsonDict): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a generic state events
|
* Send a generic state events
|
||||||
|
@ -189,7 +190,7 @@ internal interface RoomAPI {
|
||||||
fun sendStateEvent(@Path("roomId") roomId: String,
|
fun sendStateEvent(@Path("roomId") roomId: String,
|
||||||
@Path("state_event_type") stateEventType: String,
|
@Path("state_event_type") stateEventType: String,
|
||||||
@Path("state_key") stateKey: 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.
|
* 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.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
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.SessionId
|
||||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||||
|
@ -67,6 +68,12 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
|
|
||||||
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
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 {
|
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||||
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
|
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
|
||||||
.also { createLocalEcho(it) }
|
.also { createLocalEcho(it) }
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.exifinterface.media.ExifInterface
|
||||||
import im.vector.matrix.android.R
|
import im.vector.matrix.android.R
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
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)
|
return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
|
||||||
}
|
}
|
||||||
val content = MessageTextContent(msgType = msgType, body = text.toString())
|
val content = MessageTextContent(msgType = msgType, body = text.toString())
|
||||||
return createEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
|
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"
|
text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
|
||||||
|
|
||||||
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event {
|
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,
|
fun createReplaceTextEvent(roomId: String,
|
||||||
|
@ -138,7 +139,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
newBodyAutoMarkdown: Boolean,
|
newBodyAutoMarkdown: Boolean,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
compatibilityText: String): Event {
|
compatibilityText: String): Event {
|
||||||
return createEvent(roomId,
|
return createMessageEvent(roomId,
|
||||||
MessageTextContent(
|
MessageTextContent(
|
||||||
msgType = msgType,
|
msgType = msgType,
|
||||||
body = compatibilityText,
|
body = compatibilityText,
|
||||||
|
@ -153,7 +154,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
pollEventId: String,
|
pollEventId: String,
|
||||||
optionIndex: Int,
|
optionIndex: Int,
|
||||||
optionLabel: String): Event {
|
optionLabel: String): Event {
|
||||||
return createEvent(roomId,
|
return createMessageEvent(roomId,
|
||||||
MessagePollResponseContent(
|
MessagePollResponseContent(
|
||||||
body = optionLabel,
|
body = optionLabel,
|
||||||
relatesTo = RelationDefaultContent(
|
relatesTo = RelationDefaultContent(
|
||||||
|
@ -175,7 +176,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
append(it.value)
|
append(it.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createEvent(
|
return createMessageEvent(
|
||||||
roomId,
|
roomId,
|
||||||
MessageOptionsContent(
|
MessageOptionsContent(
|
||||||
body = compatLabel,
|
body = compatLabel,
|
||||||
|
@ -211,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
//
|
//
|
||||||
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
|
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
|
||||||
|
|
||||||
return createEvent(roomId,
|
return createMessageEvent(roomId,
|
||||||
MessageTextContent(
|
MessageTextContent(
|
||||||
msgType = msgType,
|
msgType = msgType,
|
||||||
body = compatibilityText,
|
body = compatibilityText,
|
||||||
|
@ -280,7 +281,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString()
|
url = attachment.queryUri.toString()
|
||||||
)
|
)
|
||||||
return createEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||||
|
@ -316,7 +317,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString()
|
url = attachment.queryUri.toString()
|
||||||
)
|
)
|
||||||
return createEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||||
|
@ -329,7 +330,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString()
|
url = attachment.queryUri.toString()
|
||||||
)
|
)
|
||||||
return createEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||||
|
@ -342,18 +343,22 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString()
|
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()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
originServerTs = dummyOriginServerTs(),
|
originServerTs = dummyOriginServerTs(),
|
||||||
senderId = userId,
|
senderId = userId,
|
||||||
eventId = localId,
|
eventId = localId,
|
||||||
type = EventType.MESSAGE,
|
type = type,
|
||||||
content = content.toContent(),
|
content = content,
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -410,7 +415,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
formattedBody = replyFormatted,
|
formattedBody = replyFormatted,
|
||||||
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||||
)
|
)
|
||||||
return createEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
||||||
|
|
|
@ -17,26 +17,21 @@
|
||||||
package im.vector.matrix.android.internal.session.room.state
|
package im.vector.matrix.android.internal.session.room.state
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import io.realm.Realm
|
|
||||||
|
|
||||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val sendStateTask: SendStateTask
|
private val sendStateTask: SendStateTask
|
||||||
) : StateService {
|
) : StateService {
|
||||||
|
@ -46,33 +41,47 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
fun create(roomId: String): StateService
|
fun create(roomId: String): StateService
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStateEvent(eventType: String, stateKey: String): Event? {
|
override fun getStateEvent(eventType: String, stateKey: QueryStringValue): Event? {
|
||||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
return stateEventDataSource.getStateEvent(roomId, eventType, stateKey)
|
||||||
CurrentStateEventEntity.getOrNull(realm, roomId, type = eventType, stateKey = stateKey)?.root?.asDomain()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>> {
|
override fun getStateEventLive(eventType: String, stateKey: QueryStringValue): LiveData<Optional<Event>> {
|
||||||
val liveData = monarchy.findAllMappedWithChanges(
|
return stateEventDataSource.getStateEventLive(roomId, eventType, stateKey)
|
||||||
{ realm -> CurrentStateEventEntity.whereStateKey(realm, roomId, type = eventType, stateKey = "") },
|
}
|
||||||
{ it.root?.asDomain() }
|
|
||||||
|
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 ->
|
return sendStateTask
|
||||||
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
|
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.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
|
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.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
@ -25,8 +26,9 @@ import javax.inject.Inject
|
||||||
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
|
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
val stateKey: String?,
|
||||||
val eventType: 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) {
|
override suspend fun execute(params: SendStateTask.Params) {
|
||||||
return executeRequest(eventBus) {
|
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)
|
eventIds.add(event.eventId)
|
||||||
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
|
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
|
||||||
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm)
|
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 {
|
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
|
||||||
eventId = event.eventId
|
eventId = event.eventId
|
||||||
root = eventEntity
|
root = eventEntity
|
||||||
|
|
|
@ -30,6 +30,8 @@ abstract class UserAccountData : AccountDataContent {
|
||||||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||||
const val TYPE_WIDGETS = "m.widgets"
|
const val TYPE_WIDGETS = "m.widgets"
|
||||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
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_IDENTITY_SERVER = "m.identity_server"
|
||||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
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.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class UserAccountDataEvent(
|
data class UserAccountDataEvent(
|
||||||
@Json(name = "type") override val type: String,
|
@Json(name = "type") override val type: String,
|
||||||
@Json(name = "content") val content: Map<String, Any>
|
@Json(name = "content") val content: JsonDict
|
||||||
) : UserAccountData()
|
) : 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.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
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.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.openid.GetOpenIdTokenTask
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
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.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.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -55,17 +56,10 @@ internal class DefaultTermsService @Inject constructor(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
callback: MatrixCallback<GetTermsResponse>): Cancelable {
|
callback: MatrixCallback<GetTermsResponse>): Cancelable {
|
||||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
val sep = if (baseUrl.endsWith("/")) "" else "/"
|
val url = buildUrl(baseUrl, serviceType)
|
||||||
|
|
||||||
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 termsResponse = executeRequest<TermsResponse>(null) {
|
val termsResponse = executeRequest<TermsResponse>(null) {
|
||||||
apiCall = termsAPI.getTerms("${url}terms")
|
apiCall = termsAPI.getTerms("${url}terms")
|
||||||
}
|
}
|
||||||
|
|
||||||
GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
|
GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,13 +70,7 @@ internal class DefaultTermsService @Inject constructor(
|
||||||
token: String?,
|
token: String?,
|
||||||
callback: MatrixCallback<Unit>): Cancelable {
|
callback: MatrixCallback<Unit>): Cancelable {
|
||||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
val sep = if (baseUrl.endsWith("/")) "" else "/"
|
val url = buildUrl(baseUrl, serviceType)
|
||||||
|
|
||||||
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 tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
|
val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
|
||||||
|
|
||||||
executeRequest<Unit>(null) {
|
executeRequest<Unit>(null) {
|
||||||
|
@ -112,6 +100,14 @@ internal class DefaultTermsService @Inject constructor(
|
||||||
return token.token
|
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> {
|
private fun getAlreadyAcceptedTermUrlsFromAccountData(): Set<String> {
|
||||||
return accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ACCEPTED_TERMS)
|
return accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ACCEPTED_TERMS)
|
||||||
?.content
|
?.content
|
||||||
|
|
|
@ -17,101 +17,41 @@
|
||||||
package im.vector.matrix.android.internal.session.user
|
package im.vector.matrix.android.internal.session.user
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
|
||||||
import androidx.paging.DataSource
|
|
||||||
import androidx.paging.LivePagedListBuilder
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
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.session.user.model.User
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.Optional
|
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.accountdata.UpdateIgnoredUserIdsTask
|
||||||
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
|
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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
|
||||||
import javax.inject.Inject
|
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 searchUserTask: SearchUserTask,
|
||||||
private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask,
|
private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask,
|
||||||
private val taskExecutor: TaskExecutor) : UserService {
|
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? {
|
override fun getUser(userId: String): User? {
|
||||||
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
return userDataSource.getUser(userId)
|
||||||
?: return null
|
|
||||||
|
|
||||||
return userEntity.asDomain()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserLive(userId: String): LiveData<Optional<User>> {
|
override fun getUserLive(userId: String): LiveData<Optional<User>> {
|
||||||
val liveData = monarchy.findAllMappedWithChanges(
|
return userDataSource.getUserLive(userId)
|
||||||
{ UserEntity.where(it, userId) },
|
|
||||||
{ it.asDomain() }
|
|
||||||
)
|
|
||||||
return Transformations.map(liveData) { results ->
|
|
||||||
results.firstOrNull().toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUsersLive(): LiveData<List<User>> {
|
override fun getUsersLive(): LiveData<List<User>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return userDataSource.getUsersLive()
|
||||||
{ realm ->
|
|
||||||
realm.where(UserEntity::class.java)
|
|
||||||
.isNotEmpty(UserEntityFields.USER_ID)
|
|
||||||
.sort(UserEntityFields.DISPLAY_NAME)
|
|
||||||
},
|
|
||||||
{ it.asDomain() }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
|
override fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
|
||||||
realmDataSourceFactory.updateQuery { realm ->
|
return userDataSource.getPagedUsersLive(filter, excludedUserIds)
|
||||||
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() }
|
override fun getIgnoredUsersLive(): LiveData<List<User>> {
|
||||||
?.let {
|
return userDataSource.getIgnoredUsersLive()
|
||||||
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
|
|
||||||
}
|
|
||||||
query.sort(UserEntityFields.DISPLAY_NAME)
|
|
||||||
}
|
|
||||||
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchUsersDirectory(search: String,
|
override fun searchUsersDirectory(search: String,
|
||||||
|
@ -126,17 +66,6 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
.executeBy(taskExecutor)
|
.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 {
|
override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList())
|
val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList())
|
||||||
return updateIgnoredUserIdsTask
|
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.
|
* 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.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
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.UserAccountDataEntity
|
||||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
|
@ -28,7 +29,6 @@ import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import javax.inject.Inject
|
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,
|
internal class AccountDataDataSource @Inject constructor(private val monarchy: Monarchy,
|
||||||
private val accountDataMapper: AccountDataMapper) {
|
private val accountDataMapper: AccountDataMapper) {
|
||||||
|
|
|
@ -17,18 +17,12 @@
|
||||||
package im.vector.matrix.android.internal.session.user.accountdata
|
package im.vector.matrix.android.internal.session.user.accountdata
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
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.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
|
||||||
import im.vector.matrix.android.api.util.Optional
|
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.UserAccountDataSyncHandler
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
@ -39,54 +33,24 @@ internal class DefaultAccountDataService @Inject constructor(
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||||
|
private val accountDataDataSource: AccountDataDataSource,
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor
|
||||||
) : AccountDataService {
|
) : AccountDataService {
|
||||||
|
|
||||||
private val moshi = MoshiProvider.providesMoshi()
|
|
||||||
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
|
|
||||||
|
|
||||||
override fun getAccountDataEvent(type: String): UserAccountDataEvent? {
|
override fun getAccountDataEvent(type: String): UserAccountDataEvent? {
|
||||||
return getAccountDataEvents(setOf(type)).firstOrNull()
|
return accountDataDataSource.getAccountDataEvent(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
|
override fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
|
||||||
return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
|
return accountDataDataSource.getLiveAccountDataEvent(type)
|
||||||
it.firstOrNull()?.toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
|
override fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
|
||||||
return monarchy.fetchAllCopiedSync { realm ->
|
return accountDataDataSource.getAccountDataEvents(types)
|
||||||
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()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
|
override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
|
||||||
return monarchy.findAllMappedWithChanges({ realm ->
|
return accountDataDataSource.getLiveAccountDataEvents(types)
|
||||||
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()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>?): Cancelable {
|
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.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.integrationmanager.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.AcceptedTermsContent
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
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,
|
data class AnyParams(override val type: String,
|
||||||
private val any: Any
|
private val any: Any
|
||||||
) : Params {
|
) : 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 im.vector.matrix.android.internal.util.toCancelable
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
|
@ -34,5 +36,7 @@ internal fun <T> CoroutineScope.launchToCallback(
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
result.foldToCallback(callback)
|
result.foldToCallback(callback)
|
||||||
|
}
|
||||||
}.toCancelable()
|
}.toCancelable()
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.util
|
package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.ArrayRes
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import dagger.Reusable
|
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 {
|
fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||||
return resources.getString(resId, *formatArgs)
|
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
|
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">%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_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_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>
|
<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" />
|
android:theme="@style/AppTheme.AttachmentsPreview" />
|
||||||
|
|
||||||
<activity android:name=".features.terms.ReviewTermsActivity" />
|
<activity android:name=".features.terms.ReviewTermsActivity" />
|
||||||
|
<activity android:name=".features.widgets.WidgetActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- 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.terms.ReviewTermsFragment
|
||||||
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||||
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||||
|
import im.vector.riotx.features.widgets.WidgetFragment
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
interface FragmentModule {
|
interface FragmentModule {
|
||||||
|
@ -510,4 +511,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(ReviewTermsFragment::class)
|
@FragmentKey(ReviewTermsFragment::class)
|
||||||
fun bindReviewTermsFragment(fragment: ReviewTermsFragment): Fragment
|
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.action.MessageActionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
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.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.filtered.FilteredRoomsActivity
|
||||||
import im.vector.riotx.features.home.room.list.RoomListModule
|
import im.vector.riotx.features.home.room.list.RoomListModule
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
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.signout.soft.SoftLogoutActivity
|
||||||
import im.vector.riotx.features.terms.ReviewTermsActivity
|
import im.vector.riotx.features.terms.ReviewTermsActivity
|
||||||
import im.vector.riotx.features.ui.UiStateRepository
|
import im.vector.riotx.features.ui.UiStateRepository
|
||||||
|
import im.vector.riotx.features.widgets.WidgetActivity
|
||||||
|
import im.vector.riotx.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -120,6 +123,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: BigImageViewerActivity)
|
fun inject(activity: BigImageViewerActivity)
|
||||||
fun inject(activity: InviteUsersToRoomActivity)
|
fun inject(activity: InviteUsersToRoomActivity)
|
||||||
fun inject(activity: ReviewTermsActivity)
|
fun inject(activity: ReviewTermsActivity)
|
||||||
|
fun inject(activity: WidgetActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
@ -134,6 +138,8 @@ interface ScreenComponent {
|
||||||
fun inject(bottomSheet: DeviceVerificationInfoBottomSheet)
|
fun inject(bottomSheet: DeviceVerificationInfoBottomSheet)
|
||||||
fun inject(bottomSheet: DeviceListBottomSheet)
|
fun inject(bottomSheet: DeviceListBottomSheet)
|
||||||
fun inject(bottomSheet: BootstrapBottomSheet)
|
fun inject(bottomSheet: BootstrapBottomSheet)
|
||||||
|
fun inject(bottomSheet: RoomWidgetPermissionBottomSheet)
|
||||||
|
fun inject(bottomSheet: RoomWidgetsBottomSheet)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Others
|
* 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.content.ContentAttachmentData
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.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.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
@ -26,6 +27,7 @@ import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
sealed class RoomDetailAction : VectorViewModelAction {
|
sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
||||||
data class SaveDraft(val draft: String) : 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 SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : RoomDetailAction()
|
||||||
data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction()
|
data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction()
|
||||||
data class TimelineEventTurnsVisible(val event: TimelineEvent) : 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 RequestVerification(val userId: String) : RoomDetailAction()
|
||||||
data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction()
|
data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction()
|
||||||
data class ReRequestKeys(val eventId: 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.os.Parcelable
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
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.Session
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
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.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.file.FileService
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
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.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
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.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.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
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.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
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.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.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
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.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
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.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.TimelineEventController
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
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.MessageTextItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
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.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.EventHtmlRenderer
|
||||||
import im.vector.riotx.features.html.PillImageSpan
|
import im.vector.riotx.features.html.PillImageSpan
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
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.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
import im.vector.riotx.features.widgets.WidgetActivity
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
@ -194,7 +202,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
VectorInviteView.Callback,
|
VectorInviteView.Callback,
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
AttachmentsHelper.Callback {
|
AttachmentsHelper.Callback,
|
||||||
|
RoomWidgetsBannerView.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -259,6 +268,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
setupNotificationView()
|
setupNotificationView()
|
||||||
setupJumpToReadMarkerView()
|
setupJumpToReadMarkerView()
|
||||||
setupJumpToBottomView()
|
setupJumpToBottomView()
|
||||||
|
setupWidgetsBannerView()
|
||||||
|
|
||||||
roomToolbarContentView.debouncedClicks {
|
roomToolbarContentView.debouncedClicks {
|
||||||
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
|
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
|
||||||
}
|
}
|
||||||
|
@ -299,10 +310,39 @@ class RoomDetailFragment @Inject constructor(
|
||||||
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
|
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
|
||||||
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
|
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
|
||||||
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
|
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
|
||||||
|
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
|
||||||
|
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
|
||||||
}.exhaustive
|
}.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) {
|
private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) {
|
||||||
updateComposerText("")
|
updateComposerText("")
|
||||||
lockSendButton = false
|
lockSendButton = false
|
||||||
|
@ -428,18 +468,24 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
if (item.itemId == R.id.clear_message_queue) {
|
return when (item.itemId) {
|
||||||
|
R.id.clear_message_queue -> {
|
||||||
// This a temporary option during dev as it is not super stable
|
// This a temporary option during dev as it is not super stable
|
||||||
// Cancel all pending actions in room queue and post a dummy
|
// Cancel all pending actions in room queue and post a dummy
|
||||||
// Then mark all sending events as undelivered
|
// Then mark all sending events as undelivered
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
|
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
|
||||||
return true
|
true
|
||||||
}
|
}
|
||||||
if (item.itemId == R.id.resend_all) {
|
R.id.resend_all -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
|
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
|
||||||
return true
|
true
|
||||||
|
}
|
||||||
|
R.id.open_matrix_apps -> {
|
||||||
|
navigator.openIntegrationManager(requireContext(), roomDetailArgs.roomId, null, null)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderRegularMode(text: String) {
|
private fun renderRegularMode(text: String) {
|
||||||
|
@ -523,6 +569,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
|
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
|
||||||
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
|
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 summary = state.asyncRoomSummary()
|
||||||
val inviter = state.asyncInviter()
|
val inviter = state.asyncInviter()
|
||||||
if (summary?.membership == Membership.JOIN) {
|
if (summary?.membership == Membership.JOIN) {
|
||||||
|
roomWidgetsBannerView.render(state.activeRoomWidgets())
|
||||||
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
||||||
timelineEventController.update(state)
|
timelineEventController.update(state)
|
||||||
inviteView.visibility = View.GONE
|
inviteView.visibility = View.GONE
|
||||||
|
@ -1379,7 +1430,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
|
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
|
||||||
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
|
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
|
||||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
|
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
|
||||||
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
|
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,4 +1463,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
val formattedContact = contactAttachment.toHumanReadable()
|
val formattedContact = contactAttachment.toHumanReadable()
|
||||||
roomDetailViewModel.handle(RoomDetailAction.SendMessage(formattedContact, false))
|
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
|
package im.vector.riotx.features.home.room.detail
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
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.core.platform.VectorViewEvents
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -49,6 +50,10 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||||
|
|
||||||
abstract class SendMessageResult : RoomDetailViewEvents()
|
abstract class SendMessageResult : RoomDetailViewEvents()
|
||||||
|
|
||||||
|
object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
|
||||||
|
|
||||||
|
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
|
||||||
|
|
||||||
object MessageSent : SendMessageResult()
|
object MessageSent : SendMessageResult()
|
||||||
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
|
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
|
||||||
class SlashCommandError(val command: Command) : SendMessageResult()
|
class SlashCommandError(val command: Command) : SendMessageResult()
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
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.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.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.file.FileService
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
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.command.ParsedCommand
|
||||||
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
|
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.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.detail.timeline.helper.TimelineDisplayableEvents
|
||||||
import im.vector.riotx.features.home.room.typing.TypingHelper
|
import im.vector.riotx.features.home.room.typing.TypingHelper
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
|
@ -74,6 +77,7 @@ import io.reactivex.Observable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
import io.reactivex.rxkotlin.subscribeBy
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import org.commonmark.renderer.html.HtmlRenderer
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -82,14 +86,15 @@ import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class RoomDetailViewModel @AssistedInject constructor(
|
class RoomDetailViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: RoomDetailViewState,
|
@Assisted private val initialState: RoomDetailViewState,
|
||||||
userPreferencesProvider: UserPreferencesProvider,
|
userPreferencesProvider: UserPreferencesProvider,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
private val rainbowGenerator: RainbowGenerator,
|
private val rainbowGenerator: RainbowGenerator,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||||
|
private val stickerPickerActionHandler: StickerPickerActionHandler
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
|
@ -157,6 +162,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
observeDrafts()
|
observeDrafts()
|
||||||
observeUnreadState()
|
observeUnreadState()
|
||||||
observeMyRoomMember()
|
observeMyRoomMember()
|
||||||
|
observeActiveRoomWidgets()
|
||||||
room.getRoomSummaryLive()
|
room.getRoomSummaryLive()
|
||||||
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback())
|
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback())
|
||||||
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||||
|
@ -164,6 +170,20 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
session.onRoomDisplayed(initialState.roomId)
|
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() {
|
private fun observeMyRoomMember() {
|
||||||
val queryParams = roomMemberQueryParams {
|
val queryParams = roomMemberQueryParams {
|
||||||
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE)
|
||||||
|
@ -185,6 +205,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||||
|
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||||
|
@ -216,6 +237,18 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(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()
|
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
||||||
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
|
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
|
||||||
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
|
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
|
||||||
|
R.id.open_matrix_apps -> session.integrationManagerService().isIntegrationEnabled()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,7 +1042,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
setState { copy(asyncInviter = Success(it)) }
|
setState { copy(asyncInviter = Success(it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, "")?.also {
|
room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE)?.also {
|
||||||
setState { copy(tombstoneEvent = it) }
|
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.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the current send mode:
|
* Describes the current send mode:
|
||||||
|
@ -55,6 +56,7 @@ data class RoomDetailViewState(
|
||||||
val myRoomMember: Async<RoomMemberSummary> = Uninitialized,
|
val myRoomMember: Async<RoomMemberSummary> = Uninitialized,
|
||||||
val asyncInviter: Async<User> = Uninitialized,
|
val asyncInviter: Async<User> = Uninitialized,
|
||||||
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
|
val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
|
||||||
val typingRoomMembers: List<MatrixItem.UserItem>? = null,
|
val typingRoomMembers: List<MatrixItem.UserItem>? = null,
|
||||||
val typingMessage: String? = null,
|
val typingMessage: String? = null,
|
||||||
val sendMode: SendMode = SendMode.REGULAR(""),
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.api.auth.data
|
|
||||||
|
|
||||||
data class WellKnownManagerConfig(
|
package im.vector.riotx.features.home.room.detail.sticker
|
||||||
val apiUrl: String,
|
|
||||||
val uiUrl: String
|
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_JOIN_RULES,
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
EventType.STATE_ROOM_GUEST_ACCESS,
|
EventType.STATE_ROOM_GUEST_ACCESS,
|
||||||
|
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||||
|
EventType.STATE_ROOM_WIDGET,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
|
|
|
@ -48,6 +48,9 @@ class DisplayableEventFormatter @Inject constructor(
|
||||||
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
||||||
|
|
||||||
when (timelineEvent.root.getClearType()) {
|
when (timelineEvent.root.getClearType()) {
|
||||||
|
EventType.STICKER -> {
|
||||||
|
return simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
|
||||||
|
}
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
||||||
when (messageContent.msgType) {
|
when (messageContent.msgType) {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.format
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.call.CallInviteContent
|
||||||
import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent
|
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.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.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
||||||
import im.vector.riotx.R
|
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_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||||
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(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_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.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
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? {
|
fun format(event: Event, senderName: String?): CharSequence? {
|
||||||
return when (val type = event.getClearType()) {
|
return when (val type = event.getClearType()) {
|
||||||
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName)
|
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName)
|
||||||
|
|
|
@ -158,7 +158,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (EventType.isStateEvent(event.root.type)) {
|
if (event.root.isStateEvent()) {
|
||||||
// Do not warn for state event, they are always in clear
|
// Do not warn for state event, they are always in clear
|
||||||
E2EDecoration.NONE
|
E2EDecoration.NONE
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,6 +24,8 @@ object TimelineDisplayableEvents {
|
||||||
|
|
||||||
val DISPLAYABLE_TYPES = listOf(
|
val DISPLAYABLE_TYPES = listOf(
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
|
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||||
|
EventType.STATE_ROOM_WIDGET,
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
EventType.STATE_ROOM_MEMBER,
|
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