Merge pull request #2217 from vector-im/feature/bma/kotlin_version

Some upgrade
This commit is contained in:
Benoit Marty 2020-10-08 13:30:23 +02:00 committed by GitHub
commit 1fd24e746c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
183 changed files with 1437 additions and 1420 deletions

View file

@ -30,6 +30,10 @@ SDK API changes ⚠️:
Build 🧱:
- Use Update Gradle Wrapper Action
- Updates Gradle Wrapper from 5.6.4 to 6.6.1. (#2193)
- Upgrade kotlin version from `1.3.72` to `1.4.10` and kotlin coroutines version from `1.3.8` to `1.3.9`
- Upgrade build tools from `3.5.3` to `4.0.1`
- Upgrade com.google.gms:google-services from `4.3.2` to `4.3.4`
- Upgrade Moshi to `1.11.0`, Dagger to `2.29.1`, Epoxy to `4.1.0`
Other changes:
- Added registration/verification automated UI tests

View file

@ -58,21 +58,16 @@ android {
}
dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'com.google.android.material:material:1.2.1'
}

View file

@ -1,7 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.72'
// Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.4.10'
ext.kotlin_coroutines_version = "1.3.9"
repositories {
google()
jcenter()
@ -10,10 +12,8 @@ buildscript {
}
}
dependencies {
// Warning: 3.6.3 leads to infinite gradle builds. Stick to 3.5.3 for the moment
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.google.gms:google-services:4.3.2'
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.google.gms:google-services:4.3.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
@ -64,7 +64,8 @@ allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them
kotlinOptions.allWarningsAsErrors = true
// You can override by passing `-PallWarningsAsErrors=false` in the command line
kotlinOptions.allWarningsAsErrors = project.properties['allWarningsAsErrors']?.toBoolean() ?: true
}
}

View file

