mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-23 17:40:37 +03:00
Merge branch 'release/0.16.0'
This commit is contained in:
commit
56677f0908
98 changed files with 2000 additions and 372 deletions
29
CHANGES.md
29
CHANGES.md
|
@ -1,9 +1,28 @@
|
||||||
|
Changes in RiotX 0.16.0 (2020-02-14)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Polls and Bot Buttons (MSC 2192 matrix-org/matrix-doc#2192)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Show confirmation dialog before deleting a message (#967, #1003)
|
||||||
|
- Open room member profile from reactions list and read receipts list (#875)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix crash by removing all notifications after clearing cache (#878)
|
||||||
|
- Fix issue with verification when other client declares it can only show QR code (#988)
|
||||||
|
- Fix too errors in the code (1941862499c9ec5268cc80882512ced379cafcfd, a250a895fe0a4acf08c671e03434edcd29ccd84f)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- Javadoc improved for PushersService
|
||||||
|
- PushersService.pushers() has been renamed to PushersService.getPushers()
|
||||||
|
|
||||||
Changes in RiotX 0.15.0 (2020-02-10)
|
Changes in RiotX 0.15.0 (2020-02-10)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Improve navigation to the timeline (#789, #862)
|
- Improve navigation to the timeline (#789, #862)
|
||||||
- Improve network detection. It is now based on the sync request status (#873, #882)
|
- Improve network detection. It is now based on the sync request status (#873, #882)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Support SSO login with Firefox account (#606)
|
- Support SSO login with Firefox account (#606)
|
||||||
|
@ -363,15 +382,17 @@ Features ✨:
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
-
|
-
|
||||||
|
|
||||||
Other changes:
|
|
||||||
-
|
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
-
|
-
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
-
|
||||||
|
|
||||||
Build 🧱:
|
Build 🧱:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
|
|
@ -66,14 +66,10 @@ class SASTest : InstrumentedTest {
|
||||||
val bobVerificationService = bobSession!!.getVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val bobTxCreatedLatch = CountDownLatch(1)
|
val bobTxCreatedLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
bobTxCreatedLatch.countDown()
|
bobTxCreatedLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
@ -106,9 +102,7 @@ class SASTest : InstrumentedTest {
|
||||||
// Let's cancel from alice side
|
// Let's cancel from alice side
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
val bobListener2 = object : VerificationService.VerificationListener {
|
val bobListener2 = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.transactionId == txID) {
|
if (tx.transactionId == txID) {
|
||||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
||||||
|
@ -117,8 +111,6 @@ class SASTest : InstrumentedTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener2)
|
bobVerificationService.addListener(bobListener2)
|
||||||
|
|
||||||
|
@ -157,17 +149,13 @@ class SASTest : InstrumentedTest {
|
||||||
var cancelReason: CancelCode? = null
|
var cancelReason: CancelCode? = null
|
||||||
val cancelLatch = CountDownLatch(1)
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
||||||
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
||||||
cancelLatch.countDown()
|
cancelLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobSession.getVerificationService().addListener(bobListener)
|
bobSession.getVerificationService().addListener(bobListener)
|
||||||
|
|
||||||
|
@ -186,16 +174,12 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceUserID = aliceSession.myUserId
|
val aliceUserID = aliceSession.myUserId
|
||||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||||
|
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceSession.getVerificationService().addListener(aliceListener)
|
aliceSession.getVerificationService().addListener(aliceListener)
|
||||||
|
|
||||||
|
@ -328,7 +312,7 @@ class SASTest : InstrumentedTest {
|
||||||
val aliceCreatedLatch = CountDownLatch(2)
|
val aliceCreatedLatch = CountDownLatch(2)
|
||||||
val aliceCancelledLatch = CountDownLatch(2)
|
val aliceCancelledLatch = CountDownLatch(2)
|
||||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
createdTx.add(tx as SASDefaultVerificationTransaction)
|
||||||
aliceCreatedLatch.countDown()
|
aliceCreatedLatch.countDown()
|
||||||
|
@ -339,8 +323,6 @@ class SASTest : InstrumentedTest {
|
||||||
aliceCancelledLatch.countDown()
|
aliceCancelledLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
|
@ -372,11 +354,7 @@ class SASTest : InstrumentedTest {
|
||||||
var startReq: KeyVerificationStart? = null
|
var startReq: KeyVerificationStart? = null
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
val at = tx as SASDefaultVerificationTransaction
|
||||||
|
@ -388,17 +366,13 @@ class SASTest : InstrumentedTest {
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||||
val at = tx as IncomingSasVerificationTransaction
|
val at = tx as IncomingSasVerificationTransaction
|
||||||
at.performAccept()
|
at.performAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
@ -433,9 +407,7 @@ class SASTest : InstrumentedTest {
|
||||||
val bobVerificationService = bobSession!!.getVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
|
@ -445,15 +417,11 @@ class SASTest : InstrumentedTest {
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
|
@ -466,8 +434,6 @@ class SASTest : InstrumentedTest {
|
||||||
bobSASLatch.countDown()
|
bobSASLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
@ -497,9 +463,7 @@ class SASTest : InstrumentedTest {
|
||||||
val bobVerificationService = bobSession!!.getVerificationService()
|
val bobVerificationService = bobSession!!.getVerificationService()
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
val aliceSASLatch = CountDownLatch(1)
|
||||||
val aliceListener = object : VerificationService.VerificationListener {
|
val aliceListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
|
@ -512,15 +476,11 @@ class SASTest : InstrumentedTest {
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
aliceVerificationService.addListener(aliceListener)
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
val bobSASLatch = CountDownLatch(1)
|
||||||
val bobListener = object : VerificationService.VerificationListener {
|
val bobListener = object : VerificationService.Listener {
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||||
when (uxState) {
|
when (uxState) {
|
||||||
|
@ -536,8 +496,6 @@ class SASTest : InstrumentedTest {
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.verification.qrcode
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||||
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||||
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
|
import im.vector.matrix.android.common.TestConstants
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class VerificationTest : InstrumentedTest {
|
||||||
|
private val mTestHelper = CommonTestHelper(context())
|
||||||
|
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||||
|
|
||||||
|
data class ExpectedResult(
|
||||||
|
val sasIsSupported: Boolean = false,
|
||||||
|
val otherCanScanQrCode: Boolean = false,
|
||||||
|
val otherCanShowQrCode: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sas = listOf(
|
||||||
|
VerificationMethod.SAS
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasShow = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SHOW
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasScan = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasShowScan = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SHOW,
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_sas() = doTest(
|
||||||
|
sas,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_show() = doTest(
|
||||||
|
sas,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_sas() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_scan() = doTest(
|
||||||
|
sas,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_sas() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_scan() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_show() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_scan() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_show() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_all_all() = doTest(
|
||||||
|
sasShowScan,
|
||||||
|
sasShowScan,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO Add tests without SAS
|
||||||
|
|
||||||
|
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
|
||||||
|
bobSupportedMethods: List<VerificationMethod>,
|
||||||
|
expectedResultForAlice: ExpectedResult,
|
||||||
|
expectedResultForBob: ExpectedResult) {
|
||||||
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
mTestHelper.doSync<Unit> { callback ->
|
||||||
|
aliceSession.getCrossSigningService()
|
||||||
|
.initializeCrossSigning(UserPasswordAuth(
|
||||||
|
user = aliceSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
mTestHelper.doSync<Unit> { callback ->
|
||||||
|
bobSession.getCrossSigningService()
|
||||||
|
.initializeCrossSigning(UserPasswordAuth(
|
||||||
|
user = bobSession.myUserId,
|
||||||
|
password = TestConstants.PASSWORD
|
||||||
|
), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.getVerificationService()
|
||||||
|
val bobVerificationService = bobSession.getVerificationService()
|
||||||
|
|
||||||
|
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null
|
||||||
|
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
|
||||||
|
|
||||||
|
val latch = CountDownLatch(2)
|
||||||
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
// Step 4: Alice receive the ready request
|
||||||
|
if (pr.isReady) {
|
||||||
|
aliceReadyPendingVerificationRequest = pr
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
|
val bobListener = object : VerificationService.Listener {
|
||||||
|
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
|
// Step 2: Bob accepts the verification request
|
||||||
|
bobVerificationService.readyPendingVerificationInDMs(
|
||||||
|
bobSupportedMethods,
|
||||||
|
aliceSession.myUserId,
|
||||||
|
cryptoTestData.roomId,
|
||||||
|
pr.transactionId!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
// Step 3: Bob is ready
|
||||||
|
if (pr.isReady) {
|
||||||
|
bobReadyPendingVerificationRequest = pr
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
val bobUserId = bobSession.myUserId
|
||||||
|
// Step 1: Alice starts a verification request
|
||||||
|
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
||||||
|
mTestHelper.await(latch)
|
||||||
|
|
||||||
|
aliceReadyPendingVerificationRequest!!.let { pr ->
|
||||||
|
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
|
||||||
|
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode
|
||||||
|
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
bobReadyPendingVerificationRequest!!.let { pr ->
|
||||||
|
pr.isSasSupported() shouldBe expectedResultForBob.sasIsSupported
|
||||||
|
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
|
||||||
|
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoTestData.close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,9 +30,9 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification
|
||||||
*/
|
*/
|
||||||
interface VerificationService {
|
interface VerificationService {
|
||||||
|
|
||||||
fun addListener(listener: VerificationListener)
|
fun addListener(listener: Listener)
|
||||||
|
|
||||||
fun removeListener(listener: VerificationListener)
|
fun removeListener(listener: Listener)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this device as verified manually
|
* Mark this device as verified manually
|
||||||
|
@ -68,11 +68,11 @@ interface VerificationService {
|
||||||
otherDevices: List<String>?): PendingVerificationRequest
|
otherDevices: List<String>?): PendingVerificationRequest
|
||||||
|
|
||||||
fun declineVerificationRequestInDMs(otherUserId: String,
|
fun declineVerificationRequestInDMs(otherUserId: String,
|
||||||
otherDeviceId: String,
|
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
roomId: String)
|
roomId: String)
|
||||||
|
|
||||||
// Only SAS method is supported for the moment
|
// Only SAS method is supported for the moment
|
||||||
|
// TODO Parameter otherDeviceId should be removed in this case
|
||||||
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
fun beginKeyVerificationInDMs(method: VerificationMethod,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
@ -95,15 +95,33 @@ interface VerificationService {
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
transactionId: String): Boolean
|
transactionId: String): Boolean
|
||||||
|
|
||||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
interface Listener {
|
||||||
|
/**
|
||||||
interface VerificationListener {
|
* Called when a verification request is created either by the user, or by the other user.
|
||||||
fun transactionCreated(tx: VerificationTransaction)
|
*/
|
||||||
fun transactionUpdated(tx: VerificationTransaction)
|
|
||||||
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
|
||||||
|
|
||||||
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
|
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a verification request is updated.
|
||||||
|
*/
|
||||||
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
|
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a transaction is created, either by the user or initiated by the other user.
|
||||||
|
*/
|
||||||
|
fun transactionCreated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a transaction is updated. You may be interested to track the state of the VerificationTransaction.
|
||||||
|
*/
|
||||||
|
fun transactionUpdated(tx: VerificationTransaction) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the the deviceId of the userId has been marked as manually verified by the SDK.
|
||||||
|
* It will be called after VerificationService.markedLocallyAsManuallyVerified() is called.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -157,6 +157,11 @@ data class Event(
|
||||||
*/
|
*/
|
||||||
fun isRedacted() = unsignedData?.redactedEvent != null
|
fun isRedacted() = unsignedData?.redactedEvent != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the event is redacted by the user himself.
|
||||||
|
*/
|
||||||
|
fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
|
@ -19,11 +19,12 @@ package im.vector.matrix.android.api.session.events.model
|
||||||
* Constants defining known event relation types from Matrix specifications
|
* Constants defining known event relation types from Matrix specifications
|
||||||
*/
|
*/
|
||||||
object RelationType {
|
object RelationType {
|
||||||
|
|
||||||
/** Lets you define an event which annotates an existing event.*/
|
/** Lets you define an event which annotates an existing event.*/
|
||||||
const val ANNOTATION = "m.annotation"
|
const val ANNOTATION = "m.annotation"
|
||||||
/** Lets you define an event which replaces an existing event.*/
|
/** Lets you define an event which replaces an existing event.*/
|
||||||
const val REPLACE = "m.replace"
|
const val REPLACE = "m.replace"
|
||||||
/** Lets you define an event which references an existing event.*/
|
/** Lets you define an event which references an existing event.*/
|
||||||
const val REFERENCE = "m.reference"
|
const val REFERENCE = "m.reference"
|
||||||
|
/** Lets you define an event which adds a response to an existing event.*/
|
||||||
|
const val RESPONSE = "m.response"
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.pushers
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface PushersService {
|
interface PushersService {
|
||||||
|
@ -28,19 +29,32 @@ interface PushersService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new HTTP pusher.
|
* Add a new HTTP pusher.
|
||||||
|
* Note that only `http` kind is supported by the SDK for now.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
|
||||||
*
|
*
|
||||||
* @param pushkey the pushkey
|
* @param pushkey This is a unique identifier for this pusher. The value you should use for
|
||||||
|
* this is the routing or destination address information for the notification,
|
||||||
|
* for example, the APNS token for APNS or the Registration ID for GCM. If your
|
||||||
|
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
|
||||||
|
* If the kind is "email", this is the email address to send notifications to.
|
||||||
* @param appId the application id
|
* @param appId the application id
|
||||||
* @param profileTag the profile tag
|
* This is a reverse-DNS style identifier for the application. It is recommended
|
||||||
* @param lang the language
|
* that this end with the platform, such that different platform versions get
|
||||||
* @param appDisplayName a human-readable application name
|
* different app identifiers. Max length, 64 chars.
|
||||||
* @param deviceDisplayName a human-readable device name
|
* @param profileTag This string determines which set of device specific rules this pusher executes.
|
||||||
* @param url the URL that should be used to send notifications
|
* @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
|
||||||
* @param append append the pusher
|
* @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
|
||||||
* @param withEventIdOnly true to limit the push content
|
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
|
||||||
|
* @param url The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify.
|
||||||
|
* @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
|
||||||
|
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
|
||||||
|
* with the same App ID and pushkey for different users.
|
||||||
|
* @param withEventIdOnly true to limit the push content to only id and not message content
|
||||||
|
* Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour
|
||||||
*
|
*
|
||||||
* @return A work request uuid. Can be used to listen to the status
|
* @return A work request uuid. Can be used to listen to the status
|
||||||
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
||||||
|
* @throws [InvalidParameterException] if a parameter is not correct
|
||||||
*/
|
*/
|
||||||
fun addHttpPusher(pushkey: String,
|
fun addHttpPusher(pushkey: String,
|
||||||
appId: String,
|
appId: String,
|
||||||
|
@ -52,13 +66,18 @@ interface PushersService {
|
||||||
append: Boolean,
|
append: Boolean,
|
||||||
withEventIdOnly: Boolean): UUID
|
withEventIdOnly: Boolean): UUID
|
||||||
|
|
||||||
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>)
|
/**
|
||||||
|
* Remove the http pusher
|
||||||
companion object {
|
*/
|
||||||
const val EVENT_ID_ONLY = "event_id_only"
|
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current pushers, as a LiveData
|
||||||
|
*/
|
||||||
fun getPushersLive(): LiveData<List<Pusher>>
|
fun getPushersLive(): LiveData<List<Pusher>>
|
||||||
|
|
||||||
fun pushers() : List<Pusher>
|
/**
|
||||||
|
* Get the current pushers
|
||||||
|
*/
|
||||||
|
fun getPushers(): List<Pusher>
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,6 @@ data class EventAnnotationsSummary(
|
||||||
var eventId: String,
|
var eventId: String,
|
||||||
var reactionsSummary: List<ReactionAggregatedSummary>,
|
var reactionsSummary: List<ReactionAggregatedSummary>,
|
||||||
var editSummary: EditAggregatedSummary?,
|
var editSummary: EditAggregatedSummary?,
|
||||||
|
var pollResponseSummary: PollResponseAggregatedSummary?,
|
||||||
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
|
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.model
|
||||||
|
|
||||||
|
data class PollResponseAggregatedSummary(
|
||||||
|
|
||||||
|
var aggregatedContent: PollSummaryContent? = null,
|
||||||
|
|
||||||
|
// If set the poll is closed (Clients SHOULD NOT consider responses after the close event)
|
||||||
|
var closedTime: Long? = null,
|
||||||
|
// Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid
|
||||||
|
var nbOptions: Int = 0,
|
||||||
|
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||||
|
val sourceEvents: List<String>,
|
||||||
|
val localEchos: List<String>
|
||||||
|
)
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains an aggregated summary info of the poll response.
|
||||||
|
* Put pre-computed info that you want to access quickly without having
|
||||||
|
* to go through all references events
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PollSummaryContent(
|
||||||
|
// Index of my vote
|
||||||
|
var myVote: Int? = null,
|
||||||
|
// Array of VoteInfo, list is constructed so that there is only one vote by user
|
||||||
|
// And that optionIndex is valid
|
||||||
|
var votes: List<VoteInfo>? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun voteCount(): Int {
|
||||||
|
return votes?.size ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun voteCountForOption(optionIndex: Int) : Int {
|
||||||
|
return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class VoteInfo(
|
||||||
|
val userId: String,
|
||||||
|
val optionIndex: Int,
|
||||||
|
val voteTimestamp: Long
|
||||||
|
)
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
// Possible values for optionType
|
||||||
|
const val OPTION_TYPE_POLL = "org.matrix.poll"
|
||||||
|
const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageOptionsContent(
|
||||||
|
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_OPTIONS,
|
||||||
|
@Json(name = "type") val optionType: String? = null,
|
||||||
|
@Json(name = "body") override val body: String,
|
||||||
|
@Json(name = "label") val label: String?,
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "options") val options: List<OptionItem>? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
) : MessageContent
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessagePollResponseContent(
|
||||||
|
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
||||||
|
@Json(name = "body") override val body: String,
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
) : MessageContent
|
|
@ -25,6 +25,9 @@ object MessageType {
|
||||||
const val MSGTYPE_VIDEO = "m.video"
|
const val MSGTYPE_VIDEO = "m.video"
|
||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
|
const val MSGTYPE_OPTIONS = "org.matrix.options"
|
||||||
|
const val MSGTYPE_RESPONSE = "org.matrix.response"
|
||||||
|
const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
|
||||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
|
|
|
@ -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.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class OptionItem(
|
||||||
|
@Json(name = "label") val label: String?,
|
||||||
|
@Json(name = "value") val value: String?
|
||||||
|
)
|
|
@ -25,5 +25,6 @@ data class ReactionInfo(
|
||||||
@Json(name = "event_id") override val eventId: String,
|
@Json(name = "event_id") override val eventId: String,
|
||||||
val key: String,
|
val key: String,
|
||||||
// always null for reaction
|
// always null for reaction
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
|
@Json(name = "option") override val option: Int? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
|
|
@ -23,4 +23,5 @@ interface RelationContent {
|
||||||
val type: String?
|
val type: String?
|
||||||
val eventId: String?
|
val eventId: String?
|
||||||
val inReplyTo: ReplyToContent?
|
val inReplyTo: ReplyToContent?
|
||||||
|
val option: Int?
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,5 +22,6 @@ import com.squareup.moshi.JsonClass
|
||||||
data class RelationDefaultContent(
|
data class RelationDefaultContent(
|
||||||
@Json(name = "rel_type") override val type: String?,
|
@Json(name = "rel_type") override val type: String?,
|
||||||
@Json(name = "event_id") override val eventId: String?,
|
@Json(name = "event_id") override val eventId: String?,
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
|
@Json(name = "option") override val option: Int? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
|
|
@ -19,6 +19,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.content.ContentAttachmentData
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -62,7 +63,24 @@ interface SendService {
|
||||||
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redacts (delete) the given event.
|
* Send a poll to the room.
|
||||||
|
* @param question the question
|
||||||
|
* @param options list of (label, value)
|
||||||
|
* @return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun sendPoll(question: String, options: List<OptionItem>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to send a poll response.
|
||||||
|
* @param pollEventId the poll currently replied to
|
||||||
|
* @param optionIndex The reply index
|
||||||
|
* @param optionValue The option value (for compatibility)
|
||||||
|
* @return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redact (delete) the given event.
|
||||||
* @param event The event to redact
|
* @param event The event to redact
|
||||||
* @param reason Optional reason string
|
* @param reason Optional reason string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -154,97 +154,95 @@ internal abstract class CryptoModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
|
abstract fun bindCryptoService(service: DefaultCryptoService): CryptoService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
abstract fun bindDeleteDeviceTask(task: DefaultDeleteDeviceTask): DeleteDeviceTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
abstract fun bindGetDevicesTask(task: DefaultGetDevicesTask): GetDevicesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
abstract fun bindSetDeviceNameTask(task: DefaultSetDeviceNameTask): SetDeviceNameTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask
|
abstract fun bindUploadKeysTask(task: DefaultUploadKeysTask): UploadKeysTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadSigningKeysTask(uploadKeysTask: DefaultUploadSigningKeysTask): UploadSigningKeysTask
|
abstract fun bindUploadSigningKeysTask(task: DefaultUploadSigningKeysTask): UploadSigningKeysTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUploadSignaturesTask(uploadSignaturesTask: DefaultUploadSignaturesTask): UploadSignaturesTask
|
abstract fun bindUploadSignaturesTask(task: DefaultUploadSignaturesTask): UploadSignaturesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
abstract fun bindDownloadKeysForUsersTask(task: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCreateKeysBackupVersionTask(createKeysBackupVersionTask: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
|
abstract fun bindCreateKeysBackupVersionTask(task: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteBackupTask(deleteBackupTask: DefaultDeleteBackupTask): DeleteBackupTask
|
abstract fun bindDeleteBackupTask(task: DefaultDeleteBackupTask): DeleteBackupTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteRoomSessionDataTask(deleteRoomSessionDataTask: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
|
abstract fun bindDeleteRoomSessionDataTask(task: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteRoomSessionsDataTask(deleteRoomSessionsDataTask: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
|
abstract fun bindDeleteRoomSessionsDataTask(task: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteSessionsDataTask(deleteSessionsDataTask: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
|
abstract fun bindDeleteSessionsDataTask(task: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetKeysBackupLastVersionTask(getKeysBackupLastVersionTask: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
|
abstract fun bindGetKeysBackupLastVersionTask(task: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetKeysBackupVersionTask(getKeysBackupVersionTask: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
|
abstract fun bindGetKeysBackupVersionTask(task: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetRoomSessionDataTask(getRoomSessionDataTask: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
|
abstract fun bindGetRoomSessionDataTask(task: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetRoomSessionsDataTask(getRoomSessionsDataTask: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
|
abstract fun bindGetRoomSessionsDataTask(task: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetSessionsDataTask(getSessionsDataTask: DefaultGetSessionsDataTask): GetSessionsDataTask
|
abstract fun bindGetSessionsDataTask(task: DefaultGetSessionsDataTask): GetSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindStoreRoomSessionDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
|
abstract fun bindStoreRoomSessionDataTask(task: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionsDataTask: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
|
abstract fun bindStoreRoomSessionsDataTask(task: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindStoreSessionsDataTask(storeSessionsDataTask: DefaultStoreSessionsDataTask): StoreSessionsDataTask
|
abstract fun bindStoreSessionsDataTask(task: DefaultStoreSessionsDataTask): StoreSessionsDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUpdateKeysBackupVersionTask(updateKeysBackupVersionTask: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
|
abstract fun bindUpdateKeysBackupVersionTask(task: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
|
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
|
abstract fun bindSendVerificationMessageTask(task: DefaultSendVerificationMessageTask): SendVerificationMessageTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
||||||
: ClaimOneTimeKeysForUsersDeviceTask
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
|
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
||||||
: DeleteDeviceWithUserPasswordTask
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
|
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCryptoStore(realmCryptoStore: RealmCryptoStore): IMXCryptoStore
|
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindComputeShieldTrustTask(defaultShieldTrustUpdater: DefaultComputeTrustTask): ComputeTrustTask
|
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -637,9 +637,9 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
|
||||||
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventBus.post(CryptoToSessionUserTrustChange(userIds))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,9 +176,9 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var listeners = ArrayList<VerificationService.VerificationListener>()
|
private var listeners = ArrayList<VerificationService.Listener>()
|
||||||
|
|
||||||
override fun addListener(listener: VerificationService.VerificationListener) {
|
override fun addListener(listener: VerificationService.Listener) {
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
if (!listeners.contains(listener)) {
|
if (!listeners.contains(listener)) {
|
||||||
listeners.add(listener)
|
listeners.add(listener)
|
||||||
|
@ -186,7 +186,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeListener(listener: VerificationService.VerificationListener) {
|
override fun removeListener(listener: VerificationService.Listener) {
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
listeners.remove(listener)
|
listeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
@ -1151,9 +1151,9 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
return verificationRequest
|
return verificationRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
|
override fun declineVerificationRequestInDMs(otherUserId: String, transactionId: String, roomId: String) {
|
||||||
verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
verificationTransportRoomMessageFactory.createTransport(roomId, null)
|
||||||
.cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)
|
.cancelTransaction(transactionId, otherUserId, null, CancelCode.User)
|
||||||
|
|
||||||
getExistingVerificationRequest(otherUserId, transactionId)?.let {
|
getExistingVerificationRequest(otherUserId, transactionId)?.let {
|
||||||
updatePendingRequest(it.copy(
|
updatePendingRequest(it.copy(
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
|
@ -46,11 +46,37 @@ data class PendingVerificationRequest(
|
||||||
|
|
||||||
val isFinished: Boolean = isSuccessful || cancelConclusion != null
|
val isFinished: Boolean = isSuccessful || cancelConclusion != null
|
||||||
|
|
||||||
fun hasMethod(method: VerificationMethod): Boolean? {
|
/**
|
||||||
return when (method) {
|
* SAS is supported if I support it and the other party support it
|
||||||
VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS)
|
*/
|
||||||
VerificationMethod.QR_CODE_SHOW -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW)
|
fun isSasSupported(): Boolean {
|
||||||
VerificationMethod.QR_CODE_SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN)
|
return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other can show QR code if I can scan QR code and other can show QR code
|
||||||
|
*/
|
||||||
|
fun otherCanShowQrCode(): Boolean {
|
||||||
|
return if (isIncoming) {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
|
} else {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other can scan QR code if I can show QR code and other can scan QR code
|
||||||
|
*/
|
||||||
|
fun otherCanScanQrCode(): Boolean {
|
||||||
|
return if (isIncoming) {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
} else {
|
||||||
|
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
|
||||||
|
&& readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ internal interface VerificationTransport {
|
||||||
|
|
||||||
fun cancelTransaction(transactionId: String,
|
fun cancelTransaction(transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherUserDeviceId: String,
|
otherUserDeviceId: String?,
|
||||||
code: CancelCode)
|
code: CancelCode)
|
||||||
|
|
||||||
fun done(transactionId: String)
|
fun done(transactionId: String)
|
||||||
|
@ -79,11 +79,13 @@ internal interface VerificationTransport {
|
||||||
|
|
||||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||||
|
|
||||||
fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady
|
fun createReady(tid: String,
|
||||||
|
fromDevice: String,
|
||||||
|
methods: List<String>): VerificationInfoReady
|
||||||
|
|
||||||
// TODO Refactor
|
// TODO Refactor
|
||||||
fun sendVerificationReady(keyReq: VerificationInfoReady,
|
fun sendVerificationReady(keyReq: VerificationInfoReady,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String?,
|
||||||
callback: (() -> Unit)?)
|
callback: (() -> Unit)?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
|
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
val event = createEventAndLocalEcho(
|
val event = createEventAndLocalEcho(
|
||||||
type = EventType.KEY_VERIFICATION_CANCEL,
|
type = EventType.KEY_VERIFICATION_CANCEL,
|
||||||
|
@ -337,7 +337,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
|
|
||||||
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String?,
|
||||||
callback: (() -> Unit)?) {
|
callback: (() -> Unit)?) {
|
||||||
// Not applicable (send event is called directly)
|
// Not applicable (send event is called directly)
|
||||||
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")
|
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")
|
||||||
|
|
|
@ -80,7 +80,7 @@ internal class VerificationTransportToDevice(
|
||||||
|
|
||||||
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
override fun sendVerificationReady(keyReq: VerificationInfoReady,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDeviceId: String,
|
otherDeviceId: String?,
|
||||||
callback: (() -> Unit)?) {
|
callback: (() -> Unit)?) {
|
||||||
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
|
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
|
@ -159,7 +159,7 @@ internal class VerificationTransportToDevice(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
|
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
|
|
|
@ -55,7 +55,11 @@ internal object EventAnnotationsSummaryMapper {
|
||||||
it.sourceEvents.toList(),
|
it.sourceEvents.toList(),
|
||||||
it.sourceLocalEcho.toList()
|
it.sourceLocalEcho.toList()
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
||||||
|
PollResponseAggregatedSummaryEntityMapper.map(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +97,9 @@ internal object EventAnnotationsSummaryMapper {
|
||||||
RealmList<String>().apply { addAll(it.localEchos) }
|
RealmList<String>().apply { addAll(it.localEchos) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
||||||
|
PollResponseAggregatedSummaryEntityMapper.map(it)
|
||||||
|
}
|
||||||
return eventAnnotationsSummaryEntity
|
return eventAnnotationsSummaryEntity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.PollResponseAggregatedSummary
|
||||||
|
import im.vector.matrix.android.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||||
|
import io.realm.RealmList
|
||||||
|
|
||||||
|
internal object PollResponseAggregatedSummaryEntityMapper {
|
||||||
|
|
||||||
|
fun map(entity: PollResponseAggregatedSummaryEntity): PollResponseAggregatedSummary {
|
||||||
|
return PollResponseAggregatedSummary(
|
||||||
|
aggregatedContent = ContentMapper.map(entity.aggregatedContent).toModel(),
|
||||||
|
closedTime = entity.closedTime,
|
||||||
|
localEchos = entity.sourceLocalEchoEvents.toList(),
|
||||||
|
sourceEvents = entity.sourceEvents.toList(),
|
||||||
|
nbOptions = entity.nbOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(model: PollResponseAggregatedSummary): PollResponseAggregatedSummaryEntity {
|
||||||
|
return PollResponseAggregatedSummaryEntity(
|
||||||
|
aggregatedContent = ContentMapper.map(model.aggregatedContent.toContent()),
|
||||||
|
nbOptions = model.nbOptions,
|
||||||
|
closedTime = model.closedTime,
|
||||||
|
sourceEvents = RealmList<String>().apply { addAll(model.sourceEvents) },
|
||||||
|
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun PollResponseAggregatedSummaryEntity.asDomain(): PollResponseAggregatedSummary {
|
||||||
|
return PollResponseAggregatedSummaryEntityMapper.map(this)
|
||||||
|
}
|
|
@ -25,7 +25,8 @@ internal open class EventAnnotationsSummaryEntity(
|
||||||
var roomId: String? = null,
|
var roomId: String? = null,
|
||||||
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
|
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
|
||||||
var editSummary: EditAggregatedSummaryEntity? = null,
|
var editSummary: EditAggregatedSummaryEntity? = null,
|
||||||
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null
|
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
|
||||||
|
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep the latest state of a poll
|
||||||
|
*/
|
||||||
|
internal open class PollResponseAggregatedSummaryEntity(
|
||||||
|
// For now we persist this a JSON for greater flexibility
|
||||||
|
// #see PollSummaryContent
|
||||||
|
var aggregatedContent: String? = null,
|
||||||
|
|
||||||
|
// If set the poll is closed (Clients SHOULD NOT consider responses after the close event)
|
||||||
|
var closedTime: Long? = null,
|
||||||
|
// Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid
|
||||||
|
var nbOptions: Int = 0,
|
||||||
|
|
||||||
|
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||||
|
var sourceEvents: RealmList<String> = RealmList(),
|
||||||
|
var sourceLocalEchoEvents: RealmList<String> = RealmList()
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import io.realm.annotations.RealmModule
|
||||||
EventAnnotationsSummaryEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
EditAggregatedSummaryEntity::class,
|
EditAggregatedSummaryEntity::class,
|
||||||
|
PollResponseAggregatedSummaryEntity::class,
|
||||||
ReferencesAggregatedSummaryEntity::class,
|
ReferencesAggregatedSummaryEntity::class,
|
||||||
PushRulesEntity::class,
|
PushRulesEntity::class,
|
||||||
PushRuleEntity::class,
|
PushRuleEntity::class,
|
||||||
|
|
|
@ -19,4 +19,5 @@ package im.vector.matrix.android.internal.database.query
|
||||||
internal object FilterContent {
|
internal object FilterContent {
|
||||||
|
|
||||||
internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
|
internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
|
||||||
|
internal const val RESPONSE_TYPE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,15 @@
|
||||||
package im.vector.matrix.android.internal.database.query
|
package im.vector.matrix.android.internal.database.query
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.internal.database.model.*
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import io.realm.*
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.Sort
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> {
|
internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> {
|
||||||
|
@ -48,10 +55,16 @@ internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm:
|
||||||
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
includesSending: Boolean,
|
includesSending: Boolean,
|
||||||
|
filterContentRelation: Boolean = false,
|
||||||
filterTypes: List<String> = emptyList()): TimelineEventEntity? {
|
filterTypes: List<String> = emptyList()): TimelineEventEntity? {
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
||||||
val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes)
|
val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes)
|
||||||
val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
|
val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
|
||||||
|
if (filterContentRelation) {
|
||||||
|
liveEvents
|
||||||
|
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||||
|
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
|
||||||
|
}
|
||||||
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
||||||
sendingTimelineEvents
|
sendingTimelineEvents
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -47,6 +47,8 @@ object MoshiProvider {
|
||||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||||
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||||
|
.registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS)
|
||||||
|
.registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_RESPONSE)
|
||||||
)
|
)
|
||||||
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -37,5 +37,5 @@ internal abstract class CacheModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCacheService(cacheService: DefaultCacheService): CacheService
|
abstract fun bindCacheService(service: DefaultCacheService): CacheService
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,11 @@ internal abstract class FilterModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFilterRepository(filterRepository: DefaultFilterRepository): FilterRepository
|
abstract fun bindFilterRepository(repository: DefaultFilterRepository): FilterRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFilterService(filterService: DefaultFilterService): FilterService
|
abstract fun bindFilterService(service: DefaultFilterService): FilterService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSaveFilterTask(saveFilterTask: DefaultSaveFilterTask): SaveFilterTask
|
abstract fun bindSaveFilterTask(task: DefaultSaveFilterTask): SaveFilterTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,8 @@ internal abstract class GroupModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetGroupDataTask(getGroupDataTask: DefaultGetGroupDataTask): GetGroupDataTask
|
abstract fun bindGetGroupDataTask(task: DefaultGetGroupDataTask): GetGroupDataTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGroupService(groupService: DefaultGroupService): GroupService
|
abstract fun bindGroupService(service: DefaultGroupService): GroupService
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,5 +36,5 @@ internal abstract class HomeServerCapabilitiesModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetHomeServerCapabilitiesTask(getHomeServerCapabilitiesTask: DefaultGetHomeServerCapabilitiesTask): GetHomeServerCapabilitiesTask
|
abstract fun bindGetHomeServerCapabilitiesTask(task: DefaultGetHomeServerCapabilitiesTask): GetHomeServerCapabilitiesTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.PusherEntity
|
import im.vector.matrix.android.internal.database.model.PusherEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
@ -29,11 +30,12 @@ import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import java.util.*
|
import java.security.InvalidParameterException
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultPusherService @Inject constructor(
|
internal class DefaultPushersService @Inject constructor(
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
|
@ -48,10 +50,21 @@ internal class DefaultPusherService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addHttpPusher(pushkey: String, appId: String, profileTag: String,
|
override fun addHttpPusher(pushkey: String,
|
||||||
lang: String, appDisplayName: String, deviceDisplayName: String,
|
appId: String,
|
||||||
url: String, append: Boolean, withEventIdOnly: Boolean)
|
profileTag: String,
|
||||||
|
lang: String,
|
||||||
|
appDisplayName: String,
|
||||||
|
deviceDisplayName: String,
|
||||||
|
url: String,
|
||||||
|
append: Boolean,
|
||||||
|
withEventIdOnly: Boolean)
|
||||||
: UUID {
|
: UUID {
|
||||||
|
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
|
||||||
|
if (pushkey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
|
||||||
|
if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
|
||||||
|
if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'")
|
||||||
|
|
||||||
val pusher = JsonPusher(
|
val pusher = JsonPusher(
|
||||||
pushKey = pushkey,
|
pushKey = pushkey,
|
||||||
kind = "http",
|
kind = "http",
|
||||||
|
@ -60,7 +73,7 @@ internal class DefaultPusherService @Inject constructor(
|
||||||
deviceDisplayName = deviceDisplayName,
|
deviceDisplayName = deviceDisplayName,
|
||||||
profileTag = profileTag,
|
profileTag = profileTag,
|
||||||
lang = lang,
|
lang = lang,
|
||||||
data = JsonPusherData(url, if (withEventIdOnly) PushersService.EVENT_ID_ONLY else null),
|
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
|
||||||
append = append)
|
append = append)
|
||||||
|
|
||||||
val params = AddHttpPusherWorker.Params(sessionId, pusher)
|
val params = AddHttpPusherWorker.Params(sessionId, pusher)
|
||||||
|
@ -74,9 +87,9 @@ internal class DefaultPusherService @Inject constructor(
|
||||||
return request.id
|
return request.id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) {
|
override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = RemovePusherTask.Params(pushkey, appId)
|
val params = RemovePusherTask.Params(pushkey, appId)
|
||||||
removePusherTask
|
return removePusherTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
|
@ -91,7 +104,11 @@ internal class DefaultPusherService @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pushers(): List<Pusher> {
|
override fun getPushers(): List<Pusher> {
|
||||||
return monarchy.fetchAllCopiedSync { PusherEntity.where(it) }.map { it.asDomain() }
|
return monarchy.fetchAllCopiedSync { PusherEntity.where(it) }.map { it.asDomain() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EVENT_ID_ONLY = "event_id_only"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -49,38 +49,38 @@ internal abstract class PushersModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPusherService(pusherService: DefaultPusherService): PushersService
|
abstract fun bindPusherService(service: DefaultPushersService): PushersService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindConditionResolver(conditionResolver: DefaultConditionResolver): ConditionResolver
|
abstract fun bindConditionResolver(resolver: DefaultConditionResolver): ConditionResolver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetPushersTask(getPushersTask: DefaultGetPushersTask): GetPushersTask
|
abstract fun bindGetPushersTask(task: DefaultGetPushersTask): GetPushersTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask
|
abstract fun bindGetPushRulesTask(task: DefaultGetPushRulesTask): GetPushRulesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSavePushRulesTask(savePushRulesTask: DefaultSavePushRulesTask): SavePushRulesTask
|
abstract fun bindSavePushRulesTask(task: DefaultSavePushRulesTask): SavePushRulesTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRemovePusherTask(removePusherTask: DefaultRemovePusherTask): RemovePusherTask
|
abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUpdatePushRuleEnableStatusTask(updatePushRuleEnableStatusTask: DefaultUpdatePushRuleEnableStatusTask): UpdatePushRuleEnableStatusTask
|
abstract fun bindUpdatePushRuleEnableStatusTask(task: DefaultUpdatePushRuleEnableStatusTask): UpdatePushRuleEnableStatusTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAddPushRuleTask(addPushRuleTask: DefaultAddPushRuleTask): AddPushRuleTask
|
abstract fun bindAddPushRuleTask(task: DefaultAddPushRuleTask): AddPushRuleTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRemovePushRuleTask(removePushRuleTask: DefaultRemovePushRuleTask): RemovePushRuleTask
|
abstract fun bindRemovePushRuleTask(task: DefaultRemovePushRuleTask): RemovePushRuleTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetRoomNotificationStateTask(setRoomNotificationStateTask: DefaultSetRoomNotificationStateTask): SetRoomNotificationStateTask
|
abstract fun bindSetRoomNotificationStateTask(task: DefaultSetRoomNotificationStateTask): SetRoomNotificationStateTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPushRuleService(pushRuleService: DefaultPushRuleService): PushRuleService
|
abstract fun bindPushRuleService(service: DefaultPushRuleService): PushRuleService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindProcessEventForPushTask(processEventForPushTask: DefaultProcessEventForPushTask): ProcessEventForPushTask
|
abstract fun bindProcessEventForPushTask(task: DefaultProcessEventForPushTask): ProcessEventForPushTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,11 @@ import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
import im.vector.matrix.android.api.session.events.model.toContent
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.PollSummaryContent
|
||||||
import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent
|
import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.VoteInfo
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.ReactionContent
|
import im.vector.matrix.android.api.session.room.model.relation.ReactionContent
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
|
@ -36,6 +39,7 @@ import im.vector.matrix.android.internal.database.mapper.EventMapper
|
||||||
import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity
|
import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
|
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntity
|
import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntity
|
||||||
|
@ -123,6 +127,9 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
|
||||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||||
// A replace!
|
// A replace!
|
||||||
handleReplace(realm, event, content, roomId, isLocalEcho)
|
handleReplace(realm, event, content, roomId, isLocalEcho)
|
||||||
|
} else if (content?.relatesTo?.type == RelationType.RESPONSE) {
|
||||||
|
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
|
||||||
|
handleResponse(realm, userId, event, content, roomId, isLocalEcho)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,13 +151,20 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
|
||||||
EventType.ENCRYPTED -> {
|
EventType.ENCRYPTED -> {
|
||||||
// Relation type is in clear
|
// Relation type is in clear
|
||||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||||
if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE) {
|
if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE
|
||||||
|
|| encryptedEventContent?.relatesTo?.type == RelationType.RESPONSE
|
||||||
|
) {
|
||||||
// we need to decrypt if needed
|
// we need to decrypt if needed
|
||||||
decryptIfNeeded(event)
|
decryptIfNeeded(event)
|
||||||
event.getClearContent().toModel<MessageContent>()?.let {
|
event.getClearContent().toModel<MessageContent>()?.let {
|
||||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
if (encryptedEventContent.relatesTo.type == RelationType.REPLACE) {
|
||||||
// A replace!
|
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||||
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
// A replace!
|
||||||
|
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
||||||
|
} else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) {
|
||||||
|
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
|
||||||
|
handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
|
} else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
|
||||||
decryptIfNeeded(event)
|
decryptIfNeeded(event)
|
||||||
|
@ -276,6 +290,94 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleResponse(realm: Realm,
|
||||||
|
userId: String,
|
||||||
|
event: Event,
|
||||||
|
content: MessageContent,
|
||||||
|
roomId: String,
|
||||||
|
isLocalEcho: Boolean,
|
||||||
|
relatedEventId: String? = null) {
|
||||||
|
val eventId = event.eventId ?: return
|
||||||
|
val senderId = event.senderId ?: return
|
||||||
|
val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return
|
||||||
|
val eventTimestamp = event.originServerTs ?: return
|
||||||
|
|
||||||
|
// ok, this is a poll response
|
||||||
|
var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
|
||||||
|
if (existing == null) {
|
||||||
|
Timber.v("## POLL creating new relation summary for $targetEventId")
|
||||||
|
existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have it
|
||||||
|
val existingPollSummary = existing.pollResponseSummary
|
||||||
|
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
||||||
|
existing.pollResponseSummary = it
|
||||||
|
}
|
||||||
|
|
||||||
|
val closedTime = existingPollSummary?.closedTime
|
||||||
|
if (closedTime != null && eventTimestamp > closedTime) {
|
||||||
|
Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sumModel = ContentMapper.map(existingPollSummary?.aggregatedContent).toModel<PollSummaryContent>() ?: PollSummaryContent()
|
||||||
|
|
||||||
|
if (existingPollSummary!!.sourceEvents.contains(eventId)) {
|
||||||
|
// ignore this event, we already know it (??)
|
||||||
|
Timber.v("## POLL ignoring event for summary, it's known eventId:$eventId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val txId = event.unsignedData?.transactionId
|
||||||
|
// is it a remote echo?
|
||||||
|
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
||||||
|
// ok it has already been managed
|
||||||
|
Timber.v("## POLL Receiving remote echo of response eventId:$eventId")
|
||||||
|
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
||||||
|
existingPollSummary.sourceEvents.add(event.eventId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseContent = event.content.toModel<MessagePollResponseContent>() ?: return Unit.also {
|
||||||
|
Timber.d("## POLL Receiving malformed response eventId:$eventId content: ${event.content}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val optionIndex = responseContent.relatesTo?.option ?: return Unit.also {
|
||||||
|
Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val votes = sumModel.votes?.toMutableList() ?: ArrayList()
|
||||||
|
val existingVoteIndex = votes.indexOfFirst { it.userId == senderId }
|
||||||
|
if (existingVoteIndex != -1) {
|
||||||
|
// Is the vote newer?
|
||||||
|
val existingVote = votes[existingVoteIndex]
|
||||||
|
if (existingVote.voteTimestamp < eventTimestamp) {
|
||||||
|
// Take the new one
|
||||||
|
votes[existingVoteIndex] = VoteInfo(senderId, optionIndex, eventTimestamp)
|
||||||
|
if (userId == senderId) {
|
||||||
|
sumModel.myVote = optionIndex
|
||||||
|
}
|
||||||
|
Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$relatedEventId ")
|
||||||
|
} else {
|
||||||
|
Timber.v("## POLL Ignoring vote (older than known one) eventId:$eventId ")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
votes.add(VoteInfo(senderId, optionIndex, eventTimestamp))
|
||||||
|
if (userId == senderId) {
|
||||||
|
sumModel.myVote = optionIndex
|
||||||
|
}
|
||||||
|
Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$relatedEventId ")
|
||||||
|
}
|
||||||
|
sumModel.votes = votes
|
||||||
|
if (isLocalEcho) {
|
||||||
|
existingPollSummary.sourceLocalEchoEvents.add(eventId)
|
||||||
|
} else {
|
||||||
|
existingPollSummary.sourceEvents.add(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
|
private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
|
||||||
if (SHOULD_HANDLE_SERVER_AGREGGATION) {
|
if (SHOULD_HANDLE_SERVER_AGREGGATION) {
|
||||||
aggregation.chunk?.forEach {
|
aggregation.chunk?.forEach {
|
||||||
|
@ -459,7 +561,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
|
||||||
EventType.KEY_VERIFICATION_ACCEPT -> {
|
EventType.KEY_VERIFICATION_ACCEPT -> {
|
||||||
updateVerificationState(currentState, VerificationState.WAITING)
|
updateVerificationState(currentState, VerificationState.WAITING)
|
||||||
}
|
}
|
||||||
EventType.KEY_VERIFICATION_READY -> {
|
EventType.KEY_VERIFICATION_READY -> {
|
||||||
updateVerificationState(currentState, VerificationState.WAITING)
|
updateVerificationState(currentState, VerificationState.WAITING)
|
||||||
}
|
}
|
||||||
EventType.KEY_VERIFICATION_KEY -> {
|
EventType.KEY_VERIFICATION_KEY -> {
|
||||||
|
|
|
@ -102,7 +102,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
roomSummaryEntity.membership = membership
|
roomSummaryEntity.membership = membership
|
||||||
}
|
}
|
||||||
|
|
||||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
|
||||||
|
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
|
||||||
|
|
||||||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
@ -80,7 +81,20 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
val event = localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType).also {
|
val event = localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType).also {
|
||||||
createLocalEcho(it)
|
createLocalEcho(it)
|
||||||
}
|
}
|
||||||
|
return sendEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendPoll(question: String, options: List<OptionItem>): Cancelable {
|
||||||
|
val event = localEchoEventFactory.createPollEvent(roomId, question, options).also {
|
||||||
|
createLocalEcho(it)
|
||||||
|
}
|
||||||
|
return sendEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable {
|
||||||
|
val event = localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue).also {
|
||||||
|
createLocalEcho(it)
|
||||||
|
}
|
||||||
return sendEvent(event)
|
return sendEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,123 +172,123 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearSendingQueue() {
|
override fun clearSendingQueue() {
|
||||||
timelineSendEventWorkCommon.cancelAllWorks(roomId)
|
timelineSendEventWorkCommon.cancelAllWorks(roomId)
|
||||||
workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK))
|
workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK))
|
||||||
|
|
||||||
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
|
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
|
||||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
|
workManagerProvider.matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
|
||||||
.build().let {
|
.build().let {
|
||||||
timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE)
|
timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE)
|
||||||
|
|
||||||
// need to clear also image sending queue
|
// need to clear also image sending queue
|
||||||
workManagerProvider.workManager
|
workManagerProvider.workManager
|
||||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
|
||||||
taskExecutor.executorScope.launch {
|
|
||||||
localEchoRepository.clearSendingQueue(roomId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resendAllFailedMessages() {
|
|
||||||
taskExecutor.executorScope.launch {
|
|
||||||
val eventsToResend = localEchoRepository.getAllFailedEventsToResend(roomId)
|
|
||||||
eventsToResend.forEach {
|
|
||||||
sendEvent(it)
|
|
||||||
}
|
}
|
||||||
localEchoRepository.updateSendState(roomId, eventsToResend.mapNotNull { it.eventId }, SendState.UNSENT)
|
taskExecutor.executorScope.launch {
|
||||||
}
|
localEchoRepository.clearSendingQueue(roomId)
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
|
|
||||||
// Create an event with the media file path
|
|
||||||
val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
|
|
||||||
createLocalEcho(it)
|
|
||||||
}
|
|
||||||
return internalSendMedia(event, attachment)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData): Cancelable {
|
|
||||||
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
|
||||||
|
|
||||||
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
|
||||||
val sendWork = createSendEventWork(localEcho, false)
|
|
||||||
|
|
||||||
if (isRoomEncrypted) {
|
|
||||||
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
|
||||||
|
|
||||||
val op: Operation = workManagerProvider.workManager
|
|
||||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
|
||||||
.then(encryptWork)
|
|
||||||
.then(sendWork)
|
|
||||||
.enqueue()
|
|
||||||
op.result.addListener(Runnable {
|
|
||||||
if (op.result.isCancelled) {
|
|
||||||
Timber.e("CHAIN WAS CANCELLED")
|
|
||||||
} else if (op.state.value is Operation.State.FAILURE) {
|
|
||||||
Timber.e("CHAIN DID FAIL")
|
|
||||||
}
|
|
||||||
}, workerFutureListenerExecutor)
|
|
||||||
} else {
|
|
||||||
workManagerProvider.workManager
|
|
||||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
|
||||||
.then(sendWork)
|
|
||||||
.enqueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
return CancelableWork(workManagerProvider.workManager, sendWork.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createLocalEcho(event: Event) {
|
|
||||||
localEchoEventFactory.createLocalEcho(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildWorkName(identifier: String): String {
|
|
||||||
return "${roomId}_$identifier"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
// Same parameter
|
|
||||||
val params = EncryptEventWorker.Params(sessionId, roomId, event)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.setInputData(sendWorkData)
|
|
||||||
.startChain(startChain)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
|
||||||
|
|
||||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
|
||||||
val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also {
|
|
||||||
createLocalEcho(it)
|
|
||||||
}
|
|
||||||
val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason)
|
|
||||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
|
||||||
return timelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createUploadMediaWork(event: Event,
|
|
||||||
attachment: ContentAttachmentData,
|
|
||||||
isRoomEncrypted: Boolean,
|
|
||||||
startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted)
|
|
||||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.startChain(startChain)
|
|
||||||
.setInputData(uploadWorkData)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun resendAllFailedMessages() {
|
||||||
|
taskExecutor.executorScope.launch {
|
||||||
|
val eventsToResend = localEchoRepository.getAllFailedEventsToResend(roomId)
|
||||||
|
eventsToResend.forEach {
|
||||||
|
sendEvent(it)
|
||||||
|
}
|
||||||
|
localEchoRepository.updateSendState(roomId, eventsToResend.mapNotNull { it.eventId }, SendState.UNSENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
|
||||||
|
// Create an event with the media file path
|
||||||
|
val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
|
||||||
|
createLocalEcho(it)
|
||||||
|
}
|
||||||
|
return internalSendMedia(event, attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData): Cancelable {
|
||||||
|
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
||||||
|
|
||||||
|
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
||||||
|
val sendWork = createSendEventWork(localEcho, false)
|
||||||
|
|
||||||
|
if (isRoomEncrypted) {
|
||||||
|
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
||||||
|
|
||||||
|
val op: Operation = workManagerProvider.workManager
|
||||||
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||||
|
.then(encryptWork)
|
||||||
|
.then(sendWork)
|
||||||
|
.enqueue()
|
||||||
|
op.result.addListener(Runnable {
|
||||||
|
if (op.result.isCancelled) {
|
||||||
|
Timber.e("CHAIN WAS CANCELLED")
|
||||||
|
} else if (op.state.value is Operation.State.FAILURE) {
|
||||||
|
Timber.e("CHAIN DID FAIL")
|
||||||
|
}
|
||||||
|
}, workerFutureListenerExecutor)
|
||||||
|
} else {
|
||||||
|
workManagerProvider.workManager
|
||||||
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||||
|
.then(sendWork)
|
||||||
|
.enqueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
return CancelableWork(workManagerProvider.workManager, sendWork.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createLocalEcho(event: Event) {
|
||||||
|
localEchoEventFactory.createLocalEcho(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildWorkName(identifier: String): String {
|
||||||
|
return "${roomId}_$identifier"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
|
// Same parameter
|
||||||
|
val params = EncryptEventWorker.Params(sessionId, roomId, event)
|
||||||
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
|
|
||||||
|
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||||
|
.setConstraints(WorkManagerProvider.workConstraints)
|
||||||
|
.setInputData(sendWorkData)
|
||||||
|
.startChain(startChain)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
|
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
|
||||||
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
|
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
||||||
|
val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also {
|
||||||
|
createLocalEcho(it)
|
||||||
|
}
|
||||||
|
val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason)
|
||||||
|
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
return timelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createUploadMediaWork(event: Event,
|
||||||
|
attachment: ContentAttachmentData,
|
||||||
|
isRoomEncrypted: Boolean,
|
||||||
|
startChain: Boolean): OneTimeWorkRequest {
|
||||||
|
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted)
|
||||||
|
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||||
|
|
||||||
|
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||||
|
.setConstraints(WorkManagerProvider.workConstraints)
|
||||||
|
.startChain(startChain)
|
||||||
|
.setInputData(uploadWorkData)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -76,9 +76,6 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
localMutableContent.remove(it)
|
localMutableContent.remove(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
crypto.downloadKeys(listOf("@testxsigningvfe:matrix.org"), true, object : MatrixCallback<Any> {
|
|
||||||
})
|
|
||||||
|
|
||||||
var error: Throwable? = null
|
var error: Throwable? = null
|
||||||
var result: MXEncryptEventContentResult? = null
|
var result: MXEncryptEventContentResult? = null
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -36,10 +36,14 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_POLL
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||||
import im.vector.matrix.android.api.session.room.model.message.ThumbnailInfo
|
import im.vector.matrix.android.api.session.room.model.message.ThumbnailInfo
|
||||||
import im.vector.matrix.android.api.session.room.model.message.VideoInfo
|
import im.vector.matrix.android.api.session.room.model.message.VideoInfo
|
||||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||||
|
@ -132,6 +136,43 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createOptionsReplyEvent(roomId: String,
|
||||||
|
pollEventId: String,
|
||||||
|
optionIndex: Int,
|
||||||
|
optionLabel: String): Event {
|
||||||
|
return createEvent(roomId,
|
||||||
|
MessagePollResponseContent(
|
||||||
|
body = optionLabel,
|
||||||
|
relatesTo = RelationDefaultContent(
|
||||||
|
type = RelationType.RESPONSE,
|
||||||
|
option = optionIndex,
|
||||||
|
eventId = pollEventId)
|
||||||
|
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPollEvent(roomId: String,
|
||||||
|
question: String,
|
||||||
|
options: List<OptionItem>): Event {
|
||||||
|
val compatLabel = buildString {
|
||||||
|
append("[Poll] ")
|
||||||
|
append(question)
|
||||||
|
options.forEach {
|
||||||
|
append("\n")
|
||||||
|
append(it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createEvent(
|
||||||
|
roomId,
|
||||||
|
MessageOptionsContent(
|
||||||
|
body = compatLabel,
|
||||||
|
label = question,
|
||||||
|
optionType = OPTION_TYPE_POLL,
|
||||||
|
options = options.toList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createReplaceTextOfReply(roomId: String,
|
fun createReplaceTextOfReply(roomId: String,
|
||||||
eventReplaced: TimelineEvent,
|
eventReplaced: TimelineEvent,
|
||||||
originalEvent: TimelineEvent,
|
originalEvent: TimelineEvent,
|
||||||
|
|
|
@ -725,6 +725,7 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
if (settings.filterEdits) {
|
if (settings.filterEdits) {
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||||
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,8 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
||||||
}
|
}
|
||||||
if (settings.filterEdits) {
|
if (settings.filterEdits) {
|
||||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE)
|
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE)
|
||||||
|
or()
|
||||||
|
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.RESPONSE_TYPE)
|
||||||
}
|
}
|
||||||
endGroup()
|
endGroup()
|
||||||
return this
|
return this
|
||||||
|
|
|
@ -36,5 +36,5 @@ internal abstract class SyncModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSyncTask(syncTask: DefaultSyncTask): SyncTask
|
abstract fun bindSyncTask(task: DefaultSyncTask): SyncTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,10 @@ internal abstract class UserModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUserService(userService: DefaultUserService): UserService
|
abstract fun bindUserService(service: DefaultUserService): UserService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSearchUserTask(searchUserTask: DefaultSearchUserTask): SearchUserTask
|
abstract fun bindSearchUserTask(task: DefaultSearchUserTask): SearchUserTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask
|
abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask
|
||||||
|
@ -55,5 +55,5 @@ internal abstract class UserModule {
|
||||||
abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask
|
abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUserStore(userStore: RealmUserStore): UserStore
|
abstract fun bindUserStore(store: RealmUserStore): UserStore
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ parser.add_argument('-b',
|
||||||
parser.add_argument('-e',
|
parser.add_argument('-e',
|
||||||
'--expecting',
|
'--expecting',
|
||||||
type=int,
|
type=int,
|
||||||
default=-1,
|
|
||||||
help='the expected number of artifacts. If omitted, no check will be done.')
|
help='the expected number of artifacts. If omitted, no check will be done.')
|
||||||
parser.add_argument('-d',
|
parser.add_argument('-d',
|
||||||
'--directory',
|
'--directory',
|
||||||
|
@ -93,8 +92,8 @@ print(" git commit message : \"%s\"" % data0.get('message'))
|
||||||
print(" build state : %s" % data0.get('state'))
|
print(" build state : %s" % data0.get('state'))
|
||||||
|
|
||||||
if data0.get('state') != 'passed':
|
if data0.get('state') != 'passed':
|
||||||
print("❌ Error, the build failed (state: %s)" % data0.get('state'))
|
print("❌ Error, the build is in state '%s', and not 'passed'" % data0.get('state'))
|
||||||
exit(0)
|
exit(1)
|
||||||
|
|
||||||
### Fetch artifacts list
|
### Fetch artifacts list
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ data = json.loads(r.content.decode())
|
||||||
|
|
||||||
print(" %d artifact(s) found." % len(data))
|
print(" %d artifact(s) found." % len(data))
|
||||||
|
|
||||||
if args.expecting != -1 and args.expecting != len(data):
|
if args.expecting is not None and args.expecting != len(data):
|
||||||
print("Error, expecting %d artifacts and found %d." % (args.expecting, len(data)))
|
print("Error, expecting %d artifacts and found %d." % (args.expecting, len(data)))
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 15
|
ext.versionMinor = 16
|
||||||
ext.versionPatch = 0
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
|
|
|
@ -47,10 +47,10 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc
|
||||||
status = TestStatus.FAILED
|
status = TestStatus.FAILED
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val pusher = session.pushers().filter {
|
val pushers = session.getPushers().filter {
|
||||||
it.pushKey == fcmToken && it.state == PusherState.REGISTERED
|
it.pushKey == fcmToken && it.state == PusherState.REGISTERED
|
||||||
}
|
}
|
||||||
if (pusher.isEmpty()) {
|
if (pushers.isEmpty()) {
|
||||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_failed,
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_failed,
|
||||||
stringProvider.getString(R.string.sas_error_unknown))
|
stringProvider.getString(R.string.sas_error_unknown))
|
||||||
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) {
|
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) {
|
||||||
|
|
|
@ -47,8 +47,8 @@ class PushersManager @Inject constructor(
|
||||||
appNameProvider.getAppName(),
|
appNameProvider.getAppName(),
|
||||||
currentSession.sessionParams.credentials.deviceId ?: "MOBILE",
|
currentSession.sessionParams.credentials.deviceId ?: "MOBILE",
|
||||||
stringProvider.getString(R.string.pusher_http_url),
|
stringProvider.getString(R.string.pusher_http_url),
|
||||||
false,
|
append = false,
|
||||||
true
|
withEventIdOnly = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ class MainActivity : VectorBaseActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
args = parseArgs()
|
args = parseArgs()
|
||||||
if (args.clearCredentials || args.isUserLoggedOut) {
|
if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) {
|
||||||
clearNotifications()
|
clearNotifications()
|
||||||
}
|
}
|
||||||
// Handle some wanted cleanup
|
// Handle some wanted cleanup
|
||||||
|
|
|
@ -41,6 +41,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
||||||
RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote),
|
RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote),
|
||||||
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
||||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler),
|
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler),
|
||||||
|
POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll),
|
||||||
SHRUG("/shrug", "<message>", R.string.command_description_shrug),
|
SHRUG("/shrug", "<message>", R.string.command_description_shrug),
|
||||||
// TODO temporary command
|
// TODO temporary command
|
||||||
VERIFY_USER("/verify", "<user-id>", R.string.command_description_verify);
|
VERIFY_USER("/verify", "<user-id>", R.string.command_description_verify);
|
||||||
|
|
|
@ -80,12 +80,12 @@ object CommandParser {
|
||||||
|
|
||||||
ParsedCommand.SendEmote(message)
|
ParsedCommand.SendEmote(message)
|
||||||
}
|
}
|
||||||
Command.RAINBOW.command -> {
|
Command.RAINBOW.command -> {
|
||||||
val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim()
|
val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim()
|
||||||
|
|
||||||
ParsedCommand.SendRainbow(message)
|
ParsedCommand.SendRainbow(message)
|
||||||
}
|
}
|
||||||
Command.RAINBOW_EMOTE.command -> {
|
Command.RAINBOW_EMOTE.command -> {
|
||||||
val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim()
|
val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim()
|
||||||
|
|
||||||
ParsedCommand.SendRainbowEmote(message)
|
ParsedCommand.SendRainbowEmote(message)
|
||||||
|
@ -251,7 +251,6 @@ object CommandParser {
|
||||||
}
|
}
|
||||||
Command.SPOILER.command -> {
|
Command.SPOILER.command -> {
|
||||||
val message = textMessage.substring(Command.SPOILER.command.length).trim()
|
val message = textMessage.substring(Command.SPOILER.command.length).trim()
|
||||||
|
|
||||||
ParsedCommand.SendSpoiler(message)
|
ParsedCommand.SendSpoiler(message)
|
||||||
}
|
}
|
||||||
Command.SHRUG.command -> {
|
Command.SHRUG.command -> {
|
||||||
|
@ -259,12 +258,20 @@ object CommandParser {
|
||||||
|
|
||||||
ParsedCommand.SendShrug(message)
|
ParsedCommand.SendShrug(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
Command.VERIFY_USER.command -> {
|
Command.VERIFY_USER.command -> {
|
||||||
val message = textMessage.substring(Command.VERIFY_USER.command.length).trim()
|
val message = textMessage.substring(Command.VERIFY_USER.command.length).trim()
|
||||||
|
|
||||||
ParsedCommand.VerifyUser(message)
|
ParsedCommand.VerifyUser(message)
|
||||||
}
|
}
|
||||||
|
Command.POLL.command -> {
|
||||||
|
val rawCommand = textMessage.substring(Command.POLL.command.length).trim()
|
||||||
|
val split = rawCommand.split("|").map { it.trim() }
|
||||||
|
if (split.size > 2) {
|
||||||
|
ParsedCommand.SendPoll(split[0], split.subList(1, split.size))
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.POLL)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Unknown command
|
// Unknown command
|
||||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||||
|
|
|
@ -50,4 +50,5 @@ sealed class ParsedCommand {
|
||||||
class SendSpoiler(val message: String) : ParsedCommand()
|
class SendSpoiler(val message: String) : ParsedCommand()
|
||||||
class SendShrug(val message: CharSequence) : ParsedCommand()
|
class SendShrug(val message: CharSequence) : ParsedCommand()
|
||||||
class VerifyUser(val userId: String) : ParsedCommand()
|
class VerifyUser(val userId: String) : ParsedCommand()
|
||||||
|
class SendPoll(val question: String, val options: List<String>) : ParsedCommand()
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ import kotlin.collections.HashMap
|
||||||
@Singleton
|
@Singleton
|
||||||
class KeyRequestHandler @Inject constructor(private val context: Context)
|
class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
: RoomKeysRequestListener,
|
: RoomKeysRequestListener,
|
||||||
VerificationService.VerificationListener {
|
VerificationService.Listener {
|
||||||
|
|
||||||
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
||||||
|
|
||||||
|
@ -262,9 +262,6 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx is SasVerificationTransaction) {
|
if (tx is SasVerificationTransaction) {
|
||||||
val state = tx.state
|
val state = tx.state
|
||||||
|
|
|
@ -34,7 +34,7 @@ import javax.inject.Singleton
|
||||||
* Listens to the VerificationManager and add a new notification when an incoming request is detected.
|
* Listens to the VerificationManager and add a new notification when an incoming request is detected.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class IncomingVerificationRequestHandler @Inject constructor(private val context: Context) : VerificationService.VerificationListener {
|
class IncomingVerificationRequestHandler @Inject constructor(private val context: Context) : VerificationService.Listener {
|
||||||
|
|
||||||
private var session: Session? = null
|
private var session: Session? = null
|
||||||
|
|
||||||
|
@ -48,8 +48,6 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
|
||||||
this.session = null
|
this.session = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (!tx.isToDeviceTransport()) return
|
if (!tx.isToDeviceTransport()) return
|
||||||
// TODO maybe check also if
|
// TODO maybe check also if
|
||||||
|
@ -111,9 +109,6 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
// For incoming request we should prompt (if not in activity where this request apply)
|
// For incoming request we should prompt (if not in activity where this request apply)
|
||||||
if (pr.isIncoming) {
|
if (pr.isIncoming) {
|
||||||
|
@ -145,7 +140,6 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
|
||||||
}
|
}
|
||||||
dismissedAction = Runnable {
|
dismissedAction = Runnable {
|
||||||
session?.getVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
|
session?.getVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
|
||||||
pr.requestInfo?.fromDevice ?: "",
|
|
||||||
pr.transactionId ?: "",
|
pr.transactionId ?: "",
|
||||||
pr.roomId ?: ""
|
pr.roomId ?: ""
|
||||||
)
|
)
|
||||||
|
@ -163,7 +157,6 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
|
||||||
if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) {
|
if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) {
|
||||||
PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr))
|
PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr))
|
||||||
}
|
}
|
||||||
super.verificationRequestUpdated(pr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
|
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
|
||||||
|
|
|
@ -60,7 +60,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||||
@Assisted args: VerificationBottomSheet.VerificationArgs,
|
@Assisted args: VerificationBottomSheet.VerificationArgs,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
|
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
|
||||||
VerificationService.VerificationListener {
|
VerificationService.Listener {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
session.getVerificationService().addListener(this)
|
session.getVerificationService().addListener(this)
|
||||||
|
|
|
@ -21,9 +21,9 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
|
@ -45,7 +45,7 @@ data class VerificationChooseMethodViewState(
|
||||||
class VerificationChooseMethodViewModel @AssistedInject constructor(
|
class VerificationChooseMethodViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: VerificationChooseMethodViewState,
|
@Assisted initialState: VerificationChooseMethodViewState,
|
||||||
private val session: Session
|
private val session: Session
|
||||||
) : VectorViewModel<VerificationChooseMethodViewState, EmptyAction, EmptyViewEvents>(initialState), VerificationService.VerificationListener {
|
) : VectorViewModel<VerificationChooseMethodViewState, EmptyAction, EmptyViewEvents>(initialState), VerificationService.Listener {
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
transactionUpdated(tx)
|
transactionUpdated(tx)
|
||||||
|
@ -66,9 +66,9 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
|
otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(),
|
||||||
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
|
otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(),
|
||||||
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
SASModeAvailable = pvr?.isSasSupported().orFalse()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,10 +103,10 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
|
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
|
||||||
transactionId = args.verificationId ?: "",
|
transactionId = args.verificationId ?: "",
|
||||||
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
|
otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(),
|
||||||
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
|
otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(),
|
||||||
qrCodeText = (qrCodeVerificationTransaction as? QrCodeVerificationTransaction)?.qrCodeText,
|
qrCodeText = (qrCodeVerificationTransaction as? QrCodeVerificationTransaction)?.qrCodeText,
|
||||||
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
SASModeAvailable = pvr?.isSasSupported().orFalse()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ data class VerificationEmojiCodeViewState(
|
||||||
class VerificationEmojiCodeViewModel @AssistedInject constructor(
|
class VerificationEmojiCodeViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: VerificationEmojiCodeViewState,
|
@Assisted initialState: VerificationEmojiCodeViewState,
|
||||||
private val session: Session
|
private val session: Session
|
||||||
) : VectorViewModel<VerificationEmojiCodeViewState, EmptyAction, EmptyViewEvents>(initialState), VerificationService.VerificationListener {
|
) : VectorViewModel<VerificationEmojiCodeViewState, EmptyAction, EmptyViewEvents>(initialState), VerificationService.Listener {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
withState { state ->
|
withState { state ->
|
||||||
|
|
|
@ -53,6 +53,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class ResendMessage(val eventId: String) : RoomDetailAction()
|
data class ResendMessage(val eventId: String) : RoomDetailAction()
|
||||||
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
||||||
|
|
||||||
|
data class ReplyToOptions(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()
|
||||||
|
|
||||||
data class ReportContent(
|
data class ReportContent(
|
||||||
val eventId: String,
|
val eventId: String,
|
||||||
val senderId: String?,
|
val senderId: String?,
|
||||||
|
@ -65,8 +67,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
object ClearSendQueue : RoomDetailAction()
|
object ClearSendQueue : RoomDetailAction()
|
||||||
object ResendAll : RoomDetailAction()
|
object ResendAll : RoomDetailAction()
|
||||||
|
|
||||||
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
|
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction()
|
||||||
data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
|
data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction()
|
||||||
data class RequestVerification(val userId: String) : RoomDetailAction()
|
data class RequestVerification(val userId: String) : RoomDetailAction()
|
||||||
data class ResumeVerification(val transactionId: String, val otherUserId: String? = null, val otherdDeviceId: String? = null) : RoomDetailAction()
|
data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,8 +61,10 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.github.piasy.biv.BigImageViewer
|
import com.github.piasy.biv.BigImageViewer
|
||||||
import com.github.piasy.biv.loader.ImageLoader
|
import com.github.piasy.biv.loader.ImageLoader
|
||||||
|
import com.google.android.material.checkbox.MaterialCheckBox
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
@ -786,6 +788,31 @@ class RoomDetailFragment @Inject constructor(
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun promptConfirmationToRedactEvent(action: EventSharedAction.Redact) {
|
||||||
|
val layout = requireActivity().layoutInflater.inflate(R.layout.dialog_delete_event, null)
|
||||||
|
val reasonCheckBox = layout.findViewById<MaterialCheckBox>(R.id.deleteEventReasonCheck)
|
||||||
|
val reasonTextInputLayout = layout.findViewById<TextInputLayout>(R.id.deleteEventReasonTextInputLayout)
|
||||||
|
val reasonInput = layout.findViewById<TextInputEditText>(R.id.deleteEventReasonInput)
|
||||||
|
|
||||||
|
reasonCheckBox.isVisible = action.askForReason
|
||||||
|
reasonTextInputLayout.isVisible = action.askForReason
|
||||||
|
|
||||||
|
reasonCheckBox.setOnCheckedChangeListener { _, isChecked -> reasonTextInputLayout.isEnabled = isChecked }
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.delete_event_dialog_title)
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.remove) { _, _ ->
|
||||||
|
val reason = reasonInput.text.toString()
|
||||||
|
.takeIf { action.askForReason }
|
||||||
|
?.takeIf { reasonCheckBox.isChecked }
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, reason))
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun displayRoomDetailActionFailure(result: RoomDetailViewEvents.ActionFailure) {
|
private fun displayRoomDetailActionFailure(result: RoomDetailViewEvents.ActionFailure) {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.dialog_title_error)
|
.setTitle(R.string.dialog_title_error)
|
||||||
|
@ -1005,7 +1032,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
|
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
|
||||||
if (messageContent is MessageVerificationRequestContent) {
|
if (messageContent is MessageVerificationRequestContent) {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId))
|
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1022,7 +1049,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAvatarClicked(informationData: MessageInformationData) {
|
override fun onAvatarClicked(informationData: MessageInformationData) {
|
||||||
// roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.senderId))
|
// roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.userId))
|
||||||
openRoomMemberProfile(informationData.senderId)
|
openRoomMemberProfile(informationData.senderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1083,7 +1110,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private fun handleActions(action: EventSharedAction) {
|
private fun handleActions(action: EventSharedAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is EventSharedAction.OpenUserProfile -> {
|
is EventSharedAction.OpenUserProfile -> {
|
||||||
openRoomMemberProfile(action.senderId)
|
openRoomMemberProfile(action.userId)
|
||||||
}
|
}
|
||||||
is EventSharedAction.AddReaction -> {
|
is EventSharedAction.AddReaction -> {
|
||||||
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
|
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
|
||||||
|
@ -1097,8 +1124,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
copyToClipboard(requireContext(), action.content, false)
|
copyToClipboard(requireContext(), action.content, false)
|
||||||
showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
is EventSharedAction.Delete -> {
|
is EventSharedAction.Redact -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
promptConfirmationToRedactEvent(action)
|
||||||
}
|
}
|
||||||
is EventSharedAction.Share -> {
|
is EventSharedAction.Share -> {
|
||||||
// TODO current data communication is too limited
|
// TODO current data communication is too limited
|
||||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||||
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
||||||
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
|
@ -199,6 +200,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||||
|
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||||
|
@ -422,6 +424,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||||
popDraft()
|
popDraft()
|
||||||
}
|
}
|
||||||
|
is ParsedCommand.SendPoll -> {
|
||||||
|
room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") })
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||||
|
popDraft()
|
||||||
|
}
|
||||||
is ParsedCommand.ChangeTopic -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(slashCommandResult)
|
handleChangeTopicSlashCommand(slashCommandResult)
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -833,7 +840,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
||||||
session.getVerificationService().declineVerificationRequestInDMs(
|
session.getVerificationService().declineVerificationRequestInDMs(
|
||||||
action.otherUserId,
|
action.otherUserId,
|
||||||
action.otherdDeviceId,
|
|
||||||
action.transactionId,
|
action.transactionId,
|
||||||
room.roomId)
|
room.roomId)
|
||||||
}
|
}
|
||||||
|
@ -855,6 +861,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleReplyToOptions(action: RoomDetailAction.ReplyToOptions) {
|
||||||
|
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeSyncState() {
|
private fun observeSyncState() {
|
||||||
session.rx()
|
session.rx()
|
||||||
.liveSyncState()
|
.liveSyncState()
|
||||||
|
|
|
@ -33,6 +33,7 @@ abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptI
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute var timestamp: CharSequence? = null
|
@EpoxyAttribute var timestamp: CharSequence? = null
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
@EpoxyAttribute var userClicked: (() -> Unit)? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
avatarRenderer.render(matrixItem, holder.avatarView)
|
avatarRenderer.render(matrixItem, holder.avatarView)
|
||||||
|
@ -43,6 +44,7 @@ abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptI
|
||||||
} ?: run {
|
} ?: run {
|
||||||
holder.timestampView.isVisible = false
|
holder.timestampView.isVisible = false
|
||||||
}
|
}
|
||||||
|
holder.view.setOnClickListener { userClicked?.invoke() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
|
|
@ -27,6 +27,8 @@ import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.extensions.cleanup
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.extensions.configureWith
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
|
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
|
||||||
|
@ -40,7 +42,7 @@ data class DisplayReadReceiptArgs(
|
||||||
/**
|
/**
|
||||||
* Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp
|
* Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp
|
||||||
*/
|
*/
|
||||||
class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment(), DisplayReadReceiptsController.Listener {
|
||||||
|
|
||||||
@Inject lateinit var epoxyController: DisplayReadReceiptsController
|
@Inject lateinit var epoxyController: DisplayReadReceiptsController
|
||||||
|
|
||||||
|
@ -49,6 +51,8 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
|
private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
|
||||||
|
|
||||||
|
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
@ -57,16 +61,23 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||||
recyclerView.configureWith(epoxyController, hasFixedSize = false)
|
recyclerView.configureWith(epoxyController, hasFixedSize = false)
|
||||||
bottomSheetTitle.text = getString(R.string.seen_by)
|
bottomSheetTitle.text = getString(R.string.seen_by)
|
||||||
|
epoxyController.listener = this
|
||||||
epoxyController.setData(displayReadReceiptArgs.readReceipts)
|
epoxyController.setData(displayReadReceiptArgs.readReceipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
recyclerView.cleanup()
|
recyclerView.cleanup()
|
||||||
|
epoxyController.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun didSelectUser(userId: String) {
|
||||||
|
sharedActionViewModel.post(EventSharedAction.OpenUserProfile(userId))
|
||||||
|
}
|
||||||
|
|
||||||
// we are not using state for this one as it's static, so no need to override invalidate()
|
// we are not using state for this one as it's static, so no need to override invalidate()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -32,6 +32,8 @@ class DisplayReadReceiptsController @Inject constructor(private val dateFormatte
|
||||||
private val avatarRender: AvatarRenderer)
|
private val avatarRender: AvatarRenderer)
|
||||||
: TypedEpoxyController<List<ReadReceiptData>>() {
|
: TypedEpoxyController<List<ReadReceiptData>>() {
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
override fun buildModels(readReceipts: List<ReadReceiptData>) {
|
override fun buildModels(readReceipts: List<ReadReceiptData>) {
|
||||||
readReceipts.forEach {
|
readReceipts.forEach {
|
||||||
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
||||||
|
@ -40,7 +42,12 @@ class DisplayReadReceiptsController @Inject constructor(private val dateFormatte
|
||||||
.matrixItem(it.toMatrixItem())
|
.matrixItem(it.toMatrixItem())
|
||||||
.avatarRenderer(avatarRender)
|
.avatarRenderer(avatarRender)
|
||||||
.timestamp(timestamp)
|
.timestamp(timestamp)
|
||||||
|
.userClicked { listener?.didSelectUser(it.userId) }
|
||||||
.addIf(session.myUserId != it.userId, this)
|
.addIf(session.myUserId != it.userId, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun didSelectUser(userId: String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,13 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorSharedAction
|
import im.vector.riotx.core.platform.VectorSharedAction
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
|
||||||
sealed class EventSharedAction(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int) : VectorSharedAction {
|
sealed class EventSharedAction(@StringRes val titleRes: Int,
|
||||||
|
@DrawableRes val iconResId: Int,
|
||||||
|
val destructive: Boolean = false) : VectorSharedAction {
|
||||||
object Separator :
|
object Separator :
|
||||||
EventSharedAction(0, 0)
|
EventSharedAction(0, 0)
|
||||||
|
|
||||||
data class OpenUserProfile(val senderId: String) :
|
data class OpenUserProfile(val userId: String) :
|
||||||
EventSharedAction(0, 0)
|
EventSharedAction(0, 0)
|
||||||
|
|
||||||
data class AddReaction(val eventId: String) :
|
data class AddReaction(val eventId: String) :
|
||||||
|
@ -51,10 +53,10 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, @DrawableRes val ic
|
||||||
EventSharedAction(R.string.global_retry, R.drawable.ic_refresh_cw)
|
EventSharedAction(R.string.global_retry, R.drawable.ic_refresh_cw)
|
||||||
|
|
||||||
data class Remove(val eventId: String) :
|
data class Remove(val eventId: String) :
|
||||||
EventSharedAction(R.string.remove, R.drawable.ic_trash)
|
EventSharedAction(R.string.remove, R.drawable.ic_trash, true)
|
||||||
|
|
||||||
data class Delete(val eventId: String) :
|
data class Redact(val eventId: String, val askForReason: Boolean) :
|
||||||
EventSharedAction(R.string.delete, R.drawable.ic_delete)
|
EventSharedAction(R.string.message_action_item_redact, R.drawable.ic_delete, true)
|
||||||
|
|
||||||
data class Cancel(val eventId: String) :
|
data class Cancel(val eventId: String) :
|
||||||
EventSharedAction(R.string.cancel, R.drawable.ic_close_round)
|
EventSharedAction(R.string.cancel, R.drawable.ic_close_round)
|
||||||
|
@ -81,7 +83,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, @DrawableRes val ic
|
||||||
EventSharedAction(R.string.report_content_custom, R.drawable.ic_report_custom)
|
EventSharedAction(R.string.report_content_custom, R.drawable.ic_report_custom)
|
||||||
|
|
||||||
data class IgnoreUser(val senderId: String?) :
|
data class IgnoreUser(val senderId: String?) :
|
||||||
EventSharedAction(R.string.message_ignore_user, R.drawable.ic_alert_triangle)
|
EventSharedAction(R.string.message_ignore_user, R.drawable.ic_alert_triangle, true)
|
||||||
|
|
||||||
data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) :
|
data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) :
|
||||||
EventSharedAction(0, 0)
|
EventSharedAction(0, 0)
|
||||||
|
|
|
@ -111,7 +111,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
showExpand(action is EventSharedAction.ReportContent)
|
showExpand(action is EventSharedAction.ReportContent)
|
||||||
expanded(state.expendedReportContentMenu)
|
expanded(state.expendedReportContentMenu)
|
||||||
listener(View.OnClickListener { listener?.didSelectMenuAction(action) })
|
listener(View.OnClickListener { listener?.didSelectMenuAction(action) })
|
||||||
destructive(action is EventSharedAction.IgnoreUser)
|
destructive(action.destructive)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action is EventSharedAction.ReportContent && state.expendedReportContentMenu) {
|
if (action is EventSharedAction.ReportContent && state.expendedReportContentMenu) {
|
||||||
|
|
|
@ -168,6 +168,10 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence {
|
private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence {
|
||||||
|
if (timelineEvent.root.isRedacted()) {
|
||||||
|
return getRedactionReason(timelineEvent)
|
||||||
|
}
|
||||||
|
|
||||||
return when (timelineEvent.root.getClearType()) {
|
return when (timelineEvent.root.getClearType()) {
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STICKER -> {
|
EventType.STICKER -> {
|
||||||
|
@ -200,6 +204,31 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
} ?: ""
|
} ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getRedactionReason(timelineEvent: TimelineEvent): String {
|
||||||
|
return (timelineEvent
|
||||||
|
.root
|
||||||
|
.unsignedData
|
||||||
|
?.redactedEvent
|
||||||
|
?.content
|
||||||
|
?.get("reason") as? String)
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
.let { reason ->
|
||||||
|
if (reason == null) {
|
||||||
|
if (timelineEvent.root.isRedactedBySameUser()) {
|
||||||
|
stringProvider.getString(R.string.event_redacted_by_user_reason)
|
||||||
|
} else {
|
||||||
|
stringProvider.getString(R.string.event_redacted_by_admin_reason)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (timelineEvent.root.isRedactedBySameUser()) {
|
||||||
|
stringProvider.getString(R.string.event_redacted_by_user_reason_with_reason, reason)
|
||||||
|
} else {
|
||||||
|
stringProvider.getString(R.string.event_redacted_by_admin_reason_with_reason, reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> {
|
private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> {
|
||||||
val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: timelineEvent.root.getClearContent().toModel()
|
?: timelineEvent.root.getClearContent().toModel()
|
||||||
|
@ -227,7 +256,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canRedact(timelineEvent, session.myUserId)) {
|
if (canRedact(timelineEvent, session.myUserId)) {
|
||||||
add(EventSharedAction.Delete(eventId))
|
add(EventSharedAction.Redact(eventId, askForReason = informationData.senderId != session.myUserId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canCopy(msgType)) {
|
if (canCopy(msgType)) {
|
||||||
|
|
|
@ -33,10 +33,14 @@ import im.vector.matrix.android.api.session.room.model.message.MessageEmoteConte
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_BUTTONS
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_POLL
|
||||||
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
|
@ -57,7 +61,6 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformat
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
|
||||||
|
@ -65,6 +68,8 @@ import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageOptionsItem_
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessagePollItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem
|
||||||
|
@ -121,7 +126,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
if (messageContent.relatesTo?.type == RelationType.REPLACE
|
if (messageContent.relatesTo?.type == RelationType.REPLACE
|
||||||
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||||
) {
|
) {
|
||||||
// This is an edit event, we should it when debugging as a notice event
|
// This is an edit event, we should display it when debugging as a notice event
|
||||||
return noticeItemFactory.create(event, highlight, callback)
|
return noticeItemFactory.create(event, highlight, callback)
|
||||||
}
|
}
|
||||||
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback)
|
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback)
|
||||||
|
@ -137,7 +142,40 @@ class MessageItemFactory @Inject constructor(
|
||||||
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
|
is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback)
|
||||||
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildOptionsMessageItem(messageContent: MessageOptionsContent,
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
highlight: Boolean,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||||
|
return when (messageContent.optionType) {
|
||||||
|
OPTION_TYPE_POLL -> {
|
||||||
|
MessagePollItem_()
|
||||||
|
.attributes(attributes)
|
||||||
|
.callback(callback)
|
||||||
|
.informationData(informationData)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
.optionsContent(messageContent)
|
||||||
|
.highlighted(highlight)
|
||||||
|
}
|
||||||
|
OPTION_TYPE_BUTTONS -> {
|
||||||
|
MessageOptionsItem_()
|
||||||
|
.attributes(attributes)
|
||||||
|
.callback(callback)
|
||||||
|
.informationData(informationData)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
.optionsContent(messageContent)
|
||||||
|
.highlighted(highlight)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Not supported optionType
|
||||||
|
buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +219,6 @@ class MessageItemFactory @Inject constructor(
|
||||||
VerificationRequestItem.Attributes(
|
VerificationRequestItem.Attributes(
|
||||||
otherUserId = otherUserId,
|
otherUserId = otherUserId,
|
||||||
otherUserName = otherUserName.toString(),
|
otherUserName = otherUserName.toString(),
|
||||||
fromDevide = messageContent.fromDevice ?: "",
|
|
||||||
referenceId = informationData.eventId,
|
referenceId = informationData.eventId,
|
||||||
informationData = informationData,
|
informationData = informationData,
|
||||||
avatarRenderer = attributes.avatarRenderer,
|
avatarRenderer = attributes.avatarRenderer,
|
||||||
|
@ -228,9 +265,10 @@ class MessageItemFactory @Inject constructor(
|
||||||
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?): DefaultItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.msgType)
|
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||||
return defaultItemFactory.create(text, informationData, highlight, callback)
|
// For compatibility reason we should display the body
|
||||||
|
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.riotx.core.extensions.localDateTime
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.utils.getColorFromUserId
|
import im.vector.riotx.core.utils.getColorFromUserId
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.PollResponseData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReferencesInfoData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||||
|
@ -82,6 +83,15 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
?.map {
|
?.map {
|
||||||
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
||||||
},
|
},
|
||||||
|
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
|
||||||
|
PollResponseData(
|
||||||
|
myVote = it.aggregatedContent?.myVote,
|
||||||
|
isClosed = it.closedTime ?: Long.MAX_VALUE > System.currentTimeMillis(),
|
||||||
|
votes = it.aggregatedContent?.votes
|
||||||
|
?.groupBy({ it.optionIndex }, { it.userId })
|
||||||
|
?.mapValues { it.value.size }
|
||||||
|
)
|
||||||
|
},
|
||||||
hasBeenEdited = event.hasBeenEdited(),
|
hasBeenEdited = event.hasBeenEdited(),
|
||||||
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false,
|
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false,
|
||||||
readReceipts = event.readReceipts
|
readReceipts = event.readReceipts
|
||||||
|
|
|
@ -34,6 +34,8 @@ data class MessageInformationData(
|
||||||
val showInformation: Boolean = true,
|
val showInformation: Boolean = true,
|
||||||
/*List of reactions (emoji,count,isSelected)*/
|
/*List of reactions (emoji,count,isSelected)*/
|
||||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||||
|
val pollResponseAggregatedSummary: PollResponseData? = null,
|
||||||
|
|
||||||
val hasBeenEdited: Boolean = false,
|
val hasBeenEdited: Boolean = false,
|
||||||
val hasPendingEdits: Boolean = false,
|
val hasPendingEdits: Boolean = false,
|
||||||
val readReceipts: List<ReadReceiptData> = emptyList(),
|
val readReceipts: List<ReadReceiptData> = emptyList(),
|
||||||
|
@ -66,4 +68,11 @@ data class ReadReceiptData(
|
||||||
val timestamp: Long
|
val timestamp: Long
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PollResponseData(
|
||||||
|
val myVote: Int?,
|
||||||
|
val votes: Map<Int, Int>?,
|
||||||
|
val isClosed: Boolean = false
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
|
abstract class MessageOptionsItem : AbsMessageItem<MessageOptionsItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var optionsContent: MessageOptionsContent? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var informationData: MessageInformationData? = null
|
||||||
|
|
||||||
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
renderSendState(holder.view, holder.labelText)
|
||||||
|
|
||||||
|
holder.labelText.setTextOrHide(optionsContent?.label)
|
||||||
|
|
||||||
|
holder.buttonContainer.removeAllViews()
|
||||||
|
|
||||||
|
val relatedEventId = informationData?.eventId ?: return
|
||||||
|
val options = optionsContent?.options?.takeIf { it.isNotEmpty() } ?: return
|
||||||
|
// Now add back the buttons
|
||||||
|
options.forEachIndexed { index, option ->
|
||||||
|
val materialButton = LayoutInflater.from(holder.view.context).inflate(R.layout.option_buttons, holder.buttonContainer, false)
|
||||||
|
as MaterialButton
|
||||||
|
holder.buttonContainer.addView(materialButton, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
materialButton.text = option.label
|
||||||
|
materialButton.setOnClickListener {
|
||||||
|
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptions(relatedEventId, index, option.value ?: "$index"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
|
|
||||||
|
val labelText by bind<TextView>(R.id.optionLabelText)
|
||||||
|
|
||||||
|
val buttonContainer by bind<ViewGroup>(R.id.optionsButtonContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STUB_ID = R.id.messageOptionsStub
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
|
abstract class MessagePollItem : AbsMessageItem<MessagePollItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var optionsContent: MessageOptionsContent? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var informationData: MessageInformationData? = null
|
||||||
|
|
||||||
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
holder.pollId = informationData?.eventId
|
||||||
|
holder.callback = callback
|
||||||
|
holder.optionValues = optionsContent?.options?.map { it.value ?: it.label }
|
||||||
|
|
||||||
|
renderSendState(holder.view, holder.labelText)
|
||||||
|
|
||||||
|
holder.labelText.setTextOrHide(optionsContent?.label)
|
||||||
|
|
||||||
|
val buttons = listOf(holder.button1, holder.button2, holder.button3, holder.button4, holder.button5)
|
||||||
|
val resultLines = listOf(holder.result1, holder.result2, holder.result3, holder.result4, holder.result5)
|
||||||
|
|
||||||
|
buttons.forEach { it.isVisible = false }
|
||||||
|
resultLines.forEach { it.isVisible = false }
|
||||||
|
|
||||||
|
val myVote = informationData?.pollResponseAggregatedSummary?.myVote
|
||||||
|
val iHaveVoted = myVote != null
|
||||||
|
val votes = informationData?.pollResponseAggregatedSummary?.votes
|
||||||
|
val totalVotes = votes?.values
|
||||||
|
?.fold(0) { acc, count -> acc + count } ?: 0
|
||||||
|
val percentMode = totalVotes > 100
|
||||||
|
|
||||||
|
if (!iHaveVoted) {
|
||||||
|
// Show buttons if i have not voted
|
||||||
|
holder.resultWrapper.isVisible = false
|
||||||
|
optionsContent?.options?.forEachIndexed { index, item ->
|
||||||
|
if (index < buttons.size) {
|
||||||
|
buttons[index].let {
|
||||||
|
it.text = item.label
|
||||||
|
it.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.resultWrapper.isVisible = true
|
||||||
|
val maxCount = votes?.maxBy { it.value }?.value ?: 0
|
||||||
|
optionsContent?.options?.forEachIndexed { index, item ->
|
||||||
|
if (index < resultLines.size) {
|
||||||
|
val optionCount = votes?.get(index) ?: 0
|
||||||
|
val count = if (percentMode) {
|
||||||
|
if (totalVotes > 0) {
|
||||||
|
(optionCount / totalVotes.toFloat() * 100).roundToInt().let { "$it%" }
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optionCount.toString()
|
||||||
|
}
|
||||||
|
resultLines[index].let {
|
||||||
|
it.label = item.label
|
||||||
|
it.isWinner = optionCount == maxCount
|
||||||
|
it.optionSelected = index == myVote
|
||||||
|
it.percent = count
|
||||||
|
it.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holder.infoText.text = holder.view.context.resources.getQuantityString(R.plurals.poll_info, totalVotes, totalVotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
holder.pollId = null
|
||||||
|
holder.callback = null
|
||||||
|
holder.optionValues = null
|
||||||
|
super.unbind(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
|
|
||||||
|
var pollId: String? = null
|
||||||
|
var optionValues: List<String?>? = null
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
val button1 by bind<Button>(R.id.pollButton1)
|
||||||
|
val button2 by bind<Button>(R.id.pollButton2)
|
||||||
|
val button3 by bind<Button>(R.id.pollButton3)
|
||||||
|
val button4 by bind<Button>(R.id.pollButton4)
|
||||||
|
val button5 by bind<Button>(R.id.pollButton5)
|
||||||
|
|
||||||
|
val result1 by bind<PollResultLineView>(R.id.pollResult1)
|
||||||
|
val result2 by bind<PollResultLineView>(R.id.pollResult2)
|
||||||
|
val result3 by bind<PollResultLineView>(R.id.pollResult3)
|
||||||
|
val result4 by bind<PollResultLineView>(R.id.pollResult4)
|
||||||
|
val result5 by bind<PollResultLineView>(R.id.pollResult5)
|
||||||
|
|
||||||
|
val labelText by bind<TextView>(R.id.pollLabelText)
|
||||||
|
val infoText by bind<TextView>(R.id.pollInfosText)
|
||||||
|
|
||||||
|
val resultWrapper by bind<ViewGroup>(R.id.pollResultsWrapper)
|
||||||
|
|
||||||
|
override fun bindView(itemView: View) {
|
||||||
|
super.bindView(itemView)
|
||||||
|
val buttons = listOf(button1, button2, button3, button4, button5)
|
||||||
|
val clickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
|
val optionIndex = buttons.indexOf(it)
|
||||||
|
if (optionIndex != -1 && pollId != null) {
|
||||||
|
val compatValue = if (optionIndex < optionValues?.size ?: 0) optionValues?.get(optionIndex) else null
|
||||||
|
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptions(pollId!!, optionIndex, compatValue ?: "$optionIndex"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
buttons.forEach { it.setOnClickListener(clickListener) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STUB_ID = R.id.messagePollStub
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
|
||||||
|
class PollResultLineView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
@BindView(R.id.pollResultItemLabel)
|
||||||
|
lateinit var labelTextView: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.pollResultItemPercent)
|
||||||
|
lateinit var percentTextView: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.pollResultItemSelectedIcon)
|
||||||
|
lateinit var selectedIcon: ImageView
|
||||||
|
|
||||||
|
var label: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
labelTextView.setTextOrHide(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var percent: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
percentTextView.setTextOrHide(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionSelected: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
selectedIcon.visibility = if (value) View.VISIBLE else View.INVISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
var isWinner: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
// Text in main color
|
||||||
|
labelTextView.setTypeface(labelTextView.getTypeface(), if (value) Typeface.BOLD else Typeface.NORMAL)
|
||||||
|
percentTextView.setTypeface(percentTextView.getTypeface(), if (value) Typeface.BOLD else Typeface.NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.item_timeline_event_poll_result_item, this)
|
||||||
|
orientation = HORIZONTAL
|
||||||
|
ButterKnife.bind(this)
|
||||||
|
|
||||||
|
val typedArray = context.obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.PollResultLineView, 0, 0)
|
||||||
|
label = typedArray.getString(R.styleable.PollResultLineView_optionName) ?: ""
|
||||||
|
percent = typedArray.getString(R.styleable.PollResultLineView_optionCount) ?: ""
|
||||||
|
optionSelected = typedArray.getBoolean(R.styleable.PollResultLineView_optionSelected, false)
|
||||||
|
isWinner = typedArray.getBoolean(R.styleable.PollResultLineView_optionIsWinner, false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,12 +134,9 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
|
||||||
private val _clickListener = DebouncedClickListener(View.OnClickListener {
|
private val _clickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
val att = attributes ?: return@OnClickListener
|
val att = attributes ?: return@OnClickListener
|
||||||
if (it == acceptButton) {
|
if (it == acceptButton) {
|
||||||
callback?.onTimelineItemAction(RoomDetailAction.AcceptVerificationRequest(
|
callback?.onTimelineItemAction(RoomDetailAction.AcceptVerificationRequest(att.referenceId, att.otherUserId))
|
||||||
att.referenceId,
|
|
||||||
att.otherUserId,
|
|
||||||
att.fromDevide))
|
|
||||||
} else if (it == declineButton) {
|
} else if (it == declineButton) {
|
||||||
callback?.onTimelineItemAction(RoomDetailAction.DeclineVerificationRequest(att.referenceId, att.otherUserId, att.fromDevide))
|
callback?.onTimelineItemAction(RoomDetailAction.DeclineVerificationRequest(att.referenceId, att.otherUserId))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -169,7 +166,6 @@ abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestI
|
||||||
data class Attributes(
|
data class Attributes(
|
||||||
val otherUserId: String,
|
val otherUserId: String,
|
||||||
val otherUserName: String,
|
val otherUserName: String,
|
||||||
val fromDevide: String,
|
|
||||||
val referenceId: String,
|
val referenceId: String,
|
||||||
// val avatarSize: Int,
|
// val avatarSize: Int,
|
||||||
override val informationData: MessageInformationData,
|
override val informationData: MessageInformationData,
|
||||||
|
|
|
@ -36,6 +36,8 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
|
||||||
lateinit var authorDisplayName: CharSequence
|
lateinit var authorDisplayName: CharSequence
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var timeStamp: CharSequence? = null
|
var timeStamp: CharSequence? = null
|
||||||
|
@EpoxyAttribute
|
||||||
|
var userClicked: (() -> Unit)? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
holder.emojiReactionView.text = reactionKey
|
holder.emojiReactionView.text = reactionKey
|
||||||
|
@ -46,6 +48,7 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
|
||||||
} ?: run {
|
} ?: run {
|
||||||
holder.timeStampView.isVisible = false
|
holder.timeStampView.isVisible = false
|
||||||
}
|
}
|
||||||
|
holder.view.setOnClickListener { userClicked?.invoke() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
|
|
@ -27,6 +27,8 @@ import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.extensions.cleanup
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.extensions.configureWith
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
|
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
|
||||||
|
@ -35,11 +37,12 @@ import javax.inject.Inject
|
||||||
/**
|
/**
|
||||||
* Bottom sheet displaying list of reactions for a given event ordered by timestamp
|
* Bottom sheet displaying list of reactions for a given event ordered by timestamp
|
||||||
*/
|
*/
|
||||||
class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment(), ViewReactionsEpoxyController.Listener {
|
||||||
|
|
||||||
private val viewModel: ViewReactionsViewModel by fragmentViewModel(ViewReactionsViewModel::class)
|
private val viewModel: ViewReactionsViewModel by fragmentViewModel(ViewReactionsViewModel::class)
|
||||||
|
|
||||||
@Inject lateinit var viewReactionsViewModelFactory: ViewReactionsViewModel.Factory
|
@Inject lateinit var viewReactionsViewModelFactory: ViewReactionsViewModel.Factory
|
||||||
|
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||||
|
|
||||||
@BindView(R.id.bottomSheetRecyclerView)
|
@BindView(R.id.bottomSheetRecyclerView)
|
||||||
lateinit var recyclerView: RecyclerView
|
lateinit var recyclerView: RecyclerView
|
||||||
|
@ -54,15 +57,22 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||||
recyclerView.configureWith(epoxyController, hasFixedSize = false)
|
recyclerView.configureWith(epoxyController, hasFixedSize = false)
|
||||||
bottomSheetTitle.text = context?.getString(R.string.reactions)
|
bottomSheetTitle.text = context?.getString(R.string.reactions)
|
||||||
|
epoxyController.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
recyclerView.cleanup()
|
recyclerView.cleanup()
|
||||||
|
epoxyController.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun didSelectUser(userId: String) {
|
||||||
|
sharedActionViewModel.post(EventSharedAction.OpenUserProfile(userId))
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
epoxyController.setData(it)
|
epoxyController.setData(it)
|
||||||
super.invalidate()
|
super.invalidate()
|
||||||
|
|
|
@ -35,6 +35,8 @@ class ViewReactionsEpoxyController @Inject constructor(
|
||||||
private val emojiCompatWrapper: EmojiCompatWrapper)
|
private val emojiCompatWrapper: EmojiCompatWrapper)
|
||||||
: TypedEpoxyController<DisplayReactionsViewState>() {
|
: TypedEpoxyController<DisplayReactionsViewState>() {
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
override fun buildModels(state: DisplayReactionsViewState) {
|
override fun buildModels(state: DisplayReactionsViewState) {
|
||||||
when (state.mapReactionKeyToMemberList) {
|
when (state.mapReactionKeyToMemberList) {
|
||||||
is Incomplete -> {
|
is Incomplete -> {
|
||||||
|
@ -55,9 +57,14 @@ class ViewReactionsEpoxyController @Inject constructor(
|
||||||
timeStamp(it.timestamp)
|
timeStamp(it.timestamp)
|
||||||
reactionKey(emojiCompatWrapper.safeEmojiSpanify(it.reactionKey))
|
reactionKey(emojiCompatWrapper.safeEmojiSpanify(it.reactionKey))
|
||||||
authorDisplayName(it.authorName ?: it.authorId)
|
authorDisplayName(it.authorName ?: it.authorId)
|
||||||
|
userClicked { listener?.didSelectUser(it.authorId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun didSelectUser(userId: String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ data class DevicesViewState(
|
||||||
|
|
||||||
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
|
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
: VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.VerificationListener {
|
: VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
@ -89,7 +89,6 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {}
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
if (tx.state == VerificationTxState.Verified) {
|
if (tx.state == VerificationTxState.Verified) {
|
||||||
refreshDevicesList()
|
refreshDevicesList()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/riotx_button_disabled_alpha12" android:state_enabled="false" />
|
||||||
|
<item android:color="@color/riotx_button_primary_accent_alpha12" android:state_enabled="true" />
|
||||||
|
</selector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/button_bot_disabled_text_color" android:state_enabled="false" />
|
||||||
|
<item android:color="@color/button_bot_enabled_text_color" />
|
||||||
|
</selector>
|
30
vector/src/main/res/drawable/ic_poll.xml
Normal file
30
vector/src/main/res/drawable/ic_poll.xml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M3.5,0.5L20.5,0.5A3,3 0,0 1,23.5 3.5L23.5,20.5A3,3 0,0 1,20.5 23.5L3.5,23.5A3,3 0,0 1,0.5 20.5L0.5,3.5A3,3 0,0 1,3.5 0.5z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M5.5,12L6.5,12A1.5,1.5 0,0 1,8 13.5L8,19.5A1.5,1.5 0,0 1,6.5 21L5.5,21A1.5,1.5 0,0 1,4 19.5L4,13.5A1.5,1.5 0,0 1,5.5 12z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.5,9L12.5,9A1.5,1.5 0,0 1,14 10.5L14,19.5A1.5,1.5 0,0 1,12.5 21L11.5,21A1.5,1.5 0,0 1,10 19.5L10,10.5A1.5,1.5 0,0 1,11.5 9z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.5,6L18.5,6A1.5,1.5 0,0 1,20 7.5L20,19.5A1.5,1.5 0,0 1,18.5 21L17.5,21A1.5,1.5 0,0 1,16 19.5L16,7.5A1.5,1.5 0,0 1,17.5 6z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
50
vector/src/main/res/layout/dialog_delete_event.xml
Normal file
50
vector/src/main/res/layout/dialog_delete_event.xml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/layout_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="?dialogPreferredPadding"
|
||||||
|
android:paddingLeft="?dialogPreferredPadding"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="?dialogPreferredPadding"
|
||||||
|
android:paddingRight="?dialogPreferredPadding"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/deleteEventConfirmationText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/delete_event_dialog_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/deleteEventReasonCheck"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:checked="true"
|
||||||
|
android:text="@string/delete_event_dialog_reason_checkbox"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/deleteEventConfirmationText" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/deleteEventReasonTextInputLayout"
|
||||||
|
style="@style/VectorTextInputLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/delete_event_dialog_reason_hint"
|
||||||
|
app:counterEnabled="true"
|
||||||
|
app:counterMaxLength="240"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/deleteEventReasonCheck">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/deleteEventReasonInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,6 +4,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="40dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="8dp"
|
android:paddingStart="8dp"
|
||||||
android:minHeight="40dp"
|
|
||||||
android:paddingEnd="8dp">
|
android:paddingEnd="8dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
android:layout_marginRight="8dp"
|
android:layout_marginRight="8dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:lines="1"
|
android:lines="1"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="@color/black"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
tools:text="@sample/reactions.json/data/reaction" />
|
tools:text="@sample/reactions.json/data/reaction" />
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,20 @@
|
||||||
android:layout_marginEnd="56dp"
|
android:layout_marginEnd="56dp"
|
||||||
android:layout="@layout/item_timeline_event_redacted_stub" />
|
android:layout="@layout/item_timeline_event_redacted_stub" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messagePollStub"
|
||||||
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="56dp"
|
||||||
|
android:layout="@layout/item_timeline_event_poll_stub" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messageOptionsStub"
|
||||||
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="56dp"
|
||||||
|
android:layout="@layout/item_timeline_event_option_buttons_stub" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/optionLabelText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="normal"
|
||||||
|
tools:text="What would you like to do?" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/optionsButtonContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Filled at runtime with buttons -->
|
||||||
|
<!--com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton1"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Create Github issue" /-->
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pollResultItemSelectedIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/poll_item_selected_aria"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="2dp"
|
||||||
|
android:src="@drawable/ic_check_white_24dp"
|
||||||
|
android:tint="?riotx_text_secondary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollResultItemLabel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Open a Github Issue" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollResultItemPercent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="47%" />
|
||||||
|
</merge>
|
143
vector/src/main/res/layout/item_timeline_event_poll_stub.xml
Normal file
143
vector/src/main/res/layout/item_timeline_event_poll_stub.xml
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:src="@drawable/ic_poll"
|
||||||
|
android:tint="@color/riotx_accent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollLabelText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="What would you like to do?" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton1"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Create Github issue"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton2"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Search Github"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton3"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Logout"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton4"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Option 4"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton5"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Option 5"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/pollResultsWrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_attachment_type_selector"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionCount="40%"
|
||||||
|
tools:optionName="Create Github issue"
|
||||||
|
tools:optionSelected="true" />
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionCount="60%"
|
||||||
|
tools:optionIsWinner="true"
|
||||||
|
tools:optionName="Search Github"
|
||||||
|
tools:optionSelected="false" />
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionCount="0%"
|
||||||
|
tools:optionName="Logout"
|
||||||
|
tools:optionSelected="false" />
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionCount="0%"
|
||||||
|
tools:optionName="Option 4"
|
||||||
|
tools:optionSelected="false" />
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionCount="0%"
|
||||||
|
tools:optionName="Option 5"
|
||||||
|
tools:optionSelected="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollInfosText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="12 votes - Final Results"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
7
vector/src/main/res/layout/option_buttons.xml
Normal file
7
vector/src/main/res/layout/option_buttons.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="@style/VectorButtonStyleInlineBot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Create Github issue" />
|
|
@ -97,4 +97,12 @@
|
||||||
<attr name="riotx_highlighted_message_background" format="reference" />
|
<attr name="riotx_highlighted_message_background" format="reference" />
|
||||||
|
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="PollResultLineView">
|
||||||
|
<attr name="optionName" format="string" localization="suggested" />
|
||||||
|
<attr name="optionCount" format="string" />
|
||||||
|
<attr name="optionSelected" format="boolean" />
|
||||||
|
<attr name="optionIsWinner" format="boolean" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -125,6 +125,8 @@
|
||||||
<color name="button_disabled_text_color">#FFFFFFFF</color>
|
<color name="button_disabled_text_color">#FFFFFFFF</color>
|
||||||
<color name="button_destructive_enabled_text_color">#FF4B55</color>
|
<color name="button_destructive_enabled_text_color">#FF4B55</color>
|
||||||
<color name="button_destructive_disabled_text_color">#FF4B55</color>
|
<color name="button_destructive_disabled_text_color">#FF4B55</color>
|
||||||
|
<color name="button_bot_enabled_text_color">#FF368BD6</color>
|
||||||
|
<color name="button_bot_disabled_text_color">#61708B</color>
|
||||||
|
|
||||||
|
|
||||||
<!-- Link color -->
|
<!-- Link color -->
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
<color name="riotx_destructive_accent">#FFFF4B55</color>
|
<color name="riotx_destructive_accent">#FFFF4B55</color>
|
||||||
<color name="riotx_destructive_accent_alpha12">#1EFF4B55</color>
|
<color name="riotx_destructive_accent_alpha12">#1EFF4B55</color>
|
||||||
|
<color name="riotx_button_primary_accent_alpha12">#14368BD6</color>
|
||||||
|
|
||||||
|
|
||||||
<color name="riotx_positive_accent">#03B381</color>
|
<color name="riotx_positive_accent">#03B381</color>
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
https://matrix.org/docs/spec/client_server/r0.4.0#id128
|
https://matrix.org/docs/spec/client_server/r0.4.0#id128
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!-- Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
|
||||||
<string name="pusher_http_url" translatable="false">https://matrix.org/_matrix/push/v1/notify</string>
|
<string name="pusher_http_url" translatable="false">https://matrix.org/_matrix/push/v1/notify</string>
|
||||||
|
<!-- Note: pusher_app_id cannot exceed 64 chars -->
|
||||||
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>
|
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>
|
||||||
|
|
||||||
<string-array name="room_directory_servers" translatable="false">
|
<string-array name="room_directory_servers" translatable="false">
|
||||||
|
|
|
@ -6,12 +6,21 @@
|
||||||
<!-- Sections has been created to avoid merge conflict. Let's see if it's better -->
|
<!-- Sections has been created to avoid merge conflict. Let's see if it's better -->
|
||||||
|
|
||||||
<!-- BEGIN Strings added by Valere -->
|
<!-- BEGIN Strings added by Valere -->
|
||||||
|
<plurals name="poll_info">
|
||||||
|
<item quantity="zero">%d vote</item>
|
||||||
|
<item quantity="other">%d votes</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="poll_info_final">
|
||||||
|
<item quantity="zero">%d vote - Final results</item>
|
||||||
|
<item quantity="other">%d votes - Final results</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="poll_item_selected_aria">Selected Option</string>
|
||||||
|
<string name="command_description_poll">Creates a simple poll</string>
|
||||||
<!-- END Strings added by Valere -->
|
<!-- END Strings added by Valere -->
|
||||||
|
|
||||||
|
|
||||||
<!-- BEGIN Strings added by Benoit -->
|
<!-- BEGIN Strings added by Benoit -->
|
||||||
|
<string name="message_action_item_redact">Remove…</string>
|
||||||
<!-- END Strings added by Benoit -->
|
<!-- END Strings added by Benoit -->
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +28,16 @@
|
||||||
|
|
||||||
<!-- END Strings added by Benoit -->
|
<!-- END Strings added by Benoit -->
|
||||||
|
|
||||||
|
<!-- BEGIN Strings added by Onuray -->
|
||||||
|
<string name="delete_event_dialog_title">Confirm Removal</string>
|
||||||
|
<string name="delete_event_dialog_content">Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.</string>
|
||||||
|
<string name="delete_event_dialog_reason_checkbox">Include a reason</string>
|
||||||
|
<string name="delete_event_dialog_reason_hint">Reason for redacting</string>
|
||||||
|
|
||||||
|
<string name="event_redacted_by_user_reason_with_reason">Event deleted by user, reason: %1$s</string>
|
||||||
|
<string name="event_redacted_by_admin_reason_with_reason">Event moderated by room admin, reason: %1$s</string>
|
||||||
|
<!-- END Strings added by Onuray -->
|
||||||
|
|
||||||
|
|
||||||
<!-- BEGIN Strings added by Others -->
|
<!-- BEGIN Strings added by Others -->
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,11 @@
|
||||||
<item name="android:textColor">@color/button_positive_text_color_selector</item>
|
<item name="android:textColor">@color/button_positive_text_color_selector</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="VectorButtonStyleInlineBot" parent="VectorButtonStyleDestructive">
|
||||||
|
<item name="backgroundTint">@color/button_bot_background_selector</item>
|
||||||
|
<item name="android:textColor">@color/button_bot_enabled_text_color</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<!--Widget.AppCompat.Button.Borderless.Colored, which sets the text color to colorAccent,
|
<!--Widget.AppCompat.Button.Borderless.Colored, which sets the text color to colorAccent,
|
||||||
using colorControlHighlight as an overlay for focused and pressed states.-->
|
using colorControlHighlight as an overlay for focused and pressed states.-->
|
||||||
<style name="VectorButtonStyleText" parent="Widget.MaterialComponents.Button.TextButton">
|
<style name="VectorButtonStyleText" parent="Widget.MaterialComponents.Button.TextButton">
|
||||||
|
@ -183,6 +188,14 @@
|
||||||
<item name="colorControlHighlight">@android:color/white</item>
|
<item name="colorControlHighlight">@android:color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="Style.Vector.Poll.Button" parent="Widget.MaterialComponents.Button.OutlinedButton">
|
||||||
|
<item name="android:minHeight">44dp</item>
|
||||||
|
<item name="android:textAllCaps">false</item>
|
||||||
|
<item name="cornerRadius">10dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="VectorSearchView" parent="Widget.AppCompat.SearchView">
|
<style name="VectorSearchView" parent="Widget.AppCompat.SearchView">
|
||||||
<item name="searchIcon">@drawable/ic_search</item>
|
<item name="searchIcon">@drawable/ic_search</item>
|
||||||
<item name="closeIcon">@drawable/ic_x_green</item>
|
<item name="closeIcon">@drawable/ic_x_green</item>
|
||||||
|
|
Loading…
Reference in a new issue