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 🧱: Build 🧱:
- Use Update Gradle Wrapper Action - Use Update Gradle Wrapper Action
- Updates Gradle Wrapper from 5.6.4 to 6.6.1. (#2193) - 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: Other changes:
- Added registration/verification automated UI tests - Added registration/verification automated UI tests

View File

@ -58,21 +58,16 @@ android {
} }
dependencies { 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:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.1.0' implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.recyclerview:recyclerview:1.1.0"
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 '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. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { 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 { repositories {
google() google()
jcenter() jcenter()
@ -10,10 +12,8 @@ buildscript {
} }
} }
dependencies { dependencies {
// Warning: 3.6.3 leads to infinite gradle builds. Stick to 3.5.3 for the moment classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.google.gms:google-services:4.3.4'
classpath 'com.google.gms:google-services:4.3.2'
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2' 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 { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them // 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 { dependencies {
implementation project(":matrix-sdk-android") 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:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging // Paging
@ -43,8 +44,4 @@ dependencies {
// Logging // Logging
implementation 'com.jakewharton.timber:timber:4.7.1' 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-kapt'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android' apply plugin: 'realm-android'
apply plugin: 'okreplay'
buildscript { buildscript {
repositories { repositories {
@ -109,21 +108,21 @@ static def gitRevisionDate() {
dependencies { dependencies {
def arrow_version = "0.8.2" 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 lifecycle_version = '2.2.0'
def arch_version = '2.1.0' def arch_version = '2.1.0'
def coroutines_version = "1.3.8"
def markwon_version = '3.1.0' def markwon_version = '3.1.0'
def daggerVersion = '2.25.4' def daggerVersion = '2.29.1'
def work_version = '2.4.0' def work_version = '2.4.0'
def retrofit_version = '2.6.2' def retrofit_version = '2.6.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.2.0" 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-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@ -143,7 +142,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:core:$markwon_version"
// Image // Image
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01' implementation 'androidx.exifinterface:exifinterface:1.3.0'
// Database // Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' 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 // 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') implementation('com.facebook.react:react-native-webrtc:1.84.0-jitsi-5112273@aar')
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0' testImplementation 'junit:junit:4.13'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0' //testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // 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 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.44' testImplementation 'org.amshove.kluent:kluent-android:1.61'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test // Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion" kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44' androidTestImplementation 'org.amshove.kluent:kluent-android:1.61'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // 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 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version" 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 // Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' 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.net.Uri
import android.os.Parcelable import android.os.Parcelable
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@JsonClass(generateAdapter = true)
data class ContentAttachmentData( data class ContentAttachmentData(
val size: Long = 0, val size: Long = 0,
val duration: Long? = 0, val duration: Long? = 0,
@ -32,10 +34,11 @@ data class ContentAttachmentData(
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
val name: String? = null, val name: String? = null,
val queryUri: Uri, val queryUri: Uri,
private val mimeType: String?, val mimeType: String?,
val type: Type val type: Type
) : Parcelable { ) : Parcelable {
@JsonClass(generateAdapter = false)
enum class Type { enum class Type {
FILE, FILE,
IMAGE, IMAGE,

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,12 @@
package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest 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( data class KeysVersion(
// the keys backup version // 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) callback.onFailure(SharedSecretStorageError.BadKeyFormat)
} }
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
kotlin.runCatching { runCatching {
// decrypt from recovery key // decrypt from recovery key
withOlmDecryption { olmPkDecryption -> withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(keySpec.privateKey) olmPkDecryption.setPrivateKey(keySpec.privateKey)
@ -390,7 +390,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
callback.onFailure(SharedSecretStorageError.BadKeyFormat) callback.onFailure(SharedSecretStorageError.BadKeyFormat)
} }
cryptoCoroutineScope.launch(coroutineDispatchers.main) { cryptoCoroutineScope.launch(coroutineDispatchers.main) {
kotlin.runCatching { runCatching {
decryptAesHmacSha2(keySpec, name, secretContent) decryptAesHmacSha2(keySpec, name, secretContent)
}.foldToCallback(callback) }.foldToCallback(callback)
} }

View File

@ -1,4 +1,3 @@
/* /*
* Copyright 2016 OpenMarket Ltd * Copyright 2016 OpenMarket Ltd
* Copyright 2018 New Vector Ltd * Copyright 2018 New Vector Ltd
@ -215,11 +214,12 @@ internal interface IMXCryptoStore {
// TODO temp // TODO temp
fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>> 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>) fun saveMyDevicesInfo(info: List<DeviceInfo>)
/** /**
* Store the crypto algorithm for a room. * Store the crypto algorithm for a room.
* *
@ -367,7 +367,19 @@ internal interface IMXCryptoStore {
fun saveGossipingEvent(event: Event) 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 * Search an IncomingRoomKeyRequest
@ -411,7 +423,7 @@ internal interface IMXCryptoStore {
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo? fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true) fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)
fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?)
@ -421,12 +433,13 @@ internal interface IMXCryptoStore {
fun updateUsersTrust(check: (String) -> Boolean) fun updateUsersTrust(check: (String) -> Boolean)
fun addWithHeldMegolmSession(withHeldContent: RoomKeyWithHeldContent) 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 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?) 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 // Dev tools
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest> 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 -> doRealmTransaction(realmConfiguration) { realm ->
realm.where<IncomingGossipingRequestEntity>() realm.where<IncomingGossipingRequestEntity>()
.equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, request.userId) .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, requestUserId)
.equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, request.deviceId) .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, requestDeviceId)
.equalTo(IncomingGossipingRequestEntityFields.REQUEST_ID, request.requestId) .equalTo(IncomingGossipingRequestEntityFields.REQUEST_ID, requestId)
.findAll().forEach { .findAll().forEach {
it.requestState = state 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.MessageType
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent 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.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.ForceToBooleanJsonAdapter
import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory 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 import org.matrix.android.sdk.internal.network.parsing.UriMoshiAdapter
object MoshiProvider { object MoshiProvider {
@ -41,6 +43,8 @@ object MoshiProvider {
private val moshi: Moshi = Moshi.Builder() private val moshi: Moshi = Moshi.Builder()
.add(UriMoshiAdapter()) .add(UriMoshiAdapter())
.add(ForceToBooleanJsonAdapter()) .add(ForceToBooleanJsonAdapter())
.add(CipherSuiteMoshiAdapter())
.add(TlsVersionMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
.registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE) .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 org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import okreplay.OkReplayInterceptor
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@Module @Module
@ -44,12 +43,6 @@ internal object NetworkModule {
return interceptor return interceptor
} }
@Provides
@JvmStatic
fun providesOkReplayInterceptor(): OkReplayInterceptor {
return OkReplayInterceptor()
}
@Provides @Provides
@JvmStatic @JvmStatic
fun providesStethoInterceptor(): StethoInterceptor { fun providesStethoInterceptor(): StethoInterceptor {
@ -71,8 +64,7 @@ internal object NetworkModule {
timeoutInterceptor: TimeOutInterceptor, timeoutInterceptor: TimeOutInterceptor,
userAgentInterceptor: UserAgentInterceptor, userAgentInterceptor: UserAgentInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor, httpLoggingInterceptor: HttpLoggingInterceptor,
curlLoggingInterceptor: CurlLoggingInterceptor, curlLoggingInterceptor: CurlLoggingInterceptor): OkHttpClient {
okReplayInterceptor: OkReplayInterceptor): OkHttpClient {
return OkHttpClient.Builder() return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS)
@ -93,7 +85,6 @@ internal object NetworkModule {
proxy(it) proxy(it)
} }
} }
.addInterceptor(okReplayInterceptor)
.build() .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 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,19 +15,21 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk package org.matrix.android.sdk.internal.network.parsing
import okreplay.OkReplayConfig import com.squareup.moshi.FromJson
import okreplay.PermissionRule import com.squareup.moshi.ToJson
import okreplay.RecorderRule import okhttp3.TlsVersion
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
class OkReplayRuleChainNoActivity( internal class TlsVersionMoshiAdapter {
private val configuration: OkReplayConfig) {
fun get(): TestRule { @ToJson
return RuleChain.outerRule(PermissionRule(configuration)) fun toJson(tlsVersion: TlsVersion): String {
.around(RecorderRule(configuration)) return tlsVersion.javaName
}
@FromJson
fun fromJson(tlsVersionString: String): TlsVersion {
return TlsVersion.forJavaName(tlsVersionString)
} }
} }

View File

@ -41,15 +41,10 @@ android {
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.0' implementation "androidx.fragment:fragment:1.3.0-beta01"
testImplementation 'junit:junit:4.12' implementation 'androidx.exifinterface:exifinterface:1.3.0'
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'
// Log // Log
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,7 @@ import butterknife.OnClick
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent 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.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO 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.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
@ -196,33 +197,29 @@ class DebugMenuActivity : VectorBaseActivity() {
} }
private fun doScanQRCode() { private fun doScanQRCode() {
QrCodeScannerActivity.startForResult(this) QrCodeScannerActivity.startForResult(this, qrStartForActivityResult)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val qrStartForActivityResult = registerStartForActivityResult { activityResult ->
super.onActivityResult(requestCode, resultCode, data) if (activityResult.resultCode == Activity.RESULT_OK) {
if (resultCode == Activity.RESULT_OK) { toast("QrCode: " + QrCodeScannerActivity.getResultText(activityResult.data)
when (requestCode) { + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(activityResult.data))
QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
toast("QrCode: " + QrCodeScannerActivity.getResultText(data) + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(data))
// Also update the current QR Code (reverse operation) // Also update the current QR Code (reverse operation)
// renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "") // renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
val result = QrCodeScannerActivity.getResultText(data)!! val result = QrCodeScannerActivity.getResultText(activityResult.data)!!
val qrCodeData = result.toQrCodeData() val qrCodeData = result.toQrCodeData()
Timber.e("qrCodeData: $qrCodeData") Timber.e("qrCodeData: $qrCodeData")
if (result.length != buffer.size) { if (result.length != buffer.size) {
Timber.e("Error, length are not the same") Timber.e("Error, length are not the same")
} else { } else {
// Convert to ByteArray // Convert to ByteArray
val byteArrayResult = result.toByteArray(Charsets.ISO_8859_1) val byteArrayResult = result.toByteArray(Charsets.ISO_8859_1)
for (i in byteArrayResult.indices) { for (i in byteArrayResult.indices) {
if (buffer[i] != byteArrayResult[i]) { if (buffer[i] != byteArrayResult[i]) {
Timber.e("Error for byte $i, expecting ${buffer[i]} and get ${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 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.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
@ -28,7 +30,7 @@ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: Vecto
private val stringProvider: StringProvider) private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) { : TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
override fun perform() { override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
if (vectorPreferences.autoStartOnBoot()) { if (vectorPreferences.autoStartOnBoot()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success) description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.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) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() { override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true) vectorPreferences.setAutoStartOnBoot(true)
manager?.retry() manager?.retry(activityResultLauncher)
} }
} }
status = TestStatus.FAILED status = TestStatus.FAILED

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,12 +31,12 @@ import android.provider.Browser
import android.provider.MediaStore import android.provider.MediaStore
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsSession import androidx.browser.customtabs.CustomTabsSession
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
@ -130,7 +130,7 @@ fun openSoundRecorder(activity: Activity, requestCode: Int) {
* Open file selection activity * Open file selection activity
*/ */
fun openFileSelection(activity: Activity, fun openFileSelection(activity: Activity,
fragment: Fragment?, activityResultLauncher: ActivityResultLauncher<Intent>?,
allowMultipleSelection: Boolean, allowMultipleSelection: Boolean,
requestCode: Int) { requestCode: Int) {
val fileIntent = Intent(Intent.ACTION_GET_CONTENT) val fileIntent = Intent(Intent.ACTION_GET_CONTENT)
@ -140,8 +140,8 @@ fun openFileSelection(activity: Activity,
fileIntent.type = "*/*" fileIntent.type = "*/*"
try { try {
fragment activityResultLauncher
?.startActivityForResult(fileIntent, requestCode) ?.launch(fileIntent)
?: run { ?: run {
activity.startActivityForResult(fileIntent, requestCode) activity.startActivityForResult(fileIntent, requestCode)
} }
@ -440,10 +440,9 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID
*/ */
fun selectTxtFileToWrite( fun selectTxtFileToWrite(
activity: Activity, activity: Activity,
fragment: Fragment?, activityResultLauncher: ActivityResultLauncher<Intent>,
defaultFileName: String, defaultFileName: String,
chooserHint: String, chooserHint: String
requestCode: Int
) { ) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
@ -452,11 +451,7 @@ fun selectTxtFileToWrite(
try { try {
val chooserIntent = Intent.createChooser(intent, chooserHint) val chooserIntent = Intent.createChooser(intent, chooserHint)
if (fragment != null) { activityResultLauncher.launch(chooserIntent)
fragment.startActivityForResult(chooserIntent, requestCode)
} else {
activity.startActivityForResult(chooserIntent, requestCode)
}
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (activityNotFoundException: ActivityNotFoundException) {
activity.toast(R.string.error_no_external_application_found) 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.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat 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] * See [.checkPermissions]
* *
@ -112,14 +120,14 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
* See [.checkPermissions] * See [.checkPermissions]
* *
* @param permissionsToBeGrantedBitMap * @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) * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/ */
fun checkPermissions(permissionsToBeGrantedBitMap: Int, fun checkPermissions(permissionsToBeGrantedBitMap: Int,
fragment: Fragment, activity: Activity,
requestCode: Int, activityResultLauncher: ActivityResultLauncher<Array<String>>,
@StringRes rationaleMessage: Int = 0): Boolean { @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 permissionsToBeGrantedBitMap the permissions bit map to be granted
* @param activity the calling Activity that is requesting the permissions (or fragment parent) * @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) * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/ */
private fun checkPermissions(permissionsToBeGrantedBitMap: Int, private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
activity: Activity?, activity: Activity,
fragment: Fragment?, activityResultLauncher: ActivityResultLauncher<Array<String>>?,
requestCode: Int, requestCode: Int,
@StringRes rationaleMessage: Int @StringRes rationaleMessage: Int
): Boolean { ): Boolean {
var isPermissionGranted = false var isPermissionGranted = false
// sanity check // sanity check
if (null == activity) { if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
Timber.w("## checkPermissions(): invalid input data")
isPermissionGranted = false
} else if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
isPermissionGranted = true isPermissionGranted = true
} else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap } else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_VIDEO_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() } .setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
if (permissionsListToBeGranted.isNotEmpty()) { if (permissionsListToBeGranted.isNotEmpty()) {
fragment?.requestPermissions(permissionsListToBeGranted.toTypedArray(), requestCode) activityResultLauncher
?.launch(permissionsListToBeGranted.toTypedArray())
?: run { ?: run {
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode) ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
} }
@ -262,7 +268,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
.show() .show()
*/ */
} else { } else {
fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode) activityResultLauncher
?.launch(permissionsArrayToBeGranted)
?: run { ?: run {
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode) ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
} }
@ -307,43 +314,6 @@ private fun updatePermissionsToBeGranted(activity: Activity,
return isRequestPermissionRequested 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 * 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.os.PowerManager
import android.provider.Settings import android.provider.Settings
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService 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". * will return false and the notification privacy will fallback to "LOW_DETAIL".
*/ */
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
fun requestDisablingBatteryOptimization(activity: Activity, fragment: Fragment?, requestCode: Int) { fun requestDisablingBatteryOptimization(activity: Activity, activityResultLauncher: ActivityResultLauncher<Intent>) {
val intent = Intent() val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + activity.packageName) intent.data = Uri.parse("package:" + activity.packageName)
if (fragment != null) { activityResultLauncher.launch(intent)
fragment.startActivityForResult(intent, requestCode)
} else {
activity.startActivityForResult(intent, requestCode)
}
} }
// ============================================================================================================== // ==============================================================================================================
@ -100,7 +97,7 @@ fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = t
* Shows notification settings for the current app. * 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 * 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() val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS 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_package", activity.packageName)
intent.putExtra("app_uid", activity.applicationInfo?.uid) 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) fragment.startActivity(intent)
} }
fun startAddGoogleAccountIntent(context: AppCompatActivity, requestCode: Int) { fun startAddGoogleAccountIntent(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
try { try {
val intent = Intent(Settings.ACTION_ADD_ACCOUNT) val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google")) intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
context.startActivityForResult(intent, requestCode) activityResultLauncher.launch(intent)
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (activityNotFoundException: ActivityNotFoundException) {
context.toast(R.string.error_no_external_application_found) 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) val share = Intent(Intent.ACTION_SEND)
share.type = "text/plain" share.type = "text/plain"
share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
// Add data to the intent, the receiving app will decide what to do with it. // 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_SUBJECT, subject)
share.putExtra(Intent.EXTRA_TEXT, text) share.putExtra(Intent.EXTRA_TEXT, text)
val intent = Intent.createChooser(share, chooserTitle)
try { try {
if (requestCode != null) { if (activityResultLauncher != null) {
fragment.startActivityForResult(Intent.createChooser(share, chooserTitle), requestCode) activityResultLauncher.launch(intent)
} else { } else {
fragment.startActivity(Intent.createChooser(share, chooserTitle)) fragment.startActivity(intent)
} }
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (activityNotFoundException: ActivityNotFoundException) {
fragment.activity?.toast(R.string.error_no_external_application_found) 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 { val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "text/plain" type = "text/plain"
} }
if (intent.resolveActivity(fragment.requireActivity().packageManager) != null) { if (intent.resolveActivity(context.packageManager) != null) {
fragment.startActivityForResult(intent, requestCode) activityResultLauncher.launch(intent)
} else { } 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 package im.vector.app.features.attachments
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.activity.result.ActivityResultLauncher
import im.vector.app.core.platform.Restorable import im.vector.app.core.platform.Restorable
import im.vector.lib.multipicker.MultiPicker import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.BuildConfig 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. // Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
private var captureUri: Uri? = null 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. // 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 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 * Starts the process for handling file picking
*/ */
fun selectFile(fragment: Fragment) { fun selectFile(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.FILE).startWith(fragment) MultiPicker.get(MultiPicker.FILE).startWith(activityResultLauncher)
} }
/** /**
* Starts the process for handling image picking * Starts the process for handling image picking
*/ */
fun selectGallery(fragment: Fragment) { fun selectGallery(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.IMAGE).startWith(fragment) MultiPicker.get(MultiPicker.IMAGE).startWith(activityResultLauncher)
} }
/** /**
* Starts the process for handling audio picking * Starts the process for handling audio picking
*/ */
fun selectAudio(fragment: Fragment) { fun selectAudio(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.AUDIO).startWith(fragment) MultiPicker.get(MultiPicker.AUDIO).startWith(activityResultLauncher)
} }
/** /**
* Starts the process for handling capture image picking * Starts the process for handling capture image picking
*/ */
fun openCamera(fragment: Fragment) { fun openCamera(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment) captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(context, activityResultLauncher)
} }
/** /**
* Starts the process for handling contact picking * Starts the process for handling contact picking
*/ */
fun selectContact(fragment: Fragment) { fun selectContact(activityResultLauncher: ActivityResultLauncher<Intent>) {
MultiPicker.get(MultiPicker.CONTACT).startWith(fragment) MultiPicker.get(MultiPicker.CONTACT).startWith(activityResultLauncher)
} }
/** /**
* This methods aims to handle on activity result data. * This methods aims to handle the result data.
*
* @return true if it can handle the data, false otherwise
*/ */
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { fun onFileResult(data: Intent?) {
if (resultCode == Activity.RESULT_OK) { callback.onContentAttachmentsReady(
when (requestCode) { MultiPicker.get(MultiPicker.FILE)
MultiPicker.REQUEST_CODE_PICK_FILE -> { .getSelectedFiles(context, data)
callback.onContentAttachmentsReady( .map { it.toContentAttachmentData() }
MultiPicker.get(MultiPicker.FILE) )
.getSelectedFiles(context, requestCode, resultCode, 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) fun onImageResult(data: Intent?) {
.getSelectedFiles(context, requestCode, resultCode, data) callback.onContentAttachmentsReady(
.map { it.toContentAttachmentData() } MultiPicker.get(MultiPicker.IMAGE)
) .getSelectedFiles(context, data)
} .map { it.toContentAttachmentData() }
MultiPicker.REQUEST_CODE_PICK_CONTACT -> { )
MultiPicker.get(MultiPicker.CONTACT) }
.getSelectedFiles(context, requestCode, resultCode, data)
.firstOrNull() fun onPhotoResult() {
?.toContactAttachment() captureUri?.let { captureUri ->
?.let { MultiPicker.get(MultiPicker.CAMERA)
callback.onContactAttachmentReady(it) .getTakenPhoto(context, captureUri)
} ?.let {
} callback.onContentAttachmentsReady(
MultiPicker.REQUEST_CODE_PICK_IMAGE -> { listOf(it).map { it.toContentAttachmentData() }
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() }
)
}
} }
}
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 { class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { companion object {
const val REQUEST_CODE = 55
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS" private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
private const val ATTACHMENTS_PREVIEW_RESULT = "ATTACHMENTS_PREVIEW_RESULT" private const val ATTACHMENTS_PREVIEW_RESULT = "ATTACHMENTS_PREVIEW_RESULT"
private const val KEEP_ORIGINAL_IMAGES_SIZE = "KEEP_ORIGINAL_IMAGES_SIZE" 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?) { 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 (resultCode == RESULT_OK) {
if (requestCode == UCrop.REQUEST_CROP && data != null) { if (requestCode == UCrop.REQUEST_CROP && data != null) {
Timber.v("Crop success") 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) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) { if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
start() start()
} else { } else {

View File

@ -150,6 +150,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
JitsiMeetActivityDelegate.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) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) { if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } 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.R
import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.observeEvent 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.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.ui.views.KeysBackupBanner 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() { class KeysBackupRestoreActivity : SimpleFragmentActivity() {
companion object { companion object {
private const val REQUEST_4S_SECRET = 100
const val SECRET_ALIAS = SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS const val SECRET_ALIAS = SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
fun intent(context: Context): Intent { fun intent(context: Context): Intent {
@ -130,22 +129,19 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME), requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME),
resultKeyStoreAlias = SECRET_ALIAS resultKeyStoreAlias = SECRET_ALIAS
).let { ).let {
startActivityForResult(it, REQUEST_4S_SECRET) secretStartForActivityResult.launch(it)
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
if (requestCode == REQUEST_4S_SECRET) { val extraResult = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
val extraResult = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT) if (activityResult.resultCode == Activity.RESULT_OK && extraResult != null) {
if (resultCode == Activity.RESULT_OK && extraResult != null) { viewModel.handleGotSecretFromSSSS(
viewModel.handleGotSecretFromSSSS( extraResult,
extraResult, SECRET_ALIAS
SECRET_ALIAS )
) } else {
} else { finish()
finish()
}
} }
super.onActivityResult(requestCode, resultCode, data)
} }
} }

View File

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

View File

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

View File

@ -16,6 +16,7 @@
package im.vector.app.features.crypto.keysbackup.restore package im.vector.app.features.crypto.keysbackup.restore
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import butterknife.BindView import butterknife.BindView
@ -36,8 +37,8 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java) sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
if (compareValues(sharedViewModel.importKeyResult?.totalNumberOfKeys, 0) > 0) { 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.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys 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.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
@ -93,7 +94,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
.show() .show()
} }
KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT -> { 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?) { private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
if (requestCode == REQUEST_CODE_SAVE_MEGOLM_EXPORT) { if (activityResult.resultCode == Activity.RESULT_OK) {
val uri = data?.data val uri = activityResult.data?.data
if (resultCode == Activity.RESULT_OK && uri != null) { if (uri != null) {
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener { ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) { override fun onPassphrase(passphrase: String) {
showWaitingView() showWaitingView()
@ -163,7 +164,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
hideWaitingView() hideWaitingView()
} }
} }
super.onActivityResult(requestCode, resultCode, data)
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -198,7 +198,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
const val KEYS_VERSION = "KEYS_VERSION" const val KEYS_VERSION = "KEYS_VERSION"
const val MANUAL_EXPORT = "MANUAL_EXPORT" const val MANUAL_EXPORT = "MANUAL_EXPORT"
const val EXTRA_SHOW_MANUAL_EXPORT = "SHOW_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 { fun intent(context: Context, showManualExport: Boolean): Intent {
val intent = Intent(context, KeysBackupSetupActivity::class.java) 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) @BindView(R.id.keys_backup_setup_step1_manualExport)
lateinit var manualExportButton: Button lateinit var manualExportButton: Button
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java) viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@
package im.vector.app.features.crypto.quads package im.vector.app.features.crypto.quads
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo 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.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
@ -61,7 +61,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) } ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
ssss_key_reset.clickableView.debouncedClicks { ssss_key_reset.clickableView.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
@ -85,9 +85,9 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text)) sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
data?.data?.let { dataURI -> activityResult.data?.data?.let { dataURI ->
tryOrNull { tryOrNull {
activity?.contentResolver?.openInputStream(dataURI) activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader() ?.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, null,
it it
) )
} ?: kotlin.run { } ?: run {
ssssService.generateKey( ssssService.generateKey(
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
params.keySpec, params.keySpec,

View File

@ -17,7 +17,6 @@
package im.vector.app.features.crypto.recover package im.vector.app.features.crypto.recover
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.InputType.TYPE_CLASS_TEXT import android.text.InputType.TYPE_CLASS_TEXT
import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE 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 com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard 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.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
@ -82,7 +82,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
bootstrapMigrateContinueButton.debouncedClicks { submit() } bootstrapMigrateContinueButton.debouncedClicks { submit() }
bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) } bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) } bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
} }
private fun submit() = withState(sharedViewModel) { state -> private fun submit() = withState(sharedViewModel) { state ->
@ -147,9 +147,9 @@ class BootstrapMigrateBackupFragment @Inject constructor(
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
data?.data?.let { dataURI -> activityResult.data?.data?.let { dataURI ->
tryOrNull { tryOrNull {
activity?.contentResolver?.openInputStream(dataURI) activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader() ?.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 package im.vector.app.features.crypto.recover
import android.app.Activity.RESULT_OK import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -25,6 +25,7 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
@ -65,43 +66,46 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
try { try {
sharedViewModel.handle(BootstrapActions.SaveReqQueryStarted) 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) { } catch (activityNotFoundException: ActivityNotFoundException) {
requireActivity().toast(R.string.error_no_external_application_found) requireActivity().toast(R.string.error_no_external_application_found)
sharedViewModel.handle(BootstrapActions.SaveReqFailed) sharedViewModel.handle(BootstrapActions.SaveReqFailed)
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
if (requestCode == REQUEST_CODE_SAVE) { if (activityResult.resultCode == Activity.RESULT_OK) {
val uri = data?.data val uri = activityResult.data?.data ?: return@registerStartForActivityResult
if (resultCode == RESULT_OK && uri != null) { GlobalScope.launch(Dispatchers.IO) {
GlobalScope.launch(Dispatchers.IO) { try {
try { sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!)) } catch (failure: Throwable) {
} catch (failure: Throwable) { sharedViewModel.handle(BootstrapActions.SaveReqFailed)
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 {
} else if (requestCode == REQUEST_CODE_COPY) { // 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) sharedViewModel.handle(BootstrapActions.RecoveryKeySaved)
} }
super.onActivityResult(requestCode, resultCode, data)
} }
private fun shareRecoveryKey() = withState(sharedViewModel) { state -> private fun shareRecoveryKey() = withState(sharedViewModel) { state ->
val recoveryKey = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() val recoveryKey = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
?: return@withState ?: return@withState
startSharePlainTextIntent(this, startSharePlainTextIntent(
this,
copyStartForActivityResult,
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title), context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
recoveryKey, recoveryKey,
context?.getString(R.string.recovery_key), REQUEST_CODE_COPY) context?.getString(R.string.recovery_key)
)
} }
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
@ -111,9 +115,4 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
recoveryContinue.isVisible = step.isSaved recoveryContinue.isVisible = step.isSaved
bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() 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 -> private fun saveRecoveryKeyToUri(os: OutputStream) = withState { state ->
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching { runCatching {
os.use { os.use {
os.write((state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() ?: "").toByteArray()) 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.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.KeyEvent 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.di.ScreenComponent
import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.exhaustive 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.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
@ -108,12 +108,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
when (it) { when (it) {
is VerificationBottomSheetViewEvents.Dismiss -> dismiss() is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
is VerificationBottomSheetViewEvents.AccessSecretStore -> { is VerificationBottomSheetViewEvents.AccessSecretStore -> {
startActivityForResult(SharedSecureStorageActivity.newIntent( secretStartForActivityResult.launch(SharedSecureStorageActivity.newIntent(
requireContext(), requireContext(),
null, // use default key null, // use default key
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME), listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
), SECRET_REQUEST_CODE) ))
} }
is VerificationBottomSheetViewEvents.ModalError -> { is VerificationBottomSheetViewEvents.ModalError -> {
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
@ -145,10 +145,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) { if (activityResult.resultCode == Activity.RESULT_OK) {
val result = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT) val result = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
val reset = data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false val reset = activityResult.data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false
if (result != null) { if (result != null) {
viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)) viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS))
} else if (reset) { } else if (reset) {
@ -156,11 +156,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
viewModel.handle(VerificationAction.SecuredStorageHasBeenReset) viewModel.handle(VerificationAction.SecuredStorageHasBeenReset)
} }
} }
super.onActivityResult(requestCode, resultCode, data)
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
state.otherUserMxItem?.let { matrixItem -> state.otherUserMxItem?.let { matrixItem ->
if (state.isMe) { if (state.isMe) {
avatarRenderer.render(matrixItem, otherUserAvatarImageView) avatarRenderer.render(matrixItem, otherUserAvatarImageView)
@ -347,9 +345,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
companion object { companion object {
const val SECRET_REQUEST_CODE = 101
fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet { fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet {
return VerificationBottomSheet().apply { return VerificationBottomSheet().apply {
arguments = Bundle().apply { arguments = Bundle().apply {

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@
package im.vector.app.features.home.room.detail package im.vector.app.features.home.room.detail
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Typeface 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.cleanup
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.hideKeyboard 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.setTextOrHide
import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.trackItemsVisibilityChange 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_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_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.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.TextUtils
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.isValidUrl 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.openUrlInExternalBrowser
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.saveMedia
import im.vector.app.core.utils.shareMedia import im.vector.app.core.utils.shareMedia
import im.vector.app.core.utils.toast 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.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet 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.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.invite.VectorInviteView
@ -206,8 +202,6 @@ data class RoomDetailArgs(
val sharedData: SharedData? = null val sharedData: SharedData? = null
) : Parcelable ) : Parcelable
private const val REACTION_SELECT_REQUEST_CODE = 0
class RoomDetailFragment @Inject constructor( class RoomDetailFragment @Inject constructor(
private val session: Session, private val session: Session,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
@ -234,11 +228,6 @@ class RoomDetailFragment @Inject constructor(
ActiveCallView.Callback { ActiveCallView.Callback {
companion object { 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. * Sanitize the display name.
* *
@ -371,6 +360,10 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
}.exhaustive }.exhaustive
} }
if (savedInstanceState == null) {
handleShareData()
}
} }
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) { 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) { private fun openIntegrationManager(screen: String? = null) {
navigator.openIntegrationManager( navigator.openIntegrationManager(
fragment = this, context = requireContext(),
activityResultLauncher = integrationManagerActivityResultLauncher,
roomId = roomDetailArgs.roomId, roomId = roomDetailArgs.roomId,
integId = null, integId = null,
screen = screen screen = screen
@ -440,7 +438,7 @@ class RoomDetailFragment @Inject constructor(
} }
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) { 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) { private fun startOpenFileIntent(action: RoomDetailViewEvents.OpenFile) {
@ -480,20 +478,17 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, action.roomId) navigator.openRoom(vectorBaseActivity, action.roomId)
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { private fun handleShareData() {
super.onActivityCreated(savedInstanceState) when (val sharedData = roomDetailArgs.sharedData) {
if (savedInstanceState == null) { is SharedData.Text -> {
when (val sharedData = roomDetailArgs.sharedData) { roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
is SharedData.Text -> { }
roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true)) is SharedData.Attachments -> {
} // open share edition
is SharedData.Attachments -> { onContentAttachmentsReady(sharedData.attachmentData)
// open share edition }
onContentAttachmentsReady(sharedData.attachmentData) null -> Timber.v("No share data to process")
} }.exhaustive
null -> Timber.v("No share data to process")
}.exhaustive
}
} }
override fun onDestroyView() { 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) { private fun safeStartCall2(isVideoCall: Boolean) {
val startCallAction = RoomDetailAction.StartCall(isVideoCall) val startCallAction = RoomDetailAction.StartCall(isVideoCall)
roomDetailViewModel.pendingAction = startCallAction roomDetailViewModel.pendingAction = startCallAction
if (isVideoCall) { if (isVideoCall) {
if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL,
this, VIDEO_CALL_PERMISSION_REQUEST_CODE, requireActivity(),
startCallActivityResultLauncher,
R.string.permissions_rationale_msg_camera_and_audio)) { R.string.permissions_rationale_msg_camera_and_audio)) {
roomDetailViewModel.pendingAction = null roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(startCallAction) roomDetailViewModel.handle(startCallAction)
} }
} else { } else {
if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL,
this, AUDIO_CALL_PERMISSION_REQUEST_CODE, requireActivity(),
startCallActivityResultLauncher,
R.string.permissions_rationale_msg_record_audio)) { R.string.permissions_rationale_msg_record_audio)) {
roomDetailViewModel.pendingAction = null roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(startCallAction) roomDetailViewModel.handle(startCallAction)
@ -879,27 +888,63 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(composerLayout.composerEditText.text.toString())) roomDetailViewModel.handle(RoomDetailAction.SaveDraft(composerLayout.composerEditText.text.toString()))
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val attachmentFileActivityResultLauncher = registerStartForActivityResult {
val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (it.resultCode == Activity.RESULT_OK) {
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { attachmentsHelper.onImageResult(it.data)
when (requestCode) { }
AttachmentsPreviewActivity.REQUEST_CODE -> { }
val sendData = AttachmentsPreviewActivity.getOutput(data)
val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data) private val attachmentAudioActivityResultLauncher = registerStartForActivityResult {
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize)) if (it.resultCode == Activity.RESULT_OK) {
} attachmentsHelper.onAudioResult(it.data)
REACTION_SELECT_REQUEST_CODE -> { }
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return }
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
} private val attachmentContactActivityResultLauncher = registerStartForActivityResult {
WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE -> { if (it.resultCode == Activity.RESULT_OK) {
val content = WidgetActivity.getOutput(data).toModel<MessageStickerContent>() ?: return attachmentsHelper.onContactResult(it.data)
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content)) }
} }
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 ***************************************************************************** // 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() { private fun setupComposer() {
autoCompleter.setup(composerLayout.composerEditText) autoCompleter.setup(composerLayout.composerEditText)
@ -1026,7 +1083,7 @@ class RoomDetailFragment @Inject constructor(
override fun onRichContentSelected(contentUri: Uri): Boolean { override fun onRichContentSelected(contentUri: Uri): Boolean {
// We need WRITE_EXTERNAL permission // 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) sendUri(contentUri)
} else { } else {
roomDetailViewModel.pendingUri = contentUri roomDetailViewModel.pendingUri = contentUri
@ -1416,52 +1473,11 @@ class RoomDetailFragment @Inject constructor(
// // } // // }
// } // }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { private fun cleanUpAfterPermissionNotGranted() {
if (allGranted(grantResults)) { // Reset all pending data
when (requestCode) { roomDetailViewModel.pendingAction = null
SAVE_ATTACHEMENT_REQUEST_CODE -> { roomDetailViewModel.pendingUri = null
sharedActionViewModel.pendingAction?.let { attachmentsHelper.pendingType = null
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
}
} }
// override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) { // 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) { private fun onSaveActionClicked(action: EventSharedAction.Save) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q 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 sharedActionViewModel.pendingAction = action
return return
} }
@ -1611,7 +1638,7 @@ class RoomDetailFragment @Inject constructor(
openRoomMemberProfile(action.userId) openRoomMemberProfile(action.userId)
} }
is EventSharedAction.AddReaction -> { is EventSharedAction.AddReaction -> {
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE) emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
} }
is EventSharedAction.ViewReactions -> { is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
@ -1816,8 +1843,20 @@ class RoomDetailFragment @Inject constructor(
// AttachmentTypeSelectorView.Callback // 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) { override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
if (checkPermissions(type.permissionsBit, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)) { if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
launchAttachmentProcess(type) launchAttachmentProcess(type)
} else { } else {
attachmentsHelper.pendingType = type attachmentsHelper.pendingType = type
@ -1826,11 +1865,11 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) { when (type) {
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(this) AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(this) AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this) AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher)
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this) AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.exhaustive }.exhaustive
} }
@ -1849,7 +1888,7 @@ class RoomDetailFragment @Inject constructor(
} }
if (grouped.previewables.isNotEmpty()) { if (grouped.previewables.isNotEmpty()) {
val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables)) 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) .buffer(1, TimeUnit.SECONDS)
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.subscribeBy(onNext = { actions -> .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 val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
if (trackUnreadMessages.get()) { if (trackUnreadMessages.get()) {
if (globalMostRecentDisplayedEvent == null) { 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.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.airbnb.mvrx.MvRx 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 getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(epoxyController, hasFixedSize = false) recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.seen_by) bottomSheetTitle.text = getString(R.string.seen_by)

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.widget package im.vector.app.features.home.room.detail.widget
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel 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 getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(epoxyController, hasFixedSize = false) recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.active_widgets_title) bottomSheetTitle.text = getString(R.string.active_widgets_title)
bottomSheetTitle.textSize = 20f 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.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
@ -67,8 +68,8 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true) recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
roomListActionsEpoxyController.listener = this roomListActionsEpoxyController.listener = this

View File

@ -112,6 +112,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) { if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } 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.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent 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.platform.VectorBaseActivity
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
@ -112,10 +113,10 @@ class BigImageViewerActivity : VectorBaseActivity() {
private fun onAvatarTypeSelected(isCamera: Boolean) { private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) { if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { 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 { } else {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this) MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
} }
} }
@ -127,33 +128,43 @@ class BigImageViewerActivity : VectorBaseActivity() {
.start(this) .start(this)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val takePhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
when (requestCode) { avatarCameraUri?.let { uri ->
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { MultiPicker.get(MultiPicker.CAMERA)
avatarCameraUri?.let { uri -> .getTakenPhoto(this, uri)
MultiPicker.get(MultiPicker.CAMERA) ?.let {
.getTakenPhoto(this, requestCode, resultCode, uri) onRoomAvatarSelected(it)
?.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 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) 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) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) { if (allGranted(grantResults)) {
when (requestCode) { when (requestCode) {
PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -> onAvatarTypeSelected(true) 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_TITLE = "EXTRA_TITLE"
private const val EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL" private const val EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"
private const val EXTRA_CAN_EDIT_IMAGE = "EXTRA_CAN_EDIT_IMAGE" 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 { fun newIntent(context: Context, title: String?, imageUrl: String, canEditImage: Boolean = false): Intent {
return Intent(context, BigImageViewerActivity::class.java).apply { return Intent(context, BigImageViewerActivity::class.java).apply {

View File

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

View File

@ -18,18 +18,16 @@ package im.vector.app.features.navigation
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.view.View import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.core.util.Pair import androidx.core.util.Pair
import androidx.fragment.app.Fragment
import im.vector.app.features.crypto.recover.SetupMode 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.media.AttachmentData
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinMode import im.vector.app.features.pin.PinMode
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData import im.vector.app.features.share.SharedData
import im.vector.app.features.terms.ReviewTermsActivity
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.terms.TermsService
@ -84,22 +82,26 @@ interface Navigator {
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) 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(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
fun openTerms(fragment: Fragment,
serviceType: TermsService.ServiceType, serviceType: TermsService.ServiceType,
baseUrl: String, baseUrl: String,
token: String?, token: String?)
requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
fun openStickerPicker(fragment: Fragment, fun openStickerPicker(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,
roomId: String, roomId: String,
widget: Widget, widget: Widget)
requestCode: Int = WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
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) 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 { class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
companion object { companion object {
const val PIN_REQUEST_CODE = 17890
fun newIntent(context: Context, args: PinArgs): Intent { fun newIntent(context: Context, args: PinArgs): Intent {
return Intent(context, PinActivity::class.java).apply { return Intent(context, PinActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, args) putExtra(MvRx.KEY_ARG, args)

View File

@ -19,7 +19,7 @@ package im.vector.app.features.qrcode
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.activity.result.ActivityResultLauncher
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import com.google.zxing.Result import com.google.zxing.Result
import com.google.zxing.ResultMetadataType 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_TEXT = "EXTRA_OUT_TEXT"
private const val EXTRA_OUT_IS_QR_CODE = "EXTRA_OUT_IS_QR_CODE" private const val EXTRA_OUT_IS_QR_CODE = "EXTRA_OUT_IS_QR_CODE"
const val QR_CODE_SCANNER_REQUEST_CODE = 429 fun startForResult(activity: Activity, activityResultLauncher: ActivityResultLauncher<Intent>) {
activityResultLauncher.launch(Intent(activity, QrCodeScannerActivity::class.java))
// 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 getResultText(data: Intent?): String? { fun getResultText(data: Intent?): String? {

View File

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

View File

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

View File

@ -284,7 +284,12 @@ class RoomMemberProfileFragment @Inject constructor(
} }
private fun handleShareRoomMemberProfile(permalink: String) { 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) { private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) {

View File

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

View File

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

View File

@ -17,6 +17,7 @@
package im.vector.app.features.roommemberprofile.devices package im.vector.app.features.roommemberprofile.devices
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
@ -41,8 +42,8 @@ class DeviceTrustInfoActionFragment @Inject constructor(
@BindView(R.id.bottomSheetRecyclerView) @BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView lateinit var recyclerView: RecyclerView
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
recyclerView.setPadding(0, dimensionConverter.dpToPx(16), 0, dimensionConverter.dpToPx(16)) recyclerView.setPadding(0, dimensionConverter.dpToPx(16), 0, dimensionConverter.dpToPx(16))
recyclerView.configureWith( recyclerView.configureWith(
epoxyController, 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.configureWith
import im.vector.app.core.extensions.copyOnLongClick import im.vector.app.core.extensions.copyOnLongClick
import im.vector.app.core.extensions.exhaustive 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.extensions.setTextOrHide
import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO 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.checkPermissions
import im.vector.app.core.utils.copyToClipboard 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.core.utils.startSharePlainTextIntent
import im.vector.app.features.crypto.util.toImageRes import im.vector.app.features.crypto.util.toImageRes
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
@ -260,14 +260,19 @@ class RoomProfileFragment @Inject constructor(
} }
private fun onShareRoomProfile(permalink: String) { 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) { private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) = withState(roomProfileViewModel) {
if (matrixItem.avatarUrl?.isNotEmpty() == true) { if (matrixItem.avatarUrl?.isNotEmpty() == true) {
val intent = BigImageViewerActivity.newIntent(requireContext(), matrixItem.getBestName(), matrixItem.avatarUrl!!, it.canChangeAvatar) val intent = BigImageViewerActivity.newIntent(requireContext(), matrixItem.getBestName(), matrixItem.avatarUrl!!, it.canChangeAvatar)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, ViewCompat.getTransitionName(view) ?: "") val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, ViewCompat.getTransitionName(view) ?: "")
startActivityForResult(intent, BigImageViewerActivity.REQUEST_CODE, options.toBundle()) bigImageStartForActivityResult.launch(intent, options)
} else if (it.canChangeAvatar) { } else if (it.canChangeAvatar) {
showAvatarSelector() showAvatarSelector()
} }
@ -285,14 +290,20 @@ class RoomProfileFragment @Inject constructor(
.show() .show()
} }
private val takePhotoPermissionActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
onAvatarTypeSelected(true)
}
}
private var avatarCameraUri: Uri? = null private var avatarCameraUri: Uri? = null
private fun onAvatarTypeSelected(isCamera: Boolean) { private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) { if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), takePhotoPermissionActivityResultLauncher)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(requireActivity(), takePhotoActivityResultLauncher)
} }
} else { } 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) .start(requireContext(), this)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val takePhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
when (requestCode) { avatarCameraUri?.let { uri ->
MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { MultiPicker.get(MultiPicker.CAMERA)
avatarCameraUri?.let { uri -> .getTakenPhoto(requireContext(), uri)
MultiPicker.get(MultiPicker.CAMERA) ?.let {
.getTakenPhoto(requireContext(), requestCode, resultCode, uri) onRoomAvatarSelected(it)
?.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) }
} }
} }
super.onActivityResult(requestCode, resultCode, data)
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (allGranted(grantResults)) { 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) { 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) { 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 // Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out) .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 com.yalantis.ucrop.UCrop
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard 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.extensions.showPassword
import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.platform.SimpleTextWatcher 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.VectorPreference
import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO 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.TextUtils
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.getSizeOfFiles 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.core.utils.toast
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
@ -279,89 +279,38 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
session.integrationManagerService().removeListener(integrationServiceListener) session.integrationManagerService().removeListener(integrationServiceListener)
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { private val attachmentPhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (allGranted(grantResults)) { if (activityResult.resultCode == Activity.RESULT_OK) {
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { avatarCameraUri?.let { uri ->
onAvatarTypeSelected(true) 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
when (requestCode) { when (requestCode) {
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList() UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
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)
}
})
}
}
})
}
}
}
*/
} }
} }
} }
@ -400,13 +349,19 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
}.show() }.show()
} }
private val takePhotoActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
onAvatarTypeSelected(true)
}
}
private fun onAvatarTypeSelected(isCamera: Boolean) { private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) { if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), takePhotoActivityResultLauncher)) {
avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(requireActivity(), attachmentPhotoActivityResultLauncher)
} }
} else { } 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 // Phone number management
// ============================================================================================================== // ==============================================================================================================
/**
* Refresh phone number list
*/
private fun refreshPhoneNumbersList() {
}
/** /**
* Update the password. * 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 androidx.preference.SwitchPreference
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder 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.VectorEditTextPreference
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorPreferenceCategory
@ -114,6 +115,10 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
handleSystemPreference() handleSystemPreference()
} }
private val batteryStartForActivityResult = registerStartForActivityResult {
// Noop
}
// BackgroundSyncModeChooserDialog.InteractionListener // BackgroundSyncModeChooserDialog.InteractionListener
override fun onOptionSelected(mode: BackgroundSyncMode) { override fun onOptionSelected(mode: BackgroundSyncMode) {
// option has change, need to act // 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 // Even if using foreground service with foreground notif, it stops to work
// in doze mode for certain devices :/ // in doze mode for certain devices :/
if (!isIgnoringBatteryOptimizations(requireContext())) { if (!isIgnoringBatteryOptimizations(requireContext())) {
requestDisablingBatteryOptimization(requireActivity(), requestDisablingBatteryOptimization(requireActivity(), batteryStartForActivityResult)
this@VectorSettingsNotificationPreferenceFragment,
REQUEST_BATTERY_OPTIMIZATION)
} }
} }
vectorPreferences.setFdroidSyncBackgroundMode(mode) vectorPreferences.setFdroidSyncBackgroundMode(mode)
@ -210,27 +213,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, vectorPreferences.getNotificationRingTone()) intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, vectorPreferences.getNotificationRingTone())
} }
startActivityForResult(intent, REQUEST_NOTIFICATION_RINGTONE) ringtoneStartForActivityResult.launch(intent)
false false
} }
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val ringtoneStartForActivityResult = registerStartForActivityResult { activityResult ->
super.onActivityResult(requestCode, resultCode, data) if (activityResult.resultCode == Activity.RESULT_OK) {
if (resultCode == Activity.RESULT_OK) { vectorPreferences.setNotificationRingTone(activityResult.data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
when (requestCode) {
REQUEST_NOTIFICATION_RINGTONE -> {
vectorPreferences.setNotificationRingTone(data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
// test if the selected ring tone can be played // test if the selected ring tone can be played
val notificationRingToneName = vectorPreferences.getNotificationRingToneName() val notificationRingToneName = vectorPreferences.getNotificationRingToneName()
if (null != notificationRingToneName) { if (null != notificationRingToneName) {
vectorPreferences.setNotificationRingTone(vectorPreferences.getNotificationRingTone()) vectorPreferences.setNotificationRingTone(vectorPreferences.getNotificationRingTone())
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY)!! findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY)!!
.summary = notificationRingToneName .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.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -30,6 +29,7 @@ import androidx.transition.TransitionManager
import butterknife.BindView import butterknife.BindView
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup 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.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.BugReporter
@ -76,7 +76,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
} }
mRunButton.debouncedClicks { mRunButton.debouncedClicks {
testManager?.retry() testManager?.retry(testStartForActivityResult)
} }
startUI() startUI()
} }
@ -134,7 +134,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
} }
} }
mRecyclerView.adapter = testManager?.adapter mRecyclerView.adapter = testManager?.adapter
testManager?.runDiagnostic() testManager?.runDiagnostic(testStartForActivityResult)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -142,12 +142,14 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
super.onDestroyView() super.onDestroyView()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private val testStartForActivityResult = registerStartForActivityResult { activityResult ->
if (resultCode == Activity.RESULT_OK && requestCode == NotificationTroubleshootTestManager.REQ_CODE_FIX) { if (activityResult.resultCode == Activity.RESULT_OK) {
testManager?.retry() retry()
return
} }
super.onActivityResult(requestCode, resultCode, data) }
private fun retry() {
testManager?.retry(testStartForActivityResult)
} }
override fun onDetach() { override fun onDetach() {

View File

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

View File

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