mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-04 04:57:39 +01:00
Merge branch 'develop' into feature/ons/fix_room_topic_scroll
This commit is contained in:
commit
474ade01cf
@ -5,11 +5,15 @@ Features ✨:
|
||||
- Enable url previews for notices (#2562)
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
- Add System theme option and set as default (#904) (#2387)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Unspecced msgType field in m.sticker (#2580)
|
||||
- Wait for all room members to be known before sending a message to a e2e room (#2518)
|
||||
- Url previews sometimes attached to wrong message (#2561)
|
||||
- Room Topic not displayed correctly after visiting a link (#2551)
|
||||
- Hiding membership events works the exact opposite (#2603)
|
||||
- Tapping drawer having more than 1 room in notifications gives "malformed link" error (#2605)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
@ -40,13 +40,16 @@ import kotlin.math.abs
|
||||
|
||||
abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener {
|
||||
|
||||
lateinit var pager2: ViewPager2
|
||||
lateinit var imageTransitionView: ImageView
|
||||
lateinit var transitionImageContainer: ViewGroup
|
||||
protected val pager2: ViewPager2
|
||||
get() = views.attachmentPager
|
||||
protected val imageTransitionView: ImageView
|
||||
get() = views.transitionImageView
|
||||
protected val transitionImageContainer: ViewGroup
|
||||
get() = views.transitionImageContainer
|
||||
|
||||
var topInset = 0
|
||||
var bottomInset = 0
|
||||
var systemUiVisibility = true
|
||||
private var topInset = 0
|
||||
private var bottomInset = 0
|
||||
private var systemUiVisibility = true
|
||||
|
||||
private var overlayView: View? = null
|
||||
set(value) {
|
||||
@ -65,14 +68,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||
private lateinit var gestureDetector: GestureDetectorCompat
|
||||
|
||||
var currentPosition = 0
|
||||
private set
|
||||
|
||||
private var swipeDirection: SwipeDirection? = null
|
||||
|
||||
private fun isScaled() = attachmentsAdapter.isScaled(currentPosition)
|
||||
|
||||
private val attachmentsAdapter = AttachmentsAdapter()
|
||||
|
||||
private var wasScaled: Boolean = false
|
||||
private var isSwipeToDismissAllowed: Boolean = true
|
||||
private lateinit var attachmentsAdapter: AttachmentsAdapter
|
||||
private var isOverlayWasClicked = false
|
||||
|
||||
// private val shouldDismissToBottom: Boolean
|
||||
@ -101,10 +106,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
|
||||
setContentView(views.root)
|
||||
views.attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
|
||||
attachmentsAdapter = AttachmentsAdapter()
|
||||
views.attachmentPager.adapter = attachmentsAdapter
|
||||
imageTransitionView = views.transitionImageView
|
||||
pager2 = views.attachmentPager
|
||||
directionDetector = createSwipeDirectionDetector()
|
||||
gestureDetector = createGestureDetector()
|
||||
|
||||
|
@ -86,7 +86,7 @@ class CommonTestHelper(context: Context) {
|
||||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun syncSession(session: Session) {
|
||||
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val job = GlobalScope.launch(Dispatchers.Main) {
|
||||
@ -109,7 +109,7 @@ class CommonTestHelper(context: Context) {
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
|
||||
|
||||
await(lock)
|
||||
await(lock, timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) {
|
||||
* @param message the message to send
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(1)
|
||||
@ -151,7 +151,7 @@ class CommonTestHelper(context: Context) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
// Wait 3 second more per message
|
||||
await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
|
||||
await(latch, timeout = timeout + 3_000L * nbOfMessages)
|
||||
timeline.dispose()
|
||||
|
||||
// Check that all events has been created
|
||||
@ -215,14 +215,14 @@ class CommonTestHelper(context: Context) {
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult> {
|
||||
doSync<RegistrationResult>(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = doSync<RegistrationResult> {
|
||||
val registrationResult = doSync<RegistrationResult>(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
@ -231,7 +231,7 @@ class CommonTestHelper(context: Context) {
|
||||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
val session = (registrationResult as RegistrationResult.Success).session
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
syncSession(session, 60_000)
|
||||
}
|
||||
|
||||
return session
|
||||
|
@ -18,14 +18,21 @@ package org.matrix.android.sdk.common
|
||||
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
data class CryptoTestData(val firstSession: Session,
|
||||
val roomId: String,
|
||||
val secondSession: Session? = null,
|
||||
val thirdSession: Session? = null) {
|
||||
data class CryptoTestData(val roomId: String,
|
||||
val sessions: List<Session>) {
|
||||
|
||||
val firstSession: Session
|
||||
get() = sessions.first()
|
||||
|
||||
val secondSession: Session?
|
||||
get() = sessions.getOrNull(1)
|
||||
|
||||
val thirdSession: Session?
|
||||
get() = sessions.getOrNull(2)
|
||||
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
testHelper.signOutAndClose(firstSession)
|
||||
secondSession?.let { testHelper.signOutAndClose(it) }
|
||||
thirdSession?.let { testHelper.signOutAndClose(it) }
|
||||
sessions.forEach {
|
||||
testHelper.signOutAndClose(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
}
|
||||
}
|
||||
|
||||
return CryptoTestData(aliceSession, roomId)
|
||||
return CryptoTestData(roomId, listOf(aliceSession))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,7 +139,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
||||
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,7 +157,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
||||
return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,4 +381,30 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
room.enableEncryption()
|
||||
}
|
||||
|
||||
val sessions = mutableListOf(aliceSession)
|
||||
for (index in 1 until numberOfMembers) {
|
||||
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
|
||||
mTestHelper.doSync<Unit>(timeout = 600_000) { room.invite(session.myUserId, null, it) }
|
||||
println("TEST -> " + session.myUserId + " invited")
|
||||
mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) }
|
||||
println("TEST -> " + session.myUserId + " joined")
|
||||
sessions.add(session)
|
||||
}
|
||||
|
||||
return CryptoTestData(roomId, sessions)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.session.room.timeline
|
||||
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.test.fail
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineWithManyMembersTest : InstrumentedTest {
|
||||
|
||||
companion object {
|
||||
private const val NUMBER_OF_MEMBERS = 6
|
||||
}
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* Ensures when someone sends a message to a crowded room, everyone can decrypt the message.
|
||||
*/
|
||||
@Test
|
||||
fun everyone_should_decrypt_message_in_a_crowded_room() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS)
|
||||
|
||||
val sessionForFirstMember = cryptoTestData.firstSession
|
||||
val roomForFirstMember = sessionForFirstMember.getRoom(cryptoTestData.roomId)!!
|
||||
|
||||
val firstMessage = "First messages from Alice"
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomForFirstMember,
|
||||
firstMessage,
|
||||
1,
|
||||
600_000
|
||||
)
|
||||
|
||||
for (index in 1 until cryptoTestData.sessions.size) {
|
||||
val session = cryptoTestData.sessions[index]
|
||||
val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
|
||||
val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30))
|
||||
timelineForCurrentMember.start()
|
||||
|
||||
session.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
snapshot
|
||||
.find { it.isEncrypted() }
|
||||
?.let {
|
||||
val body = it.root.getClearContent()?.toModel<MessageContent>()?.body
|
||||
if (body?.startsWith(firstMessage).orFalse()) {
|
||||
println("User " + session.myUserId + " decrypted as " + body)
|
||||
return@createEventListener true
|
||||
} else {
|
||||
fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError)
|
||||
}
|
||||
} ?: return@createEventListener false
|
||||
}
|
||||
timelineForCurrentMember.addListener(eventsListener)
|
||||
commonTestHelper.await(lock, 600_000)
|
||||
}
|
||||
session.stopSync()
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,16 @@ interface AuthenticationService {
|
||||
*/
|
||||
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Get a SSO url
|
||||
*/
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
|
||||
|
||||
/**
|
||||
* Get the sign in or sign up fallback URL
|
||||
*/
|
||||
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String?
|
||||
|
||||
/**
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
|
@ -25,7 +25,6 @@ interface PermalinkService {
|
||||
|
||||
companion object {
|
||||
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||
const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,7 @@ data class MessageStickerContent(
|
||||
/**
|
||||
* Set in local, not from server
|
||||
*/
|
||||
@Transient
|
||||
override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL,
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.util
|
||||
|
||||
import java.net.URLEncoder
|
||||
|
||||
/**
|
||||
* Append param and value to a Url, using "?" or "&". Value parameter will be encoded
|
||||
* Return this for chaining purpose
|
||||
*/
|
||||
fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
|
||||
if (contains("?")) {
|
||||
append("&")
|
||||
} else {
|
||||
append("?")
|
||||
}
|
||||
|
||||
append(param)
|
||||
append("=")
|
||||
append(URLEncoder.encode(value, "utf-8"))
|
||||
|
||||
return this
|
||||
}
|
@ -14,25 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.auth
|
||||
package org.matrix.android.sdk.internal.auth
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all login flows
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
|
||||
*/
|
||||
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
internal const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all registration flows
|
||||
* Not documented
|
||||
*/
|
||||
const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||
internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||
|
||||
/**
|
||||
* Path to use when the client want to connect using SSO
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||
*/
|
||||
const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||
internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||
|
||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
|
||||
import org.matrix.android.sdk.internal.auth.data.RiotConfig
|
||||
@ -99,6 +100,52 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
val homeServerUrlBase = getHomeServerUrlBase() ?: return null
|
||||
|
||||
return buildString {
|
||||
append(homeServerUrlBase)
|
||||
if (providerId != null) {
|
||||
append(MSC2858_SSO_REDIRECT_PATH)
|
||||
append("/$providerId")
|
||||
} else {
|
||||
append(SSO_REDIRECT_PATH)
|
||||
}
|
||||
// Set the redirect url
|
||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
|
||||
deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
|
||||
val homeServerUrlBase = getHomeServerUrlBase() ?: return null
|
||||
|
||||
return buildString {
|
||||
append(homeServerUrlBase)
|
||||
if (forSignIn) {
|
||||
append(LOGIN_FALLBACK_PATH)
|
||||
deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
} else {
|
||||
// For sign up
|
||||
append(REGISTER_FALLBACK_PATH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHomeServerUrlBase(): String? {
|
||||
return pendingSessionData
|
||||
?.homeServerConnectionConfig
|
||||
?.homeServerUri
|
||||
?.toString()
|
||||
?.trim { it == '/' }
|
||||
}
|
||||
|
||||
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
pendingSessionData = null
|
||||
|
||||
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
@ -35,11 +36,19 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||
internal class DefaultSendEventTask @Inject constructor(
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val encryptEventTask: DefaultEncryptEventTask,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val roomAPI: RoomAPI,
|
||||
private val eventBus: EventBus) : SendEventTask {
|
||||
|
||||
override suspend fun execute(params: SendEventTask.Params): String {
|
||||
try {
|
||||
// Make sure to load all members in the room before sending the event.
|
||||
params.event.roomId
|
||||
?.takeIf { params.encrypt }
|
||||
?.let { roomId ->
|
||||
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
|
||||
}
|
||||
|
||||
val event = handleEncryption(params)
|
||||
val localId = event.eventId!!
|
||||
|
||||
|
@ -21,6 +21,8 @@ import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -28,7 +30,7 @@ import javax.inject.Inject
|
||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 6L
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 7L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
@ -40,6 +42,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
if (oldVersion <= 4) migrateTo5(realm)
|
||||
if (oldVersion <= 5) migrateTo6(realm)
|
||||
if (oldVersion <= 6) migrateTo7(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
@ -105,4 +108,18 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
.addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo7(realm: DynamicRealm) {
|
||||
Timber.d("Step 6 -> 7")
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java)
|
||||
?.transform { obj ->
|
||||
if (obj.getBoolean("areAllMembersLoaded")) {
|
||||
obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name)
|
||||
} else {
|
||||
obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name)
|
||||
}
|
||||
}
|
||||
?.removeField("areAllMembersLoaded")
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,7 @@ import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||
var areAllMembersLoaded: Boolean = false
|
||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
@ -36,5 +35,14 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||
membershipStr = value.name
|
||||
}
|
||||
|
||||
private var membersLoadStatusStr: String = RoomMembersLoadStatusType.NONE.name
|
||||
var membersLoadStatus: RoomMembersLoadStatusType
|
||||
get() {
|
||||
return RoomMembersLoadStatusType.valueOf(membersLoadStatusStr)
|
||||
}
|
||||
set(value) {
|
||||
membersLoadStatusStr = value.name
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.database.model
|
||||
|
||||
internal enum class RoomMembersLoadStatusType {
|
||||
NONE,
|
||||
LOADING,
|
||||
LOADED
|
||||
}
|
@ -17,12 +17,19 @@
|
||||
package org.matrix.android.sdk.internal.session.room.membership
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
@ -33,9 +40,7 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit> {
|
||||
@ -56,13 +61,40 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
|
||||
) : LoadRoomMembersTask {
|
||||
|
||||
override suspend fun execute(params: LoadRoomMembersTask.Params) {
|
||||
if (areAllMembersAlreadyLoaded(params.roomId)) {
|
||||
return
|
||||
when (getRoomMembersLoadStatus(params.roomId)) {
|
||||
RoomMembersLoadStatusType.NONE -> doRequest(params)
|
||||
RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params)
|
||||
RoomMembersLoadStatusType.LOADED -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun waitPreviousRequestToFinish(params: LoadRoomMembersTask.Params) {
|
||||
try {
|
||||
awaitNotEmptyResult(monarchy.realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||
realm.where(RoomEntity::class.java)
|
||||
.equalTo(RoomEntityFields.ROOM_ID, params.roomId)
|
||||
.equalTo(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, RoomMembersLoadStatusType.LOADED.name)
|
||||
}
|
||||
} catch (exception: TimeoutCancellationException) {
|
||||
// Timeout, do the request anyway (?)
|
||||
doRequest(params)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doRequest(params: LoadRoomMembersTask.Params) {
|
||||
setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.LOADING)
|
||||
|
||||
val lastToken = syncTokenStore.getLastToken()
|
||||
val response = executeRequest<RoomMembersResponse>(eventBus) {
|
||||
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
|
||||
val response = try {
|
||||
executeRequest<RoomMembersResponse>(eventBus) {
|
||||
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
// Revert status to NONE
|
||||
setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.NONE)
|
||||
throw throwable
|
||||
}
|
||||
// This will also set the status to LOADED
|
||||
insertInDb(response, params.roomId)
|
||||
}
|
||||
|
||||
@ -84,14 +116,23 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
|
||||
}
|
||||
roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
|
||||
}
|
||||
roomEntity.areAllMembersLoaded = true
|
||||
roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
|
||||
roomSummaryUpdater.update(realm, roomId, updateMembers = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false
|
||||
private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType {
|
||||
var result: RoomMembersLoadStatusType?
|
||||
Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus
|
||||
}
|
||||
return result ?: RoomMembersLoadStatusType.NONE
|
||||
}
|
||||
|
||||
private suspend fun setRoomMembersLoadStatus(roomId: String, status: RoomMembersLoadStatusType) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
roomEntity.membersLoadStatus = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
@ -53,6 +54,7 @@ import org.matrix.android.sdk.internal.database.query.filterEvents
|
||||
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import org.matrix.android.sdk.internal.util.Debouncer
|
||||
@ -81,7 +83,8 @@ internal class DefaultTimeline(
|
||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts,
|
||||
private val eventBus: EventBus,
|
||||
private val eventDecryptor: TimelineEventDecryptor,
|
||||
private val realmSessionProvider: RealmSessionProvider
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask
|
||||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||
|
||||
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
|
||||
@ -184,6 +187,13 @@ internal class DefaultTimeline(
|
||||
if (settings.shouldHandleHiddenReadReceipts()) {
|
||||
hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
|
||||
}
|
||||
|
||||
loadRoomMembersTask
|
||||
.configureWith(LoadRoomMembersTask.Params(roomId)) {
|
||||
this.callback = NoOpMatrixCallback()
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
|
||||
isReady.set(true)
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
|
||||
internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
@ -51,7 +52,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
||||
private val paginationTask: PaginationTask,
|
||||
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask
|
||||
) : TimelineService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
@ -73,7 +75,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
||||
eventBus = eventBus,
|
||||
eventDecryptor = eventDecryptor,
|
||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||
realmSessionProvider = realmSessionProvider
|
||||
realmSessionProvider = realmSessionProvider,
|
||||
loadRoomMembersTask = loadRoomMembersTask
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
|
||||
# android\.text\.TextUtils
|
||||
|
||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||
enum class===84
|
||||
enum class===85
|
||||
|
||||
### Do not import temporary legacy classes
|
||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||
|
@ -16,26 +16,6 @@
|
||||
|
||||
package im.vector.app.core.extensions
|
||||
|
||||
import java.net.URLEncoder
|
||||
|
||||
/**
|
||||
* Append param and value to a Url, using "?" or "&". Value parameter will be encoded
|
||||
* Return this for chaining purpose
|
||||
*/
|
||||
fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
|
||||
if (contains("?")) {
|
||||
append("&")
|
||||
} else {
|
||||
append("?")
|
||||
}
|
||||
|
||||
append(param)
|
||||
append("=")
|
||||
append(URLEncoder.encode(value, "utf-8"))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Ex: "https://matrix.org/" -> "matrix.org"
|
||||
*/
|
||||
|
@ -28,7 +28,7 @@ import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.databinding.ItemVerificationActionBinding
|
||||
import im.vector.app.databinding.ViewBottomSheetActionButtonBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class BottomSheetActionButton @JvmOverloads constructor(
|
||||
@ -36,7 +36,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
val views : ItemVerificationActionBinding
|
||||
val views: ViewBottomSheetActionButtonBinding
|
||||
|
||||
var title: String? = null
|
||||
set(value) {
|
||||
@ -97,8 +97,8 @@ class BottomSheetActionButton @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.item_verification_action, this)
|
||||
views = ItemVerificationActionBinding.bind(this)
|
||||
inflate(context, R.layout.view_bottom_sheet_action_button, this)
|
||||
views = ViewBottomSheetActionButtonBinding.bind(this)
|
||||
|
||||
context.withStyledAttributes(attrs, R.styleable.BottomSheetActionButton) {
|
||||
title = getString(R.styleable.BottomSheetActionButton_actionTitle) ?: ""
|
||||
|
@ -18,7 +18,7 @@ package im.vector.app.features.call
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.databinding.ViewCallControlsBinding
|
||||
@ -28,7 +28,7 @@ import org.webrtc.PeerConnection
|
||||
|
||||
class CallControlsView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val views: ViewCallControlsBinding
|
||||
|
||||
|
@ -39,7 +39,6 @@ import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.databinding.ActivityHomeBinding
|
||||
import im.vector.app.features.disclaimer.showDisclaimerDialog
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
@ -166,8 +165,8 @@ class HomeActivity :
|
||||
private fun handleIntent(intent: Intent?) {
|
||||
intent?.dataString?.let { deepLink ->
|
||||
val resolvedLink = when {
|
||||
deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> deepLink
|
||||
deepLink.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> {
|
||||
deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> deepLink
|
||||
deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> {
|
||||
// This is a bit ugly, but for now just convert to matrix.to link for compatibility
|
||||
when {
|
||||
deepLink.startsWith(USER_LINK_PREFIX) -> deepLink.substring(USER_LINK_PREFIX.length)
|
||||
@ -177,7 +176,7 @@ class HomeActivity :
|
||||
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(it)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
else -> return@let
|
||||
}
|
||||
|
||||
permalinkHandler.launch(
|
||||
@ -190,7 +189,11 @@ class HomeActivity :
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { isHandled ->
|
||||
if (!isHandled) {
|
||||
toast(R.string.permalink_malformed)
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(R.string.permalink_malformed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
@ -410,7 +413,8 @@ class HomeActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private const val ROOM_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
|
||||
private const val USER_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
|
||||
private const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
|
||||
private const val ROOM_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
|
||||
private const val USER_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ import im.vector.app.R
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.subscribeLogError
|
||||
import im.vector.app.features.call.WebRtcPeerConnectionManager
|
||||
import im.vector.app.features.command.CommandParser
|
||||
import im.vector.app.features.command.ParsedCommand
|
||||
@ -168,7 +167,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
observePowerLevel()
|
||||
room.getRoomSummaryLive()
|
||||
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback())
|
||||
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||
// Inform the SDK that the room is displayed
|
||||
session.onRoomDisplayed(initialState.roomId)
|
||||
chatEffectManager.delegate = this
|
||||
|
@ -57,7 +57,7 @@ class TimelineSettingsFactory @Inject constructor(
|
||||
return map {
|
||||
EventTypeFilter(
|
||||
eventType = it,
|
||||
stateKey = if (it == EventType.STATE_ROOM_MEMBER && userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null
|
||||
stateKey = if (it == EventType.STATE_ROOM_MEMBER && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import android.widget.LinearLayout
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.databinding.ItemTimelineEventPollResultItemBinding
|
||||
import im.vector.app.databinding.ViewPollResultLineBinding
|
||||
|
||||
class PollResultLineView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@ -31,7 +31,7 @@ class PollResultLineView @JvmOverloads constructor(
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val views: ItemTimelineEventPollResultItemBinding
|
||||
private val views: ViewPollResultLineBinding
|
||||
|
||||
var label: String? = null
|
||||
set(value) {
|
||||
@ -60,8 +60,8 @@ class PollResultLineView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.item_timeline_event_poll_result_item, this)
|
||||
views = ItemTimelineEventPollResultItemBinding.bind(this)
|
||||
inflate(context, R.layout.view_poll_result_line, this)
|
||||
views = ViewPollResultLineBinding.bind(this)
|
||||
orientation = HORIZONTAL
|
||||
|
||||
context.withStyledAttributes(attrs, R.styleable.PollResultLineView) {
|
||||
|
@ -87,7 +87,12 @@ abstract class AbstractSSOLoginFragment<VB: ViewBinding> : AbstractLoginFragment
|
||||
withState(loginViewModel) { state ->
|
||||
if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) {
|
||||
// in this case we can prefetch (not other cases for privacy concerns)
|
||||
prefetchUrl(state.getSsoUrl(null))
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = null
|
||||
)
|
||||
?.let { prefetchUrl(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -360,6 +360,9 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
|
||||
|
||||
private const val EXTRA_CONFIG = "EXTRA_CONFIG"
|
||||
|
||||
// Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
|
||||
const val VECTOR_REDIRECT_URL = "element://connect"
|
||||
|
||||
fun newIntent(context: Context, loginConfig: LoginConfig?): Intent {
|
||||
return Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_CONFIG, loginConfig)
|
||||
|
@ -193,7 +193,12 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders
|
||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||
override fun onProviderSelected(id: String?) {
|
||||
openInCustomTab(state.getSsoUrl(id))
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = id
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -76,8 +76,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
||||
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()
|
||||
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||
override fun onProviderSelected(id: String?) {
|
||||
val url = withState(loginViewModel) { it.getSsoUrl(id) }
|
||||
openInCustomTab(url)
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = id
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +109,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
||||
|
||||
private fun submit() = withState(loginViewModel) { state ->
|
||||
if (state.loginMode is LoginMode.Sso) {
|
||||
openInCustomTab(state.getSsoUrl(null))
|
||||
loginViewModel.getSsoUrl(
|
||||
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = state.deviceId,
|
||||
providerId = null
|
||||
)
|
||||
?.let { openInCustomTab(it) }
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
|
||||
}
|
||||
|
@ -818,4 +818,12 @@ class LoginViewModel @AssistedInject constructor(
|
||||
fun getInitialHomeServerUrl(): String? {
|
||||
return loginConfig?.homeServerUrl
|
||||
}
|
||||
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId)
|
||||
}
|
||||
|
||||
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
|
||||
return authenticationService.getFallbackUrl(forSignIn, deviceId)
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,6 @@ import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.PersistState
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.core.extensions.appendParamToUrl
|
||||
import org.matrix.android.sdk.api.auth.MSC2858_SSO_REDIRECT_PATH
|
||||
import org.matrix.android.sdk.api.auth.SSO_REDIRECT_PATH
|
||||
import org.matrix.android.sdk.api.auth.SSO_REDIRECT_URL_PARAM
|
||||
|
||||
data class LoginViewState(
|
||||
val asyncLoginAction: Async<Unit> = Uninitialized,
|
||||
@ -69,27 +65,4 @@ data class LoginViewState(
|
||||
fun isUserLogged(): Boolean {
|
||||
return asyncLoginAction is Success
|
||||
}
|
||||
|
||||
fun getSsoUrl(providerId: String?): String {
|
||||
return buildString {
|
||||
append(homeServerUrl?.trim { it == '/' })
|
||||
if (providerId != null) {
|
||||
append(MSC2858_SSO_REDIRECT_PATH)
|
||||
append("/$providerId")
|
||||
} else {
|
||||
append(SSO_REDIRECT_PATH)
|
||||
}
|
||||
// Set a redirect url we will intercept later
|
||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM, VECTOR_REDIRECT_URL)
|
||||
deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
|
||||
private const val VECTOR_REDIRECT_URL = "element://connect"
|
||||
}
|
||||
}
|
||||
|
@ -33,14 +33,11 @@ import android.webkit.WebViewClient
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.appendParamToUrl
|
||||
import im.vector.app.core.utils.AssetReader
|
||||
import im.vector.app.databinding.FragmentLoginWebBinding
|
||||
import im.vector.app.features.signout.soft.SoftLogoutAction
|
||||
import im.vector.app.features.signout.soft.SoftLogoutViewModel
|
||||
|
||||
import org.matrix.android.sdk.api.auth.LOGIN_FALLBACK_PATH
|
||||
import org.matrix.android.sdk.api.auth.REGISTER_FALLBACK_PATH
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
@ -119,19 +116,7 @@ class LoginWebFragment @Inject constructor(
|
||||
}
|
||||
|
||||
private fun launchWebView(state: LoginViewState) {
|
||||
val url = buildString {
|
||||
append(state.homeServerUrl?.trim { it == '/' })
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
append(LOGIN_FALLBACK_PATH)
|
||||
state.deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
append(REGISTER_FALLBACK_PATH)
|
||||
}
|
||||
}
|
||||
val url = loginViewModel.getFallbackUrl(state.signMode == SignMode.SignIn, state.deviceId) ?: return
|
||||
|
||||
views.loginWebWebView.loadUrl(url)
|
||||
|
||||
|
@ -18,6 +18,8 @@ package im.vector.app.features.themes
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
@ -39,10 +41,14 @@ object ThemeUtils {
|
||||
const val APPLICATION_THEME_KEY = "APPLICATION_THEME_KEY"
|
||||
|
||||
// the theme possible values
|
||||
private const val SYSTEM_THEME_VALUE = "system"
|
||||
private const val THEME_DARK_VALUE = "dark"
|
||||
private const val THEME_LIGHT_VALUE = "light"
|
||||
private const val THEME_BLACK_VALUE = "black"
|
||||
|
||||
// The default theme
|
||||
private const val DEFAULT_THEME = SYSTEM_THEME_VALUE
|
||||
|
||||
private var currentTheme = AtomicReference<String>(null)
|
||||
|
||||
private val mColorByAttr = HashMap<Int, Int>()
|
||||
@ -54,13 +60,12 @@ object ThemeUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if current theme is Light or Status
|
||||
* @return true if current theme is Light or current theme is System and system theme is light
|
||||
*/
|
||||
fun isLightTheme(context: Context): Boolean {
|
||||
return when (getApplicationTheme(context)) {
|
||||
THEME_LIGHT_VALUE -> true
|
||||
else -> false
|
||||
}
|
||||
val theme = getApplicationTheme(context)
|
||||
return theme == THEME_LIGHT_VALUE
|
||||
|| (theme == SYSTEM_THEME_VALUE && !isSystemDarkTheme(context.resources))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,11 +78,11 @@ object ThemeUtils {
|
||||
val currentTheme = this.currentTheme.get()
|
||||
return if (currentTheme == null) {
|
||||
val prefs = DefaultSharedPreferences.getInstance(context)
|
||||
var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) ?: THEME_LIGHT_VALUE
|
||||
var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, DEFAULT_THEME) ?: DEFAULT_THEME
|
||||
if (themeFromPref == "status") {
|
||||
// Migrate to light theme, which is the closest theme
|
||||
themeFromPref = THEME_LIGHT_VALUE
|
||||
prefs.edit { putString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) }
|
||||
// Migrate to the default theme
|
||||
themeFromPref = DEFAULT_THEME
|
||||
prefs.edit { putString(APPLICATION_THEME_KEY, DEFAULT_THEME) }
|
||||
}
|
||||
this.currentTheme.set(themeFromPref)
|
||||
themeFromPref
|
||||
@ -86,6 +91,13 @@ object ThemeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if system theme is dark
|
||||
*/
|
||||
private fun isSystemDarkTheme(resources: Resources): Boolean {
|
||||
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the application theme
|
||||
*
|
||||
@ -93,11 +105,14 @@ object ThemeUtils {
|
||||
*/
|
||||
fun setApplicationTheme(context: Context, aTheme: String) {
|
||||
currentTheme.set(aTheme)
|
||||
when (aTheme) {
|
||||
THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark)
|
||||
THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black)
|
||||
else -> context.setTheme(R.style.AppTheme_Light)
|
||||
}
|
||||
context.setTheme(
|
||||
when (aTheme) {
|
||||
SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.AppTheme_Dark else R.style.AppTheme_Light
|
||||
THEME_DARK_VALUE -> R.style.AppTheme_Dark
|
||||
THEME_BLACK_VALUE -> R.style.AppTheme_Black
|
||||
else -> R.style.AppTheme_Light
|
||||
}
|
||||
)
|
||||
|
||||
// Clear the cache
|
||||
mColorByAttr.clear()
|
||||
@ -110,6 +125,7 @@ object ThemeUtils {
|
||||
*/
|
||||
fun setActivityTheme(activity: Activity, otherThemes: ActivityOtherThemes) {
|
||||
when (getApplicationTheme(activity)) {
|
||||
SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(activity.resources)) activity.setTheme(otherThemes.dark)
|
||||
THEME_DARK_VALUE -> activity.setTheme(otherThemes.dark)
|
||||
THEME_BLACK_VALUE -> activity.setTheme(otherThemes.black)
|
||||
}
|
||||
@ -117,40 +133,6 @@ object ThemeUtils {
|
||||
mColorByAttr.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TabLayout colors.
|
||||
* It seems that there is no proper way to manage it with the manifest file.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param layout the layout
|
||||
*/
|
||||
/*
|
||||
fun setTabLayoutTheme(activity: Activity, layout: TabLayout) {
|
||||
if (activity is VectorGroupDetailsActivity) {
|
||||
val textColor: Int
|
||||
val underlineColor: Int
|
||||
val backgroundColor: Int
|
||||
|
||||
if (TextUtils.equals(getApplicationTheme(activity), THEME_LIGHT_VALUE)) {
|
||||
textColor = ContextCompat.getColor(activity, android.R.color.white)
|
||||
underlineColor = textColor
|
||||
backgroundColor = ContextCompat.getColor(activity, R.color.tab_groups)
|
||||
} else if (TextUtils.equals(getApplicationTheme(activity), THEME_STATUS_VALUE)) {
|
||||
textColor = ContextCompat.getColor(activity, android.R.color.white)
|
||||
underlineColor = textColor
|
||||
backgroundColor = getColor(activity, R.attr.colorPrimary)
|
||||
} else {
|
||||
textColor = ContextCompat.getColor(activity, R.color.tab_groups)
|
||||
underlineColor = textColor
|
||||
backgroundColor = getColor(activity, R.attr.colorPrimary)
|
||||
}
|
||||
|
||||
layout.setTabTextColors(textColor, textColor)
|
||||
layout.setSelectedTabIndicatorColor(underlineColor)
|
||||
layout.setBackgroundColor(backgroundColor)
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Translates color attributes to colors
|
||||
*
|
||||
|
@ -20,19 +20,19 @@ import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.databinding.ItemSignoutActionBinding
|
||||
import im.vector.app.databinding.ViewSignOutBottomSheetActionButtonBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class SignOutBottomSheetActionButton @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val views: ItemSignoutActionBinding
|
||||
private val views: ViewSignOutBottomSheetActionButtonBinding
|
||||
|
||||
var action: (() -> Unit)? = null
|
||||
|
||||
@ -67,8 +67,8 @@ class SignOutBottomSheetActionButton @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.item_signout_action, this)
|
||||
views = ItemSignoutActionBinding.bind(this)
|
||||
inflate(context, R.layout.view_sign_out_bottom_sheet_action_button, this)
|
||||
views = ViewSignOutBottomSheetActionButtonBinding.bind(this)
|
||||
|
||||
context.withStyledAttributes(attrs, R.styleable.SignOutBottomSheetActionButton) {
|
||||
title = getString(R.styleable.SignOutBottomSheetActionButton_actionTitle) ?: ""
|
||||
|
@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/signedOutActionClickable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="50dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionIconImageView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_secure_backup"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionTitleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/secure_backup_setup"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="17sp" />
|
||||
|
||||
</LinearLayout>
|
@ -24,10 +24,10 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_share"
|
||||
tools:visibility="visible"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@drawable/ic_share"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<TextView
|
||||
@ -70,8 +70,8 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_arrow_right"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@drawable/ic_arrow_right" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="android.widget.FrameLayout">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/itemVerificationClickableZone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?riotx_bottom_sheet_background"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="64dp"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemVerificationLeftIcon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:scaleType="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@drawable/ic_share"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemVerificationActionTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/riotx_accent"
|
||||
android:textSize="16sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/itemVerificationActionSubTitle"
|
||||
app:layout_constraintEnd_toStartOf="@+id/itemVerificationActionIcon"
|
||||
app:layout_constraintStart_toEndOf="@+id/itemVerificationLeftIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_goneMarginStart="0dp"
|
||||
tools:text="@string/start_verification" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemVerificationActionSubTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/itemVerificationActionIcon"
|
||||
app:layout_constraintStart_toStartOf="@+id/itemVerificationActionTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/itemVerificationActionTitle"
|
||||
tools:text="For maximum security, do this in person"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemVerificationActionIcon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@drawable/ic_arrow_right" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</merge>
|
@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="android.widget.FrameLayout">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/ringingControls"
|
||||
@ -23,8 +24,8 @@
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="@color/white" />
|
||||
app:tint="@color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ringingControlDecline"
|
||||
@ -36,8 +37,8 @@
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call_end"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="@color/white" />
|
||||
app:tint="@color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:layout_width="match_parent"
|
||||
@ -69,8 +70,8 @@
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_home_bottom_chat"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/muteIcon"
|
||||
@ -82,10 +83,10 @@
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_microphone_off"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:contentDescription="@string/a11y_mute_microphone"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
tools:src="@drawable/ic_microphone_on"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
tools:src="@drawable/ic_microphone_on" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_end_call"
|
||||
@ -97,8 +98,8 @@
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call_end"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="@color/white" />
|
||||
app:tint="@color/white"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/videoToggleIcon"
|
||||
@ -110,9 +111,9 @@
|
||||
android:padding="16dp"
|
||||
android:src="@drawable/ic_call_videocam_off_default"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:contentDescription="@string/a11y_stop_camera"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_more"
|
||||
@ -125,8 +126,8 @@
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_more_vertical"
|
||||
app:backgroundTint="?attr/riotx_background"
|
||||
tools:ignore="MissingConstraints,MissingPrefix"
|
||||
app:tint="?attr/riotx_text_primary" />
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
tools:ignore="MissingConstraints,MissingPrefix" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:layout_width="match_parent"
|
||||
@ -202,4 +203,4 @@
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent" />-->
|
||||
|
||||
</FrameLayout>
|
||||
</merge>
|
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="android.widget.FrameLayout">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/signedOutActionClickable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="50dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionIconImageView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_secure_backup"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionTitleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/secure_backup_setup"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="17sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
@ -92,12 +92,14 @@
|
||||
|
||||
<!-- Theme -->
|
||||
<string-array name="theme_entries">
|
||||
<item>@string/system_theme</item>
|
||||
<item>@string/light_theme</item>
|
||||
<item>@string/dark_theme</item>
|
||||
<item>@string/black_them</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="theme_values">
|
||||
<item>system</item>
|
||||
<item>light</item>
|
||||
<item>dark</item>
|
||||
<item>black</item>
|
||||
|
@ -8,6 +8,7 @@
|
||||
<string name="resources_script">Latn</string>
|
||||
|
||||
<!-- theme -->
|
||||
<string name="system_theme">System Default</string>
|
||||
<string name="light_theme">Light Theme</string>
|
||||
<string name="dark_theme">Dark Theme</string>
|
||||
<string name="black_them">Black Theme</string>
|
||||
|
@ -13,7 +13,7 @@
|
||||
app:fragment="im.vector.app.features.settings.locale.LocalePickerFragment" />
|
||||
|
||||
<im.vector.app.core.preference.VectorListPreference
|
||||
android:defaultValue="light"
|
||||
android:defaultValue="system"
|
||||
android:entries="@array/theme_entries"
|
||||
android:entryValues="@array/theme_values"
|
||||
android:key="APPLICATION_THEME_KEY"
|
||||
|
Loading…
x
Reference in New Issue
Block a user