mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 01:45:52 +03:00
Merge remote-tracking branch 'upstream/develop' into sc
This commit is contained in:
commit
0ba49f5596
328 changed files with 9681 additions and 1443 deletions
39
CHANGES.md
39
CHANGES.md
|
@ -1,3 +1,42 @@
|
|||
Changes in RiotX 0.22.0 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Integration Manager and Widget support (#48)
|
||||
- Send stickers (#51)
|
||||
|
||||
Improvements 🙌:
|
||||
- New wording for notice when current user is the sender
|
||||
- Hide "X made no changes" event by default in timeline (#1430)
|
||||
- Hide left rooms in breadcrumbs (#766)
|
||||
- Handle PowerLevel properly (#627)
|
||||
- Correctly handle SSO login redirection
|
||||
- SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400)
|
||||
- Improve checking of homeserver version support (#1442)
|
||||
- Add capability to add and remove a room from the favorites (#1217)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Switch theme is not fully taken into account without restarting the app
|
||||
- Temporary fix to show error when user is creating an account on matrix.org with userId containing only digits (#1410)
|
||||
- Reply composer overlay stays on screen too long after send (#1169)
|
||||
- Fix navigation bar icon contrast on API in [21,27[ (#1342)
|
||||
- Fix status bar icon contrast on API in [21,23[
|
||||
- Wrong /query request (#1444)
|
||||
- Make Credentials.homeServer optional because it is deprecated (#1443)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
- Send plain text in the body of events containing formatted body, as per https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||
- Update link to Modular url from "https://modular.im/" to "https://modular.im/services/matrix-hosting-riot" and open it using ChromeCustomTab
|
||||
|
||||
Changes in RiotX 0.21.0 (2020-05-28)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -59,6 +59,12 @@ It's recommended to run tests using an Android Emulator and not a real device. F
|
|||
|
||||
You can run all the tests in the `androidTest` folders.
|
||||
|
||||
It can be done using this command:
|
||||
|
||||
```bash
|
||||
./gradlew vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
|
||||
```
|
||||
|
||||
## Stop Synapse
|
||||
|
||||
To stop Synapse, you can run the following commands:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
|
||||
|
||||
## Sign up flows
|
||||
## Sign in flows
|
||||
|
||||
### Get the flow
|
||||
|
||||
|
@ -58,7 +58,7 @@ We get credential (200)
|
|||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lr",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "GTVREDALBF",
|
||||
"well_known": {
|
||||
|
@ -117,7 +117,7 @@ We get the credentials (200)
|
|||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3Jnfrfdegfszsefddvf",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "WBSREDASND",
|
||||
"well_known": {
|
||||
|
@ -145,12 +145,59 @@ Not supported yet in RiotX
|
|||
"flows": [
|
||||
{
|
||||
"type": "m.login.sso"
|
||||
},
|
||||
{
|
||||
"type": "m.login.token"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge
|
||||
In this case, the user can click on "Sign in with SSO" and the native web browser, or a ChromeCustomTab if the device supports it, will be launched on the page
|
||||
|
||||
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=riotx%3A%2F%2Friotx
|
||||
|
||||
The parameter `redirectUrl` is set to `riotx://riotx`.
|
||||
|
||||
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
|
||||
|
||||
The browser will then take care of the SSO login, which may include creating a third party account, entering an email, settings a display name, or any other possibilities.
|
||||
|
||||
During the process, user may be asked to validate an email by clicking on a link it contains. The link has to be opened in the browser which initiates the authentication. This is why we cannot use WebView anymore.
|
||||
|
||||
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
|
||||
|
||||
> riotx://riotx?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
|
||||
|
||||
This navigation is intercepted by RiotX by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
||||
|
||||
> curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "m.login.token",
|
||||
"token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"
|
||||
}
|
||||
```
|
||||
|
||||
We get the credentials (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:homeserver.with.sso",
|
||||
"access_token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVyIGtleQowMDEwY2lkIGdlbiA9IDEKMDAyY2NpZCB1c2",
|
||||
"home_server": "homeserver.with.sso",
|
||||
"device_id": "DETBTVAHCH",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https:\/\/homeserver.with.sso\/"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https:\/\/vector.im"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reset password
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
|
@ -61,7 +62,7 @@ class RxRoom(private val room: Room) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||
fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||
.startWithCallable {
|
||||
room.getStateEvent(eventType, stateKey).toOptional()
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
|
@ -28,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
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.widgets.model.Widget
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
|
@ -54,10 +56,10 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive().asObservable()
|
||||
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.getBreadcrumbs()
|
||||
session.getBreadcrumbs(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +153,18 @@ class RxSession(private val session: Session) {
|
|||
session.getAccountDataEvents(types)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveRoomWidgets(
|
||||
roomId: String,
|
||||
widgetId: QueryStringValue,
|
||||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): Observable<List<Widget>> {
|
||||
return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asObservable()
|
||||
.startWithCallable {
|
||||
session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* 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.send
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
/**
|
||||
* It will not be possible to test all combinations. For the moment I add a few tests, then, depending on the problem discovered in the wild,
|
||||
* we can add more tests to cover the edge cases.
|
||||
* Some tests are suffixed with `_not_passing`, maybe one day we will fix them...
|
||||
* Riot-Web should be used as a reference for expected results, but not always. Especially Riot-Web add lots of `\n` in the
|
||||
* formatted body, which is quite useless.
|
||||
* Also Riot-Web does not provide plain text body when formatted text is provided. The body contains what the user has entered.
|
||||
* See https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class MarkdownParserTest : InstrumentedTest {
|
||||
|
||||
/**
|
||||
* Create the same parser than in the RoomModule
|
||||
*/
|
||||
private val markdownParser = MarkdownParser(
|
||||
Parser.builder().build(),
|
||||
HtmlRenderer.builder().build(),
|
||||
TextContentRenderer.builder().build()
|
||||
)
|
||||
|
||||
@Test
|
||||
fun parseNoMarkdown() {
|
||||
testIdentity("")
|
||||
testIdentity("a")
|
||||
testIdentity("1")
|
||||
testIdentity("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " +
|
||||
"dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" +
|
||||
"modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pari" +
|
||||
"atur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseSpaces() {
|
||||
testIdentity(" ")
|
||||
testIdentity(" ")
|
||||
testIdentity("\n")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseNewLines() {
|
||||
testIdentity("line1\nline2")
|
||||
testIdentity("line1\nline2\nline3")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBold() {
|
||||
testType(
|
||||
name = "bold",
|
||||
markdownPattern = "**",
|
||||
htmlExpectedTag = "strong"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalic() {
|
||||
testType(
|
||||
name = "italic",
|
||||
markdownPattern = "*",
|
||||
htmlExpectedTag = "em"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalic2() {
|
||||
// Riot-Web format
|
||||
"_italic_".let { markdownParser.parse(it) }.expect("italic", "<em>italic</em>")
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the test is not passing, it does not work on Riot-Web neither
|
||||
*/
|
||||
@Test
|
||||
fun parseStrike_not_passing() {
|
||||
testType(
|
||||
name = "strike",
|
||||
markdownPattern = "~~",
|
||||
htmlExpectedTag = "del"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "`",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode2() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "``",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode3() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "```",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseUnorderedList() {
|
||||
"- item1".let { markdownParser.parse(it).expect(it, "<ul><li>item1</li></ul>") }
|
||||
"- item1\n- item2".let { markdownParser.parse(it).expect(it, "<ul><li>item1</li><li>item2</li></ul>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseOrderedList() {
|
||||
"1. item1".let { markdownParser.parse(it).expect(it, "<ol><li>item1</li></ol>") }
|
||||
"1. item1\n2. item2".let { markdownParser.parse(it).expect(it, "<ol><li>item1</li><li>item2</li></ol>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHorizontalLine() {
|
||||
"---".let { markdownParser.parse(it) }.expect("***", "<hr />")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseH2AndContent() {
|
||||
"a\n---\nb".let { markdownParser.parse(it) }.expect("a\nb", "<h2>a</h2><p>b</p>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQuote() {
|
||||
"> quoted".let { markdownParser.parse(it) }.expect("«quoted»", "<blockquote><p>quoted</p></blockquote>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQuote_not_passing() {
|
||||
"> quoted\nline2".let { markdownParser.parse(it) }.expect("«quoted\nline2»", "<blockquote><p>quoted<br/>line2</p></blockquote>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldItalic() {
|
||||
"*italic* **bold**".let { markdownParser.parse(it) }.expect("italic bold", "<em>italic</em> <strong>bold</strong>")
|
||||
"**bold** *italic*".let { markdownParser.parse(it) }.expect("bold italic", "<strong>bold</strong> <em>italic</em>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHead() {
|
||||
"# head1".let { markdownParser.parse(it) }.expect("head1", "<h1>head1</h1>")
|
||||
"## head2".let { markdownParser.parse(it) }.expect("head2", "<h2>head2</h2>")
|
||||
"### head3".let { markdownParser.parse(it) }.expect("head3", "<h3>head3</h3>")
|
||||
"#### head4".let { markdownParser.parse(it) }.expect("head4", "<h4>head4</h4>")
|
||||
"##### head5".let { markdownParser.parse(it) }.expect("head5", "<h5>head5</h5>")
|
||||
"###### head6".let { markdownParser.parse(it) }.expect("head6", "<h6>head6</h6>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHeads() {
|
||||
"# head1\n# head2".let { markdownParser.parse(it) }.expect("head1\nhead2", "<h1>head1</h1><h1>head2</h1>")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldNewLines_not_passing() {
|
||||
"**bold**\nline2".let { markdownParser.parse(it) }.expect("bold\nline2", "<strong>bold</strong><br />line2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseLinks() {
|
||||
"[link](target)".let { markdownParser.parse(it) }.expect(""""link" (target)""", """<a href="target">link</a>""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseParagraph() {
|
||||
"# head\ncontent".let { markdownParser.parse(it) }.expect("head\ncontent", "<h1>head</h1><p>content</p>")
|
||||
}
|
||||
|
||||
private fun testIdentity(text: String) {
|
||||
markdownParser.parse(text).expect(text, null)
|
||||
}
|
||||
|
||||
private fun testType(name: String,
|
||||
markdownPattern: String,
|
||||
htmlExpectedTag: String,
|
||||
plainTextPrefix: String = "",
|
||||
plainTextSuffix: String = "") {
|
||||
// Test simple case
|
||||
"$markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
|
||||
// Test twice the same tag
|
||||
"$markdownPattern$name$markdownPattern and $markdownPattern$name bis$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix and $plainTextPrefix$name bis$plainTextSuffix",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> and <$htmlExpectedTag>$name bis</$htmlExpectedTag>")
|
||||
|
||||
val textBefore = "a"
|
||||
val textAfter = "b"
|
||||
|
||||
// With sticked text before
|
||||
"$textBefore$markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore$plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "$textBefore<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
|
||||
// With text before and space
|
||||
"$textBefore $markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore $plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
|
||||
// With sticked text after
|
||||
"$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix$textAfter",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
|
||||
// With space and text after
|
||||
"$markdownPattern$name$markdownPattern $textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix $textAfter",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
|
||||
// With sticked text before and text after
|
||||
"$textBefore$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore$plainTextPrefix$name$plainTextSuffix$textAfter",
|
||||
expectedFormattedText = "a<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
|
||||
// With text before and after, with spaces
|
||||
"$textBefore $markdownPattern$name$markdownPattern $textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore $plainTextPrefix$name$plainTextSuffix $textAfter",
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
}
|
||||
|
||||
private fun TextContent.expect(expectedText: String, expectedFormattedText: String?) {
|
||||
assertEquals("TextContent are not identical", TextContent(expectedText, expectedFormattedText), this)
|
||||
}
|
||||
}
|
54
matrix-sdk-android/src/main/assets/postMessageAPI.js
Executable file
54
matrix-sdk-android/src/main/assets/postMessageAPI.js
Executable file
|
@ -0,0 +1,54 @@
|
|||
var android_widget_events = {};
|
||||
|
||||
var sendObjectMessageToRiotAndroid = function(parameters) {
|
||||
Android.onWidgetEvent(JSON.stringify(parameters));
|
||||
};
|
||||
|
||||
var onWidgetMessageToRiotAndroid = function(event) {
|
||||
/* Use an internal "_id" field for matching onMessage events and requests
|
||||
_id was originally used by the Modular API. Keep it */
|
||||
if (!event.data._id) {
|
||||
/* The Matrix Widget API v2 spec says:
|
||||
"The requestId field should be unique and included in all requests" */
|
||||
event.data._id = event.data.requestId;
|
||||
}
|
||||
/* Make sure to have one id */
|
||||
if (!event.data._id) {
|
||||
event.data._id = Date.now() + "-" + Math.random().toString(36);
|
||||
}
|
||||
|
||||
console.log("onWidgetMessageToRiotAndroid " + event.data._id);
|
||||
|
||||
if (android_widget_events[event.data._id]) {
|
||||
console.log("onWidgetMessageToRiotAndroid : already managed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.origin) {
|
||||
event.origin = event.originalEvent.origin;
|
||||
}
|
||||
|
||||
android_widget_events[event.data._id] = event;
|
||||
|
||||
console.log("onWidgetMessageToRiotAndroid : manage " + event.data);
|
||||
sendObjectMessageToRiotAndroid({'event.data': event.data});
|
||||
};
|
||||
|
||||
var sendResponseFromRiotAndroid = function(eventId, res) {
|
||||
var event = android_widget_events[eventId];
|
||||
|
||||
console.log("sendResponseFromRiotAndroid to " + event.data.action + " for "+ eventId + ": " + JSON.stringify(res));
|
||||
|
||||
var data = JSON.parse(JSON.stringify(event.data));
|
||||
|
||||
data.response = res;
|
||||
|
||||
console.log("sendResponseFromRiotAndroid ---> " + data);
|
||||
|
||||
event.source.postMessage(data, event.origin);
|
||||
android_widget_events[eventId] = true;
|
||||
|
||||
console.log("sendResponseFromRiotAndroid to done");
|
||||
};
|
||||
|
||||
window.addEventListener('message', onWidgetMessageToRiotAndroid, false);
|
|
@ -22,6 +22,15 @@ import java.net.Proxy
|
|||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
||||
val integrationUIUrl: String = "https://scalar.vector.im/",
|
||||
val integrationRestUrl: String = "https://scalar.vector.im/api",
|
||||
val integrationWidgetUrls: List<String> = listOf(
|
||||
"https://scalar.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar.vector.im/api",
|
||||
"https://scalar-staging.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
),
|
||||
/**
|
||||
* Optional proxy to connect to the matrix servers
|
||||
* You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port)
|
||||
|
|
|
@ -32,6 +32,6 @@ const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
|||
* Path to use when the client want to connect using SSO
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||
*/
|
||||
const val SSO_FALLBACK_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
|
||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||
|
|
|
@ -45,7 +45,7 @@ data class Credentials(
|
|||
* @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
|
||||
* if they require it. Note also that homeserver is not spelt this way.
|
||||
*/
|
||||
@Json(name = "home_server") val homeServer: String,
|
||||
@Json(name = "home_server") val homeServer: String?,
|
||||
/**
|
||||
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
|
||||
*/
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.net.Uri
|
|||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig.Builder
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import okhttp3.CipherSuite
|
||||
import okhttp3.TlsVersion
|
||||
|
||||
|
@ -71,15 +72,11 @@ data class HomeServerConnectionConfig(
|
|||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
||||
}
|
||||
// ensure trailing /
|
||||
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = hsUri.toString()
|
||||
Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
} else {
|
||||
hsUri
|
||||
val hsString = hsUri.toString().ensureTrailingSlash()
|
||||
homeServerUri = try {
|
||||
Uri.parse(hsString)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@ -97,15 +94,11 @@ data class HomeServerConnectionConfig(
|
|||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
// ensure trailing /
|
||||
if (!identityServerUri.toString().endsWith("/")) {
|
||||
try {
|
||||
val url = identityServerUri.toString()
|
||||
this.identityServerUri = Uri.parse("$url/")
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
} else {
|
||||
this.identityServerUri = identityServerUri
|
||||
val isString = identityServerUri.toString().ensureTrailingSlash()
|
||||
this.identityServerUri = try {
|
||||
Uri.parse(isString)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
|
|
@ -16,12 +16,10 @@
|
|||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
|
||||
// Either a LoginFlowResponse, or an error if the homeserver is outdated
|
||||
// Either a list of supported login types, or an error if the homeserver is outdated
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
val loginFlowResponse: LoginFlowResponse,
|
||||
val supportedLoginTypes: List<String>,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String
|
||||
) : LoginFlowResult()
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.auth.data
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
object LoginFlowTypes {
|
||||
const val PASSWORD = "m.login.password"
|
|
@ -54,30 +54,4 @@ data class WellKnown(
|
|||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: JsonDict? = null
|
||||
) {
|
||||
/**
|
||||
* Returns the list of integration managers proposed
|
||||
*/
|
||||
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
|
||||
val managers = ArrayList<WellKnownManagerConfig>()
|
||||
integrations?.get("managers")?.let {
|
||||
(it as? ArrayList<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
managers.add(WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return managers
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -34,6 +34,12 @@ interface LoginWizard {
|
|||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Exchange a login token to an access token
|
||||
*/
|
||||
fun loginWithToken(loginToken: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Reset user password
|
||||
*/
|
||||
|
|
|
@ -21,15 +21,12 @@ sealed class Stage(open val mandatory: Boolean) {
|
|||
// m.login.recaptcha
|
||||
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
|
||||
|
||||
// m.login.oauth2
|
||||
// m.login.email.identity
|
||||
data class Email(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.msisdn
|
||||
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.token
|
||||
|
||||
// m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
|
||||
// and a password, the dummy stage has to be done
|
||||
data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
|
|
@ -39,6 +39,6 @@ class SenderNotificationPermissionCondition(
|
|||
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevel(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ sealed class QueryStringValue {
|
|||
object IsNotNull : QueryStringValue()
|
||||
object IsEmpty : QueryStringValue()
|
||||
object IsNotEmpty : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case) : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
|
||||
enum class Case {
|
||||
SENSITIVE,
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.file.FileService
|
|||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.identity.IdentityService
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
|
@ -42,6 +43,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
|
|||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
|
||||
/**
|
||||
* This interface defines interactions with a session.
|
||||
|
@ -153,6 +155,16 @@ interface Session :
|
|||
*/
|
||||
fun identityService(): IdentityService
|
||||
|
||||
/**
|
||||
* Returns the widget service associated with the session
|
||||
*/
|
||||
fun widgetService(): WidgetService
|
||||
|
||||
/**
|
||||
* Returns the integration manager service associated with the session
|
||||
*/
|
||||
fun integrationManagerService(): IntegrationManagerService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
|
|
@ -98,7 +98,7 @@ data class Event(
|
|||
* @return true if event is state event.
|
||||
*/
|
||||
fun isStateEvent(): Boolean {
|
||||
return EventType.isStateEvent(getClearType())
|
||||
return stateKey != null
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
|
@ -162,6 +162,8 @@ data class Event(
|
|||
*/
|
||||
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
|
||||
|
||||
fun resolvedPrevContent(): Content? = prevContent ?: unsignedData?.prevContent
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
|
|
@ -38,6 +38,8 @@ object EventType {
|
|||
|
||||
// State Events
|
||||
|
||||
const val STATE_ROOM_WIDGET_LEGACY = "im.vector.modular.widgets"
|
||||
const val STATE_ROOM_WIDGET = "m.widget"
|
||||
const val STATE_ROOM_NAME = "m.room.name"
|
||||
const val STATE_ROOM_TOPIC = "m.room.topic"
|
||||
const val STATE_ROOM_AVATAR = "m.room.avatar"
|
||||
|
@ -84,29 +86,6 @@ object EventType {
|
|||
// Unwedging
|
||||
internal const val DUMMY = "m.dummy"
|
||||
|
||||
private val STATE_EVENTS = listOf(
|
||||
STATE_ROOM_NAME,
|
||||
STATE_ROOM_TOPIC,
|
||||
STATE_ROOM_AVATAR,
|
||||
STATE_ROOM_MEMBER,
|
||||
STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
STATE_ROOM_CREATE,
|
||||
STATE_ROOM_JOIN_RULES,
|
||||
STATE_ROOM_GUEST_ACCESS,
|
||||
STATE_ROOM_POWER_LEVELS,
|
||||
STATE_ROOM_ALIASES,
|
||||
STATE_ROOM_TOMBSTONE,
|
||||
STATE_ROOM_CANONICAL_ALIAS,
|
||||
STATE_ROOM_HISTORY_VISIBILITY,
|
||||
STATE_ROOM_RELATED_GROUPS,
|
||||
STATE_ROOM_PINNED_EVENT,
|
||||
STATE_ROOM_ENCRYPTION
|
||||
)
|
||||
|
||||
fun isStateEvent(type: String): Boolean {
|
||||
return STATE_EVENTS.contains(type)
|
||||
}
|
||||
|
||||
fun isCallEvent(type: String): Boolean {
|
||||
return type == CALL_INVITE
|
||||
|| type == CALL_CANDIDATES
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
sealed class IdentityServiceError : Throwable() {
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
||||
sealed class IdentityServiceError : Failure.FeatureFailure() {
|
||||
object OutdatedIdentityServer : IdentityServiceError()
|
||||
object OutdatedHomeServer : IdentityServiceError()
|
||||
object NoIdentityServerConfigured : IdentityServiceError()
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.integrationmanager
|
||||
|
||||
/**
|
||||
* This class holds configuration of integration manager.
|
||||
*/
|
||||
data class IntegrationManagerConfig(
|
||||
val uiUrl: String,
|
||||
val restUrl: String,
|
||||
val kind: Kind
|
||||
) {
|
||||
|
||||
// Order matters, first is preferred
|
||||
/**
|
||||
* The kind of config, it will reflect where the data is coming from.
|
||||
*/
|
||||
enum class Kind {
|
||||
/**
|
||||
* Defined in UserAccountData
|
||||
*/
|
||||
ACCOUNT,
|
||||
/**
|
||||
* Defined in Wellknown
|
||||
*/
|
||||
HOMESERVER,
|
||||
/**
|
||||
* Fallback value, hardcoded by the SDK
|
||||
*/
|
||||
DEFAULT
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This is the entry point to manage integration. You can grab an instance of this service through an active session.
|
||||
*/
|
||||
interface IntegrationManagerService {
|
||||
|
||||
/**
|
||||
* This listener allow you to observe change related to integrations.
|
||||
*/
|
||||
interface Listener {
|
||||
/**
|
||||
* Is called whenever integration is enabled or disabled, comes from user account data.
|
||||
*/
|
||||
fun onIsEnabledChanged(enabled: Boolean) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called whenever configs from user account data or wellknown are updated.
|
||||
*/
|
||||
fun onConfigurationChanged(configs: List<IntegrationManagerConfig>) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called whenever widget permissions from user account data are updated.
|
||||
*/
|
||||
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to observe changes.
|
||||
*/
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Removes a previously added listener.
|
||||
*/
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Return the list of current configurations, sorted by kind. First one is preferred.
|
||||
* See [IntegrationManagerConfig.Kind]
|
||||
*/
|
||||
fun getOrderedConfigs(): List<IntegrationManagerConfig>
|
||||
|
||||
/**
|
||||
* Return the preferred current configuration.
|
||||
* See [IntegrationManagerConfig.Kind]
|
||||
*/
|
||||
fun getPreferredConfig(): IntegrationManagerConfig
|
||||
|
||||
/**
|
||||
* Returns true if integration is enabled, false otherwise.
|
||||
*/
|
||||
fun isIntegrationEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Offers to enable or disable the integration.
|
||||
* @param enable the param to change
|
||||
* @param callback the matrix callback to listen for result.
|
||||
* @return Cancelable
|
||||
*/
|
||||
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Offers to allow or disallow a widget.
|
||||
* @param stateEventId the eventId of the state event defining the widget.
|
||||
* @param allowed the param to change
|
||||
* @param callback the matrix callback to listen for result.
|
||||
* @return Cancelable
|
||||
*/
|
||||
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Returns true if the widget is allowed, false otherwise.
|
||||
* @param stateEventId the eventId of the state event defining the widget.
|
||||
*/
|
||||
fun isWidgetAllowed(stateEventId: String): Boolean
|
||||
|
||||
/**
|
||||
* Offers to allow or disallow a native widget domain.
|
||||
* @param widgetType the widget type to check for
|
||||
* @param domain the domain to check for
|
||||
*/
|
||||
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Returns true if the widget domain is allowed, false otherwise.
|
||||
* @param widgetType the widget type to check for
|
||||
* @param domain the domain to check for
|
||||
*/
|
||||
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean
|
||||
}
|
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.room.reporting.ReportingService
|
|||
import im.vector.matrix.android.api.session.room.send.DraftService
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.session.room.tags.TagsService
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.session.room.uploads.UploadsService
|
||||
|
@ -41,6 +42,7 @@ interface Room :
|
|||
DraftService,
|
||||
ReadService,
|
||||
TypingService,
|
||||
TagsService,
|
||||
MembershipService,
|
||||
StateService,
|
||||
UploadsService,
|
||||
|
|
|
@ -73,15 +73,17 @@ interface RoomService {
|
|||
|
||||
/**
|
||||
* Get a snapshot list of Breadcrumbs
|
||||
* @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
|
||||
* @return the immutable list of [RoomSummary]
|
||||
*/
|
||||
fun getBreadcrumbs(): List<RoomSummary>
|
||||
fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary>
|
||||
|
||||
/**
|
||||
* Get a live list of Breadcrumbs
|
||||
* @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
|
||||
* @return the [LiveData] of [RoomSummary]
|
||||
*/
|
||||
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
|
||||
fun getBreadcrumbsLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
|
||||
|
||||
/**
|
||||
* Inform the Matrix SDK that a room is displayed.
|
||||
|
|
|
@ -63,6 +63,27 @@ interface MembershipService {
|
|||
reason: String? = null,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Ban a user from the room
|
||||
*/
|
||||
fun ban(userId: String,
|
||||
reason: String? = null,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Unban a user from the room
|
||||
*/
|
||||
fun unban(userId: String,
|
||||
reason: String? = null,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Kick a user from the room
|
||||
*/
|
||||
fun kick(userId: String,
|
||||
reason: String? = null,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Join the room, or accept an invitation.
|
||||
*/
|
||||
|
|
|
@ -44,6 +44,10 @@ enum class Membership(val value: String) {
|
|||
return this == KNOCK || this == LEAVE || this == BAN
|
||||
}
|
||||
|
||||
fun isActive(): Boolean {
|
||||
return activeMemberships().contains(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun activeMemberships(): List<Membership> {
|
||||
return listOf(INVITE, JOIN)
|
||||
|
|
|
@ -18,21 +18,21 @@ package im.vector.matrix.android.api.session.room.model
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.Role
|
||||
|
||||
/**
|
||||
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PowerLevelsContent(
|
||||
@Json(name = "ban") val ban: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "kick") val kick: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "invite") val invite: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "redact") val redact: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "events_default") val eventsDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
|
||||
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
||||
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
||||
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
||||
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
||||
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
||||
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
|
||||
@Json(name = "users_default") val usersDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
|
||||
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
||||
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
|
||||
@Json(name = "state_default") val stateDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
||||
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
|
||||
)
|
||||
|
|
|
@ -59,6 +59,9 @@ data class RoomSummary constructor(
|
|||
val hasNewMessages: Boolean
|
||||
get() = notificationCount != 0
|
||||
|
||||
val isFavorite: Boolean
|
||||
get() = tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE }
|
||||
|
||||
companion object {
|
||||
const val NOT_IN_BREADCRUMBS = -1
|
||||
}
|
||||
|
|
|
@ -22,9 +22,8 @@ data class RoomTag(
|
|||
) {
|
||||
|
||||
companion object {
|
||||
val ROOM_TAG_FAVOURITE = "m.favourite"
|
||||
val ROOM_TAG_LOW_PRIORITY = "m.lowpriority"
|
||||
val ROOM_TAG_NO_TAG = "m.recent"
|
||||
val ROOM_TAG_SERVER_NOTICE = "m.server_notice"
|
||||
const val ROOM_TAG_FAVOURITE = "m.favourite"
|
||||
const val ROOM_TAG_LOW_PRIORITY = "m.lowpriority"
|
||||
const val ROOM_TAG_SERVER_NOTICE = "m.server_notice"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.room.powerlevels
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
|
||||
/**
|
||||
|
@ -31,32 +30,84 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
* @param userId the user id
|
||||
* @return the power level
|
||||
*/
|
||||
fun getUserPowerLevel(userId: String): Int {
|
||||
fun getUserPowerLevelValue(userId: String): Int {
|
||||
return powerLevelsContent.users.getOrElse(userId) {
|
||||
powerLevelsContent.usersDefault
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user power level of a dedicated user Id
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return the power level
|
||||
*/
|
||||
fun getUserRole(userId: String): Role {
|
||||
val value = getUserPowerLevelValue(userId)
|
||||
return Role.fromValue(value, powerLevelsContent.eventsDefault)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if an user can send an event of a certain type
|
||||
*
|
||||
* @param userId the id of the user to check for.
|
||||
* @param isState true if the event is a state event (ie. state key is not null)
|
||||
* @param eventType the event type to check for
|
||||
* @param userId the user id
|
||||
* @return true if the user can send this type of event
|
||||
*/
|
||||
fun isAllowedToSend(eventType: String, userId: String): Boolean {
|
||||
return if (eventType.isNotEmpty() && userId.isNotEmpty()) {
|
||||
val powerLevel = getUserPowerLevel(userId)
|
||||
fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean {
|
||||
return if (userId.isNotEmpty()) {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
||||
?: if (EventType.isStateEvent(eventType)) {
|
||||
powerLevelsContent.stateDefault
|
||||
} else {
|
||||
powerLevelsContent.eventsDefault
|
||||
}
|
||||
?: if (isState) {
|
||||
powerLevelsContent.stateDefault
|
||||
} else {
|
||||
powerLevelsContent.eventsDefault
|
||||
}
|
||||
powerLevel >= minimumPowerLevel
|
||||
} else false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user have the necessary power level to invite
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to invite
|
||||
*/
|
||||
fun isUserAbleToInvite(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
return powerLevel >= powerLevelsContent.invite
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user have the necessary power level to ban
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to ban
|
||||
*/
|
||||
fun isUserAbleToBan(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
return powerLevel >= powerLevelsContent.ban
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user have the necessary power level to kick
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to kick
|
||||
*/
|
||||
fun isUserAbleToKick(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
return powerLevel >= powerLevelsContent.kick
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user have the necessary power level to redact
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to redact
|
||||
*/
|
||||
fun isUserAbleToRedact(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
return powerLevel >= powerLevelsContent.redact
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification level for a dedicated key.
|
||||
*
|
||||
|
@ -68,7 +119,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
// the first implementation was a string value
|
||||
is String -> value.toInt()
|
||||
is Int -> value
|
||||
else -> PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL
|
||||
else -> Role.Moderator.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 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.room.powerlevels
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.matrix.android.R
|
||||
|
||||
sealed class Role(open val value: Int, @StringRes val res: Int) : Comparable<Role> {
|
||||
object Admin : Role(100, R.string.power_level_admin)
|
||||
object Moderator : Role(50, R.string.power_level_moderator)
|
||||
object Default : Role(0, R.string.power_level_default)
|
||||
data class Custom(override val value: Int) : Role(value, R.string.power_level_custom)
|
||||
|
||||
override fun compareTo(other: Role): Int {
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// Order matters, default value should be checked after defined roles
|
||||
fun fromValue(value: Int, default: Int): Role {
|
||||
return when (value) {
|
||||
Admin.value -> Admin
|
||||
Moderator.value -> Moderator
|
||||
Default.value,
|
||||
default -> Default
|
||||
else -> Custom(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.api.session.room.send
|
||||
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
|
@ -28,6 +29,14 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
*/
|
||||
interface SendService {
|
||||
|
||||
/**
|
||||
* Method to send a generic event asynchronously. If you want to send a state event, please use [StateService] instead.
|
||||
* @param eventType the type of the event
|
||||
* @param content the optional body as a json dict.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendEvent(eventType: String, content: Content?): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a text message asynchronously.
|
||||
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||
|
|
|
@ -18,7 +18,10 @@ package im.vector.matrix.android.api.session.room.state
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
||||
interface StateService {
|
||||
|
@ -26,9 +29,15 @@ interface StateService {
|
|||
/**
|
||||
* Update the topic of the room
|
||||
*/
|
||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>)
|
||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun getStateEvent(eventType: String, stateKey: String): Event?
|
||||
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
|
||||
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
||||
|
||||
fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>>
|
||||
|
||||
fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event>
|
||||
|
||||
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,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.room.tags
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to handle tags of a room. It's implemented at the room level.
|
||||
*/
|
||||
interface TagsService {
|
||||
/**
|
||||
* Add a tag to a room
|
||||
*/
|
||||
fun addTag(tag: String, order: Double?, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Remove tag from a room
|
||||
*/
|
||||
fun deleteTag(tag: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
|
@ -32,6 +32,10 @@ data class TimelineSettings(
|
|||
* A flag to filter redacted events
|
||||
*/
|
||||
val filterRedacted: Boolean = false,
|
||||
/**
|
||||
* A flag to filter useless events, such as membership events without any change
|
||||
*/
|
||||
val filterUseless: Boolean = false,
|
||||
/**
|
||||
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||
*/
|
||||
|
@ -44,5 +48,4 @@ data class TimelineSettings(
|
|||
* If true, will build read receipts for each event.
|
||||
*/
|
||||
val buildReadReceipts: Boolean = true
|
||||
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,6 +47,10 @@ data class Optional<T : Any> constructor(private val value: T?) {
|
|||
fun <T : Any> from(value: T?): Optional<T> {
|
||||
return Optional(value)
|
||||
}
|
||||
|
||||
fun <T: Any> empty(): Optional<T> {
|
||||
return Optional(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,16 +17,17 @@
|
|||
package im.vector.matrix.android.internal.auth
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.Versions
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
|
||||
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
|
||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationParams
|
||||
import im.vector.matrix.android.internal.auth.registration.SuccessResult
|
||||
import im.vector.matrix.android.internal.auth.registration.ValidationCodeBody
|
||||
import im.vector.matrix.android.internal.auth.version.Versions
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
|
@ -54,7 +55,7 @@ internal interface AuthAPI {
|
|||
fun versions(): Call<Versions>
|
||||
|
||||
/**
|
||||
* Register to the homeserver
|
||||
* Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
|
||||
|
@ -91,6 +92,11 @@ internal interface AuthAPI {
|
|||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
||||
|
||||
// Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams
|
||||
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||
fun login(@Body loginParams: TokenLoginParams): Call<Credentials>
|
||||
|
||||
/**
|
||||
* Ask the homeserver to reset the password associated with the provided email.
|
||||
*/
|
||||
|
|
|
@ -23,9 +23,6 @@ import im.vector.matrix.android.api.auth.AuthenticationService
|
|||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
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.isSupportedBySdk
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
|
@ -40,6 +37,9 @@ import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
|||
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
||||
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
|
||||
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
||||
import im.vector.matrix.android.internal.auth.version.Versions
|
||||
import im.vector.matrix.android.internal.auth.version.isLoginAndRegistrationSupportedBySdk
|
||||
import im.vector.matrix.android.internal.auth.version.isSupportedBySdk
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
|
@ -236,7 +236,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
|
||||
apiCall = authAPI.getLoginFlows()
|
||||
}
|
||||
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
||||
LoginFlowResult.Success(loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
||||
} else {
|
||||
// Not supported
|
||||
LoginFlowResult.OutdatedHomeserver
|
||||
|
|
|
@ -20,7 +20,19 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LoginFlowResponse(
|
||||
internal data class LoginFlowResponse(
|
||||
/**
|
||||
* The homeserver's supported login types
|
||||
*/
|
||||
@Json(name = "flows")
|
||||
val flows: List<InteractiveAuthenticationFlow>
|
||||
val flows: List<LoginFlow>?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class LoginFlow(
|
||||
/**
|
||||
* The login type. This is supplied as the type when logging in.
|
||||
*/
|
||||
@Json(name = "type")
|
||||
val type: String?
|
||||
)
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.data
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
|
||||
/**
|
||||
* Ref:
|
||||
|
|
|
@ -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.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class TokenLoginParams(
|
||||
@Json(name = "type") override val type: String = LoginFlowTypes.TOKEN,
|
||||
@Json(name = "token") val token: String
|
||||
) : LoginParams
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
|
|||
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
|
||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
||||
|
@ -65,6 +66,22 @@ internal class DefaultLoginWizard(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
|
||||
*/
|
||||
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val loginParams = TokenLoginParams(
|
||||
token = loginToken
|
||||
)
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loginInternal(login: String,
|
||||
password: String,
|
||||
deviceName: String) = withContext(coroutineDispatchers.computation) {
|
||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.auth.registration
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
|
||||
/**
|
||||
* Open class, parent to all possible authentication parameters
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.registration
|
|||
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
|
@ -28,7 +29,6 @@ import im.vector.matrix.android.api.util.NoOpCancellable
|
|||
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||
import im.vector.matrix.android.internal.auth.PendingSessionStore
|
||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
|
|
|
@ -18,12 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.api.auth.registration.FlowResult
|
||||
import im.vector.matrix.android.api.auth.registration.Stage
|
||||
import im.vector.matrix.android.api.auth.registration.TermPolicies
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RegistrationFlowResponse(
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.auth.version
|
||||
|
||||
/**
|
||||
* Values will take the form "rX.Y.Z".
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
|
||||
*/
|
||||
internal data class HomeServerVersion(
|
||||
val major: Int,
|
||||
val minor: Int,
|
||||
val patch: Int
|
||||
) : Comparable<HomeServerVersion> {
|
||||
override fun compareTo(other: HomeServerVersion): Int {
|
||||
return when {
|
||||
major > other.major -> 1
|
||||
major < other.major -> -1
|
||||
minor > other.minor -> 1
|
||||
minor < other.minor -> -1
|
||||
patch > other.patch -> 1
|
||||
patch < other.patch -> -1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val pattern = Regex("""r(\d+)\.(\d+)\.(\d+)""")
|
||||
|
||||
internal fun parse(value: String): HomeServerVersion? {
|
||||
val result = pattern.matchEntire(value) ?: return null
|
||||
return HomeServerVersion(
|
||||
major = result.groupValues[1].toInt(),
|
||||
minor = result.groupValues[2].toInt(),
|
||||
patch = result.groupValues[3].toInt()
|
||||
)
|
||||
}
|
||||
|
||||
val r0_0_0 = HomeServerVersion(major = 0, minor = 0, patch = 0)
|
||||
val r0_1_0 = HomeServerVersion(major = 0, minor = 1, patch = 0)
|
||||
val r0_2_0 = HomeServerVersion(major = 0, minor = 2, patch = 0)
|
||||
val r0_3_0 = HomeServerVersion(major = 0, minor = 3, patch = 0)
|
||||
val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0)
|
||||
val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0)
|
||||
val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
package im.vector.matrix.android.internal.auth.version
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
@ -38,7 +38,7 @@ import com.squareup.moshi.JsonClass
|
|||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Versions(
|
||||
internal data class Versions(
|
||||
@Json(name = "versions")
|
||||
val supportedVersions: List<String>? = null,
|
||||
|
||||
|
@ -46,15 +46,6 @@ data class Versions(
|
|||
val unstableFeatures: Map<String, Boolean>? = null
|
||||
)
|
||||
|
||||
// MatrixClientServerAPIVersion
|
||||
private const val r0_0_1 = "r0.0.1"
|
||||
private const val r0_1_0 = "r0.1.0"
|
||||
private const val r0_2_0 = "r0.2.0"
|
||||
private const val r0_3_0 = "r0.3.0"
|
||||
private const val r0_4_0 = "r0.4.0"
|
||||
private const val r0_5_0 = "r0.5.0"
|
||||
private const val r0_6_0 = "r0.6.0"
|
||||
|
||||
// MatrixVersionsFeature
|
||||
private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members"
|
||||
private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server"
|
||||
|
@ -64,14 +55,14 @@ private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
|
|||
/**
|
||||
* Return true if the SDK supports this homeserver version
|
||||
*/
|
||||
fun Versions.isSupportedBySdk(): Boolean {
|
||||
internal fun Versions.isSupportedBySdk(): Boolean {
|
||||
return supportLazyLoadMembers()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the SDK supports this homeserver version for login and registration
|
||||
*/
|
||||
fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
|
||||
internal fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
|
||||
return !doesServerRequireIdentityServerParam()
|
||||
&& doesServerAcceptIdentityAccessToken()
|
||||
&& doesServerSeparatesAddAndBind()
|
||||
|
@ -83,7 +74,7 @@ fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
|
|||
* @return true if the server support the lazy loading of room members
|
||||
*/
|
||||
private fun Versions.supportLazyLoadMembers(): Boolean {
|
||||
return supportedVersions?.contains(r0_5_0) == true
|
||||
return getMaxVersion() >= HomeServerVersion.r0_5_0
|
||||
|| unstableFeatures?.get(FEATURE_LAZY_LOAD_MEMBERS) == true
|
||||
}
|
||||
|
||||
|
@ -92,7 +83,7 @@ private fun Versions.supportLazyLoadMembers(): Boolean {
|
|||
* adding a 3pid or resetting password.
|
||||
*/
|
||||
private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
|
||||
if (supportedVersions?.contains(r0_6_0) == true) return false
|
||||
if (getMaxVersion() >= HomeServerVersion.r0_6_0) return false
|
||||
return unstableFeatures?.get(FEATURE_REQUIRE_IDENTITY_SERVER) ?: true
|
||||
}
|
||||
|
||||
|
@ -101,11 +92,18 @@ private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
|
|||
* Some homeservers may trigger errors if they are not prepared for the new parameter.
|
||||
*/
|
||||
private fun Versions.doesServerAcceptIdentityAccessToken(): Boolean {
|
||||
return supportedVersions?.contains(r0_6_0) == true
|
||||
return getMaxVersion() >= HomeServerVersion.r0_6_0
|
||||
|| unstableFeatures?.get(FEATURE_ID_ACCESS_TOKEN) ?: false
|
||||
}
|
||||
|
||||
private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
|
||||
return supportedVersions?.contains(r0_6_0) == true
|
||||
return getMaxVersion() >= HomeServerVersion.r0_6_0
|
||||
|| unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false
|
||||
}
|
||||
|
||||
private fun Versions.getMaxVersion(): HomeServerVersion {
|
||||
return supportedVersions
|
||||
?.mapNotNull { HomeServerVersion.parse(it) }
|
||||
?.max()
|
||||
?: HomeServerVersion.r0_0_0
|
||||
}
|
|
@ -36,7 +36,7 @@ internal data class KeysQueryBody(
|
|||
* A map from user ID, to a list of device IDs, or to an empty list to indicate all devices for the corresponding user.
|
||||
*/
|
||||
@Json(name = "device_keys")
|
||||
val deviceKeys: Map<String, Any>,
|
||||
val deviceKeys: Map<String, List<String>>,
|
||||
|
||||
/**
|
||||
* If the client is fetching keys as a result of a device update received in a sync request, this should be the 'since' token
|
||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
|
||||
/**
|
||||
* This class provides the authentication data by using user and password
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.crypto.tasks
|
||||
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
|
|
|
@ -27,7 +27,7 @@ import javax.inject.Inject
|
|||
internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
|
||||
data class Params(
|
||||
// the list of users to get keys for.
|
||||
val userIds: List<String>?,
|
||||
val userIds: List<String>,
|
||||
// the up-to token
|
||||
val token: String?
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
|
|||
) : DownloadKeysForUsersTask {
|
||||
|
||||
override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
|
||||
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty()
|
||||
val downloadQuery = params.userIds.associateWith { emptyList<String>() }
|
||||
|
||||
val body = KeysQueryBody(
|
||||
deviceKeys = downloadQuery,
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.identity.todelete
|
||||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
|||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
// There will be a duplicated class when Integration manager will be merged, so delete this one
|
||||
internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
|
||||
|
||||
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
|
|
@ -36,8 +36,8 @@ internal object EventMapper {
|
|||
eventEntity.eventId = event.eventId ?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
|
||||
eventEntity.roomId = event.roomId ?: roomId
|
||||
eventEntity.content = ContentMapper.map(event.content)
|
||||
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
|
||||
eventEntity.prevContent = ContentMapper.map(resolvedPrevContent)
|
||||
eventEntity.prevContent = ContentMapper.map(event.resolvedPrevContent())
|
||||
eventEntity.isUseless = IsUselessResolver.isUseless(event)
|
||||
eventEntity.stateKey = event.stateKey
|
||||
eventEntity.type = event.type
|
||||
eventEntity.sender = event.senderId
|
||||
|
|
|
@ -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.database.mapper
|
||||
|
||||
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.toContent
|
||||
|
||||
internal object IsUselessResolver {
|
||||
|
||||
/**
|
||||
* @return true if the event is useless
|
||||
*/
|
||||
fun isUseless(event: Event): Boolean {
|
||||
return when (event.type) {
|
||||
EventType.STATE_ROOM_MEMBER -> {
|
||||
// Call toContent(), to filter out null value
|
||||
event.content != null
|
||||
&& event.content.toContent() == event.resolvedPrevContent()?.toContent()
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ internal open class EventEntity(@Index var eventId: String = "",
|
|||
@Index var type: String = "",
|
||||
var content: String? = null,
|
||||
var prevContent: String? = null,
|
||||
var isUseless: Boolean = false,
|
||||
@Index var stateKey: String? = null,
|
||||
var originServerTs: Long? = null,
|
||||
@Index var sender: String? = null,
|
||||
|
|
|
@ -14,11 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.extensions
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
/**
|
||||
* Ex: "abcdef".subStringBetween("a", "f") -> "bcde"
|
||||
* Ex: "abcdefff".subStringBetween("a", "f") -> "bcdeff"
|
||||
* Ex: "aaabcdef".subStringBetween("a", "f") -> "aabcde"
|
||||
*/
|
||||
internal fun String.subStringBetween(prefix: String, suffix: String) = substringAfter(prefix).substringBeforeLast(suffix)
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class ScalarTokenEntity(
|
||||
@PrimaryKey var serverUrl: String = "",
|
||||
var token: String = ""
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
}
|
|
@ -55,6 +55,8 @@ import io.realm.annotations.RealmModule
|
|||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberSummaryEntity::class,
|
||||
CurrentStateEventEntity::class,
|
||||
UserAccountDataEntity::class
|
||||
UserAccountDataEntity::class,
|
||||
ScalarTokenEntity::class,
|
||||
WellknownIntegrationManagerConfigEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class WellknownIntegrationManagerConfigEntity(
|
||||
@PrimaryKey var id: Long = 0,
|
||||
var apiUrl: String = "",
|
||||
var uiUrl: String = ""
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
}
|
|
@ -23,7 +23,7 @@ import io.realm.Realm
|
|||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.createObject
|
||||
|
||||
internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: String, type: String): RealmQuery<CurrentStateEventEntity> {
|
||||
internal fun CurrentStateEventEntity.Companion.whereType(realm: Realm, roomId: String, type: String): RealmQuery<CurrentStateEventEntity> {
|
||||
return realm.where(CurrentStateEventEntity::class.java)
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
||||
|
@ -31,7 +31,7 @@ internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: Strin
|
|||
|
||||
internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, roomId: String, type: String, stateKey: String)
|
||||
: RealmQuery<CurrentStateEventEntity> {
|
||||
return where(realm = realm, roomId = roomId, type = type)
|
||||
return whereType(realm = realm, roomId = roomId, type = type)
|
||||
.equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.ScalarTokenEntity
|
||||
import im.vector.matrix.android.internal.database.model.ScalarTokenEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun ScalarTokenEntity.Companion.where(realm: Realm, serverUrl: String): RealmQuery<ScalarTokenEntity> {
|
||||
return realm
|
||||
.where<ScalarTokenEntity>()
|
||||
.equalTo(ScalarTokenEntityFields.SERVER_URL, serverUrl)
|
||||
}
|
|
@ -14,13 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.identity.todelete
|
||||
package im.vector.matrix.android.internal.extensions
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
|
||||
// There will be a duplicated class when Integration manager will be merged, so delete this one
|
||||
inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
|
||||
this.observe(owner, Observer { observer(it) })
|
||||
}
|
|
@ -31,6 +31,5 @@ internal object NetworkConstants {
|
|||
const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
|
||||
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
|
||||
|
||||
// TODO Ganfra, use correct value
|
||||
const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
|
||||
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.network
|
|||
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import okhttp3.Call
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
@ -29,7 +30,7 @@ class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
|||
|
||||
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.baseUrl(baseUrl.ensureTrailingSlash())
|
||||
.callFactory(object : Call.Factory {
|
||||
override fun newCall(request: Request): Call {
|
||||
return okHttpClient.get().newCall(request)
|
||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
|||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
|
@ -45,6 +46,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
|
|||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
import im.vector.matrix.android.api.session.widgets.WidgetService
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||
|
@ -56,6 +58,7 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecr
|
|||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||
import im.vector.matrix.android.internal.session.widgets.WidgetDependenciesHolder
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -90,6 +93,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val fileService: Lazy<FileService>,
|
||||
private val secureStorageService: Lazy<SecureStorageService>,
|
||||
private val profileService: Lazy<ProfileService>,
|
||||
private val widgetService: Lazy<WidgetService>,
|
||||
private val syncThreadProvider: Provider<SyncThread>,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
private val syncTokenStore: SyncTokenStore,
|
||||
|
@ -101,11 +105,13 @@ internal class DefaultSession @Inject constructor(
|
|||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val accountService: Lazy<AccountService>,
|
||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val defaultIdentityService: DefaultIdentityService,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : Session,
|
||||
private val integrationManagerService: IntegrationManagerService,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val widgetDependenciesHolder: WidgetDependenciesHolder,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
GroupService by groupService.get(),
|
||||
|
@ -142,6 +148,7 @@ internal class DefaultSession @Inject constructor(
|
|||
timelineEventDecryptor.start()
|
||||
shieldTrustUpdater.start()
|
||||
defaultIdentityService.start()
|
||||
widgetDependenciesHolder.start()
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -187,6 +194,7 @@ internal class DefaultSession @Inject constructor(
|
|||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
// This has to be done on main thread
|
||||
defaultIdentityService.stop()
|
||||
widgetDependenciesHolder.stop()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,6 +241,10 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
override fun identityService() = defaultIdentityService
|
||||
|
||||
override fun widgetService(): WidgetService = widgetService.get()
|
||||
|
||||
override fun integrationManagerService() = integrationManagerService
|
||||
|
||||
override fun addListener(listener: Session.Listener) {
|
||||
sessionListeners.addListener(listener)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
|||
import im.vector.matrix.android.internal.session.group.GroupModule
|
||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
||||
import im.vector.matrix.android.internal.session.identity.IdentityModule
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManagerModule
|
||||
import im.vector.matrix.android.internal.session.openid.OpenIdModule
|
||||
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
||||
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
||||
|
@ -55,6 +56,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
|||
import im.vector.matrix.android.internal.session.terms.TermsModule
|
||||
import im.vector.matrix.android.internal.session.user.UserModule
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
||||
import im.vector.matrix.android.internal.session.widgets.WidgetModule
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
|
||||
|
@ -74,6 +76,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||
CryptoModule::class,
|
||||
PushersModule::class,
|
||||
OpenIdModule::class,
|
||||
WidgetModule::class,
|
||||
IntegrationManagerModule::class,
|
||||
IdentityModule::class,
|
||||
TermsModule::class,
|
||||
AccountDataModule::class,
|
||||
|
|
|
@ -19,16 +19,16 @@ package im.vector.matrix.android.internal.session.content
|
|||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.util.ensureTrailingSlash
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||
|
||||
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
||||
|
||||
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString()
|
||||
private val sep = if (baseUrl.endsWith("/")) "" else "/"
|
||||
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash()
|
||||
|
||||
override val uploadUrl = baseUrl + sep + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
||||
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
||||
|
||||
override fun resolveFullSize(contentUrl: String?): String? {
|
||||
return contentUrl
|
||||
|
@ -66,7 +66,7 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
|||
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
||||
}
|
||||
|
||||
return baseUrl + sep + prefix + serverAndMediaId + params + fragment
|
||||
return baseUrl + prefix + serverAndMediaId + params + fragment
|
||||
}
|
||||
|
||||
private fun String.isValidMatrixContentUrl(): Boolean {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.homeserver
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Versions
|
||||
import im.vector.matrix.android.internal.auth.version.Versions
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
|
|
|
@ -17,18 +17,20 @@
|
|||
package im.vector.matrix.android.internal.session.homeserver
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.auth.data.Versions
|
||||
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||
import im.vector.matrix.android.internal.auth.version.Versions
|
||||
import im.vector.matrix.android.internal.auth.version.isLoginAndRegistrationSupportedBySdk
|
||||
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManagerConfigExtractor
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -39,6 +41,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
private val monarchy: Monarchy,
|
||||
private val eventBus: EventBus,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val configExtractor: IntegrationManagerConfigExtractor,
|
||||
@UserId
|
||||
private val userId: String
|
||||
) : GetHomeServerCapabilitiesTask {
|
||||
|
@ -102,8 +105,14 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
|
||||
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
||||
}
|
||||
|
||||
// We are also checking for integration manager configurations
|
||||
val config = configExtractor.extract(getWellknownResult.wellKnown)
|
||||
if (config != null) {
|
||||
Timber.v("Extracted integration config : $config")
|
||||
realm.insertOrUpdate(config)
|
||||
}
|
||||
}
|
||||
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,16 +37,16 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.extensions.observeNotNull
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
|
||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||
import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
|
||||
import im.vector.matrix.android.internal.session.profile.UnbindThreePidsTask
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AllowedWidgetsContent(
|
||||
/**
|
||||
* Map of stateEventId to Allowed
|
||||
*/
|
||||
@Json(name = "widgets") val widgets: Map<String, Boolean> = emptyMap(),
|
||||
|
||||
/**
|
||||
* Map of native widgetType to a map of domain to Allowed
|
||||
* {
|
||||
* "jitsi" : {
|
||||
* "jitsi.domain.org" : true,
|
||||
* "jitsi.other.org" : false
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@Json(name = "native_widgets") val native: Map<String, Map<String, Boolean>> = emptyMap()
|
||||
)
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService {
|
||||
|
||||
override fun addListener(listener: IntegrationManagerService.Listener) {
|
||||
integrationManager.addListener(listener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: IntegrationManagerService.Listener) {
|
||||
integrationManager.removeListener(listener)
|
||||
}
|
||||
|
||||
override fun getOrderedConfigs(): List<IntegrationManagerConfig> {
|
||||
return integrationManager.getOrderedConfigs()
|
||||
}
|
||||
|
||||
override fun getPreferredConfig(): IntegrationManagerConfig {
|
||||
return integrationManager.getPreferredConfig()
|
||||
}
|
||||
|
||||
override fun isIntegrationEnabled(): Boolean {
|
||||
return integrationManager.isIntegrationEnabled()
|
||||
}
|
||||
|
||||
override fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return integrationManager.setIntegrationEnabled(enable, callback)
|
||||
}
|
||||
|
||||
override fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return integrationManager.setWidgetAllowed(stateEventId, allowed, callback)
|
||||
}
|
||||
|
||||
override fun isWidgetAllowed(stateEventId: String): Boolean {
|
||||
return integrationManager.isWidgetAllowed(stateEventId)
|
||||
}
|
||||
|
||||
override fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback)
|
||||
}
|
||||
|
||||
override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean {
|
||||
return integrationManager.isNativeWidgetDomainAllowed(widgetType, domain)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerConfig
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetType
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
import im.vector.matrix.android.internal.database.model.WellknownIntegrationManagerConfigEntity
|
||||
import im.vector.matrix.android.internal.extensions.observeNotNull
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.WidgetFactory
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.extractWidgetSequence
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* The integration manager allows to
|
||||
* - Get the Integration Manager that a user has explicitly set for its account (via account data)
|
||||
* - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown)
|
||||
* - Check if the user has disabled the integration manager feature
|
||||
* - Allow / Disallow Integration manager (propagated to other riot clients)
|
||||
*
|
||||
* The integration manager listen to account data, and can notify observer for changes.
|
||||
*
|
||||
* The wellknown is refreshed at each application fresh start
|
||||
*
|
||||
*/
|
||||
@SessionScope
|
||||
internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val accountDataDataSource: AccountDataDataSource,
|
||||
private val widgetFactory: WidgetFactory) {
|
||||
|
||||
private val currentConfigs = ArrayList<IntegrationManagerConfig>()
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||
|
||||
private val listeners = HashSet<IntegrationManagerService.Listener>()
|
||||
fun addListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.add(listener) }
|
||||
fun removeListener(listener: IntegrationManagerService.Listener) = synchronized(listeners) { listeners.remove(listener) }
|
||||
|
||||
init {
|
||||
val defaultConfig = IntegrationManagerConfig(
|
||||
uiUrl = matrixConfiguration.integrationUIUrl,
|
||||
restUrl = matrixConfiguration.integrationRestUrl,
|
||||
kind = IntegrationManagerConfig.Kind.DEFAULT
|
||||
)
|
||||
currentConfigs.add(defaultConfig)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
observeWellknownConfig()
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val allowedWidgetsContent = it.getOrNull()?.content?.toModel<AllowedWidgetsContent>()
|
||||
if (allowedWidgetsContent != null) {
|
||||
notifyWidgetPermissionsChanged(allowedWidgetsContent)
|
||||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationProvisioningContent = it.getOrNull()?.content?.toModel<IntegrationProvisioningContent>()
|
||||
if (integrationProvisioningContent != null) {
|
||||
notifyIsEnabledChanged(integrationProvisioningContent)
|
||||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationManagerContent = it.getOrNull()?.asIntegrationManagerWidgetContent()
|
||||
val config = integrationManagerContent?.extractIntegrationManagerConfig()
|
||||
updateCurrentConfigs(IntegrationManagerConfig.Kind.ACCOUNT, config)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
fun hasConfig() = currentConfigs.isNotEmpty()
|
||||
|
||||
fun getOrderedConfigs(): List<IntegrationManagerConfig> {
|
||||
return currentConfigs.sortedBy {
|
||||
it.kind
|
||||
}
|
||||
}
|
||||
|
||||
fun getPreferredConfig(): IntegrationManagerConfig {
|
||||
// This can't be null as we should have at least the default one registered
|
||||
return getOrderedConfigs().first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the user as disabled integration manager feature
|
||||
*/
|
||||
fun isIntegrationEnabled(): Boolean {
|
||||
val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
|
||||
val integrationProvisioningContent = integrationProvisioningData?.content?.toModel<IntegrationProvisioningContent>()
|
||||
return integrationProvisioningContent?.enabled ?: false
|
||||
}
|
||||
|
||||
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val isIntegrationEnabled = isIntegrationEnabled()
|
||||
if (enable == isIntegrationEnabled) {
|
||||
callback.onSuccess(Unit)
|
||||
return NoOpCancellable
|
||||
}
|
||||
val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable)
|
||||
val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val allowedWidget = mapOf(stateEventId to allowed)
|
||||
AllowedWidgetsContent(widgets = allowedWidget, native = emptyMap())
|
||||
} else {
|
||||
val allowedWidgets = currentContent.widgets.toMutableMap().apply {
|
||||
put(stateEventId, allowed)
|
||||
}
|
||||
currentContent.copy(widgets = allowedWidgets)
|
||||
}
|
||||
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun isWidgetAllowed(stateEventId: String): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.widgets?.get(stateEventId) ?: false
|
||||
}
|
||||
|
||||
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed))
|
||||
AllowedWidgetsContent(widgets = emptyMap(), native = nativeAllowedWidgets)
|
||||
} else {
|
||||
val nativeAllowedWidgets = currentContent.native.toMutableMap().apply {
|
||||
(get(widgetType))?.let {
|
||||
set(widgetType, it.toMutableMap().apply { set(domain, allowed) })
|
||||
} ?: run {
|
||||
set(widgetType, mapOf(domain to allowed))
|
||||
}
|
||||
}
|
||||
currentContent.copy(native = nativeAllowedWidgets)
|
||||
}
|
||||
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.native?.get(widgetType)?.get(domain) ?: false
|
||||
}
|
||||
|
||||
private fun notifyConfigurationChanged() {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onConfigurationChanged(currentConfigs)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyWidgetPermissionsChanged(allowedWidgets: AllowedWidgetsContent) {
|
||||
Timber.v("On widget permissions changed: $allowedWidgets")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onWidgetPermissionsChanged(allowedWidgets.widgets)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyIsEnabledChanged(provisioningContent: IntegrationProvisioningContent) {
|
||||
Timber.v("On provisioningContent changed : $provisioningContent")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onIsEnabledChanged(provisioningContent.enabled)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WidgetContent.extractIntegrationManagerConfig(): IntegrationManagerConfig? {
|
||||
if (url.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
val integrationManagerData = data.toModel<IntegrationManagerWidgetData>()
|
||||
return IntegrationManagerConfig(
|
||||
uiUrl = url,
|
||||
restUrl = integrationManagerData?.apiUrl ?: url,
|
||||
kind = IntegrationManagerConfig.Kind.ACCOUNT
|
||||
)
|
||||
}
|
||||
|
||||
private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
|
||||
return extractWidgetSequence(widgetFactory)
|
||||
.filter {
|
||||
WidgetType.IntegrationManager == it.type
|
||||
}
|
||||
.firstOrNull()?.widgetContent
|
||||
}
|
||||
|
||||
private fun observeWellknownConfig() {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ it.where(WellknownIntegrationManagerConfigEntity::class.java) },
|
||||
{ IntegrationManagerConfig(it.uiUrl, it.apiUrl, IntegrationManagerConfig.Kind.HOMESERVER) }
|
||||
)
|
||||
liveData.observeNotNull(lifecycleOwner) {
|
||||
val config = it.firstOrNull()
|
||||
updateCurrentConfigs(IntegrationManagerConfig.Kind.HOMESERVER, config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCurrentConfigs(kind: IntegrationManagerConfig.Kind, config: IntegrationManagerConfig?) {
|
||||
val hasBeenRemoved = currentConfigs.removeAll { currentConfig ->
|
||||
currentConfig.kind == kind
|
||||
}
|
||||
if (config != null) {
|
||||
currentConfigs.add(config)
|
||||
}
|
||||
if (hasBeenRemoved || config != null) {
|
||||
notifyConfigurationChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||
import im.vector.matrix.android.internal.database.model.WellknownIntegrationManagerConfigEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class IntegrationManagerConfigExtractor @Inject constructor() {
|
||||
|
||||
fun extract(wellKnown: WellKnown): WellknownIntegrationManagerConfigEntity? {
|
||||
wellKnown.integrations?.get("managers")?.let {
|
||||
(it as? List<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
return WellknownIntegrationManagerConfigEntity(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import im.vector.matrix.android.api.session.integrationmanager.IntegrationManagerService
|
||||
|
||||
@Module
|
||||
internal abstract class IntegrationManagerModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindIntegrationManagerService(service: DefaultIntegrationManagerService): IntegrationManagerService
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IntegrationManagerWidgetData(
|
||||
@Json(name = "api_url") val apiUrl: String? = null
|
||||
)
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IntegrationProvisioningContent(
|
||||
@Json(name = "enabled") val enabled: Boolean
|
||||
)
|
|
@ -48,7 +48,7 @@ internal class DefaultConditionResolver @Inject constructor(
|
|||
val roomId = event.roomId ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
|
||||
val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
?: PowerLevelsContent()
|
||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.reporting.ReportingService
|
|||
import im.vector.matrix.android.api.session.room.send.DraftService
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.session.room.tags.TagsService
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.session.room.uploads.UploadsService
|
||||
|
@ -59,6 +60,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
private val reportingService: ReportingService,
|
||||
private val readService: ReadService,
|
||||
private val typingService: TypingService,
|
||||
private val tagsService: TagsService,
|
||||
private val cryptoService: CryptoService,
|
||||
private val relationService: RelationService,
|
||||
private val roomMembersService: MembershipService,
|
||||
|
@ -74,6 +76,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
ReportingService by reportingService,
|
||||
ReadService by readService,
|
||||
TypingService by typingService,
|
||||
TagsService by tagsService,
|
||||
RelationService by relationService,
|
||||
MembershipService by roomMembersService,
|
||||
RoomPushRuleService by roomPushRuleService {
|
||||
|
@ -116,9 +119,11 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported"))
|
||||
}
|
||||
else -> {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_ENCRYPTION,
|
||||
mapOf(
|
||||
val params = SendStateTask.Params(
|
||||
roomId = roomId,
|
||||
stateKey = null,
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
body = mapOf(
|
||||
"algorithm" to algorithm
|
||||
))
|
||||
|
||||
|
|
|
@ -111,24 +111,22 @@ internal class DefaultRoomService @Inject constructor(
|
|||
return query
|
||||
}
|
||||
|
||||
override fun getBreadcrumbs(): List<RoomSummary> {
|
||||
override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ breadcrumbsQuery(it) },
|
||||
{ breadcrumbsQuery(it, queryParams) },
|
||||
{ roomSummaryMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getBreadcrumbsLive(): LiveData<List<RoomSummary>> {
|
||||
override fun getBreadcrumbsLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ breadcrumbsQuery(it) },
|
||||
{ breadcrumbsQuery(it, queryParams) },
|
||||
{ roomSummaryMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun breadcrumbsQuery(realm: Realm): RealmQuery<RoomSummaryEntity> {
|
||||
return RoomSummaryEntity.where(realm)
|
||||
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||
private fun breadcrumbsQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
|
||||
return roomSummariesQuery(realm, queryParams)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
|
||||
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||
}
|
||||
|
|
|
@ -24,18 +24,22 @@ import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse
|
|||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
|
||||
import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
|
||||
import im.vector.matrix.android.internal.session.room.relation.RelationsResponse
|
||||
import im.vector.matrix.android.internal.session.room.reporting.ReportContentBody
|
||||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||
import im.vector.matrix.android.internal.session.room.tags.TagBody
|
||||
import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
|
||||
import im.vector.matrix.android.internal.session.room.typing.TypingBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
|
@ -175,7 +179,7 @@ internal interface RoomAPI {
|
|||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
|
||||
fun sendStateEvent(@Path("roomId") roomId: String,
|
||||
@Path("state_event_type") stateEventType: String,
|
||||
@Body params: Map<String, String>): Call<Unit>
|
||||
@Body params: JsonDict): Call<Unit>
|
||||
|
||||
/**
|
||||
* Send a generic state events
|
||||
|
@ -189,7 +193,7 @@ internal interface RoomAPI {
|
|||
fun sendStateEvent(@Path("roomId") roomId: String,
|
||||
@Path("state_event_type") stateEventType: String,
|
||||
@Path("state_key") stateKey: String,
|
||||
@Body params: Map<String, String>): Call<Unit>
|
||||
@Body params: JsonDict): Call<Unit>
|
||||
|
||||
/**
|
||||
* Send a relation event to a room.
|
||||
|
@ -242,6 +246,33 @@ internal interface RoomAPI {
|
|||
fun leave(@Path("roomId") roomId: String,
|
||||
@Body params: Map<String, String?>): Call<Unit>
|
||||
|
||||
/**
|
||||
* Ban a user from the given room.
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @param userIdAndReason the banned user object (userId and reason for ban)
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban")
|
||||
fun ban(@Path("roomId") roomId: String, @Body userIdAndReason: UserIdAndReason): Call<Unit>
|
||||
|
||||
/**
|
||||
* unban a user from the given room.
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @param userIdAndReason the unbanned user object (userId and reason for unban)
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban")
|
||||
fun unban(@Path("roomId") roomId: String, @Body userIdAndReason: UserIdAndReason): Call<Unit>
|
||||
|
||||
/**
|
||||
* Kick a user from the given room.
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @param userIdAndReason the kicked user object (userId and reason for kicking)
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick")
|
||||
fun kick(@Path("roomId") roomId: String, @Body userIdAndReason: UserIdAndReason): Call<Unit>
|
||||
|
||||
/**
|
||||
* Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room.
|
||||
* This cannot be undone.
|
||||
|
@ -287,4 +318,25 @@ internal interface RoomAPI {
|
|||
fun sendTypingState(@Path("roomId") roomId: String,
|
||||
@Path("userId") userId: String,
|
||||
@Body body: TypingBody): Call<Unit>
|
||||
|
||||
/**
|
||||
* Room tagging
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add a tag to a room.
|
||||
*/
|
||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}")
|
||||
fun putTag(@Path("userId") userId: String,
|
||||
@Path("roomId") roomId: String,
|
||||
@Path("tag") tag: String,
|
||||
@Body body: TagBody): Call<Unit>
|
||||
|
||||
/**
|
||||
* Delete a tag from a room.
|
||||
*/
|
||||
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}")
|
||||
fun deleteTag(@Path("userId") userId: String,
|
||||
@Path("roomId") roomId: String,
|
||||
@Path("tag") tag: String): Call<Unit>
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReporting
|
|||
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
||||
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.tags.DefaultTagsService
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
||||
import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService
|
||||
import im.vector.matrix.android.internal.session.room.uploads.DefaultUploadsService
|
||||
|
@ -52,6 +53,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
|
|||
private val reportingServiceFactory: DefaultReportingService.Factory,
|
||||
private val readServiceFactory: DefaultReadService.Factory,
|
||||
private val typingServiceFactory: DefaultTypingService.Factory,
|
||||
private val tagsServiceFactory: DefaultTagsService.Factory,
|
||||
private val relationServiceFactory: DefaultRelationService.Factory,
|
||||
private val membershipServiceFactory: DefaultMembershipService.Factory,
|
||||
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
|
||||
|
@ -72,6 +74,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
|
|||
reportingService = reportingServiceFactory.create(roomId),
|
||||
readService = readServiceFactory.create(roomId),
|
||||
typingService = typingServiceFactory.create(roomId),
|
||||
tagsService = tagsServiceFactory.create(roomId),
|
||||
cryptoService = cryptoService,
|
||||
relationService = relationServiceFactory.create(roomId),
|
||||
roomMembersService = membershipServiceFactory.create(roomId),
|
||||
|
|
|
@ -34,6 +34,8 @@ import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTas
|
|||
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.admin.DefaultMembershipAdminTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.admin.MembershipAdminTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
||||
|
@ -56,6 +58,10 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReportCon
|
|||
import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask
|
||||
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.tags.AddTagToRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.tags.DefaultAddTagToRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.tags.DefaultDeleteTagFromRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.tags.DeleteTagFromRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchNextTokenAndPaginateTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
|
||||
|
@ -66,6 +72,9 @@ import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTa
|
|||
import im.vector.matrix.android.internal.session.room.typing.SendTypingTask
|
||||
import im.vector.matrix.android.internal.session.room.uploads.DefaultGetUploadsTask
|
||||
import im.vector.matrix.android.internal.session.room.uploads.GetUploadsTask
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
@ -79,6 +88,28 @@ internal abstract class RoomModule {
|
|||
fun providesRoomAPI(retrofit: Retrofit): RoomAPI {
|
||||
return retrofit.create(RoomAPI::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesParser(): Parser {
|
||||
return Parser.builder().build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesHtmlRenderer(): HtmlRenderer {
|
||||
return HtmlRenderer
|
||||
.builder()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesTextContentRenderer(): TextContentRenderer {
|
||||
return TextContentRenderer
|
||||
.builder()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
@ -117,6 +148,9 @@ internal abstract class RoomModule {
|
|||
@Binds
|
||||
abstract fun bindLeaveRoomTask(task: DefaultLeaveRoomTask): LeaveRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindMembershipAdminTask(task: DefaultMembershipAdminTask): MembershipAdminTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindLoadRoomMembersTask(task: DefaultLoadRoomMembersTask): LoadRoomMembersTask
|
||||
|
||||
|
@ -161,4 +195,10 @@ internal abstract class RoomModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindGetUploadsTask(task: DefaultGetUploadsTask): GetUploadsTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindAddTagToRoomTask(task: DefaultAddTagToRoomTask): AddTagToRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindDeleteTagFromRoomTask(task: DefaultDeleteTagFromRoomTask): DeleteTagFromRoomTask
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
|||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.query.process
|
||||
import im.vector.matrix.android.internal.session.room.membership.admin.MembershipAdminTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
||||
|
@ -48,6 +49,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
private val inviteTask: InviteTask,
|
||||
private val joinTask: JoinRoomTask,
|
||||
private val leaveRoomTask: LeaveRoomTask,
|
||||
private val membershipAdminTask: MembershipAdminTask,
|
||||
@UserId
|
||||
private val userId: String
|
||||
) : MembershipService {
|
||||
|
@ -113,6 +115,33 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun ban(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val params = MembershipAdminTask.Params(MembershipAdminTask.Type.BAN, roomId, userId, reason)
|
||||
return membershipAdminTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun unban(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val params = MembershipAdminTask.Params(MembershipAdminTask.Type.UNBAN, roomId, userId, reason)
|
||||
return membershipAdminTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun kick(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason)
|
||||
return membershipAdminTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun invite(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val params = InviteTask.Params(roomId, userId, reason)
|
||||
return inviteTask
|
||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.room.membership
|
|||
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.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
||||
import io.realm.Realm
|
||||
|
@ -35,7 +34,7 @@ internal class RoomMemberEventHandler @Inject constructor() {
|
|||
val userId = event.stateKey ?: return false
|
||||
val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember)
|
||||
realm.insertOrUpdate(roomMemberEntity)
|
||||
if (roomMember.membership in Membership.activeMemberships()) {
|
||||
if (roomMember.membership.isActive()) {
|
||||
val userEntity = UserEntityFactory.create(userId, roomMember)
|
||||
realm.insertOrUpdate(userEntity)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.membership.admin
|
||||
|
||||
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 javax.inject.Inject
|
||||
|
||||
internal interface MembershipAdminTask : Task<MembershipAdminTask.Params, Unit> {
|
||||
|
||||
enum class Type {
|
||||
BAN,
|
||||
UNBAN,
|
||||
KICK
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val type: Type,
|
||||
val roomId: String,
|
||||
val userId: String,
|
||||
val reason: String?
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultMembershipAdminTask @Inject constructor(private val roomAPI: RoomAPI) : MembershipAdminTask {
|
||||
|
||||
override suspend fun execute(params: MembershipAdminTask.Params) {
|
||||
val userIdAndReason = UserIdAndReason(params.userId, params.reason)
|
||||
executeRequest<Unit>(null) {
|
||||
apiCall = when (params.type) {
|
||||
MembershipAdminTask.Type.BAN -> roomAPI.ban(params.roomId, userIdAndReason)
|
||||
MembershipAdminTask.Type.UNBAN -> roomAPI.unban(params.roomId, userIdAndReason)
|
||||
MembershipAdminTask.Type.KICK -> roomAPI.kick(params.roomId, userIdAndReason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.room.membership.admin
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserIdAndReason(
|
||||
@Json(name = "user_id") val userId: String,
|
||||
@Json(name = "reason") val reason: String? = null
|
||||
)
|
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||
|
@ -67,6 +68,12 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
|
||||
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
override fun sendEvent(eventType: String, content: JsonDict?): Cancelable {
|
||||
return localEchoEventFactory.createEvent(roomId, eventType, content)
|
||||
.also { createLocalEcho(it) }
|
||||
.let { sendEvent(it) }
|
||||
}
|
||||
|
||||
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
|
||||
.also { createLocalEcho(it) }
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.exifinterface.media.ExifInterface
|
|||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
|
@ -57,14 +58,11 @@ import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
|||
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.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.extensions.subStringBetween
|
||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||
import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.StringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -80,41 +78,23 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
private val context: Context,
|
||||
@UserId private val userId: String,
|
||||
private val stringProvider: StringProvider,
|
||||
private val markdownParser: MarkdownParser,
|
||||
private val textPillsUtils: TextPillsUtils,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val localEchoRepository: LocalEchoRepository
|
||||
) {
|
||||
// TODO Inject
|
||||
private val parser = Parser.builder().build()
|
||||
|
||||
// TODO Inject
|
||||
private val renderer = HtmlRenderer.builder().build()
|
||||
|
||||
fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
|
||||
if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
|
||||
return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
|
||||
}
|
||||
val content = MessageTextContent(msgType = msgType, body = text.toString())
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
|
||||
if (autoMarkdown) {
|
||||
val source = textPillsUtils.processSpecialSpansToMarkdown(text)
|
||||
?: text.toString()
|
||||
val document = parser.parse(source)
|
||||
val htmlText = renderer.render(document)
|
||||
|
||||
// Cleanup extra paragraph
|
||||
val cleanHtmlText = if (htmlText.startsWith("<p>") && htmlText.endsWith("</p>\n")) {
|
||||
htmlText.subStringBetween("<p>", "</p>\n")
|
||||
} else {
|
||||
htmlText
|
||||
}
|
||||
|
||||
if (isFormattedTextPertinent(source, cleanHtmlText)) {
|
||||
return TextContent(text.toString(), cleanHtmlText)
|
||||
}
|
||||
val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString()
|
||||
return markdownParser.parse(source)
|
||||
} else {
|
||||
// Try to detect pills
|
||||
textPillsUtils.processSpecialSpansToHtml(text)?.let {
|
||||
|
@ -125,11 +105,8 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
return TextContent(text.toString())
|
||||
}
|
||||
|
||||
private fun isFormattedTextPertinent(text: String, htmlText: String?) =
|
||||
text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
|
||||
|
||||
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event {
|
||||
return createEvent(roomId, textContent.toMessageTextContent(msgType))
|
||||
return createMessageEvent(roomId, textContent.toMessageTextContent(msgType))
|
||||
}
|
||||
|
||||
fun createReplaceTextEvent(roomId: String,
|
||||
|
@ -138,7 +115,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
newBodyAutoMarkdown: Boolean,
|
||||
msgType: String,
|
||||
compatibilityText: String): Event {
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessageTextContent(
|
||||
msgType = msgType,
|
||||
body = compatibilityText,
|
||||
|
@ -153,7 +130,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
pollEventId: String,
|
||||
optionIndex: Int,
|
||||
optionLabel: String): Event {
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessagePollResponseContent(
|
||||
body = optionLabel,
|
||||
relatesTo = RelationDefaultContent(
|
||||
|
@ -175,7 +152,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
append(it.value)
|
||||
}
|
||||
}
|
||||
return createEvent(
|
||||
return createMessageEvent(
|
||||
roomId,
|
||||
MessageOptionsContent(
|
||||
body = compatLabel,
|
||||
|
@ -211,7 +188,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
//
|
||||
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
|
||||
|
||||
return createEvent(roomId,
|
||||
return createMessageEvent(roomId,
|
||||
MessageTextContent(
|
||||
msgType = msgType,
|
||||
body = compatibilityText,
|
||||
|
@ -280,7 +257,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -316,7 +293,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -329,7 +306,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||
|
@ -342,18 +319,22 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
),
|
||||
url = attachment.queryUri.toString()
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun createEvent(roomId: String, content: Any? = null): Event {
|
||||
private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event {
|
||||
return createEvent(roomId, EventType.MESSAGE, content.toContent())
|
||||
}
|
||||
|
||||
fun createEvent(roomId: String, type: String, content: Content?): Event {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
return Event(
|
||||
roomId = roomId,
|
||||
originServerTs = dummyOriginServerTs(),
|
||||
senderId = userId,
|
||||
eventId = localId,
|
||||
type = EventType.MESSAGE,
|
||||
content = content.toContent(),
|
||||
type = type,
|
||||
content = content,
|
||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||
)
|
||||
}
|
||||
|
@ -410,7 +391,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
formattedBody = replyFormatted,
|
||||
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||
)
|
||||
return createEvent(roomId, content)
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.send
|
||||
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This class convert a text to an html text
|
||||
* This class is tested by [MarkdownParserTest].
|
||||
* If any change is required, please add a test covering the problem and make sure all the tests are still passing.
|
||||
*/
|
||||
internal class MarkdownParser @Inject constructor(
|
||||
private val parser: Parser,
|
||||
private val htmlRenderer: HtmlRenderer,
|
||||
private val textContentRenderer: TextContentRenderer
|
||||
) {
|
||||
|
||||
private val mdSpecialChars = "[`_\\-\\*>\\.\\[\\]#~]".toRegex()
|
||||
|
||||
fun parse(text: String): TextContent {
|
||||
// If no special char are detected, just return plain text
|
||||
if (text.contains(mdSpecialChars).not()) {
|
||||
return TextContent(text.toString())
|
||||
}
|
||||
|
||||
val document = parser.parse(text)
|
||||
val htmlText = htmlRenderer.render(document)
|
||||
|
||||
// Cleanup extra paragraph
|
||||
val cleanHtmlText = if (htmlText.lastIndexOf("<p>") == 0) {
|
||||
htmlText.removeSurrounding("<p>", "</p>\n")
|
||||
} else {
|
||||
htmlText
|
||||
}
|
||||
|
||||
return if (isFormattedTextPertinent(text, cleanHtmlText)) {
|
||||
// According to https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes:
|
||||
// The plain text version of the HTML should be provided in the body.
|
||||
val plainText = textContentRenderer.render(document)
|
||||
TextContent(plainText, cleanHtmlText.postTreatment())
|
||||
} else {
|
||||
TextContent(text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFormattedTextPertinent(text: String, htmlText: String?) =
|
||||
text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
|
||||
|
||||
/**
|
||||
* The parser makes some mistakes, so deal with it here
|
||||
*/
|
||||
private fun String.postTreatment(): String {
|
||||
return this
|
||||
// Remove extra space before and after the content
|
||||
.trim()
|
||||
// There is no need to include new line in an html-like source
|
||||
.replace("\n", "")
|
||||
}
|
||||
}
|
|
@ -17,26 +17,21 @@
|
|||
package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.getOrNull
|
||||
import im.vector.matrix.android.internal.database.query.whereStateKey
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import io.realm.Realm
|
||||
|
||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendStateTask: SendStateTask
|
||||
) : StateService {
|
||||
|
@ -46,33 +41,47 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
|||
fun create(roomId: String): StateService
|
||||
}
|
||||
|
||||
override fun getStateEvent(eventType: String, stateKey: String): Event? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
CurrentStateEventEntity.getOrNull(realm, roomId, type = eventType, stateKey = stateKey)?.root?.asDomain()
|
||||
}
|
||||
override fun getStateEvent(eventType: String, stateKey: QueryStringValue): Event? {
|
||||
return stateEventDataSource.getStateEvent(roomId, eventType, stateKey)
|
||||
}
|
||||
|
||||
override fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm -> CurrentStateEventEntity.whereStateKey(realm, roomId, type = eventType, stateKey = "") },
|
||||
{ it.root?.asDomain() }
|
||||
override fun getStateEventLive(eventType: String, stateKey: QueryStringValue): LiveData<Optional<Event>> {
|
||||
return stateEventDataSource.getStateEventLive(roomId, eventType, stateKey)
|
||||
}
|
||||
|
||||
override fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> {
|
||||
return stateEventDataSource.getStateEvents(roomId, eventTypes, stateKey)
|
||||
}
|
||||
|
||||
override fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue): LiveData<List<Event>> {
|
||||
return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey)
|
||||
}
|
||||
|
||||
override fun sendStateEvent(
|
||||
eventType: String,
|
||||
stateKey: String?,
|
||||
body: JsonDict,
|
||||
callback: MatrixCallback<Unit>
|
||||
): Cancelable {
|
||||
val params = SendStateTask.Params(
|
||||
roomId = roomId,
|
||||
stateKey = stateKey,
|
||||
eventType = eventType,
|
||||
body = body
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
mapOf(
|
||||
"topic" to topic
|
||||
))
|
||||
|
||||
sendStateTask
|
||||
return sendStateTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_TOPIC,
|
||||
body = mapOf("topic" to topic),
|
||||
callback = callback,
|
||||
stateKey = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
|
@ -25,8 +26,9 @@ import javax.inject.Inject
|
|||
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val stateKey: String?,
|
||||
val eventType: String,
|
||||
val body: Map<String, String>
|
||||
val body: JsonDict
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -37,7 +39,20 @@ internal class DefaultSendStateTask @Inject constructor(
|
|||
|
||||
override suspend fun execute(params: SendStateTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body)
|
||||
apiCall = if (params.stateKey == null) {
|
||||
roomAPI.sendStateEvent(
|
||||
roomId = params.roomId,
|
||||
stateEventType = params.eventType,
|
||||
params = params.body
|
||||
)
|
||||
} else {
|
||||
roomAPI.sendStateEvent(
|
||||
roomId = params.roomId,
|
||||
stateEventType = params.eventType,
|
||||
stateKey = params.stateKey,
|
||||
params = params.body
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue