Merge branch 'develop' into feature/fix_call_sound_device

This commit is contained in:
Onuray Sahin 2020-08-28 02:53:46 +03:00 committed by GitHub
commit 7d76264b25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 546 additions and 306 deletions

View file

@ -1,6 +1,6 @@
### Pull Request Checklist ### Pull Request Checklist
<!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) before submitting your pull request --> <!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
- [ ] Changes has been tested on an Android device or Android emulator with API 21 - [ ] Changes has been tested on an Android device or Android emulator with API 21
- [ ] UI change has been tested on both light and dark themes - [ ] UI change has been tested on both light and dark themes

View file

@ -1,4 +1,4 @@
A full developer contributors list can be found [here](https://github.com/vector-im/riotX-android/graphs/contributors). A full developer contributors list can be found [here](https://github.com/vector-im/element-android/graphs/contributors).
# Core team: # Core team:

View file

@ -5,6 +5,7 @@ Features ✨:
- -
Improvements 🙌: Improvements 🙌:
- You can now join room through permalink and within room directory search
- Add long click gesture to copy userId, user display name, room name, room topic and room alias (#1774) - Add long click gesture to copy userId, user display name, room name, room topic and room alias (#1774)
Bugfix 🐛: Bugfix 🐛:
@ -15,6 +16,8 @@ Bugfix 🐛:
- Fix bad color for settings icon on Android < 24 (#1786) - Fix bad color for settings icon on Android < 24 (#1786)
- Change user or room avatar: when selecting Gallery, I'm not proposed to crop the selected image (#1590) - Change user or room avatar: when selecting Gallery, I'm not proposed to crop the selected image (#1590)
- Loudspeaker is always used (#1685) - Loudspeaker is always used (#1685)
- Fix uploads still don't work with room v6 (#1879)
- Can't handle ongoing call events in background (#1992)
Translations 🗣: Translations 🗣:
- -

View file

@ -1,9 +1,9 @@
[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop) [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
[![Weblate](https://translate.riot.im/widgets/element-android/-/svg-badge.svg)](https://translate.riot.im/engage/element-android/?utm_source=widget) [![Weblate](https://translate.riot.im/widgets/element-android/-/svg-badge.svg)](https://translate.riot.im/engage/element-android/?utm_source=widget)
[![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org)
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=alert_status)](https://sonarcloud.io/dashboard?id=vector.android.riotx) [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=alert_status)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=vector.android.riotx) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=bugs)](https://sonarcloud.io/dashboard?id=vector.android.riotx) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=bugs)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
# Element Android # Element Android
@ -27,6 +27,6 @@ The team will work to add them on a regular basis.
## Contributing ## Contributing
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects! Please refer to [CONTRIBUTING.md](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#element-android:matrix.org). Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#element-android:matrix.org).

View file

@ -81,15 +81,15 @@ apply plugin: 'org.sonarqube'
sonarqube { sonarqube {
properties { properties {
property "sonar.projectName", "RiotX-Android" property "sonar.projectName", "Element-Android"
property "sonar.projectKey", "vector.android.riotx" property "sonar.projectKey", "im.vector.app.android"
property "sonar.host.url", "https://sonarcloud.io" property "sonar.host.url", "https://sonarcloud.io"
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
property "sonar.sourceEncoding", "UTF-8" property "sonar.sourceEncoding", "UTF-8"
property "sonar.links.homepage", "https://github.com/vector-im/riotX-android/" property "sonar.links.homepage", "https://github.com/vector-im/element-android/"
property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/riotx-android" property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/riotx-android"
property "sonar.links.scm", "https://github.com/vector-im/riotX-android/" property "sonar.links.scm", "https://github.com/vector-im/element-android/"
property "sonar.links.issue", "https://github.com/vector-im/riotX-android/issues" property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
property "sonar.organization", "new_vector_ltd_organization" property "sonar.organization", "new_vector_ltd_organization"
property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid" property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid"
} }
@ -100,11 +100,18 @@ project(":vector") {
properties { properties {
property "sonar.sources", project(":vector").android.sourceSets.main.java.srcDirs property "sonar.sources", project(":vector").android.sourceSets.main.java.srcDirs
// exclude source code from analyses separated by a colon (:) // exclude source code from analyses separated by a colon (:)
// property "sonar.exclusions", "**/*.*" // Exclude Java source
property "sonar.exclusions", "**/BugReporterMultipartBody.java"
} }
} }
} }
project(":diff-match-patch") {
sonarqube {
skipProject = true
}
}
//project(":matrix-sdk-android") { //project(":matrix-sdk-android") {
// sonarqube { // sonarqube {
// properties { // properties {

View file

@ -131,8 +131,6 @@ dependencies {
// Network // Network
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version"
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1")) implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1"))
implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:okhttp'

View file

@ -34,4 +34,6 @@ interface CallSignalingService {
fun removeCallListener(listener: CallsListener) fun removeCallListener(listener: CallsListener)
fun getCallWithId(callId: String) : MxCall? fun getCallWithId(callId: String) : MxCall?
fun isThereAnyActiveCall(): Boolean
} }

View file

@ -25,7 +25,12 @@ import android.net.Uri
*/ */
sealed class PermalinkData { sealed class PermalinkData {
data class RoomLink(val roomIdOrAlias: String, val isRoomAlias: Boolean, val eventId: String?) : PermalinkData() data class RoomLink(
val roomIdOrAlias: String,
val isRoomAlias: Boolean,
val eventId: String?,
val viaParameters: List<String>
) : PermalinkData()
data class UserLink(val userId: String) : PermalinkData() data class UserLink(val userId: String) : PermalinkData()

View file

@ -18,6 +18,7 @@
package org.matrix.android.sdk.api.session.permalinks package org.matrix.android.sdk.api.session.permalinks
import android.net.Uri import android.net.Uri
import android.net.UrlQuerySanitizer
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
/** /**
@ -40,14 +41,13 @@ object PermalinkParser {
if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) { if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) {
return PermalinkData.FallbackLink(uri) return PermalinkData.FallbackLink(uri)
} }
val fragment = uri.fragment val fragment = uri.fragment
if (fragment.isNullOrEmpty()) { if (fragment.isNullOrEmpty()) {
return PermalinkData.FallbackLink(uri) return PermalinkData.FallbackLink(uri)
} }
val indexOfQuery = fragment.indexOf("?") val indexOfQuery = fragment.indexOf("?")
val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment
val viaQueryParameters = fragment.getViaParameters()
// we are limiting to 2 params // we are limiting to 2 params
val params = safeFragment val params = safeFragment
@ -65,17 +65,29 @@ object PermalinkParser {
PermalinkData.RoomLink( PermalinkData.RoomLink(
roomIdOrAlias = identifier, roomIdOrAlias = identifier,
isRoomAlias = false, isRoomAlias = false,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) } eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters
) )
} }
MatrixPatterns.isRoomAlias(identifier) -> { MatrixPatterns.isRoomAlias(identifier) -> {
PermalinkData.RoomLink( PermalinkData.RoomLink(
roomIdOrAlias = identifier, roomIdOrAlias = identifier,
isRoomAlias = true, isRoomAlias = true,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) } eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters
) )
} }
else -> PermalinkData.FallbackLink(uri) else -> PermalinkData.FallbackLink(uri)
} }
} }
private fun String.getViaParameters(): List<String> {
return UrlQuerySanitizer(this)
.parameterList
.filter {
it.mParameter == "via"
}.map {
it.mValue
}
}
} }

View file

@ -19,13 +19,12 @@ package org.matrix.android.sdk.internal.network
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import dagger.Lazy import dagger.Lazy
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import okhttp3.Call import okhttp3.Call
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import javax.inject.Inject import javax.inject.Inject
internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) { internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
@ -50,7 +49,6 @@ internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
return okHttpClient.get().newCall(request) return okHttpClient.get().newCall(request)
} }
}) })
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(UnitConverterFactory) .addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi)) .addConverterFactory(MoshiConverterFactory.create(moshi))
.build() .build()

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.network.parsing
import androidx.annotation.Nullable
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import java.io.IOException
import java.lang.reflect.Type
import java.math.BigDecimal
/**
* This is used to check if NUMBER in json is integer or double, so we can preserve typing when serializing/deserializing in a row.
*/
interface CheckNumberType {
companion object {
val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory {
@Nullable
override fun create(type: Type, annotations: Set<Annotation?>?, moshi: Moshi): JsonAdapter<*>? {
if (type !== Any::class.java) {
return null
}
val delegate: JsonAdapter<Any> = moshi.nextAdapter(this, Any::class.java, emptySet())
return object : JsonAdapter<Any?>() {
@Nullable
@Throws(IOException::class)
override fun fromJson(reader: JsonReader): Any? {
return if (reader.peek() !== JsonReader.Token.NUMBER) {
delegate.fromJson(reader)
} else {
val numberAsString = reader.nextString()
val decimal = BigDecimal(numberAsString)
if (decimal.scale() <= 0) {
decimal.intValueExact()
} else {
decimal.toDouble()
}
}
}
override fun toJson(writer: JsonWriter, value: Any?) {
delegate.toJson(writer, value)
}
}
}
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.call
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
@SessionScope
internal class ActiveCallHandler @Inject constructor() {
private val activeCallListLiveData: MutableLiveData<MutableList<MxCall>> by lazy {
MutableLiveData<MutableList<MxCall>>(mutableListOf())
}
fun addCall(call: MxCall) {
activeCallListLiveData.postValue(activeCallListLiveData.value?.apply { add(call) })
}
fun removeCall(callId: String) {
activeCallListLiveData.postValue(activeCallListLiveData.value?.apply { removeAll { it.callId == callId } })
}
fun getCallWithId(callId: String): MxCall? {
return activeCallListLiveData.value?.find { it.callId == callId }
}
fun getActiveCallsLiveData(): LiveData<MutableList<MxCall>> = activeCallListLiveData
}

View file

@ -49,6 +49,7 @@ import javax.inject.Inject
internal class DefaultCallSignalingService @Inject constructor( internal class DefaultCallSignalingService @Inject constructor(
@UserId @UserId
private val userId: String, private val userId: String,
private val activeCallHandler: ActiveCallHandler,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val roomEventSender: RoomEventSender, private val roomEventSender: RoomEventSender,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
@ -57,8 +58,6 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callListeners = mutableSetOf<CallsListener>() private val callListeners = mutableSetOf<CallsListener>()
private val activeCalls = mutableListOf<MxCall>()
private val cachedTurnServerResponse = object { private val cachedTurnServerResponse = object {
// Keep one minute safe to avoid considering the data is valid and then actually it is not when effectively using it. // Keep one minute safe to avoid considering the data is valid and then actually it is not when effectively using it.
private val MIN_TTL = 60 private val MIN_TTL = 60
@ -97,7 +96,7 @@ internal class DefaultCallSignalingService @Inject constructor(
} }
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
return MxCallImpl( val call = MxCallImpl(
callId = UUID.randomUUID().toString(), callId = UUID.randomUUID().toString(),
isOutgoing = true, isOutgoing = true,
roomId = roomId, roomId = roomId,
@ -106,8 +105,9 @@ internal class DefaultCallSignalingService @Inject constructor(
isVideoCall = isVideoCall, isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
roomEventSender = roomEventSender roomEventSender = roomEventSender
).also { )
activeCalls.add(it) activeCallHandler.addCall(call).also {
return call
} }
} }
@ -120,8 +120,12 @@ internal class DefaultCallSignalingService @Inject constructor(
} }
override fun getCallWithId(callId: String): MxCall? { override fun getCallWithId(callId: String): MxCall? {
Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}") Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}")
return activeCalls.find { it.callId == callId } return activeCallHandler.getCallWithId(callId)
}
override fun isThereAnyActiveCall(): Boolean {
return activeCallHandler.getActiveCallsLiveData().value?.isNotEmpty() == true
} }
internal fun onCallEvent(event: Event) { internal fun onCallEvent(event: Event) {
@ -152,6 +156,7 @@ internal class DefaultCallSignalingService @Inject constructor(
// Always ignore local echos of invite // Always ignore local echos of invite
return return
} }
event.getClearContent().toModel<CallInviteContent>()?.let { content -> event.getClearContent().toModel<CallInviteContent>()?.let { content ->
val incomingCall = MxCallImpl( val incomingCall = MxCallImpl(
callId = content.callId ?: return@let, callId = content.callId ?: return@let,
@ -163,7 +168,7 @@ internal class DefaultCallSignalingService @Inject constructor(
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
roomEventSender = roomEventSender roomEventSender = roomEventSender
) )
activeCalls.add(incomingCall) activeCallHandler.addCall(incomingCall)
onCallInvite(incomingCall, content) onCallInvite(incomingCall, content)
} }
} }
@ -185,8 +190,8 @@ internal class DefaultCallSignalingService @Inject constructor(
return return
} }
activeCallHandler.removeCall(content.callId)
onCallHangup(content) onCallHangup(content)
activeCalls.removeAll { it.callId == content.callId }
} }
} }
EventType.CALL_CANDIDATES -> { EventType.CALL_CANDIDATES -> {
@ -195,7 +200,7 @@ internal class DefaultCallSignalingService @Inject constructor(
return return
} }
event.getClearContent().toModel<CallCandidatesContent>()?.let { content -> event.getClearContent().toModel<CallCandidatesContent>()?.let { content ->
activeCalls.firstOrNull { it.callId == content.callId }?.let { activeCallHandler.getCallWithId(content.callId)?.let {
onCallIceCandidate(it, content) onCallIceCandidate(it, content)
} }
} }

View file

@ -130,21 +130,6 @@ internal interface RoomAPI {
@Body content: Content? @Body content: Content?
): Call<SendResponse> ): Call<SendResponse>
/**
* Send an event to a room.
*
* @param txId the transaction Id
* @param roomId the room id
* @param eventType the event type
* @param content the event content as string
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}")
fun send(@Path("txId") txId: String,
@Path("roomId") roomId: String,
@Path("eventType") eventType: String,
@Body content: String?
): Call<SendResponse>
/** /**
* Get the context surrounding an event. * Get the context surrounding an event.
* *

View file

@ -208,7 +208,7 @@ internal class DefaultRelationService @AssistedInject constructor(
} }
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event) val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
} }

View file

@ -120,7 +120,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho) localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
} }
val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent) val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, event = encryptedEvent)
return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
} else { } else {
val sendState = when (error) { val sendState = when (error) {
@ -129,8 +129,11 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
} }
localEchoRepository.updateSendState(localEvent.eventId, sendState) localEchoRepository.updateSendState(localEvent.eventId, sendState)
// always return success, or the chain will be stuck for ever! // always return success, or the chain will be stuck for ever!
val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage val nextWorkerParams = SendEventWorker.Params(
?: "Error") sessionId = params.sessionId,
event = localEvent,
lastFailureMessage = error?.localizedMessage ?: "Error"
)
return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
} }
} }

View file

@ -105,7 +105,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
} }
private fun createSendEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest { private fun createSendEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event) val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)

View file

@ -65,7 +65,7 @@ internal class RoomEventSender @Inject constructor(
} }
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event) val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)

View file

@ -21,16 +21,16 @@ import android.content.Context
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event 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.api.session.room.send.SendState
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.network.executeRequest 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.RoomAPI
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -47,24 +47,11 @@ internal class SendEventWorker(context: Context,
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
override val sessionId: String, override val sessionId: String,
// TODO remove after some time, it's used for compat override val lastFailureMessage: String? = null,
val event: Event? = null, val event: Event? = null,
val eventId: String? = null, // Keep for compat at the moment, will be removed later
val roomId: String? = null, val eventId: String? = null
val type: String? = null, ) : SessionWorkerParams
val contentStr: String? = null,
override val lastFailureMessage: String? = null
) : SessionWorkerParams {
constructor(sessionId: String, event: Event, lastFailureMessage: String? = null) : this(
sessionId = sessionId,
eventId = event.eventId,
roomId = event.roomId,
type = event.type,
contentStr = ContentMapper.map(event.content),
lastFailureMessage = lastFailureMessage
)
}
@Inject lateinit var localEchoRepository: LocalEchoRepository @Inject lateinit var localEchoRepository: LocalEchoRepository
@Inject lateinit var roomAPI: RoomAPI @Inject lateinit var roomAPI: RoomAPI
@ -77,27 +64,31 @@ internal class SendEventWorker(context: Context,
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this) sessionComponent.inject(this)
if (params.eventId == null || params.roomId == null || params.type == null) {
// compat with old params, make it fail if any val event = params.event
if (params.event?.eventId != null) { if (event?.eventId == null || event.roomId == null) {
localEchoRepository.updateSendState(params.event.eventId, SendState.UNDELIVERED) // Old way of sending
if (params.eventId != null) {
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
} }
return Result.success() return Result.success()
.also { Timber.e("Work cancelled due to bad input data") }
} }
if (params.lastFailureMessage != null) { if (params.lastFailureMessage != null) {
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED) localEchoRepository.updateSendState(event.eventId, SendState.UNDELIVERED)
// Transmit the error // Transmit the error
return Result.success(inputData) return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") } .also { Timber.e("Work cancelled due to input error from parent") }
} }
return try { return try {
sendEvent(params.eventId, params.roomId, params.type, params.contentStr) sendEvent(event.eventId, event.roomId, event.type, event.content)
Result.success() Result.success()
} catch (exception: Throwable) { } catch (exception: Throwable) {
// It does start from 0, we want it to stop if it fails the third time // It does start from 0, we want it to stop if it fails the third time
val currentAttemptCount = runAttemptCount + 1 val currentAttemptCount = runAttemptCount + 1
if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) { if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) {
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED) localEchoRepository.updateSendState(event.eventId, SendState.UNDELIVERED)
return Result.success() return Result.success()
} else { } else {
Result.retry() Result.retry()
@ -105,10 +96,10 @@ internal class SendEventWorker(context: Context,
} }
} }
private suspend fun sendEvent(eventId: String, roomId: String, type: String, contentStr: String?) { private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
localEchoRepository.updateSendState(eventId, SendState.SENDING) localEchoRepository.updateSendState(eventId, SendState.SENDING)
executeRequest<SendResponse>(eventBus) { executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.send(eventId, roomId, type, contentStr) apiCall = roomAPI.send(eventId, roomId, type, content)
} }
localEchoRepository.updateSendState(eventId, SendState.SENT) localEchoRepository.updateSendState(eventId, SendState.SENT)
} }

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.job
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.squareup.moshi.JsonEncodingException import com.squareup.moshi.JsonEncodingException
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.failure.isTokenError
@ -30,11 +31,14 @@ import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.Debouncer import org.matrix.android.sdk.internal.util.Debouncer
import org.matrix.android.sdk.internal.util.createUIHandler import org.matrix.android.sdk.internal.util.createUIHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
import timber.log.Timber import timber.log.Timber
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.Timer import java.util.Timer
@ -48,8 +52,9 @@ private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
internal class SyncThread @Inject constructor(private val syncTask: SyncTask, internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val typingUsersTracker: DefaultTypingUsersTracker, private val typingUsersTracker: DefaultTypingUsersTracker,
private val networkConnectivityChecker: NetworkConnectivityChecker, private val networkConnectivityChecker: NetworkConnectivityChecker,
private val backgroundDetectionObserver: BackgroundDetectionObserver) private val backgroundDetectionObserver: BackgroundDetectionObserver,
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private val activeCallHandler: ActiveCallHandler
) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
private var state: SyncState = SyncState.Idle private var state: SyncState = SyncState.Idle
private var liveState = MutableLiveData<SyncState>(state) private var liveState = MutableLiveData<SyncState>(state)
@ -62,6 +67,12 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private var isTokenValid = true private var isTokenValid = true
private var retryNoNetworkTask: TimerTask? = null private var retryNoNetworkTask: TimerTask? = null
private val activeCallListObserver = Observer<MutableList<MxCall>> { activeCalls ->
if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) {
pause()
}
}
init { init {
updateStateTo(SyncState.Idle) updateStateTo(SyncState.Idle)
} }
@ -115,9 +126,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
override fun run() { override fun run() {
Timber.v("Start syncing...") Timber.v("Start syncing...")
isStarted = true isStarted = true
networkConnectivityChecker.register(this) networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
registerActiveCallsObserver()
while (state != SyncState.Killing) { while (state != SyncState.Killing) {
Timber.v("Entering loop, state: $state") Timber.v("Entering loop, state: $state")
if (!isStarted) { if (!isStarted) {
@ -163,6 +176,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
updateStateTo(SyncState.Killed) updateStateTo(SyncState.Killed)
backgroundDetectionObserver.unregister(this) backgroundDetectionObserver.unregister(this)
networkConnectivityChecker.unregister(this) networkConnectivityChecker.unregister(this)
unregisterActiveCallsObserver()
}
private fun registerActiveCallsObserver() {
syncScope.launch(Dispatchers.Main) {
activeCallHandler.getActiveCallsLiveData().observeForever(activeCallListObserver)
}
}
private fun unregisterActiveCallsObserver() {
syncScope.launch(Dispatchers.Main) {
activeCallHandler.getActiveCallsLiveData().removeObserver(activeCallListObserver)
}
} }
private suspend fun doSync(params: SyncTask.Params) { private suspend fun doSync(params: SyncTask.Params) {
@ -215,6 +241,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
override fun onMoveToBackground() { override fun onMoveToBackground() {
if (activeCallHandler.getActiveCallsLiveData().value.isNullOrEmpty()) {
pause() pause()
} }
}
} }

View file

@ -19,13 +19,23 @@ package org.matrix.android.sdk.internal.worker
import androidx.work.Data import androidx.work.Data
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
object WorkerParamsFactory { internal object WorkerParamsFactory {
val moshi by lazy {
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
// and we lost typing information doing so.
// We don't want this check to be done on all adapters, so we just add it here.
MoshiProvider.providesMoshi()
.newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build()
}
const val KEY = "WORKER_PARAMS_JSON" const val KEY = "WORKER_PARAMS_JSON"
inline fun <reified T> toData(params: T): Data { inline fun <reified T> toData(params: T): Data {
val moshi = MoshiProvider.providesMoshi()
val adapter = moshi.adapter(T::class.java) val adapter = moshi.adapter(T::class.java)
val json = adapter.toJson(params) val json = adapter.toJson(params)
return Data.Builder().putString(KEY, json).build() return Data.Builder().putString(KEY, json).build()
@ -36,7 +46,6 @@ object WorkerParamsFactory {
return if (json == null) { return if (json == null) {
null null
} else { } else {
val moshi = MoshiProvider.providesMoshi()
val adapter = moshi.adapter(T::class.java) val adapter = moshi.adapter(T::class.java)
adapter.fromJson(json) adapter.fromJson(json)
} }

View file

@ -122,6 +122,9 @@ android {
// Other branches (master, features, etc.) will have version code based on application version. // Other branches (master, features, etc.) will have version code based on application version.
versionCode project.getVersionCode() versionCode project.getVersionCode()
// Required for sonar analysis
versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar"
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
resValue "string", "git_revision", "\"${gitRevision()}\"" resValue "string", "git_revision", "\"${gitRevision()}\""

View file

@ -68,7 +68,7 @@ class DebugMenuActivity : VectorBaseActivity() {
} }
private fun renderQrCode(text: String) { private fun renderQrCode(text: String) {
debug_qr_code.setData(text, true) debug_qr_code.setData(text)
} }
@OnClick(R.id.debug_test_text_view_link) @OnClick(R.id.debug_test_text_view_link)

View file

@ -18,28 +18,23 @@ package im.vector.app.core.ui.views
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import im.vector.app.core.qrcode.toBitMatrix import im.vector.app.core.qrcode.toBitMatrix
import im.vector.app.core.qrcode.toBitmap import im.vector.app.core.qrcode.toBitmap
import kotlin.random.Random
class QrCodeImageView @JvmOverloads constructor( class QrCodeImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) { ) : AppCompatImageView(context, attrs, defStyleAttr) {
private var data: String? = null private var data: String? = null
private var animate = false
init { init {
setBackgroundColor(Color.WHITE) setBackgroundColor(Color.WHITE)
} }
fun setData(data: String, animate: Boolean) { fun setData(data: String) {
this.data = data this.data = data
this.animate = animate
render() render()
} }
@ -53,47 +48,8 @@ class QrCodeImageView @JvmOverloads constructor(
data data
?.takeIf { height > 0 } ?.takeIf { height > 0 }
?.let { ?.let {
if (animate) {
// NOT SUPPORTED YET val anim = createAnimation(it)
// NOT SUPPORTED YET setImageDrawable(anim)
// NOT SUPPORTED YET anim.start()
// NOT SUPPORTED YET setImageDrawable(BitmapDrawable(resources, it.toBitMatrix(height).toBitmap()))
val bitmap = it.toBitMatrix(height).toBitmap()
post { setImageBitmap(bitmap) }
} else {
val bitmap = it.toBitMatrix(height).toBitmap() val bitmap = it.toBitMatrix(height).toBitmap()
post { setImageBitmap(bitmap) } post { setImageBitmap(bitmap) }
} }
} }
}
private fun createAnimation(data: String): AnimationDrawable {
val finalQr = data.toBitMatrix(height)
val list = mutableListOf(finalQr)
val random = Random(System.currentTimeMillis())
val repeatTime = 8
repeat(repeatTime) { index ->
val alteredQr = finalQr.clone()
for (x in 0 until alteredQr.width) {
for (y in 0 until alteredQr.height) {
if (random.nextInt(repeatTime - index) == 0) {
// Pb is that it does not toggle a whole black square, but only a pixel
alteredQr.unset(x, y)
}
}
}
list.add(alteredQr)
}
val animDrawable = AnimationDrawable()
list.asReversed()
.forEach {
animDrawable.addFrame(BitmapDrawable(resources, it.toBitmap()), 150)
}
return animDrawable
}
} }

View file

@ -53,7 +53,6 @@ class VerificationChooseMethodController @Inject constructor(
bottomSheetVerificationQrCodeItem { bottomSheetVerificationQrCodeItem {
id("qr") id("qr")
data(state.qrCodeText) data(state.qrCodeText)
animate(false)
} }
dividerItem { dividerItem {

View file

@ -32,12 +32,9 @@ abstract class BottomSheetVerificationQrCodeItem : VectorEpoxyModel<BottomSheetV
@EpoxyAttribute @EpoxyAttribute
lateinit var data: String lateinit var data: String
@EpoxyAttribute
var animate = false
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.qsrCodeImage.setData(data, animate) holder.qsrCodeImage.setData(data)
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View file

@ -77,16 +77,15 @@ class RoomDetailActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
waitingView = waiting_view waitingView = waiting_view
if (isFirstCreation()) {
val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) { val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) {
RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!) RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!)
} else { } else {
intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
} }
if (roomDetailArgs == null) return if (roomDetailArgs == null) return
currentRoomId = roomDetailArgs.roomId currentRoomId = roomDetailArgs.roomId
if (isFirstCreation()) {
replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs) replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs)
replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java) replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java)
} }

View file

@ -54,6 +54,7 @@ import im.vector.app.features.pin.PinMode
import im.vector.app.features.roomdirectory.RoomDirectoryActivity import im.vector.app.features.roomdirectory.RoomDirectoryActivity
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity
import im.vector.app.features.roommemberprofile.RoomMemberProfileArgs import im.vector.app.features.roommemberprofile.RoomMemberProfileArgs
import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.RoomProfileActivity
@ -156,14 +157,6 @@ class DefaultNavigator @Inject constructor(
} }
} }
override fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String?, buildTask: Boolean) {
if (context is VectorBaseActivity) {
context.notImplemented("Open not joined room")
} else {
context.toast(R.string.not_implemented)
}
}
override fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean) { override fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean) {
if (context is VectorBaseActivity) { if (context is VectorBaseActivity) {
context.notImplemented("Open group detail") context.notImplemented("Open group detail")
@ -186,7 +179,12 @@ class DefaultNavigator @Inject constructor(
} }
override fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData) { override fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData) {
val intent = RoomPreviewActivity.getIntent(context, publicRoom, roomDirectoryData) val intent = RoomPreviewActivity.newIntent(context, publicRoom, roomDirectoryData)
context.startActivity(intent)
}
override fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData) {
val intent = RoomPreviewActivity.newIntent(context, roomPreviewData)
context.startActivity(intent) context.startActivity(intent)
} }

View file

@ -25,6 +25,7 @@ import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.AttachmentData
import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinMode import im.vector.app.features.pin.PinMode
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData import im.vector.app.features.share.SharedData
import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.terms.ReviewTermsActivity
@ -50,10 +51,10 @@ interface Navigator {
fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData) fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData)
fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String? = null, buildTask: Boolean = false)
fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData) fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData)
fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData)
fun openCreateRoom(context: Context, initialName: String = "") fun openCreateRoom(context: Context, initialName: String = "")
fun openCreateDirectRoom(context: Context) fun openCreateDirectRoom(context: Context)

View file

@ -18,13 +18,18 @@ package im.vector.app.features.permalink
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.utils.toast
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
import javax.inject.Inject import javax.inject.Inject
@ -51,14 +56,37 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
if (deepLink == null) { if (deepLink == null) {
return Single.just(false) return Single.just(false)
} }
return when (val permalinkData = PermalinkParser.parse(deepLink)) { return Single
.fromCallable {
PermalinkParser.parse(deepLink)
}
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { permalinkData ->
handlePermalink(permalinkData, context, navigationInterceptor, buildTask)
}
.onErrorReturnItem(false)
}
private fun handlePermalink(
permalinkData: PermalinkData,
context: Context,
navigationInterceptor: NavigationInterceptor?,
buildTask: Boolean
): Single<Boolean> {
return when (permalinkData) {
is PermalinkData.RoomLink -> { is PermalinkData.RoomLink -> {
permalinkData.getRoomId() permalinkData.getRoomId()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.map { .map {
val roomId = it.getOrNull() val roomId = it.getOrNull()
if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) { if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) {
openRoom(context, roomId, permalinkData.eventId, buildTask) openRoom(
context = context,
roomId = roomId,
permalinkData = permalinkData,
buildTask = buildTask
)
} }
true true
} }
@ -82,22 +110,55 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
private fun PermalinkData.RoomLink.getRoomId(): Single<Optional<String>> { private fun PermalinkData.RoomLink.getRoomId(): Single<Optional<String>> {
val session = activeSessionHolder.getSafeActiveSession() val session = activeSessionHolder.getSafeActiveSession()
return if (isRoomAlias && session != null) { return if (isRoomAlias && session != null) {
// At the moment we are not fetching on the server as we don't handle not join room session.rx().getRoomIdByAlias(roomIdOrAlias, true).subscribeOn(Schedulers.io())
session.rx().getRoomIdByAlias(roomIdOrAlias, false).subscribeOn(Schedulers.io())
} else { } else {
Single.just(Optional.from(roomIdOrAlias)) Single.just(Optional.from(roomIdOrAlias))
} }
} }
private fun PermalinkData.RoomLink.getRoomAliasOrNull(): String? {
return if (isRoomAlias) {
roomIdOrAlias
} else {
null
}
}
/** /**
* Open room either joined, or not * Open room either joined, or not
*/ */
private fun openRoom(context: Context, roomId: String?, eventId: String?, buildTask: Boolean) { private fun openRoom(
context: Context,
roomId: String?,
permalinkData: PermalinkData.RoomLink,
buildTask: Boolean
) {
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
return if (roomId != null && session.getRoom(roomId) != null) { if (roomId == null) {
context.toast(R.string.room_error_not_found)
return
}
val roomSummary = session.getRoomSummary(roomId)
val membership = roomSummary?.membership
val eventId = permalinkData.eventId
val roomAlias = permalinkData.getRoomAliasOrNull()
return when {
membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room)
membership?.isActive().orFalse() -> {
navigator.openRoom(context, roomId, eventId, buildTask) navigator.openRoom(context, roomId, eventId, buildTask)
} else { }
navigator.openNotJoinedRoom(context, roomId, eventId, buildTask) else -> {
val roomPreviewData = RoomPreviewData(
roomId = roomId,
eventId = eventId,
roomAlias = roomAlias ?: roomSummary?.canonicalAlias,
roomName = roomSummary?.displayName,
avatarUrl = roomSummary?.avatarUrl,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
}
} }
} }
} }

View file

@ -28,8 +28,10 @@ import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -42,8 +44,10 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri
override fun buildModels(viewState: PublicRoomsViewState) { override fun buildModels(viewState: PublicRoomsViewState) {
val publicRooms = viewState.publicRooms val publicRooms = viewState.publicRooms
if (publicRooms.isEmpty() val unknownRoomItem = viewState.buildUnknownRoomIfNeeded()
&& viewState.asyncPublicRoomsRequest is Success) {
val noResult = publicRooms.isEmpty() && viewState.asyncPublicRoomsRequest is Success && unknownRoomItem == null
if (noResult) {
// No result // No result
noResultItem { noResultItem {
id("noResult") id("noResult")
@ -54,6 +58,8 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri
buildPublicRoom(it, viewState) buildPublicRoom(it, viewState)
} }
unknownRoomItem?.addTo(this)
if ((viewState.hasMore && viewState.asyncPublicRoomsRequest is Success) if ((viewState.hasMore && viewState.asyncPublicRoomsRequest is Success)
|| viewState.asyncPublicRoomsRequest is Incomplete) { || viewState.asyncPublicRoomsRequest is Incomplete) {
loadingItem { loadingItem {
@ -109,7 +115,29 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri
} }
} }
private fun PublicRoomsViewState.buildUnknownRoomIfNeeded(): UnknownRoomItem? {
val roomIdOrAlias = currentFilter.trim()
val isAlias = MatrixPatterns.isRoomAlias(roomIdOrAlias) && !publicRooms.any { it.canonicalAlias == roomIdOrAlias }
val isRoomId = !isAlias && MatrixPatterns.isRoomId(roomIdOrAlias) && !publicRooms.any { it.roomId == roomIdOrAlias }
val roomItem = when {
isAlias -> MatrixItem.RoomAliasItem(roomIdOrAlias, roomIdOrAlias)
isRoomId -> MatrixItem.RoomItem(roomIdOrAlias)
else -> null
}
return roomItem?.let {
UnknownRoomItem_().apply {
id(roomIdOrAlias)
matrixItem(it)
avatarRenderer(this@PublicRoomsController.avatarRenderer)
globalListener {
callback?.onUnknownRoomClicked(roomIdOrAlias)
}
}
}
}
interface Callback { interface Callback {
fun onUnknownRoomClicked(roomIdOrAlias: String)
fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState)
fun onPublicRoomJoin(publicRoom: PublicRoom) fun onPublicRoomJoin(publicRoom: PublicRoom)
fun loadMore() fun loadMore()

View file

@ -29,9 +29,13 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.extensions.trackItemsVisibilityChange
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import im.vector.app.core.utils.toast
import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.fragment_public_rooms.* import kotlinx.android.synthetic.main.fragment_public_rooms.*
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -41,7 +45,9 @@ import javax.inject.Inject
* - When filtering more (when entering new chars), we could filter on result we already have, during the new server request, to avoid empty screen effect * - When filtering more (when entering new chars), we could filter on result we already have, during the new server request, to avoid empty screen effect
*/ */
class PublicRoomsFragment @Inject constructor( class PublicRoomsFragment @Inject constructor(
private val publicRoomsController: PublicRoomsController private val publicRoomsController: PublicRoomsController,
private val permalinkHandler: PermalinkHandler,
private val session: Session
) : VectorBaseFragment(), PublicRoomsController.Callback { ) : VectorBaseFragment(), PublicRoomsController.Callback {
private val viewModel: RoomDirectoryViewModel by activityViewModel() private val viewModel: RoomDirectoryViewModel by activityViewModel()
@ -112,6 +118,23 @@ class PublicRoomsFragment @Inject constructor(
publicRoomsController.callback = this publicRoomsController.callback = this
} }
override fun onUnknownRoomClicked(roomIdOrAlias: String) {
val permalink = session.permalinkService().createPermalink(roomIdOrAlias)
permalinkHandler
.launch(requireContext(), permalink, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
requireActivity().finish()
return false
}
})
.subscribe { isSuccessful ->
if (!isSuccessful) {
requireContext().toast(R.string.room_error_not_found)
}
}
.disposeOnDestroyView()
}
override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) { override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) {
Timber.v("PublicRoomClicked: $publicRoom") Timber.v("PublicRoomClicked: $publicRoom")
withState(viewModel) { state -> withState(viewModel) { state ->

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 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.app.features.roomdirectory
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_unknown_room)
abstract class UnknownRoomItem : VectorEpoxyModel<UnknownRoomItem.Holder>() {
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
var globalListener: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener { globalListener?.invoke() }
avatarRenderer.render(matrixItem, holder.avatarView)
holder.nameView.text = matrixItem.displayName
}
class Holder : VectorEpoxyHolder() {
val rootView by bind<ViewGroup>(R.id.itemUnknownRoomLayout)
val avatarView by bind<ImageView>(R.id.itemUnknownRoomAvatar)
val nameView by bind<TextView>(R.id.itemUnknownRoomName)
}
}

View file

@ -24,20 +24,23 @@ import im.vector.app.R
import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import kotlinx.android.parcel.Parcelize
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import kotlinx.android.parcel.Parcelize import timber.log.Timber
@Parcelize @Parcelize
data class RoomPreviewData( data class RoomPreviewData(
val roomId: String, val roomId: String,
val roomName: String?, val eventId: String? = null,
val roomAlias: String?, val roomName: String? = null,
val topic: String?, val roomAlias: String? = null,
val worldReadable: Boolean, val topic: String? = null,
val avatarUrl: String?, val worldReadable: Boolean = false,
val homeServer: String? val avatarUrl: String? = null,
val homeServers: List<String> = emptyList(),
val buildTask: Boolean = false
) : Parcelable { ) : Parcelable {
val matrixItem: MatrixItem val matrixItem: MatrixItem
get() = MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl) get() = MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
@ -48,18 +51,23 @@ class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { companion object {
private const val ARG = "ARG" private const val ARG = "ARG"
fun getIntent(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData): Intent { fun newIntent(context: Context, roomPreviewData: RoomPreviewData): Intent {
return Intent(context, RoomPreviewActivity::class.java).apply { return Intent(context, RoomPreviewActivity::class.java).apply {
putExtra(ARG, RoomPreviewData( putExtra(ARG, roomPreviewData)
}
}
fun newIntent(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData): Intent {
val roomPreviewData = RoomPreviewData(
roomId = publicRoom.roomId, roomId = publicRoom.roomId,
roomName = publicRoom.name, roomName = publicRoom.name,
roomAlias = publicRoom.getPrimaryAlias(), roomAlias = publicRoom.getPrimaryAlias(),
topic = publicRoom.topic, topic = publicRoom.topic,
worldReadable = publicRoom.worldReadable, worldReadable = publicRoom.worldReadable,
avatarUrl = publicRoom.avatarUrl, avatarUrl = publicRoom.avatarUrl,
homeServer = roomDirectoryData.homeServer homeServers = listOfNotNull(roomDirectoryData.homeServer)
)) )
} return newIntent(context, roomPreviewData)
} }
} }
@ -76,6 +84,7 @@ class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
if (args?.worldReadable == true) { if (args?.worldReadable == true) {
// TODO Room preview: Note: M does not recommend to use /events anymore, so for now we just display the room preview // TODO Room preview: Note: M does not recommend to use /events anymore, so for now we just display the room preview
// TODO the same way if it was not world readable // TODO the same way if it was not world readable
Timber.d("just display the room preview the same way if it was not world readable")
addFragment(R.id.simpleFragmentContainer, RoomPreviewNoPreviewFragment::class.java, args) addFragment(R.id.simpleFragmentContainer, RoomPreviewNoPreviewFragment::class.java, args)
} else { } else {
addFragment(R.id.simpleFragmentContainer, RoomPreviewNoPreviewFragment::class.java, args) addFragment(R.id.simpleFragmentContainer, RoomPreviewNoPreviewFragment::class.java, args)

View file

@ -48,13 +48,15 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar(roomPreviewNoPreviewToolbar) setupToolbar(roomPreviewNoPreviewToolbar)
val titleText = roomPreviewData.roomName ?: roomPreviewData.roomAlias ?: roomPreviewData.roomId
// Toolbar // Toolbar
avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewToolbarAvatar) avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewToolbarAvatar)
roomPreviewNoPreviewToolbarTitle.text = roomPreviewData.roomName ?: roomPreviewData.roomAlias roomPreviewNoPreviewToolbarTitle.text = titleText
// Screen // Screen
avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewAvatar) avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewAvatar)
roomPreviewNoPreviewName.text = roomPreviewData.roomName ?: roomPreviewData.roomAlias roomPreviewNoPreviewName.text = titleText
roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic) roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic)
if (roomPreviewData.worldReadable) { if (roomPreviewData.worldReadable) {
@ -98,7 +100,7 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
// Quit this screen // Quit this screen
requireActivity().finish() requireActivity().finish()
// Open room // Open room
navigator.openRoom(requireActivity(), roomPreviewData.roomId) navigator.openRoom(requireActivity(), roomPreviewData.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
} }
} }
} }

View file

@ -106,10 +106,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
Timber.w("Try to join an already joining room. Should not happen") Timber.w("Try to join an already joining room. Should not happen")
return@withState return@withState
} }
val viaServers = state.homeServer?.let { session.joinRoom(state.roomId, viaServers = state.homeServers, callback = object : MatrixCallback<Unit> {
listOf(it)
} ?: emptyList()
session.joinRoom(state.roomId, viaServers = viaServers, callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
// Instead, we wait for the room to be joined // Instead, we wait for the room to be joined

View file

@ -24,10 +24,9 @@ data class RoomPreviewViewState(
val roomId: String = "", val roomId: String = "",
val roomAlias: String? = null, val roomAlias: String? = null,
/** /**
* The server name (might be null) * Can be empty when the server is the current user's home server.
* Set null when the server is the current user's home server.
*/ */
val homeServer: String? = null, val homeServers: List<String> = emptyList(),
// Current state of the room in preview // Current state of the room in preview
val roomJoinState: JoinState = JoinState.NOT_JOINED, val roomJoinState: JoinState = JoinState.NOT_JOINED,
// Last error of join room request // Last error of join room request
@ -37,6 +36,6 @@ data class RoomPreviewViewState(
constructor(args: RoomPreviewData) : this( constructor(args: RoomPreviewData) : this(
roomId = args.roomId, roomId = args.roomId,
roomAlias = args.roomAlias, roomAlias = args.roomAlias,
homeServer = args.homeServer homeServers = args.homeServers
) )
} }

View file

@ -17,7 +17,6 @@
package im.vector.app.features.settings package im.vector.app.features.settings
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.preference.VectorSwitchPreference
import javax.inject.Inject import javax.inject.Inject
class VectorSettingsLabsFragment @Inject constructor( class VectorSettingsLabsFragment @Inject constructor(
@ -28,103 +27,6 @@ class VectorSettingsLabsFragment @Inject constructor(
override val preferenceXmlRes = R.xml.vector_settings_labs override val preferenceXmlRes = R.xml.vector_settings_labs
override fun bindPref() { override fun bindPref() {
// Lab // Nothing to do
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_ALLOW_EXTENDED_LOGS)?.let {
it.isChecked = vectorPreferences.labAllowedExtendedLogging()
}
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB)?.let {
it.isChecked = vectorPreferences.labAddNotificationTab()
}
// val useCryptoPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference
// val cryptoIsEnabledPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY)
if (session.cryptoService().isCryptoEnabled()) {
// mLabsCategory.removePreference(useCryptoPref)
//
// cryptoIsEnabledPref.isEnabled = false
} else {
// mLabsCategory.removePreference(cryptoIsEnabledPref)
//
// useCryptoPref.isChecked = false
//
// useCryptoPref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValueAsVoid ->
// if (TextUtils.isEmpty(mSession.sessionParams.deviceId)) {
// activity?.let { activity ->
// AlertDialog.Builder(activity)
// .setMessage(R.string.room_settings_labs_end_to_end_warnings)
// .setPositiveButton(R.string.logout) { _, _ ->
// notImplemented()
// // TODO CommonActivityUtils.logout(activity)
// }
// .setNegativeButton(R.string.cancel) { _, _ ->
// useCryptoPref.isChecked = false
// }
// .setOnCancelListener {
// useCryptoPref.isChecked = false
// }
// .show()
// }
// } else {
// val newValue = newValueAsVoid as Boolean
//
// if (mSession.isCryptoEnabled() != newValue) {
// notImplemented()
// /* TODO
// displayLoadingView()
//
// session.enableCrypto(newValue, object : MatrixCallback<Unit> {
// private fun refresh() {
// activity?.runOnUiThread {
// hideLoadingView()
// useCryptoPref.isChecked = session.isCryptoEnabled
//
// if (session.isCryptoEnabled) {
// mLabsCategory.removePreference(useCryptoPref)
// mLabsCategory.addPreference(cryptoIsEnabledPref)
// }
// }
// }
//
// override fun onSuccess(info: Void?) {
// useCryptoPref.isEnabled = false
// refresh()
// }
//
// override fun onNetworkError(e: Exception) {
// useCryptoPref.isChecked = false
// }
//
// override fun onMatrixError(e: MatrixError) {
// useCryptoPref.isChecked = false
// }
//
// override fun onUnexpectedError(e: Exception) {
// useCryptoPref.isChecked = false
// }
// })
// */
// }
// }
//
// true
// }
}
// SaveMode Management
// findPreference(VectorPreferences.SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY)
// .onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
// notImplemented()
// /* TODO
// val sessions = Matrix.getMXSessions(activity)
// for (session in sessions) {
// session.setUseDataSaveMode(newValue as Boolean)
// }
// */
//
// true
// }
} }
} }

View file

@ -48,7 +48,8 @@ class VectorSettingsDevicesFragment @Inject constructor(
) : VectorBaseFragment(), DevicesController.Callback { ) : VectorBaseFragment(), DevicesController.Callback {
// used to avoid requesting to enter the password for each deletion // used to avoid requesting to enter the password for each deletion
private var mAccountPassword: String = "" // Note: Sonar does not like to use password for member name.
private var mAccountPass: String = ""
override fun getLayoutResId() = R.layout.fragment_generic_recycler override fun getLayoutResId() = R.layout.fragment_generic_recycler
@ -91,7 +92,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
super.showFailure(throwable) super.showFailure(throwable)
// Password is maybe not good, for safety measure, reset it here // Password is maybe not good, for safety measure, reset it here
mAccountPassword = "" mAccountPass = ""
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -153,12 +154,12 @@ class VectorSettingsDevicesFragment @Inject constructor(
* Show a dialog to ask for user password, or use a previously entered password. * Show a dialog to ask for user password, or use a previously entered password.
*/ */
private fun maybeShowDeleteDeviceWithPasswordDialog() { private fun maybeShowDeleteDeviceWithPasswordDialog() {
if (mAccountPassword.isNotEmpty()) { if (mAccountPass.isNotEmpty()) {
viewModel.handle(DevicesAction.Password(mAccountPassword)) viewModel.handle(DevicesAction.Password(mAccountPass))
} else { } else {
PromptPasswordDialog().show(requireActivity()) { password -> PromptPasswordDialog().show(requireActivity()) { password ->
mAccountPassword = password mAccountPass = password
viewModel.handle(DevicesAction.Password(mAccountPassword)) viewModel.handle(DevicesAction.Password(mAccountPass))
} }
} }
} }

View file

@ -0,0 +1,52 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemUnknownRoomLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="64dp">
<ImageView
android:id="@+id/itemUnknownRoomAvatar"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/itemUnknownRoomName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/itemUnknownRoomAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@sample/matrix.json/data/roomName" />
<View
android:id="@+id/itemPublicRoomBottomSeparator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?riotx_header_panel_border_mobile"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2539,4 +2539,6 @@
<string name="settings_security_pin_code_title">Enable PIN</string> <string name="settings_security_pin_code_title">Enable PIN</string>
<string name="settings_security_pin_code_summary">If you want to reset your PIN, tap Forgot PIN to logout and reset.</string> <string name="settings_security_pin_code_summary">If you want to reset your PIN, tap Forgot PIN to logout and reset.</string>
<string name="auth_pin_confirm_to_disable_title">Confirm PIN to disable PIN</string> <string name="auth_pin_confirm_to_disable_title">Confirm PIN to disable PIN</string>
<string name="error_opening_banned_room">Can\'t open a room where you are banned from.</string>
<string name="room_error_not_found">Can\'t find this room. Make sure it exists.</string>
</resources> </resources>