@ -35,7 +35,8 @@ android {
dependencies {
implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging
@ -43,8 +44,4 @@ dependencies {
// Logging
implementation 'com.jakewharton.timber:timber:4.7.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

View file

@ -3,7 +3,6 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android'
apply plugin: 'okreplay'
buildscript {
repositories {
@ -109,21 +108,21 @@ static def gitRevisionDate() {
dependencies {
def arrow_version = "0.8.2"
def moshi_version = '1.8.0'
def moshi_version = '1.11.0'
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def coroutines_version = "1.3.8"
def markwon_version = '3.1.0'
def daggerVersion = '2.25.4'
def daggerVersion = '2.29.1'
def work_version = '2.4.0'
def retrofit_version = '2.6.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.core:core-ktx:1.3.1"
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@ -143,7 +142,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
implementation 'androidx.exifinterface:exifinterface:1.3.0'
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
@ -181,33 +180,29 @@ dependencies {
// Use the same WebRTC library than the one used by Jitsi library
implementation('com.facebook.react:react-native-webrtc:1.84.0-jitsi-5112273@aar')
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.44'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
testImplementation 'org.amshove.kluent:kluent-android:1.61'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.61'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
androidTestUtil 'androidx.test:orchestrator:1.2.0'
androidTestUtil 'androidx.test:orchestrator:1.3.0'
}

View file

@ -20,9 +20,11 @@ package org.matrix.android.sdk.api.session.content
import android.net.Uri
import android.os.Parcelable
import androidx.exifinterface.media.ExifInterface
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonClass(generateAdapter = true)
data class ContentAttachmentData(
val size: Long = 0,
val duration: Long? = 0,
@ -32,10 +34,11 @@ data class ContentAttachmentData(
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
val name: String? = null,
val queryUri: Uri,
private val mimeType: String?,
val mimeType: String?,
val type: Type
) : Parcelable {
@JsonClass(generateAdapter = false)
enum class Type {
FILE,
IMAGE,

View file

@ -18,7 +18,9 @@
package org.matrix.android.sdk.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Signed(
@Json(name = "token") val token: String,
@Json(name = "signatures") val signatures: Any,

View file

@ -105,6 +105,6 @@ private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
private fun Versions.getMaxVersion(): HomeServerVersion {
return supportedVersions
?.mapNotNull { HomeServerVersion.parse(it) }
?.max()
?.maxOrNull()
?: HomeServerVersion.r0_0_0
}

View file

@ -17,6 +17,8 @@
package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
@ -36,8 +38,6 @@ import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -327,7 +327,9 @@ internal class IncomingGossipingRequestManager @Inject constructor(
val params = SendGossipWorker.Params(
sessionId = sessionId,
secretValue = secretValue,
request = request
requestUserId = request.userId,
requestDeviceId = request.deviceId,
requestId = request.requestId
)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
@ -351,7 +353,9 @@ internal class IncomingGossipingRequestManager @Inject constructor(
val params = SendGossipWorker.Params(
sessionId = userId,
secretValue = secretValue,
request = request
requestUserId = request.userId,
requestDeviceId = request.deviceId,
requestId = request.requestId
)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)

View file

@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
/**
* IncomingRoomKeyRequest class defines the incoming room keys request.
* IncomingSecretShareRequest class defines the incoming secret keys request.
*/
data class IncomingSecretShareRequest(
/**

View file

@ -47,7 +47,9 @@ internal class SendGossipWorker(context: Context,
internal data class Params(
override val sessionId: String,
val secretValue: String,
val request: IncomingSecretShareRequest,
val requestUserId: String?,
val requestDeviceId: String?,
val requestId: String?,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@ -67,16 +69,21 @@ internal class SendGossipWorker(context: Context,
val eventType: String = EventType.SEND_SECRET
val toDeviceContent = SecretSendEventContent(
requestId = params.request.requestId ?: "",
requestId = params.requestId ?: "",
secretValue = params.secretValue
)
val requestingUserId = params.request.userId ?: ""
val requestingDeviceId = params.request.deviceId ?: ""
val requestingUserId = params.requestUserId ?: ""
val requestingDeviceId = params.requestDeviceId ?: ""
val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
?: return buildErrorResult(params, "Unknown deviceInfo, cannot send message").also {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
cryptoStore.updateGossipingRequestState(
requestUserId = params.requestUserId,
requestDeviceId = params.requestDeviceId,
requestId = params.requestId,
state = GossipingRequestState.FAILED_TO_ACCEPTED
)
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.requestDeviceId}")
}
val sendToDeviceMap = MXUsersDevicesMap<Any>()
@ -88,7 +95,12 @@ internal class SendGossipWorker(context: Context,
// no session with this device, probably because there
// were no one-time keys.
return buildErrorResult(params, "no session with this device").also {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
cryptoStore.updateGossipingRequestState(
requestUserId = params.requestUserId,
requestDeviceId = params.requestDeviceId,
requestId = params.requestId,
state = GossipingRequestState.FAILED_TO_ACCEPTED
)
Timber.e("no session with this device, probably because there were no one-time keys.")
}
}
@ -121,13 +133,23 @@ internal class SendGossipWorker(context: Context,
transactionId = localId
)
)
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
cryptoStore.updateGossipingRequestState(
requestUserId = params.requestUserId,
requestDeviceId = params.requestDeviceId,
requestId = params.requestId,
state = GossipingRequestState.ACCEPTED
)
return Result.success()
} catch (throwable: Throwable) {
return if (throwable.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
cryptoStore.updateGossipingRequestState(
requestUserId = params.requestUserId,
requestDeviceId = params.requestDeviceId,
requestId = params.requestId,
state = GossipingRequestState.FAILED_TO_ACCEPTED
)
buildErrorResult(params, throwable.localizedMessage ?: "error")
}
}

View file

@ -17,7 +17,12 @@
package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class KeysVersion(
// the keys backup version
var version: String? = null
@Json(name = "version")
val version: String? = null
)

View file

@ -371,7 +371,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
callback.onFailure(SharedSecretStorageError.BadKeyFormat)
}
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
kotlin.runCatching {
runCatching {
// decrypt from recovery key
withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(keySpec.privateKey)
@ -390,7 +390,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
callback.onFailure(SharedSecretStorageError.BadKeyFormat)
}
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
kotlin.runCatching {
runCatching {
decryptAesHmacSha2(keySpec, name, secretContent)
}.foldToCallback(callback)
}

View file

@ -1,4 +1,3 @@
/*
* Copyright 2016 OpenMarket Ltd
* Copyright 2018 New Vector Ltd
@ -215,11 +214,12 @@ internal interface IMXCryptoStore {
// TODO temp
fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>>
fun getMyDevicesInfo() : List<DeviceInfo>
fun getMyDevicesInfo(): List<DeviceInfo>
fun getLiveMyDevicesInfo() : LiveData<List<DeviceInfo>>
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
fun saveMyDevicesInfo(info: List<DeviceInfo>)
/**
* Store the crypto algorithm for a room.
*
@ -367,7 +367,19 @@ internal interface IMXCryptoStore {
fun saveGossipingEvent(event: Event)
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState)
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
updateGossipingRequestState(
requestUserId = request.userId,
requestDeviceId = request.deviceId,
requestId = request.requestId,
state = state
)
}
fun updateGossipingRequestState(requestUserId: String?,
requestDeviceId: String?,
requestId: String?,
state: GossipingRequestState)
/**
* Search an IncomingRoomKeyRequest
@ -411,7 +423,7 @@ internal interface IMXCryptoStore {
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)
fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?)
@ -421,12 +433,13 @@ internal interface IMXCryptoStore {
fun updateUsersTrust(check: (String) -> Boolean)
fun addWithHeldMegolmSession(withHeldContent: RoomKeyWithHeldContent)
fun getWithHeldMegolmSession(roomId: String, sessionId: String) : RoomKeyWithHeldContent?
fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent?
fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, chainIndex: Int)
fun wasSessionSharedWithUser(roomId: String?, sessionId: String, userId: String, deviceId: String) : SharedSessionResult
fun wasSessionSharedWithUser(roomId: String?, sessionId: String, userId: String, deviceId: String): SharedSessionResult
data class SharedSessionResult(val found: Boolean, val chainIndex: Int?)
fun getSharedWithInfo(roomId: String?, sessionId: String) : MXUsersDevicesMap<Int>
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
// Dev tools
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>

View file

@ -1134,12 +1134,15 @@ internal class RealmCryptoStore @Inject constructor(
// }
// }
override fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
override fun updateGossipingRequestState(requestUserId: String?,
requestDeviceId: String?,
requestId: String?,
state: GossipingRequestState) {
doRealmTransaction(realmConfiguration) { realm ->
realm.where<IncomingGossipingRequestEntity>()
.equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, request.userId)
.equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, request.deviceId)
.equalTo(IncomingGossipingRequestEntityFields.REQUEST_ID, request.requestId)
.equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, requestUserId)
.equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, requestDeviceId)
.equalTo(IncomingGossipingRequestEntityFields.REQUEST_ID, requestId)
.findAll().forEach {
it.requestState = state
}

View file

@ -32,8 +32,10 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.internal.network.parsing.CipherSuiteMoshiAdapter
import org.matrix.android.sdk.internal.network.parsing.ForceToBooleanJsonAdapter
import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory
import org.matrix.android.sdk.internal.network.parsing.TlsVersionMoshiAdapter
import org.matrix.android.sdk.internal.network.parsing.UriMoshiAdapter
object MoshiProvider {
@ -41,6 +43,8 @@ object MoshiProvider {
private val moshi: Moshi = Moshi.Builder()
.add(UriMoshiAdapter())
.add(ForceToBooleanJsonAdapter())
.add(CipherSuiteMoshiAdapter())
.add(TlsVersionMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
.registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)

View file

@ -29,7 +29,6 @@ import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingIntercept
import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import okreplay.OkReplayInterceptor
import java.util.concurrent.TimeUnit
@Module
@ -44,12 +43,6 @@ internal object NetworkModule {
return interceptor
}
@Provides
@JvmStatic
fun providesOkReplayInterceptor(): OkReplayInterceptor {
return OkReplayInterceptor()
}
@Provides
@JvmStatic
fun providesStethoInterceptor(): StethoInterceptor {
@ -71,8 +64,7 @@ internal object NetworkModule {
timeoutInterceptor: TimeOutInterceptor,
userAgentInterceptor: UserAgentInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor,
curlLoggingInterceptor: CurlLoggingInterceptor,
okReplayInterceptor: OkReplayInterceptor): OkHttpClient {
curlLoggingInterceptor: CurlLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
@ -93,7 +85,6 @@ internal object NetworkModule {
proxy(it)
}
}
.addInterceptor(okReplayInterceptor)
.build()
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2019 New Vector Ltd
* 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.network.parsing
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import okhttp3.CipherSuite
internal class CipherSuiteMoshiAdapter {
@ToJson
fun toJson(cipherSuite: CipherSuite): String {
return cipherSuite.javaName
}
@FromJson
fun fromJson(cipherSuiteString: String): CipherSuite {
return CipherSuite.forJavaName(cipherSuiteString)
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright 2019 New Vector Ltd
* 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.
@ -14,19 +15,21 @@
* limitations under the License.
*/
package org.matrix.android.sdk
package org.matrix.android.sdk.internal.network.parsing
import okreplay.OkReplayConfig
import okreplay.PermissionRule
import okreplay.RecorderRule
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import okhttp3.TlsVersion
class OkReplayRuleChainNoActivity(
private val configuration: OkReplayConfig) {
internal class TlsVersionMoshiAdapter {
fun get(): TestRule {
return RuleChain.outerRule(PermissionRule(configuration))
.around(RecorderRule(configuration))
@ToJson
fun toJson(tlsVersion: TlsVersion): String {
return tlsVersion.javaName
}
@FromJson
fun fromJson(tlsVersionString: String): TlsVersion {
return TlsVersion.forJavaName(tlsVersionString)
}
}

View file

@ -41,15 +41,10 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation 'androidx.exifinterface:exifinterface:1.3.0'
// Log
implementation 'com.jakewharton.timber:timber:4.7.1'

View file

@ -16,7 +16,6 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.MediaMetadataRetriever
@ -26,19 +25,13 @@ import im.vector.lib.multipicker.entity.MultiPickerAudioType
/**
* Audio file picker implementation
*/
class AudioPicker(override val requestCode: Int) : Picker<MultiPickerAudioType>(requestCode) {
class AudioPicker : Picker<MultiPickerAudioType>() {
/**
* Call this function from onActivityResult(int, int, Intent).
* Returns selected audio files or empty list if request code is wrong
* or result code is not Activity.RESULT_OK
* or user did not select any files.
* Returns selected audio files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerAudioType> {
if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
return emptyList()
}
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerAudioType> {
val audioList = mutableListOf<MultiPickerAudioType>()
getSelectedUriList(data).forEach { selectedUri ->

View file

@ -16,13 +16,12 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import im.vector.lib.multipicker.entity.MultiPickerImageType
import im.vector.lib.multipicker.utils.ImageUtils
import java.io.File
@ -33,33 +32,18 @@ import java.util.Locale
/**
* Implementation of taking a photo with Camera
*/
class CameraPicker(val requestCode: Int) {
class CameraPicker {
/**
* Start camera by using an Activity
* @param activity Activity to handle onActivityResult().
* Start camera by using a ActivityResultLauncher
* @return Uri of taken photo or null if the operation is cancelled.
*/
fun startWithExpectingFile(activity: Activity): Uri? {
val photoUri = createPhotoUri(activity)
fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>): Uri? {
val photoUri = createPhotoUri(context)
val intent = createIntent().apply {
putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
}
activity.startActivityForResult(intent, requestCode)
return photoUri
}
/**
* Start camera by using a Fragment
* @param fragment Fragment to handle onActivityResult().
* @return Uri of taken photo or null if the operation is cancelled.
*/
fun startWithExpectingFile(fragment: Fragment): Uri? {
val photoUri = createPhotoUri(fragment.requireContext())
val intent = createIntent().apply {
putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
}
fragment.startActivityForResult(intent, requestCode)
activityResultLauncher.launch(intent)
return photoUri
}
@ -69,40 +53,38 @@ class CameraPicker(val requestCode: Int) {
* or result code is not Activity.RESULT_OK
* or user cancelled the operation.
*/
fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? {
if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
val projection = arrayOf(
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE
)
fun getTakenPhoto(context: Context, photoUri: Uri): MultiPickerImageType? {
val projection = arrayOf(
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE
)
context.contentResolver.query(
photoUri,
projection,
null,
null,
null
)?.use { cursor ->
val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
context.contentResolver.query(
photoUri,
projection,
null,
null,
null
)?.use { cursor ->
val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
if (cursor.moveToNext()) {
val name = cursor.getString(nameColumn)
val size = cursor.getLong(sizeColumn)
if (cursor.moveToNext()) {
val name = cursor.getString(nameColumn)
val size = cursor.getLong(sizeColumn)
val bitmap = ImageUtils.getBitmap(context, photoUri)
val orientation = ImageUtils.getOrientation(context, photoUri)
val bitmap = ImageUtils.getBitmap(context, photoUri)
val orientation = ImageUtils.getOrientation(context, photoUri)
return MultiPickerImageType(
name,
size,
context.contentResolver.getType(photoUri),
photoUri,
bitmap?.width ?: 0,
bitmap?.height ?: 0,
orientation
)
}
return MultiPickerImageType(
name,
size,
context.contentResolver.getType(photoUri),
photoUri,
bitmap?.width ?: 0,
bitmap?.height ?: 0,
orientation
)
}
}
return null

View file

@ -16,7 +16,6 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@ -26,19 +25,13 @@ import im.vector.lib.multipicker.entity.MultiPickerContactType
/**
* Contact Picker implementation
*/
class ContactPicker(override val requestCode: Int) : Picker<MultiPickerContactType>(requestCode) {
class ContactPicker : Picker<MultiPickerContactType>() {
/**
* Call this function from onActivityResult(int, int, Intent).
* Returns selected contact or empty list if request code is wrong
* or result code is not Activity.RESULT_OK
* or user did not select any files.
* Returns selected contact or empty list if user did not select any contacts.
*/
override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerContactType> {
if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
return emptyList()
}
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerContactType> {
val contactList = mutableListOf<MultiPickerContactType>()
data?.data?.let { selectedUri ->

View file

@ -16,7 +16,6 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.provider.OpenableColumns
@ -25,19 +24,13 @@ import im.vector.lib.multipicker.entity.MultiPickerFileType
/**
* Implementation of selecting any type of files
*/
class FilePicker(override val requestCode: Int) : Picker<MultiPickerFileType>(requestCode) {
class FilePicker : Picker<MultiPickerFileType>() {
/**
* Call this function from onActivityResult(int, int, Intent).
* Returns selected files or empty list if request code is wrong
* or result code is not Activity.RESULT_OK
* or user did not select any files.
* Returns selected files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerFileType> {
if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
return emptyList()
}
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerFileType> {
val fileList = mutableListOf<MultiPickerFileType>()
getSelectedUriList(data).forEach { selectedUri ->

View file

@ -16,7 +16,6 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.provider.MediaStore
@ -26,19 +25,13 @@ import im.vector.lib.multipicker.utils.ImageUtils
/**
* Image Picker implementation
*/
class ImagePicker(override val requestCode: Int) : Picker<MultiPickerImageType>(requestCode) {
class ImagePicker : Picker<MultiPickerImageType>() {
/**
* Call this function from onActivityResult(int, int, Intent).
* Returns selected image files or empty list if request code is wrong
* or result code is not Activity.RESULT_OK
* or user did not select any files.
* Returns selected image files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerImageType> {
if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
return emptyList()
}
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerImageType> {
val imageList = mutableListOf<MultiPickerImageType>()
getSelectedUriList(data).forEach { selectedUri ->

View file

@ -26,23 +26,16 @@ class MultiPicker<T> {
val CONTACT by lazy { MultiPicker<ContactPicker>() }
val CAMERA by lazy { MultiPicker<CameraPicker>() }
const val REQUEST_CODE_PICK_IMAGE = 5000
const val REQUEST_CODE_PICK_VIDEO = 5001
const val REQUEST_CODE_PICK_FILE = 5002
const val REQUEST_CODE_PICK_AUDIO = 5003
const val REQUEST_CODE_PICK_CONTACT = 5004
const val REQUEST_CODE_TAKE_PHOTO = 5005
@Suppress("UNCHECKED_CAST")
fun <T> get(type: MultiPicker<T>): T {
return when (type) {
IMAGE -> ImagePicker(REQUEST_CODE_PICK_IMAGE) as T
VIDEO -> VideoPicker(REQUEST_CODE_PICK_VIDEO) as T
FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T
AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T
CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T
CAMERA -> CameraPicker(REQUEST_CODE_TAKE_PHOTO) as T
else -> throw IllegalArgumentException("Unsupported type $type")
IMAGE -> ImagePicker() as T
VIDEO -> VideoPicker() as T
FILE -> FilePicker() as T
AUDIO -> AudioPicker() as T
CONTACT -> ContactPicker() as T
CAMERA -> CameraPicker() as T
else -> throw IllegalArgumentException("Unsupported type $type")
}
}
}

View file

@ -16,28 +16,25 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import androidx.fragment.app.Fragment
import androidx.activity.result.ActivityResultLauncher
/**
* Abstract class to provide all types of Pickers
*/
abstract class Picker<T>(open val requestCode: Int) {
abstract class Picker<T> {
protected var single = false
/**
* Call this function from onActivityResult(int, int, Intent).
* @return selected files or empty list if request code is wrong
* or result code is not Activity.RESULT_OK
* or user did not select any files.
* @return selected files or empty list if user did not select any files.
*/
abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<T>
abstract fun getSelectedFiles(context: Context, data: Intent?): List<T>
/**
* Use this function to retrieve files which are shared from another application or internally
@ -61,7 +58,7 @@ abstract class Picker<T>(open val requestCode: Int) {
context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data)
return getSelectedFiles(context, data)
}
/**
@ -75,19 +72,11 @@ abstract class Picker<T>(open val requestCode: Int) {
abstract fun createIntent(): Intent
/**
* Start Storage Access Framework UI by using an Activity.
* @param activity Activity to handle onActivityResult().
* Start Storage Access Framework UI by using a ActivityResultLauncher.
* @param activityResultLauncher to handle the result.
*/
fun startWith(activity: Activity) {
activity.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
}
/**
* Start Storage Access Framework UI by using a Fragment.
* @param fragment Fragment to handle onActivityResult().
*/
fun startWith(fragment: Fragment) {
fragment.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
fun startWith(activityResultLauncher: ActivityResultLauncher<Intent>) {
activityResultLauncher.launch(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) })
}
protected fun getSelectedUriList(data: Intent?): List<Uri> {

View file

@ -16,7 +16,6 @@
package im.vector.lib.multipicker
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.MediaMetadataRetriever
@ -26,19 +25,13 @@ import im.vector.lib.multipicker.entity.MultiPickerVideoType
/**
* Video Picker implementation
*/
class VideoPicker(override val requestCode: Int) : Picker<MultiPickerVideoType>(requestCode) {
class VideoPicker : Picker<MultiPickerVideoType>() {
/**
* Call this function from onActivityResult(int, int, Intent).
* Returns selected video files or empty list if request code is wrong
* or result code is not Activity.RESULT_OK
* or user did not select any files.
* Returns selected video files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List<MultiPickerVideoType> {
if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
return emptyList()
}
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerVideoType> {
val videoList = mutableListOf<MultiPickerVideoType>()
getSelectedUriList(data).forEach { selectedUri ->

View file

@ -280,22 +280,21 @@ android {
dependencies {
def epoxy_version = '3.11.0'
def fragment_version = '1.2.5'
def epoxy_version = '4.1.0'
def fragment_version = '1.3.0-beta01'
def arrow_version = "0.8.2"
def coroutines_version = "1.3.8"
def markwon_version = '4.1.2'
def big_image_viewer_version = '1.6.2'
def glide_version = '4.11.0'
def moshi_version = '1.8.0'
def daggerVersion = '2.25.4'
def moshi_version = '1.11.0'
def daggerVersion = '2.29.1'
def autofill_version = "1.0.0"
def work_version = '2.4.0'
def arch_version = '2.1.0'
def lifecycle_version = '2.2.0'
// Tests
def kluent_version = '1.44'
def kluent_version = '1.61'
def androidxTest_version = '1.3.0'
def espresso_version = '3.3.0'
@ -307,16 +306,16 @@ dependencies {
implementation 'com.android.support:multidex:1.0.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
// Keep at 2.0.0-beta4 at the moment, as updating is breaking some UI
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.core:core-ktx:1.3.2'
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0"
@ -347,7 +346,7 @@ dependencies {
implementation "com.airbnb.android:epoxy-glide-preloading:$epoxy_version"
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
implementation 'com.airbnb.android:mvrx:1.3.0'
implementation 'com.airbnb.android:mvrx:1.5.1'
// Work
implementation "androidx.work:work-runtime-ktx:$work_version"
@ -363,7 +362,7 @@ dependencies {
// UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.3.0-alpha01'
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
@ -398,7 +397,7 @@ dependencies {
implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version"
// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
@ -415,7 +414,7 @@ dependencies {
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
// gplay flavor only
gplayImplementation('com.google.firebase:firebase-messaging:20.2.4') {
gplayImplementation('com.google.firebase:firebase-messaging:20.3.0') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
@ -439,7 +438,7 @@ dependencies {
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
// TESTS
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'
testImplementation "org.amshove.kluent:kluent-android:$kluent_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'

View file

@ -27,6 +27,7 @@
<issue id="DisableBaselineAlignment" severity="error" />
<issue id="ScrollViewSize" severity="error" />
<issue id="NegativeMargin" severity="error" />
<issue id="UseCompatTextViewDrawableXml" severity="error" />
<!-- RTL -->
<issue id="RtlEnabled" severity="error" />

View file

@ -28,6 +28,7 @@ import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
@ -196,33 +197,29 @@ class DebugMenuActivity : VectorBaseActivity() {
}
private fun doScanQRCode() {
QrCodeScannerActivity.startForResult(this)
QrCodeScannerActivity.startForResult(this, qrStartForActivityResult)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
toast("QrCode: " + QrCodeScannerActivity.getResultText(data) + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(data))
private val qrStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
toast("QrCode: " + QrCodeScannerActivity.getResultText(activityResult.data)
+ " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(activityResult.data))
// Also update the current QR Code (reverse operation)
// renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
val result = QrCodeScannerActivity.getResultText(data)!!
// Also update the current QR Code (reverse operation)
// renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
val result = QrCodeScannerActivity.getResultText(activityResult.data)!!
val qrCodeData = result.toQrCodeData()
Timber.e("qrCodeData: $qrCodeData")
val qrCodeData = result.toQrCodeData()
Timber.e("qrCodeData: $qrCodeData")
if (result.length != buffer.size) {
Timber.e("Error, length are not the same")
} else {
// Convert to ByteArray
val byteArrayResult = result.toByteArray(Charsets.ISO_8859_1)
for (i in byteArrayResult.indices) {
if (buffer[i] != byteArrayResult[i]) {
Timber.e("Error for byte $i, expecting ${buffer[i]} and get ${byteArrayResult[i]}")
}
}
if (result.length != buffer.size) {
Timber.e("Error, length are not the same")
} else {
// Convert to ByteArray
val byteArrayResult = result.toByteArray(Charsets.ISO_8859_1)
for (i in byteArrayResult.indices) {
if (buffer[i] != byteArrayResult[i]) {
Timber.e("Error for byte $i, expecting ${buffer[i]} and get ${byteArrayResult[i]}")
}
}
}

View file

@ -15,6 +15,8 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences
@ -28,7 +30,7 @@ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: Vecto
private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
override fun perform() {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
if (vectorPreferences.autoStartOnBoot()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.SUCCESS
@ -38,7 +40,7 @@ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: Vecto
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true)
manager?.retry()
manager?.retry(activityResultLauncher)
}
}
status = TestStatus.FAILED

View file

@ -15,7 +15,9 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import android.net.ConnectivityManager
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.core.net.ConnectivityManagerCompat
@ -28,7 +30,7 @@ class TestBackgroundRestrictions @Inject constructor(private val context: AppCom
private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
override fun perform() {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
context.getSystemService<ConnectivityManager>()!!.apply {
// Checks if the device is on a metered network
if (isActiveNetworkMetered) {

View file

@ -15,12 +15,13 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
import im.vector.app.core.utils.requestDisablingBatteryOptimization
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import javax.inject.Inject
@ -29,7 +30,7 @@ class TestBatteryOptimization @Inject constructor(
private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
override fun perform() {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
if (isIgnoringBatteryOptimizations(context)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success)
status = TestStatus.SUCCESS
@ -38,7 +39,7 @@ class TestBatteryOptimization @Inject constructor(
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) {
override fun doFix() {
requestDisablingBatteryOptimization(context, null, NotificationTroubleshootTestManager.REQ_CODE_FIX)
requestDisablingBatteryOptimization(context, activityResultLauncher)
}
}
status = TestStatus.FAILED

View file

@ -15,12 +15,13 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.iid.FirebaseInstanceId
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startAddGoogleAccountIntent
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper
import timber.log.Timber
@ -32,7 +33,7 @@ import javax.inject.Inject
class TestFirebaseToken @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
override fun perform() {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
status = TestStatus.RUNNING
try {
FirebaseInstanceId.getInstance().instanceId
@ -48,7 +49,7 @@ class TestFirebaseToken @Inject constructor(private val context: AppCompatActivi
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
startAddGoogleAccountIntent(context, NotificationTroubleshootTestManager.REQ_CODE_FIX)
startAddGoogleAccountIntent(context, activityResultLauncher)
}
}
} else {

View file

@ -15,6 +15,8 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
@ -31,7 +33,7 @@ class TestPlayServices @Inject constructor(private val context: AppCompatActivit
private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
override fun perform() {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) {

View file

@ -15,6 +15,8 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
@ -37,7 +39,7 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc
private val activeSessionHolder: ActiveSessionHolder)
: TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
override fun perform() {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
// Check if we have a registered pusher for this token
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
@ -59,9 +61,9 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
manager?.retry()
manager?.retry(activityResultLauncher)
} else if (workInfo.state == WorkInfo.State.FAILED) {
manager?.retry()
manager?.retry(activityResultLauncher)
}
}
})

View file

@ -232,9 +232,11 @@
</intent-filter>
</service>
<!-- Add tools:ignore="Instantiatable" for the error reported only by Buildkite and for lintGplayRelease check :/ -->
<service
android:name=".core.services.VectorSyncService"
android:exported="false" />
android:exported="false"
tools:ignore="Instantiatable" />
<service
android:name=".features.call.telecom.VectorConnectionService"

View file

@ -17,11 +17,20 @@
package im.vector.app.core.extensions
import android.app.Activity
import android.content.Intent
import android.os.Parcelable
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import im.vector.app.core.platform.VectorBaseActivity
fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
}
fun VectorBaseActivity.addFragment(
frameId: Int,
fragment: Fragment,

View file

@ -17,7 +17,11 @@
package im.vector.app.core.extensions
import android.app.Activity
import android.content.Intent
import android.os.Parcelable
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
@ -26,6 +30,10 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
fun Fragment.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
}
fun VectorBaseFragment.addFragment(
frameId: Int,
fragment: Fragment,
@ -160,26 +168,24 @@ fun Fragment.getAllChildFragments(): List<Fragment> {
// Define a missing constant
const val POP_BACK_STACK_EXCLUSIVE = 0
fun Fragment.queryExportKeys(userId: String, requestCode: Int) {
fun Fragment.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher<Intent>) {
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
activity = requireActivity(),
fragment = this,
activityResultLauncher = activityResultLauncher,
defaultFileName = "element-megolm-export-$userId-$timestamp.txt",
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export),
requestCode = requestCode
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export)
)
}
fun Activity.queryExportKeys(userId: String, requestCode: Int) {
fun Activity.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher<Intent>) {
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
activity = this,
fragment = null,
activityResultLauncher = activityResultLauncher,
defaultFileName = "element-megolm-export-$userId-$timestamp.txt",
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export),
requestCode = requestCode
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export)
)
}

View file

@ -18,7 +18,6 @@ package im.vector.app.core.platform
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.Parcelable
@ -60,6 +59,7 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.observeNotNull
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.utils.toast
@ -68,7 +68,6 @@ import im.vector.app.features.MainActivityArgs
import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.consent.ConsentNotGivenHelper
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.PinMode
import im.vector.app.features.pin.UnlockedActivity
@ -206,7 +205,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
})
pinLocker.getLiveState().observeNotNull(this) {
if (this@VectorBaseActivity !is UnlockedActivity && it == PinLocker.State.LOCKED) {
navigator.openPinCode(this, PinMode.AUTH)
navigator.openPinCode(this, pinStartForActivityResult, PinMode.AUTH)
}
}
sessionListener = vectorComponent.sessionListener()
@ -313,22 +312,20 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
uiDisposables.dispose()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
when (resultCode) {
Activity.RESULT_OK -> {
Timber.v("Pin ok, unlock app")
pinLocker.unlock()
private val pinStartForActivityResult = registerStartForActivityResult { activityResult ->
when (activityResult.resultCode) {
Activity.RESULT_OK -> {
Timber.v("Pin ok, unlock app")
pinLocker.unlock()
// Cancel any new started PinActivity, after a screen rotation for instance
finishActivity(PinActivity.PIN_REQUEST_CODE)
}
else -> {
if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
// Remove the task, to be sure that PIN code will be requested when resumed
finishAndRemoveTask()
}
// Cancel any new started PinActivity, after a screen rotation for instance
// FIXME I cannot use this anymore :/
// finishActivity(PinActivity.PIN_REQUEST_CODE)
}
else -> {
if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
// Remove the task, to be sure that PIN code will be requested when resumed
finishAndRemoveTask()
}
}
}

View file

@ -42,9 +42,11 @@ abstract class VectorViewModel<S : MvRxState, VA : VectorViewModelAction, VE : V
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
* so you can use this in a switchMap or a flatMap
*/
// False positive
@Suppress("USELESS_CAST")
fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> {
setState { stateReducer(Loading()) }
return this.map { Success(it) as Async<T> }
return map { Success(it) as Async<T> }
.onErrorReturn { Fail(it) }
.doOnSuccess { setState { stateReducer(it) } }
}
@ -53,9 +55,11 @@ abstract class VectorViewModel<S : MvRxState, VA : VectorViewModelAction, VE : V
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
* so you can use this in a switchMap or a flatMap
*/
// False positive
@Suppress("USELESS_CAST")
fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> {
setState { stateReducer(Loading()) }
return this.map { Success(it) as Async<T> }
return map { Success(it) as Async<T> }
.onErrorReturn { Fail(it) }
.doOnNext { setState { stateReducer(it) } }
}

View file

@ -31,12 +31,12 @@ import android.provider.Browser
import android.provider.MediaStore
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsSession
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils
@ -130,7 +130,7 @@ fun openSoundRecorder(activity: Activity, requestCode: Int) {
* Open file selection activity
*/
fun openFileSelection(activity: Activity,
fragment: Fragment?,
activityResultLauncher: ActivityResultLauncher<Intent>?,
allowMultipleSelection: Boolean,
requestCode: Int) {
val fileIntent = Intent(Intent.ACTION_GET_CONTENT)
@ -140,8 +140,8 @@ fun openFileSelection(activity: Activity,
fileIntent.type = "*/*"
try {
fragment
?.startActivityForResult(fileIntent, requestCode)
activityResultLauncher
?.launch(fileIntent)
?: run {
activity.startActivityForResult(fileIntent, requestCode)
}
@ -440,10 +440,9 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID
*/
fun selectTxtFileToWrite(
activity: Activity,
fragment: Fragment?,
activityResultLauncher: ActivityResultLauncher<Intent>,
defaultFileName: String,
chooserHint: String,
requestCode: Int
chooserHint: String
) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
@ -452,11 +451,7 @@ fun selectTxtFileToWrite(
try {
val chooserIntent = Intent.createChooser(intent, chooserHint)
if (fragment != null) {
fragment.startActivityForResult(chooserIntent, requestCode)
} else {
activity.startActivityForResult(chooserIntent, requestCode)
}
activityResultLauncher.launch(chooserIntent)
} catch (activityNotFoundException: ActivityNotFoundException) {
activity.toast(R.string.error_no_external_application_found)
}

View file

@ -22,6 +22,8 @@ import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
@ -94,6 +96,12 @@ fun logPermissionStatuses(context: Context) {
}
}
fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher<Array<String>> {
return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
allGranted.invoke(result.keys.all { result[it] == true })
}
}
/**
* See [.checkPermissions]
*
@ -112,14 +120,14 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
* See [.checkPermissions]
*
* @param permissionsToBeGrantedBitMap
* @param fragment
* @param activityResultLauncher from the calling fragment that is requesting the permissions
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
fun checkPermissions(permissionsToBeGrantedBitMap: Int,
fragment: Fragment,
requestCode: Int,
activity: Activity,
activityResultLauncher: ActivityResultLauncher<Array<String>>,
@StringRes rationaleMessage: Int = 0): Boolean {
return checkPermissions(permissionsToBeGrantedBitMap, fragment.activity, fragment, requestCode, rationaleMessage)
return checkPermissions(permissionsToBeGrantedBitMap, activity, activityResultLauncher, 0, rationaleMessage)
}
/**
@ -137,22 +145,19 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
*
* @param permissionsToBeGrantedBitMap the permissions bit map to be granted
* @param activity the calling Activity that is requesting the permissions (or fragment parent)
* @param fragment the calling fragment that is requesting the permissions
* @param activityResultLauncher from the calling fragment that is requesting the permissions
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
activity: Activity?,
fragment: Fragment?,
activity: Activity,
activityResultLauncher: ActivityResultLauncher<Array<String>>?,
requestCode: Int,
@StringRes rationaleMessage: Int
): Boolean {
var isPermissionGranted = false
// sanity check
if (null == activity) {
Timber.w("## checkPermissions(): invalid input data")
isPermissionGranted = false
} else if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
isPermissionGranted = true
} else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap
@ -222,7 +227,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
.setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
.setPositiveButton(R.string.ok) { _, _ ->
if (permissionsListToBeGranted.isNotEmpty()) {
fragment?.requestPermissions(permissionsListToBeGranted.toTypedArray(), requestCode)
activityResultLauncher
?.launch(permissionsListToBeGranted.toTypedArray())
?: run {
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
}
@ -262,7 +268,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
.show()
*/
} else {
fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
activityResultLauncher
?.launch(permissionsArrayToBeGranted)
?: run {
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
}
@ -307,43 +314,6 @@ private fun updatePermissionsToBeGranted(activity: Activity,
return isRequestPermissionRequested
}
/**
* Helper method to process [.PERMISSIONS_FOR_AUDIO_IP_CALL]
* on onRequestPermissionsResult() methods.
*
* @param context App context
* @param grantResults permissions granted results
* @return true if audio IP call is permitted, false otherwise
*/
fun onPermissionResultAudioIpCall(context: Context, grantResults: IntArray): Boolean {
val arePermissionsGranted = allGranted(grantResults)
if (!arePermissionsGranted) {
Toast.makeText(context, R.string.permissions_action_not_performed_missing_permissions, Toast.LENGTH_SHORT).show()
}
return arePermissionsGranted
}
/**
* Helper method to process [.PERMISSIONS_FOR_VIDEO_IP_CALL]
* on onRequestPermissionsResult() methods.
* For video IP calls, record audio and camera permissions are both mandatory.
*
* @param context App context
* @param grantResults permissions granted results
* @return true if video IP call is permitted, false otherwise
*/
fun onPermissionResultVideoIpCall(context: Context, grantResults: IntArray): Boolean {
val arePermissionsGranted = allGranted(grantResults)
if (!arePermissionsGranted) {
Toast.makeText(context, R.string.permissions_action_not_performed_missing_permissions, Toast.LENGTH_SHORT).show()
}
return arePermissionsGranted
}
/**
* Return true if all permissions are granted, false if not or if permission request has been cancelled
*/

View file

@ -28,6 +28,7 @@ import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
@ -67,15 +68,11 @@ fun isAnimationDisabled(context: Context): Boolean {
* will return false and the notification privacy will fallback to "LOW_DETAIL".
*/
@TargetApi(Build.VERSION_CODES.M)
fun requestDisablingBatteryOptimization(activity: Activity, fragment: Fragment?, requestCode: Int) {
fun requestDisablingBatteryOptimization(activity: Activity, activityResultLauncher: ActivityResultLauncher<Intent>) {
val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + activity.packageName)
if (fragment != null) {
fragment.startActivityForResult(intent, requestCode)
} else {
activity.startActivityForResult(intent, requestCode)
}
activityResultLauncher.launch(intent)
}
// ==============================================================================================================
@ -100,7 +97,7 @@ fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = t
* Shows notification settings for the current app.
* In android O will directly opens the notification settings, in lower version it will show the App settings
*/
fun startNotificationSettingsIntent(activity: AppCompatActivity, requestCode: Int) {
fun startNotificationSettingsIntent(activity: AppCompatActivity, activityResultLauncher: ActivityResultLauncher<Intent>) {
val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
@ -110,7 +107,7 @@ fun startNotificationSettingsIntent(activity: AppCompatActivity, requestCode: In
intent.putExtra("app_package", activity.packageName)
intent.putExtra("app_uid", activity.applicationInfo?.uid)
}
activity.startActivityForResult(intent, requestCode)
activityResultLauncher.launch(intent)
}
/**
@ -126,42 +123,47 @@ fun startNotificationChannelSettingsIntent(fragment: Fragment, channelID: String
fragment.startActivity(intent)
}
fun startAddGoogleAccountIntent(context: AppCompatActivity, requestCode: Int) {
fun startAddGoogleAccountIntent(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
try {
val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
context.startActivityForResult(intent, requestCode)
activityResultLauncher.launch(intent)
} catch (activityNotFoundException: ActivityNotFoundException) {
context.toast(R.string.error_no_external_application_found)
}
}
fun startSharePlainTextIntent(fragment: Fragment, chooserTitle: String?, text: String, subject: String? = null, requestCode: Int? = null) {
fun startSharePlainTextIntent(fragment: Fragment,
activityResultLauncher: ActivityResultLauncher<Intent>?,
chooserTitle: String?,
text: String,
subject: String? = null) {
val share = Intent(Intent.ACTION_SEND)
share.type = "text/plain"
share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
// Add data to the intent, the receiving app will decide what to do with it.
share.putExtra(Intent.EXTRA_SUBJECT, subject)
share.putExtra(Intent.EXTRA_TEXT, text)
val intent = Intent.createChooser(share, chooserTitle)
try {
if (requestCode != null) {
fragment.startActivityForResult(Intent.createChooser(share, chooserTitle), requestCode)
if (activityResultLauncher != null) {
activityResultLauncher.launch(intent)
} else {
fragment.startActivity(Intent.createChooser(share, chooserTitle))
fragment.startActivity(intent)
}
} catch (activityNotFoundException: ActivityNotFoundException) {
fragment.activity?.toast(R.string.error_no_external_application_found)
}
}
fun startImportTextFromFileIntent(fragment: Fragment, requestCode: Int) {
fun startImportTextFromFileIntent(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "text/plain"
}
if (intent.resolveActivity(fragment.requireActivity().packageManager) != null) {
fragment.startActivityForResult(intent, requestCode)
if (intent.resolveActivity(context.packageManager) != null) {
activityResultLauncher.launch(intent)
} else {
fragment.activity?.toast(R.string.error_no_external_application_found)
context.toast(R.string.error_no_external_application_found)
}
}

View file

@ -15,12 +15,11 @@
*/
package im.vector.app.features.attachments
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.core.platform.Restorable
import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.BuildConfig
@ -48,6 +47,7 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
// Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
private var captureUri: Uri? = null
// The pending type is set if we have to handle permission request. It must be restored if the activity gets killed.
var pendingType: AttachmentTypeSelectorView.Type? = null
@ -72,99 +72,93 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
/**
* Starts the process for handling file picking
*/
fun selectFile(fragment: Fragment) {
MultiPicker.get(MultiPicker.FILE).startWith(fragment)
fun selectFile(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.FILE).startWith(activityResultLauncher)
}
/**
* Starts the process for handling image picking
*/
fun selectGallery(fragment: Fragment) {
MultiPicker.get(MultiPicker.IMAGE).startWith(fragment)
fun selectGallery(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.IMAGE).startWith(activityResultLauncher)
}
/**
* Starts the process for handling audio picking
*/
fun selectAudio(fragment: Fragment) {
MultiPicker.get(MultiPicker.AUDIO).startWith(fragment)
fun selectAudio(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.AUDIO).startWith(activityResultLauncher)
}
/**
* Starts the process for handling capture image picking
*/
fun openCamera(fragment: Fragment) {
captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment)
fun openCamera(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(context, activityResultLauncher)
}
/**
* Starts the process for handling contact picking
*/
fun selectContact(fragment: Fragment) {
MultiPicker.get(MultiPicker.CONTACT).startWith(fragment)
fun selectContact(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.CONTACT).startWith(activityResultLauncher)
}
/**
* This methods aims to handle on activity result data.
*
* @return true if it can handle the data, false otherwise
* This methods aims to handle the result data.
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
MultiPicker.REQUEST_CODE_PICK_FILE -> {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.FILE)
.getSelectedFiles(context, requestCode, resultCode, data)
.map { it.toContentAttachmentData() }
)
fun onFileResult(data: Intent?) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.FILE)
.getSelectedFiles(context, data)
.map { it.toContentAttachmentData() }
)
}
fun onAudioResult(data: Intent?) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.AUDIO)
.getSelectedFiles(context, data)
.map { it.toContentAttachmentData() }
)
}
fun onContactResult(data: Intent?) {
MultiPicker.get(MultiPicker.CONTACT)
.getSelectedFiles(context, data)
.firstOrNull()
?.toContactAttachment()
?.let {
callback.onContactAttachmentReady(it)
}
MultiPicker.REQUEST_CODE_PICK_AUDIO -> {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.AUDIO)
.getSelectedFiles(context, requestCode, resultCode, data)
.map { it.toContentAttachmentData() }
)
}
MultiPicker.REQUEST_CODE_PICK_CONTACT -> {
MultiPicker.get(MultiPicker.CONTACT)
.getSelectedFiles(context, requestCode, resultCode, data)
.firstOrNull()
?.toContactAttachment()
?.let {
callback.onContactAttachmentReady(it)
}
}
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.IMAGE)
.getSelectedFiles(context, requestCode, resultCode, data)
.map { it.toContentAttachmentData() }
)
}
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
captureUri?.let { captureUri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(context, requestCode, resultCode, captureUri)
?.let {
callback.onContentAttachmentsReady(
listOf(it).map { it.toContentAttachmentData() }
)
}
}
fun onImageResult(data: Intent?) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.IMAGE)
.getSelectedFiles(context, data)
.map { it.toContentAttachmentData() }
)
}
fun onPhotoResult() {
captureUri?.let { captureUri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(context, captureUri)
?.let {
callback.onContentAttachmentsReady(
listOf(it).map { it.toContentAttachmentData() }
)
}
}
MultiPicker.REQUEST_CODE_PICK_VIDEO -> {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.VIDEO)
.getSelectedFiles(context, requestCode, resultCode, data)
.map { it.toContentAttachmentData() }
)
}
else -> return false
}
return true
}
return false
}
fun onVideoResult(data: Intent?) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.VIDEO)
.getSelectedFiles(context, data)
.map { it.toContentAttachmentData() }
)
}
/**

View file

@ -30,8 +30,6 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object {
const val REQUEST_CODE = 55
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
private const val ATTACHMENTS_PREVIEW_RESULT = "ATTACHMENTS_PREVIEW_RESULT"
private const val KEEP_ORIGINAL_IMAGES_SIZE = "KEEP_ORIGINAL_IMAGES_SIZE"

View file

@ -81,6 +81,10 @@ class AttachmentsPreviewFragment @Inject constructor(
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
if (requestCode == UCrop.REQUEST_CROP && data != null) {
Timber.v("Crop success")

View file

@ -298,6 +298,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
start()
} else {

View file

@ -150,6 +150,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

View file

@ -111,6 +111,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }

View file

@ -23,6 +23,7 @@ import androidx.lifecycle.Observer
import im.vector.app.R
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.ui.views.KeysBackupBanner
@ -32,8 +33,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
class KeysBackupRestoreActivity : SimpleFragmentActivity() {
companion object {
private const val REQUEST_4S_SECRET = 100
const val SECRET_ALIAS = SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
fun intent(context: Context): Intent {
@ -130,22 +129,19 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME),
resultKeyStoreAlias = SECRET_ALIAS
).let {
startActivityForResult(it, REQUEST_4S_SECRET)
secretStartForActivityResult.launch(it)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_4S_SECRET) {
val extraResult = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
if (resultCode == Activity.RESULT_OK && extraResult != null) {
viewModel.handleGotSecretFromSSSS(
extraResult,
SECRET_ALIAS
)
} else {
finish()
}
private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
val extraResult = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
if (activityResult.resultCode == Activity.RESULT_OK && extraResult != null) {
viewModel.handleGotSecretFromSSSS(
extraResult,
SECRET_ALIAS
)
} else {
finish()
}
super.onActivityResult(requestCode, resultCode, data)
}
}

View file

@ -16,9 +16,9 @@
package im.vector.app.features.crypto.keysbackup.restore
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.lifecycle.Observer
@ -27,19 +27,15 @@ import butterknife.OnClick
import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent
import timber.log.Timber
import org.matrix.android.sdk.api.extensions.tryOrNull
import javax.inject.Inject
class KeysBackupRestoreFromKeyFragment @Inject constructor()
: VectorBaseFragment() {
companion object {
private const val REQUEST_TEXT_FILE_GET = 1
}
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_key
private lateinit var viewModel: KeysBackupRestoreFromKeyViewModel
@ -47,11 +43,12 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
@BindView(R.id.keys_backup_key_enter_til)
lateinit var mKeyInputLayout: TextInputLayout
@BindView(R.id.keys_restore_key_enter_edittext)
lateinit var mKeyTextEdit: EditText
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromKeyViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
mKeyTextEdit.setText(viewModel.recoveryCode.value)
@ -88,29 +85,23 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
@OnClick(R.id.keys_backup_import)
fun onImport() {
startImportTextFromFileIntent(this, REQUEST_TEXT_FILE_GET)
startImportTextFromFileIntent(requireContext(), textFileStartForActivityResult)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_TEXT_FILE_GET && resultCode == Activity.RESULT_OK) {
val dataURI = data?.data
if (dataURI != null) {
try {
activity
?.contentResolver
?.openInputStream(dataURI)
?.bufferedReader()
?.use { it.readText() }
?.let {
mKeyTextEdit.setText(it)
mKeyTextEdit.setSelection(it.length)
}
} catch (e: Exception) {
Timber.e(e, "Failed to read recovery kay from text")
}
private val textFileStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val dataURI = activityResult.data?.data ?: return@registerStartForActivityResult
tryOrNull(message = "Failed to read recovery kay from text") {
activity
?.contentResolver
?.openInputStream(dataURI)
?.bufferedReader()
?.use { it.readText() }
?.let {
mKeyTextEdit.setText(it)
mKeyTextEdit.setSelection(it.length)
}
}
return
}
super.onActivityResult(requestCode, resultCode, data)
}
}

View file

@ -59,8 +59,8 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromPassphraseViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)

View file

@ -16,6 +16,7 @@
package im.vector.app.features.crypto.keysbackup.restore
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.core.view.isVisible
import butterknife.BindView
@ -36,8 +37,8 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
if (compareValues(sharedViewModel.importKeyResult?.totalNumberOfKeys, 0) > 0) {

View file

@ -26,6 +26,7 @@ import im.vector.app.R
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.utils.toast
@ -93,7 +94,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
.show()
}
KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT -> {
queryExportKeys(session.myUserId, REQUEST_CODE_SAVE_MEGOLM_EXPORT)
queryExportKeys(session.myUserId, saveStartForActivityResult)
}
}
}
@ -125,10 +126,10 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
})
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SAVE_MEGOLM_EXPORT) {
val uri = data?.data
if (resultCode == Activity.RESULT_OK && uri != null) {
private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val uri = activityResult.data?.data
if (uri != null) {
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
showWaitingView()
@ -163,7 +164,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
hideWaitingView()
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onBackPressed() {
@ -198,7 +198,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
const val KEYS_VERSION = "KEYS_VERSION"
const val MANUAL_EXPORT = "MANUAL_EXPORT"
const val EXTRA_SHOW_MANUAL_EXPORT = "SHOW_MANUAL_EXPORT"
const val REQUEST_CODE_SAVE_MEGOLM_EXPORT = 101
fun intent(context: Context, showManualExport: Boolean): Intent {
val intent = Intent(context, KeysBackupSetupActivity::class.java)

View file

@ -40,8 +40,8 @@ class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment()
@BindView(R.id.keys_backup_setup_step1_manualExport)
lateinit var manualExportButton: Button
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)

View file

@ -16,6 +16,7 @@
package im.vector.app.features.crypto.keysbackup.setup
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
@ -77,8 +78,8 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
private lateinit var viewModel: KeysBackupSetupSharedViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)

View file

@ -16,7 +16,6 @@
package im.vector.app.features.crypto.keysbackup.setup
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
@ -31,6 +30,7 @@ import butterknife.BindView
import butterknife.OnClick
import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.LiveEvent
import im.vector.app.core.utils.copyToClipboard
@ -48,10 +48,6 @@ import javax.inject.Inject
class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() {
companion object {
private const val SAVE_RECOVERY_KEY_REQUEST_CODE = 2754
}
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step3
@BindView(R.id.keys_backup_setup_step3_button)
@ -65,8 +61,8 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
private lateinit var viewModel: KeysBackupSetupSharedViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)
viewModel.shouldPromptOnBack = false
@ -138,19 +134,20 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
activity = requireActivity(),
fragment = this,
activityResultLauncher = saveRecoveryActivityResultLauncher,
defaultFileName = "recovery-key-$userId-$timestamp.txt",
chooserHint = getString(R.string.save_recovery_key_chooser_hint),
requestCode = SAVE_RECOVERY_KEY_REQUEST_CODE
chooserHint = getString(R.string.save_recovery_key_chooser_hint)
)
dialog.dismiss()
}
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.setOnClickListener {
startSharePlainTextIntent(this,
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
recoveryKey,
context?.getString(R.string.recovery_key))
startSharePlainTextIntent(
fragment = this,
activityResultLauncher = null,
chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
text = recoveryKey,
subject = context?.getString(R.string.recovery_key))
viewModel.copyHasBeenMade = true
dialog.dismiss()
}
@ -202,15 +199,11 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SAVE_RECOVERY_KEY_REQUEST_CODE -> {
val uri = data?.data
if (resultCode == Activity.RESULT_OK && uri != null) {
viewModel.recoveryKey.value?.let {
exportRecoveryKeyToFile(uri, it)
}
}
private val saveRecoveryActivityResultLauncher = registerStartForActivityResult { activityRessult ->
val uri = activityRessult.data?.data ?: return@registerStartForActivityResult
if (activityRessult.resultCode == Activity.RESULT_OK) {
viewModel.recoveryKey.value?.let {
exportRecoveryKeyToFile(uri, it)
}
}
}

View file

@ -126,7 +126,7 @@ class KeyRequestHandler @Inject constructor(
// can we get more info on this device?
session?.cryptoService()?.getMyDevicesInfo()?.firstOrNull { it.deviceId == deviceId }?.let {
postAlert(context, userId, deviceId, true, deviceInfo, it)
} ?: kotlin.run {
} ?: run {
postAlert(context, userId, deviceId, true, deviceInfo)
}
} else {

View file

@ -24,6 +24,8 @@ import android.os.Parcelable
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentOnAttachListener
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import im.vector.app.R
@ -38,7 +40,10 @@ import kotlinx.android.synthetic.main.activity.*
import javax.inject.Inject
import kotlin.reflect.KClass
class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSheetDialogFragment.ResultListener {
class SharedSecureStorageActivity :
SimpleFragmentActivity(),
VectorBaseBottomSheetDialogFragment.ResultListener,
FragmentOnAttachListener {
@Parcelize
data class Args(
@ -58,6 +63,8 @@ class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSh
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager.addFragmentOnAttachListener(this)
toolbar.visibility = View.GONE
viewModel.observeViewEvents { observeViewEvents(it) }
@ -65,6 +72,11 @@ class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSh
viewModel.subscribe(this) { renderState(it) }
}
override fun onDestroy() {
super.onDestroy()
supportFragmentManager.removeFragmentOnAttachListener(this)
}
override fun onBackPressed() {
viewModel.handle(SharedSecureStorageAction.Back)
}
@ -119,8 +131,7 @@ class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSh
}
}
override fun onAttachFragment(fragment: Fragment) {
super.onAttachFragment(fragment)
override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) {
if (fragment is VectorBaseBottomSheetDialogFragment) {
fragment.resultListener = this
}

View file

@ -17,7 +17,6 @@
package im.vector.app.features.crypto.quads
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
@ -25,6 +24,7 @@ import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent
import io.reactivex.android.schedulers.AndroidSchedulers
@ -61,7 +61,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
}
.disposeOnDestroyView()
ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) }
ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
ssss_key_reset.clickableView.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
@ -85,9 +85,9 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
data?.data?.let { dataURI ->
private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
activityResult.data?.data?.let { dataURI ->
tryOrNull {
activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader()
@ -97,12 +97,6 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
}
}
}
return
}
super.onActivityResult(requestCode, resultCode, data)
}
companion object {
private const val IMPORT_FILE_REQ = 0
}
}

View file

@ -139,7 +139,7 @@ class BootstrapCrossSigningTask @Inject constructor(
null,
it
)
} ?: kotlin.run {
} ?: run {
ssssService.generateKey(
UUID.randomUUID().toString(),
params.keySpec,

View file

@ -17,7 +17,6 @@
package im.vector.app.features.crypto.recover
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.InputType.TYPE_CLASS_TEXT
import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
@ -32,6 +31,7 @@ import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
@ -82,7 +82,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
bootstrapMigrateContinueButton.debouncedClicks { submit() }
bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) }
bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
}
private fun submit() = withState(sharedViewModel) { state ->
@ -147,9 +147,9 @@ class BootstrapMigrateBackupFragment @Inject constructor(
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
data?.data?.let { dataURI ->
private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
activityResult.data?.data?.let { dataURI ->
tryOrNull {
activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader()
@ -159,12 +159,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
}
}
}
return
}
super.onActivityResult(requestCode, resultCode, data)
}
companion object {
private const val IMPORT_FILE_REQ = 0
}
}

View file

@ -16,7 +16,7 @@
package im.vector.app.features.crypto.recover
import android.app.Activity.RESULT_OK
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
@ -25,6 +25,7 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.startSharePlainTextIntent
@ -65,43 +66,46 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
try {
sharedViewModel.handle(BootstrapActions.SaveReqQueryStarted)
startActivityForResult(Intent.createChooser(intent, getString(R.string.keys_backup_setup_step3_please_make_copy)), REQUEST_CODE_SAVE)
saveStartForActivityResult.launch(Intent.createChooser(intent, getString(R.string.keys_backup_setup_step3_please_make_copy)))
} catch (activityNotFoundException: ActivityNotFoundException) {
requireActivity().toast(R.string.error_no_external_application_found)
sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SAVE) {
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
GlobalScope.launch(Dispatchers.IO) {
try {
sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
} catch (failure: Throwable) {
sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val uri = activityResult.data?.data ?: return@registerStartForActivityResult
GlobalScope.launch(Dispatchers.IO) {
try {
sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
} catch (failure: Throwable) {
sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
} else {
// result code seems to be always cancelled here.. so act as if it was saved
sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
return
} else if (requestCode == REQUEST_CODE_COPY) {
} else {
// result code seems to be always cancelled here.. so act as if it was saved
sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
}
private val copyStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
sharedViewModel.handle(BootstrapActions.RecoveryKeySaved)
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun shareRecoveryKey() = withState(sharedViewModel) { state ->
val recoveryKey = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
?: return@withState
startSharePlainTextIntent(this,
startSharePlainTextIntent(
this,
copyStartForActivityResult,
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
recoveryKey,
context?.getString(R.string.recovery_key), REQUEST_CODE_COPY)
context?.getString(R.string.recovery_key)
)
}
override fun invalidate() = withState(sharedViewModel) { state ->
@ -111,9 +115,4 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
recoveryContinue.isVisible = step.isSaved
bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
}
companion object {
const val REQUEST_CODE_SAVE = 123
const val REQUEST_CODE_COPY = 124
}
}

View file

@ -301,7 +301,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
// =======================================
private fun saveRecoveryKeyToUri(os: OutputStream) = withState { state ->
viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching {
runCatching {
os.use {
os.write((state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() ?: "").toByteArray())
}

View file

@ -17,7 +17,6 @@ package im.vector.app.features.crypto.verification
import android.app.Activity
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.KeyEvent
@ -35,6 +34,7 @@ import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
@ -108,12 +108,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
when (it) {
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
startActivityForResult(SharedSecureStorageActivity.newIntent(
secretStartForActivityResult.launch(SharedSecureStorageActivity.newIntent(
requireContext(),
null, // use default key
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
), SECRET_REQUEST_CODE)
))
}
is VerificationBottomSheetViewEvents.ModalError -> {
AlertDialog.Builder(requireContext())
@ -145,10 +145,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) {
val result = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
val reset = data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false
private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val result = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
val reset = activityResult.data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false
if (result != null) {
viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS))
} else if (reset) {
@ -156,11 +156,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
viewModel.handle(VerificationAction.SecuredStorageHasBeenReset)
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun invalidate() = withState(viewModel) { state ->
state.otherUserMxItem?.let { matrixItem ->
if (state.isMe) {
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
@ -347,9 +345,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
companion object {
const val SECRET_REQUEST_CODE = 101
fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet {
return VerificationBottomSheet().apply {
arguments = Bundle().apply {

View file

@ -16,7 +16,6 @@
package im.vector.app.features.crypto.verification.choose
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.fragmentViewModel
@ -25,11 +24,11 @@ import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import im.vector.app.features.qrcode.QrCodeScannerActivity
@ -75,16 +74,14 @@ class VerificationChooseMethodFragment @Inject constructor(
state.pendingRequest.invoke()?.transactionId ?: ""))
}
override fun openCamera() {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
doOpenQRCodeScanner()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
override fun openCamera() {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
doOpenQRCodeScanner()
}
}
@ -94,24 +91,18 @@ class VerificationChooseMethodFragment @Inject constructor(
}
private fun doOpenQRCodeScanner() {
QrCodeScannerActivity.startForResult(this)
QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
private val scanActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val scannedQrCode = QrCodeScannerActivity.getResultText(activityResult.data)
val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(activityResult.data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
val scannedQrCode = QrCodeScannerActivity.getResultText(data)
val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(data)
if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
onRemoteQrCodeScanned(scannedQrCode)
} else {
Timber.w("It was not a QR code, or empty result")
}
}
if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
onRemoteQrCodeScanned(scannedQrCode)
} else {
Timber.w("It was not a QR code, or empty result")
}
}
}

View file

@ -72,7 +72,7 @@ abstract class BottomSheetVerificationEmojisItem : VectorEpoxyModel<BottomSheetV
view.findViewById<TextView>(R.id.item_emoji_tv).isVisible = false
view.findViewById<ImageView>(R.id.item_emoji_image).isVisible = true
view.findViewById<ImageView>(R.id.item_emoji_image).setImageDrawable(ContextCompat.getDrawable(view.context, it))
} ?: kotlin.run {
} ?: run {
view.findViewById<TextView>(R.id.item_emoji_tv).isVisible = true
view.findViewById<ImageView>(R.id.item_emoji_image).isVisible = false
view.findViewById<TextView>(R.id.item_emoji_tv).text = rep.emoji

View file

@ -16,7 +16,6 @@
package im.vector.app.features.discovery
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
@ -27,12 +26,12 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.terms.ReviewTermsActivity
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.terms.TermsService
@ -92,22 +91,19 @@ class DiscoverySettingsFragment @Inject constructor(
viewModel.handle(DiscoverySettingsAction.Refresh)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
if (Activity.RESULT_OK == resultCode) {
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
} else {
// add some error?
}
private val termsActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
} else {
// add some error?
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun openIdentityServerTerms() = withState(viewModel) { state ->
if (state.termsNotSigned) {
navigator.openTerms(
this,
requireContext(),
termsActivityResultLauncher,
TermsService.ServiceType.IdentityService,
state.identityServer()?.ensureProtocol() ?: "",
null)

View file

@ -16,7 +16,6 @@
package im.vector.app.features.discovery.change
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
@ -28,13 +27,13 @@ import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.features.discovery.DiscoverySharedViewModel
import im.vector.app.features.terms.ReviewTermsActivity
import org.matrix.android.sdk.api.session.terms.TermsService
import kotlinx.android.synthetic.main.fragment_set_identity_server.*
import javax.inject.Inject
@ -121,7 +120,8 @@ class SetIdentityServerFragment @Inject constructor(
is SetIdentityServerViewEvents.TermsAccepted -> processIdentityServerChange()
is SetIdentityServerViewEvents.ShowTerms -> {
navigator.openTerms(
this,
requireContext(),
termsActivityResultLauncher,
TermsService.ServiceType.IdentityService,
it.identityServerUrl,
null)
@ -150,15 +150,12 @@ class SetIdentityServerFragment @Inject constructor(
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.identity_server)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
if (Activity.RESULT_OK == resultCode) {
processIdentityServerChange()
} else {
// add some error?
}
private val termsActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
processIdentityServerChange()
} else {
// add some error?
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun processIdentityServerChange() {

View file

@ -17,7 +17,7 @@
package im.vector.app.features.home.room.detail
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Typeface
@ -73,6 +73,7 @@ import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.trackItemsVisibilityChange
@ -91,19 +92,15 @@ import im.vector.app.core.utils.KeyboardStateUtils
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_INCOMING_URI
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_PICK_ATTACHMENT
import im.vector.app.core.utils.TextUtils
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.isValidUrl
import im.vector.app.core.utils.onPermissionResultAudioIpCall
import im.vector.app.core.utils.onPermissionResultVideoIpCall
import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.saveMedia
import im.vector.app.core.utils.shareMedia
import im.vector.app.core.utils.toast
@ -138,7 +135,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.invite.VectorInviteView
@ -206,8 +202,6 @@ data class RoomDetailArgs(
val sharedData: SharedData? = null
) : Parcelable
private const val REACTION_SELECT_REQUEST_CODE = 0
class RoomDetailFragment @Inject constructor(
private val session: Session,
private val avatarRenderer: AvatarRenderer,
@ -234,11 +228,6 @@ class RoomDetailFragment @Inject constructor(
ActiveCallView.Callback {
companion object {
private const val AUDIO_CALL_PERMISSION_REQUEST_CODE = 1
private const val VIDEO_CALL_PERMISSION_REQUEST_CODE = 2
private const val SAVE_ATTACHEMENT_REQUEST_CODE = 3
/**
* Sanitize the display name.
*
@ -371,6 +360,10 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
}.exhaustive
}
if (savedInstanceState == null) {
handleShareData()
}
}
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
@ -401,9 +394,14 @@ class RoomDetailFragment @Inject constructor(
}
}
private val integrationManagerActivityResultLauncher = registerStartForActivityResult {
// Noop
}
private fun openIntegrationManager(screen: String? = null) {
navigator.openIntegrationManager(
fragment = this,
context = requireContext(),
activityResultLauncher = integrationManagerActivityResultLauncher,
roomId = roomDetailArgs.roomId,
integId = null,
screen = screen
@ -440,7 +438,7 @@ class RoomDetailFragment @Inject constructor(
}
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) {
navigator.openStickerPicker(this, roomDetailArgs.roomId, event.widget)
navigator.openStickerPicker(requireContext(), stickerActivityResultLauncher, roomDetailArgs.roomId, event.widget)
}
private fun startOpenFileIntent(action: RoomDetailViewEvents.OpenFile) {
@ -480,20 +478,17 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, action.roomId)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) {
when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> {
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
}
is SharedData.Attachments -> {
// open share edition
onContentAttachmentsReady(sharedData.attachmentData)
}
null -> Timber.v("No share data to process")
}.exhaustive
}
private fun handleShareData() {
when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> {
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
}
is SharedData.Attachments -> {
// open share edition
onContentAttachmentsReady(sharedData.attachmentData)
}
null -> Timber.v("No share data to process")
}.exhaustive
}
override fun onDestroyView() {
@ -791,19 +786,33 @@ class RoomDetailFragment @Inject constructor(
}
}
private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(it)
}
} else {
context?.toast(R.string.permissions_action_not_performed_missing_permissions)
cleanUpAfterPermissionNotGranted()
}
}
private fun safeStartCall2(isVideoCall: Boolean) {
val startCallAction = RoomDetailAction.StartCall(isVideoCall)
roomDetailViewModel.pendingAction = startCallAction
if (isVideoCall) {
if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL,
this, VIDEO_CALL_PERMISSION_REQUEST_CODE,
requireActivity(),
startCallActivityResultLauncher,
R.string.permissions_rationale_msg_camera_and_audio)) {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(startCallAction)
}
} else {
if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL,
this, AUDIO_CALL_PERMISSION_REQUEST_CODE,
requireActivity(),
startCallActivityResultLauncher,
R.string.permissions_rationale_msg_record_audio)) {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(startCallAction)
@ -879,27 +888,63 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(composerLayout.composerEditText.text.toString()))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data)
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
when (requestCode) {
AttachmentsPreviewActivity.REQUEST_CODE -> {
val sendData = AttachmentsPreviewActivity.getOutput(data)
val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
}
REACTION_SELECT_REQUEST_CODE -> {
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
}
WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE -> {
val content = WidgetActivity.getOutput(data).toModel<MessageStickerContent>() ?: return
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
}
private val attachmentFileActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
attachmentsHelper.onImageResult(it.data)
}
}
private val attachmentAudioActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
attachmentsHelper.onAudioResult(it.data)
}
}
private val attachmentContactActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
attachmentsHelper.onContactResult(it.data)
}
}
private val attachmentImageActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
attachmentsHelper.onImageResult(it.data)
}
}
private val attachmentPhotoActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
attachmentsHelper.onPhotoResult()
}
}
private val contentAttachmentActivityResultLauncher = registerStartForActivityResult { activityResult ->
val data = activityResult.data ?: return@registerStartForActivityResult
if (activityResult.resultCode == Activity.RESULT_OK) {
val sendData = AttachmentsPreviewActivity.getOutput(data)
val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
}
}
private val emojiActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
val eventId = EmojiReactionPickerActivity.getOutputEventId(activityResult.data)
val reaction = EmojiReactionPickerActivity.getOutputReaction(activityResult.data)
if (eventId != null && reaction != null) {
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
}
}
// TODO why don't we call super here?
// super.onActivityResult(requestCode, resultCode, data)
}
private val stickerActivityResultLauncher = registerStartForActivityResult { activityResult ->
val data = activityResult.data ?: return@registerStartForActivityResult
if (activityResult.resultCode == Activity.RESULT_OK) {
WidgetActivity.getOutput(data).toModel<MessageStickerContent>()
?.let { content ->
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
}
}
}
// PRIVATE METHODS *****************************************************************************
@ -994,6 +1039,18 @@ class RoomDetailFragment @Inject constructor(
}
}
private val writingFileActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
val pendingUri = roomDetailViewModel.pendingUri
if (pendingUri != null) {
roomDetailViewModel.pendingUri = null
sendUri(pendingUri)
}
} else {
cleanUpAfterPermissionNotGranted()
}
}
private fun setupComposer() {
autoCompleter.setup(composerLayout.composerEditText)
@ -1026,7 +1083,7 @@ class RoomDetailFragment @Inject constructor(
override fun onRichContentSelected(contentUri: Uri): Boolean {
// We need WRITE_EXTERNAL permission
return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this@RoomDetailFragment, PERMISSION_REQUEST_CODE_INCOMING_URI)) {
return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), writingFileActivityResultLauncher)) {
sendUri(contentUri)
} else {
roomDetailViewModel.pendingUri = contentUri
@ -1416,52 +1473,11 @@ class RoomDetailFragment @Inject constructor(
// // }
// }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (allGranted(grantResults)) {
when (requestCode) {
SAVE_ATTACHEMENT_REQUEST_CODE -> {
sharedActionViewModel.pendingAction?.let {
handleActions(it)
sharedActionViewModel.pendingAction = null
}
}
PERMISSION_REQUEST_CODE_INCOMING_URI -> {
val pendingUri = roomDetailViewModel.pendingUri
if (pendingUri != null) {
roomDetailViewModel.pendingUri = null
sendUri(pendingUri)
}
}
PERMISSION_REQUEST_CODE_PICK_ATTACHMENT -> {
val pendingType = attachmentsHelper.pendingType
if (pendingType != null) {
attachmentsHelper.pendingType = null
launchAttachmentProcess(pendingType)
}
}
AUDIO_CALL_PERMISSION_REQUEST_CODE -> {
if (onPermissionResultAudioIpCall(requireContext(), grantResults)) {
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(it)
}
}
}
VIDEO_CALL_PERMISSION_REQUEST_CODE -> {
if (onPermissionResultVideoIpCall(requireContext(), grantResults)) {
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(it)
}
}
}
}
} else {
// Reset all pending data
roomDetailViewModel.pendingAction = null
roomDetailViewModel.pendingUri = null
attachmentsHelper.pendingType = null
}
private fun cleanUpAfterPermissionNotGranted() {
// Reset all pending data
roomDetailViewModel.pendingAction = null
roomDetailViewModel.pendingUri = null
attachmentsHelper.pendingType = null
}
// override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) {
@ -1576,9 +1592,20 @@ class RoomDetailFragment @Inject constructor(
)
}
private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
sharedActionViewModel.pendingAction?.let {
handleActions(it)
sharedActionViewModel.pendingAction = null
}
} else {
cleanUpAfterPermissionNotGranted()
}
}
private fun onSaveActionClicked(action: EventSharedAction.Save) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
&& !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, SAVE_ATTACHEMENT_REQUEST_CODE)) {
&& !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), saveActionActivityResultLauncher)) {
sharedActionViewModel.pendingAction = action
return
}
@ -1611,7 +1638,7 @@ class RoomDetailFragment @Inject constructor(
openRoomMemberProfile(action.userId)
}
is EventSharedAction.AddReaction -> {
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
}
is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
@ -1816,8 +1843,20 @@ class RoomDetailFragment @Inject constructor(
// AttachmentTypeSelectorView.Callback
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
val pendingType = attachmentsHelper.pendingType
if (pendingType != null) {
attachmentsHelper.pendingType = null
launchAttachmentProcess(pendingType)
}
} else {
cleanUpAfterPermissionNotGranted()
}
}
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
if (checkPermissions(type.permissionsBit, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)) {
if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
launchAttachmentProcess(type)
} else {
attachmentsHelper.pendingType = type
@ -1826,11 +1865,11 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) {
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(this)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(this)
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher)
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.exhaustive
}
@ -1849,7 +1888,7 @@ class RoomDetailFragment @Inject constructor(
}
if (grouped.previewables.isNotEmpty()) {
val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables))
startActivityForResult(intent, AttachmentsPreviewActivity.REQUEST_CODE)
contentAttachmentActivityResultLauncher.launch(intent)
}
}
}

View file

@ -1074,7 +1074,7 @@ class RoomDetailViewModel @AssistedInject constructor(
.buffer(1, TimeUnit.SECONDS)
.filter { it.isNotEmpty() }
.subscribeBy(onNext = { actions ->
val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event ?: return@subscribeBy
val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy
val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
if (trackUnreadMessages.get()) {
if (globalMostRecentDisplayedEvent == null) {

View file

@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.readreceipts
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
@ -59,8 +60,8 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment(), Di
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.seen_by)

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.action
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.fragmentViewModel
@ -51,8 +52,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false, disableItemAnimation = true)
messageActionsEpoxyController.listener = this

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.edithistory
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
@ -55,8 +56,8 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(
epoxyController,
showDivider = true,

View file

@ -84,7 +84,7 @@ abstract class MessagePollItem : AbsMessageItem<MessagePollItem.Holder>() {
}
} else {
holder.resultWrapper.isVisible = true
val maxCount = votes?.maxBy { it.value }?.value ?: 0
val maxCount = votes?.maxByOrNull { it.value }?.value ?: 0
optionsContent?.options?.forEachIndexed { index, item ->
if (index < resultLines.size) {
val optionCount = votes?.get(index) ?: 0

View file

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.timeline.reactions
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
@ -55,8 +56,8 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment(), ViewReac
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(epoxyController, hasFixedSize = false, showDivider = true)
bottomSheetTitle.text = context?.getString(R.string.reactions)

View file

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.widget
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel
@ -55,8 +56,8 @@ class RoomWidgetsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidget
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.active_widgets_title)
bottomSheetTitle.textSize = 20f

View file

@ -1,22 +0,0 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.detail.widget
object WidgetRequestCodes {
const val STICKER_PICKER_REQUEST_CODE = 16000
const val INTEGRATION_MANAGER_REQUEST_CODE = 16001
}

View file

@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list.actions
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.fragmentViewModel
@ -67,8 +68,8 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
roomListActionsEpoxyController.listener = this

View file

@ -112,6 +112,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }

View file

@ -30,6 +30,7 @@ import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
@ -112,10 +113,10 @@ class BigImageViewerActivity : VectorBaseActivity() {
private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this, takePhotoActivityResultLauncher)
}
} else {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
}
}
@ -127,33 +128,43 @@ class BigImageViewerActivity : VectorBaseActivity() {
.start(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(this, requestCode, resultCode, uri)
?.let {
onRoomAvatarSelected(it)
}
}
}
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(this, requestCode, resultCode, data)
.firstOrNull()?.let {
onRoomAvatarSelected(it)
}
}
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
private val takePhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(this, uri)
?.let {
onRoomAvatarSelected(it)
}
}
}
}
private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(this, activityResult.data)
.firstOrNull()?.let {
onRoomAvatarSelected(it)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
when (requestCode) {
PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -> onAvatarTypeSelected(true)
@ -174,7 +185,6 @@ class BigImageViewerActivity : VectorBaseActivity() {
private const val EXTRA_TITLE = "EXTRA_TITLE"
private const val EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"
private const val EXTRA_CAN_EDIT_IMAGE = "EXTRA_CAN_EDIT_IMAGE"
const val REQUEST_CODE = 1000
fun newIntent(context: Context, title: String?, imageUrl: String, canEditImage: Boolean = false): Intent {
return Intent(context, BigImageViewerActivity::class.java).apply {

View file

@ -21,11 +21,11 @@ import android.content.Context
import android.content.Intent
import android.view.View
import android.view.Window
import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.TaskStackBuilder
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError
@ -45,7 +45,6 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
import im.vector.app.features.home.room.detail.search.SearchActivity
import im.vector.app.features.home.room.detail.search.SearchArgs
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.invite.InviteUsersToRoomActivity
import im.vector.app.features.media.AttachmentData
@ -266,21 +265,32 @@ class DefaultNavigator @Inject constructor(
}
}
override fun openTerms(fragment: Fragment, serviceType: TermsService.ServiceType, baseUrl: String, token: String?, requestCode: Int) {
val intent = ReviewTermsActivity.intent(fragment.requireContext(), serviceType, baseUrl, token)
fragment.startActivityForResult(intent, requestCode)
override fun openTerms(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
serviceType: TermsService.ServiceType,
baseUrl: String,
token: String?) {
val intent = ReviewTermsActivity.intent(context, serviceType, baseUrl, token)
activityResultLauncher.launch(intent)
}
override fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, requestCode: Int) {
override fun openStickerPicker(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
roomId: String,
widget: Widget) {
val widgetArgs = widgetArgsBuilder.buildStickerPickerArgs(roomId, widget)
val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
fragment.startActivityForResult(intent, WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
val intent = WidgetActivity.newIntent(context, widgetArgs)
activityResultLauncher.launch(intent)
}
override fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?) {
override fun openIntegrationManager(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
roomId: String,
integId: String?,
screen: String?) {
val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screen)
val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
fragment.startActivityForResult(intent, WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE)
val intent = WidgetActivity.newIntent(context, widgetArgs)
activityResultLauncher.launch(intent)
}
override fun openRoomWidget(context: Context, roomId: String, widget: Widget, options: Map<String, Any>?) {
@ -293,14 +303,11 @@ class DefaultNavigator @Inject constructor(
}
}
override fun openPinCode(fragment: Fragment, pinMode: PinMode, requestCode: Int) {
val intent = PinActivity.newIntent(fragment.requireContext(), PinArgs(pinMode))
fragment.startActivityForResult(intent, requestCode)
}
override fun openPinCode(activity: Activity, pinMode: PinMode, requestCode: Int) {
val intent = PinActivity.newIntent(activity, PinArgs(pinMode))
activity.startActivityForResult(intent, requestCode)
override fun openPinCode(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
pinMode: PinMode) {
val intent = PinActivity.newIntent(context, PinArgs(pinMode))
activityResultLauncher.launch(intent)
}
override fun openMediaViewer(activity: Activity,

View file

@ -18,18 +18,16 @@ package im.vector.app.features.navigation
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.core.util.Pair
import androidx.fragment.app.Fragment
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.media.AttachmentData
import im.vector.app.features.pin.PinActivity
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.share.SharedData
import im.vector.app.features.terms.ReviewTermsActivity
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.terms.TermsService
@ -84,22 +82,26 @@ interface Navigator {
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
fun openPinCode(fragment: Fragment, pinMode: PinMode, requestCode: Int = PinActivity.PIN_REQUEST_CODE)
fun openPinCode(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
pinMode: PinMode)
fun openPinCode(activity: Activity, pinMode: PinMode, requestCode: Int = PinActivity.PIN_REQUEST_CODE)
fun openTerms(fragment: Fragment,
fun openTerms(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
serviceType: TermsService.ServiceType,
baseUrl: String,
token: String?,
requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
token: String?)
fun openStickerPicker(fragment: Fragment,
fun openStickerPicker(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
roomId: String,
widget: Widget,
requestCode: Int = WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
widget: Widget)
fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?)
fun openIntegrationManager(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
roomId: String,
integId: String?,
screen: String?)
fun openRoomWidget(context: Context, roomId: String, widget: Widget, options: Map<String, Any>? = null)

View file

@ -28,8 +28,6 @@ import im.vector.app.core.platform.VectorBaseActivity
class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
companion object {
const val PIN_REQUEST_CODE = 17890
fun newIntent(context: Context, args: PinArgs): Intent {
return Intent(context, PinActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, args)

View file

@ -19,7 +19,7 @@ package im.vector.app.features.qrcode
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.activity.result.ActivityResultLauncher
import com.google.zxing.BarcodeFormat
import com.google.zxing.Result
import com.google.zxing.ResultMetadataType
@ -75,15 +75,8 @@ class QrCodeScannerActivity : VectorBaseActivity() {
private const val EXTRA_OUT_TEXT = "EXTRA_OUT_TEXT"
private const val EXTRA_OUT_IS_QR_CODE = "EXTRA_OUT_IS_QR_CODE"
const val QR_CODE_SCANNER_REQUEST_CODE = 429
// For test only
fun startForResult(activity: Activity, requestCode: Int = QR_CODE_SCANNER_REQUEST_CODE) {
activity.startActivityForResult(Intent(activity, QrCodeScannerActivity::class.java), requestCode)
}
fun startForResult(fragment: Fragment, requestCode: Int = QR_CODE_SCANNER_REQUEST_CODE) {
fragment.startActivityForResult(Intent(fragment.requireActivity(), QrCodeScannerActivity::class.java), requestCode)
fun startForResult(activity: Activity, activityResultLauncher: ActivityResultLauncher<Intent>) {
activityResultLauncher.launch(Intent(activity, QrCodeScannerActivity::class.java))
}
fun getResultText(data: Intent?): String? {

View file

@ -17,7 +17,6 @@ package im.vector.app.features.reactions
import android.os.Bundle
import android.view.View
import androidx.lifecycle.observe
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.platform.VectorBaseFragment

View file

@ -212,10 +212,8 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
return intent
}
fun getOutput(data: Intent): Pair<String, String>? {
val eventId = data.getStringExtra(EXTRA_EVENT_ID) ?: return null
val reaction = data.getStringExtra(EXTRA_REACTION_RESULT) ?: return null
return eventId to reaction
}
fun getOutputEventId(data: Intent?): String? = data?.getStringExtra(EXTRA_EVENT_ID)
fun getOutputReaction(data: Intent?): String? = data?.getStringExtra(EXTRA_REACTION_RESULT)
}
}

View file

@ -284,7 +284,12 @@ class RoomMemberProfileFragment @Inject constructor(
}
private fun handleShareRoomMemberProfile(permalink: String) {
startSharePlainTextIntent(fragment = this, chooserTitle = null, text = permalink)
startSharePlainTextIntent(
fragment = this,
activityResultLauncher = null,
chooserTitle = null,
text = permalink
)
}
private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) {

View file

@ -20,6 +20,7 @@ import android.content.DialogInterface
import android.os.Bundle
import android.os.Parcelable
import android.view.KeyEvent
import android.view.View
import androidx.fragment.app.Fragment
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel
@ -46,8 +47,8 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.observeViewEvents {
when (it) {
is DeviceListBottomSheetViewEvents.Verify -> {

View file

@ -17,6 +17,7 @@
package im.vector.app.features.roommemberprofile.devices
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel
@ -41,8 +42,8 @@ class DeviceListFragment @Inject constructor(
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.setPadding(0, dimensionConverter.dpToPx(16), 0, dimensionConverter.dpToPx(16))
recyclerView.configureWith(
epoxyController,

View file

@ -17,6 +17,7 @@
package im.vector.app.features.roommemberprofile.devices
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel
@ -41,8 +42,8 @@ class DeviceTrustInfoActionFragment @Inject constructor(
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.setPadding(0, dimensionConverter.dpToPx(16), 0, dimensionConverter.dpToPx(16))
recyclerView.configureWith(
epoxyController,

View file

@ -42,14 +42,14 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.copyOnLongClick
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.features.crypto.util.toImageRes
import im.vector.app.features.home.AvatarRenderer
@ -260,14 +260,19 @@ class RoomProfileFragment @Inject constructor(
}
private fun onShareRoomProfile(permalink: String) {
startSharePlainTextIntent(fragment = this, chooserTitle = null, text = permalink)
startSharePlainTextIntent(
fragment = this,
activityResultLauncher = null,
chooserTitle = null,
text = permalink
)
}
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) = withState(roomProfileViewModel) {
if (matrixItem.avatarUrl?.isNotEmpty() == true) {
val intent = BigImageViewerActivity.newIntent(requireContext(), matrixItem.getBestName(), matrixItem.avatarUrl!!, it.canChangeAvatar)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, ViewCompat.getTransitionName(view) ?: "")
startActivityForResult(intent, BigImageViewerActivity.REQUEST_CODE, options.toBundle())
bigImageStartForActivityResult.launch(intent, options)
} else if (it.canChangeAvatar) {
showAvatarSelector()
}
@ -285,14 +290,20 @@ class RoomProfileFragment @Inject constructor(
.show()
}
private val takePhotoPermissionActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
onAvatarTypeSelected(true)
}
}
private var avatarCameraUri: Uri? = null
private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), takePhotoPermissionActivityResultLauncher)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(requireActivity(), takePhotoActivityResultLauncher)
}
} else {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
}
}
@ -304,37 +315,43 @@ class RoomProfileFragment @Inject constructor(
.start(requireContext(), this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(requireContext(), requestCode, resultCode, uri)
?.let {
onRoomAvatarSelected(it)
}
}
}
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(requireContext(), requestCode, resultCode, data)
.firstOrNull()?.let {
onRoomAvatarSelected(it)
}
}
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
BigImageViewerActivity.REQUEST_CODE -> data?.let { onAvatarCropped(it.data) }
private val takePhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(requireContext(), uri)
?.let {
onRoomAvatarSelected(it)
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (allGranted(grantResults)) {
private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(requireContext(), activityResult.data)
.firstOrNull()?.let {
onRoomAvatarSelected(it)
}
}
}
private val bigImageStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
activityResult.data?.let { onAvatarCropped(it.data) }
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -> onAvatarTypeSelected(true)
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
}
}
}

View file

@ -107,7 +107,8 @@ class VectorSettingsActivity : VectorBaseActivity(),
}
if (oFragment != null) {
oFragment.setTargetFragment(caller, 0)
// Deprecated, I comment it, I think it is useless
// oFragment.setTargetFragment(caller, 0)
// Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out)

View file

@ -41,6 +41,7 @@ import com.google.android.material.textfield.TextInputLayout
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.platform.SimpleTextWatcher
@ -48,11 +49,10 @@ import im.vector.app.core.preference.UserAvatarPreference
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.app.core.utils.TextUtils
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.getSizeOfFiles
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
@ -279,89 +279,38 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
session.integrationManagerService().removeListener(integrationServiceListener)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
onAvatarTypeSelected(true)
private val attachmentPhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(requireContext(), uri)
?.let {
onAvatarSelected(it)
}
}
}
}
private val attachmentImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
val data = activityResult.data ?: return@registerStartForActivityResult
if (activityResult.resultCode == Activity.RESULT_OK) {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(requireContext(), data)
.firstOrNull()?.let {
onAvatarSelected(it)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
avatarCameraUri?.let { uri ->
MultiPicker.get(MultiPicker.CAMERA)
.getTakenPhoto(requireContext(), requestCode, resultCode, uri)
?.let {
onAvatarSelected(it)
}
}
}
MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
MultiPicker
.get(MultiPicker.IMAGE)
.getSelectedFiles(requireContext(), requestCode, resultCode, data)
.firstOrNull()?.let {
onAvatarSelected(it)
}
}
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
/* TODO
VectorUtils.TAKE_IMAGE -> {
val thumbnailUri = VectorUtils.getThumbnailUriFromIntent(activity, data, session.mediaCache)
if (null != thumbnailUri) {
displayLoadingView()
val resource = ResourceUtils.openResource(activity, thumbnailUri, null)
if (null != resource) {
session.mediaCache.uploadContent(resource.mContentStream, null, resource.mMimeType, null, object : MXMediaUploadListener() {
override fun onUploadError(uploadId: String?, serverResponseCode: Int, serverErrorMessage: String?) {
activity?.runOnUiThread { onCommonDone(serverResponseCode.toString() + " : " + serverErrorMessage) }
}
override fun onUploadComplete(uploadId: String?, contentUri: String?) {
activity?.runOnUiThread {
session.myUser.updateAvatarUrl(contentUri, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
onCommonDone(null)
refreshDisplay()
}
override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}
override fun onMatrixError(e: MatrixError) {
if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) {
activity?.runOnUiThread {
hideLoadingView()
(activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e)
}
} else {
onCommonDone(e.localizedMessage)
}
}
override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
}
}
})
}
}
}
*/
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
}
}
}
@ -400,13 +349,19 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
}.show()
}
private val takePhotoActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
onAvatarTypeSelected(true)
}
}
private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), takePhotoActivityResultLauncher)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(requireActivity(), attachmentPhotoActivityResultLauncher)
}
} else {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
MultiPicker.get(MultiPicker.IMAGE).single().startWith(attachmentImageActivityResultLauncher)
}
}
@ -464,29 +419,10 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
*/
}
private fun onPhonebookCountryUpdate(data: Intent?) {
/* TODO
if (data != null && data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME)
&& data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE)) {
val countryCode = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE)
if (!TextUtils.equals(countryCode, PhoneNumberUtils.getCountryCode(activity))) {
PhoneNumberUtils.setCountryCode(activity, countryCode)
mContactPhonebookCountryPreference.summary = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME)
}
}
*/
}
// ==============================================================================================================
// Phone number management
// ==============================================================================================================
/**
* Refresh phone number list
*/
private fun refreshPhoneNumbersList() {
}
/**
* Update the password.
*/
@ -646,9 +582,4 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
})
}
}
companion object {
private const val REQUEST_NEW_PHONE_NUMBER = 456
private const val REQUEST_PHONEBOOK_COUNTRY = 789
}
}

View file

@ -27,6 +27,7 @@ import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.preference.VectorEditTextPreference
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory
@ -114,6 +115,10 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
handleSystemPreference()
}
private val batteryStartForActivityResult = registerStartForActivityResult {
// Noop
}
// BackgroundSyncModeChooserDialog.InteractionListener
override fun onOptionSelected(mode: BackgroundSyncMode) {
// option has change, need to act
@ -122,9 +127,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
// Even if using foreground service with foreground notif, it stops to work
// in doze mode for certain devices :/
if (!isIgnoringBatteryOptimizations(requireContext())) {
requestDisablingBatteryOptimization(requireActivity(),
this@VectorSettingsNotificationPreferenceFragment,
REQUEST_BATTERY_OPTIMIZATION)
requestDisablingBatteryOptimization(requireActivity(), batteryStartForActivityResult)
}
}
vectorPreferences.setFdroidSyncBackgroundMode(mode)
@ -210,27 +213,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, vectorPreferences.getNotificationRingTone())
}
startActivityForResult(intent, REQUEST_NOTIFICATION_RINGTONE)
ringtoneStartForActivityResult.launch(intent)
false
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_NOTIFICATION_RINGTONE -> {
vectorPreferences.setNotificationRingTone(data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
private val ringtoneStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
vectorPreferences.setNotificationRingTone(activityResult.data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
// test if the selected ring tone can be played
val notificationRingToneName = vectorPreferences.getNotificationRingToneName()
if (null != notificationRingToneName) {
vectorPreferences.setNotificationRingTone(vectorPreferences.getNotificationRingTone())
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY)!!
.summary = notificationRingToneName
}
}
// test if the selected ring tone can be played
val notificationRingToneName = vectorPreferences.getNotificationRingToneName()
if (null != notificationRingToneName) {
vectorPreferences.setNotificationRingTone(vectorPreferences.getNotificationRingTone())
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY)!!
.summary = notificationRingToneName
}
}
}
@ -340,9 +338,4 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
})
}
}
companion object {
private const val REQUEST_NOTIFICATION_RINGTONE = 888
private const val REQUEST_BATTERY_OPTIMIZATION = 500
}
}

View file

@ -17,7 +17,6 @@ package im.vector.app.features.settings
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
@ -30,6 +29,7 @@ import androidx.transition.TransitionManager
import butterknife.BindView
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.rageshake.BugReporter
@ -76,7 +76,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
}
mRunButton.debouncedClicks {
testManager?.retry()
testManager?.retry(testStartForActivityResult)
}
startUI()
}
@ -134,7 +134,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
}
}
mRecyclerView.adapter = testManager?.adapter
testManager?.runDiagnostic()
testManager?.runDiagnostic(testStartForActivityResult)
}
override fun onDestroyView() {
@ -142,12 +142,14 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
super.onDestroyView()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && requestCode == NotificationTroubleshootTestManager.REQ_CODE_FIX) {
testManager?.retry()
return
private val testStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
retry()
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun retry() {
testManager?.retry(testStartForActivityResult)
}
override fun onDetach() {

View file

@ -16,14 +16,13 @@
package im.vector.app.features.settings
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinMode
import kotlinx.coroutines.launch
@ -67,17 +66,18 @@ class VectorSettingsPinFragment @Inject constructor(
refreshPinCodeStatus()
}
} else {
navigator.openPinCode(this@VectorSettingsPinFragment, PinMode.CREATE)
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.CREATE
)
}
true
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
refreshPinCodeStatus()
}
private val pinActivityResultLauncher = registerStartForActivityResult {
refreshPinCodeStatus()
}
}

View file

@ -39,6 +39,7 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.queryExportKeys
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.intent.ExternalIntentData
import im.vector.app.core.intent.analyseIntent
@ -55,7 +56,6 @@ import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivit
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinMode
import im.vector.app.features.raw.wellknown.getElementWellknown
@ -320,48 +320,46 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_SAVE_MEGOLM_EXPORT -> {
val uri = data?.data
if (uri != null) {
activity?.let { activity ->
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
displayLoadingView()
private val saveMegolmStartForActivityResult = registerStartForActivityResult {
val uri = it.data?.data ?: return@registerStartForActivityResult
if (it.resultCode == Activity.RESULT_OK) {
ExportKeysDialog().show(requireActivity(), object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
displayLoadingView()
KeysExporter(session)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
KeysExporter(session)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
}
})
}
}
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
}
PinActivity.PIN_REQUEST_CODE -> {
doOpenPinCodePreferenceScreen()
}
REQUEST_E2E_FILE_REQUEST_CODE -> {
importKeys(data)
}
}
})
}
}
private val pinActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
doOpenPinCodePreferenceScreen()
}
}
private val importKeysActivityResultLauncher = registerStartForActivityResult {
val data = it.data ?: return@registerStartForActivityResult
if (it.resultCode == Activity.RESULT_OK) {
importKeys(data)
}
}
@ -369,7 +367,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
lifecycleScope.launchWhenResumed {
val hasPinCode = pinCodeStore.hasEncodedPin()
if (hasPinCode) {
navigator.openPinCode(this@VectorSettingsSecurityPrivacyFragment, PinMode.AUTH)
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.AUTH)
} else {
doOpenPinCodePreferenceScreen()
}
@ -391,7 +392,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
exportPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
queryExportKeys(activeSessionHolder.getSafeActiveSession()?.myUserId ?: "", REQUEST_CODE_SAVE_MEGOLM_EXPORT)
queryExportKeys(activeSessionHolder.getSafeActiveSession()?.myUserId ?: "", saveMegolmStartForActivityResult)
true
}
@ -406,7 +407,12 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
*/
@SuppressLint("NewApi")
private fun importKeys() {
activity?.let { openFileSelection(it, this, false, REQUEST_E2E_FILE_REQUEST_CODE) }
openFileSelection(
requireActivity(),
importKeysActivityResultLauncher,
false,
0
)
}
/**
@ -414,12 +420,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
*
* @param intent the intent result
*/
private fun importKeys(intent: Intent?) {
// sanity check
if (null == intent) {
return
}
private fun importKeys(intent: Intent) {
val sharedDataItems = analyseIntent(intent)
val thisActivity = activity
@ -605,9 +606,4 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
})
}
companion object {
private const val REQUEST_E2E_FILE_REQUEST_CODE = 123
private const val REQUEST_CODE_SAVE_MEGOLM_EXPORT = 124
}
}

Some files were not shown because too many files have changed in this diff Show more