mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Merge branch 'develop' of github.com:vector-im/element-android into feature/dla/reorg_account_notification_settings
This commit is contained in:
commit
baab726df2
1
changelog.d/3655.bugfix
Normal file
1
changelog.d/3655.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix unread messages marker being hidden in collapsed membership item
|
1
changelog.d/3661.bugfix
Normal file
1
changelog.d/3661.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Ensure reaction emoji picker tabs look fine on small displays
|
1
changelog.d/3667.feature
Normal file
1
changelog.d/3667.feature
Normal file
@ -0,0 +1 @@
|
||||
Better management of permission requests
|
@ -169,7 +169,7 @@ dependencies {
|
||||
implementation 'com.otaliastudios:transcoder:0.10.3'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
|
@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
|
||||
|
||||
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
|
||||
|
||||
// Sign userSigningKey with master
|
||||
// Sign selfSigningKey with master
|
||||
val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
||||
.key(sskPublicKey)
|
||||
.build()
|
||||
|
@ -363,7 +363,7 @@ dependencies {
|
||||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
|
||||
|
||||
// rx
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
<application>
|
||||
<activity android:name=".features.debug.TestLinkifyActivity" />
|
||||
<activity android:name=".features.debug.DebugPermissionActivity" />
|
||||
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
|
||||
</application>
|
||||
|
||||
|
@ -30,9 +30,8 @@ import im.vector.app.core.di.ScreenComponent
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.databinding.ActivityDebugMenuBinding
|
||||
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
|
||||
@ -48,7 +47,6 @@ import im.vector.lib.ui.styles.debug.DebugVectorButtonStylesLightActivity
|
||||
import im.vector.lib.ui.styles.debug.DebugVectorTextViewDarkActivity
|
||||
import im.vector.lib.ui.styles.debug.DebugVectorTextViewLightActivity
|
||||
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
|
||||
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -115,6 +113,9 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
|
||||
}
|
||||
views.debugTestCrash.setOnClickListener { testCrash() }
|
||||
views.debugScanQrCode.setOnClickListener { scanQRCode() }
|
||||
views.debugPermission.setOnClickListener {
|
||||
startActivity(Intent(this, DebugPermissionActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderQrCode(text: String) {
|
||||
@ -217,15 +218,13 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
|
||||
}
|
||||
|
||||
private fun scanQRCode() {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
|
||||
doScanQRCode()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
|
||||
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
|
||||
if (allGranted) {
|
||||
doScanQRCode()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2021 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.debug
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.databinding.ActivityDebugPermissionBinding
|
||||
import timber.log.Timber
|
||||
|
||||
class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBinding>() {
|
||||
|
||||
override fun getBinding() = ActivityDebugPermissionBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
// For debug
|
||||
private val allPermissions = listOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_CONTACTS)
|
||||
|
||||
private var lastPermissions = emptyList<String>()
|
||||
|
||||
override fun initUiAndData() {
|
||||
views.status.setOnClickListener { refresh() }
|
||||
|
||||
views.camera.setOnClickListener {
|
||||
lastPermissions = listOf(Manifest.permission.CAMERA)
|
||||
checkPerm()
|
||||
}
|
||||
views.audio.setOnClickListener {
|
||||
lastPermissions = listOf(Manifest.permission.RECORD_AUDIO)
|
||||
checkPerm()
|
||||
}
|
||||
views.cameraAudio.setOnClickListener {
|
||||
lastPermissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
checkPerm()
|
||||
}
|
||||
views.write.setOnClickListener {
|
||||
lastPermissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
checkPerm()
|
||||
}
|
||||
views.read.setOnClickListener {
|
||||
lastPermissions = listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
checkPerm()
|
||||
}
|
||||
views.contact.setOnClickListener {
|
||||
lastPermissions = listOf(Manifest.permission.READ_CONTACTS)
|
||||
checkPerm()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPerm() {
|
||||
if (checkPermissions(lastPermissions, this, launcher, R.string.debug_rationale)) {
|
||||
Toast.makeText(this, "Already granted, sync call", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private var dialogOrSnackbar = false
|
||||
|
||||
private val launcher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
Toast.makeText(this, "All granted", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (deniedPermanently) {
|
||||
dialogOrSnackbar = !dialogOrSnackbar
|
||||
if (dialogOrSnackbar) {
|
||||
onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||
} else {
|
||||
onPermissionDeniedSnackbar(R.string.denied_permission_generic)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Denied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
views.status.text = getStatus()
|
||||
}
|
||||
|
||||
private fun getStatus(): String {
|
||||
return buildString {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Timber.v("## debugPermission() : log the permissions status used by the app")
|
||||
allPermissions.forEach { permission ->
|
||||
append("[$permission] : ")
|
||||
if (ContextCompat.checkSelfPermission(this@DebugPermissionActivity, permission) == PackageManager.PERMISSION_GRANTED) {
|
||||
append("PERMISSION_GRANTED")
|
||||
} else {
|
||||
append("PERMISSION_DENIED")
|
||||
}
|
||||
append(" show rational: ")
|
||||
append(ActivityCompat.shouldShowRequestPermissionRationale(this@DebugPermissionActivity, permission))
|
||||
append("\n")
|
||||
}
|
||||
} else {
|
||||
append("Before M!")
|
||||
}
|
||||
append("\n")
|
||||
append("(Click to refresh)")
|
||||
}
|
||||
}
|
||||
}
|
@ -152,6 +152,12 @@
|
||||
android:layout_height="200dp"
|
||||
tools:src="@drawable/ic_qr_code_add" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_permission"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Permissions" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
75
vector/src/debug/res/layout/activity_debug_permission.xml
Normal file
75
vector/src/debug/res/layout/activity_debug_permission.xml
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".features.debug.DebugPermissionActivity"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/linear_divider"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_horizontal_margin"
|
||||
android:showDividers="middle">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Status" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/camera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CAMERA"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/audio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RECORD_AUDIO"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/camera_audio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CAMERA + RECORD_AUDIO"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/write"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="WRITE_EXTERNAL_STORAGE"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="READ_EXTERNAL_STORAGE"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/contact"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="READ_CONTACTS"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
4
vector/src/debug/res/values/strings.xml
Normal file
4
vector/src/debug/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="debug_rationale">Rationale!</string>
|
||||
</resources>
|
@ -29,6 +29,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.features.media.createUCropWithDefaultSettings
|
||||
import im.vector.lib.multipicker.MultiPicker
|
||||
@ -55,9 +56,11 @@ class GalleryOrCameraDialogHelper(
|
||||
|
||||
private val listener = fragment as? Listener ?: error("Fragment must implement GalleryOrCameraDialogHelper.Listener")
|
||||
|
||||
private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted ->
|
||||
private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
doOpenCamera()
|
||||
} else if (deniedPermanently) {
|
||||
activity.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +119,7 @@ class GalleryOrCameraDialogHelper(
|
||||
|
||||
private fun onAvatarTypeSelected(type: Type) {
|
||||
when (type) {
|
||||
Type.Camera ->
|
||||
Type.Camera ->
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
|
||||
doOpenCamera()
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ import androidx.annotation.MainThread
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
@ -42,6 +41,7 @@ import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.bumptech.glide.util.Util
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
import im.vector.app.BuildConfig
|
||||
@ -82,14 +82,13 @@ import im.vector.app.receivers.DebugReceiver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
||||
abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
||||
/* ==========================================================================================
|
||||
* View
|
||||
* ========================================================================================== */
|
||||
@ -596,12 +595,19 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
|
||||
}
|
||||
|
||||
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
|
||||
getCoordinatorLayout()?.let {
|
||||
Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
|
||||
val coordinatorLayout = getCoordinatorLayout()
|
||||
if (coordinatorLayout != null) {
|
||||
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).apply {
|
||||
withActionTitle?.let {
|
||||
setAction(withActionTitle, { action?.invoke() })
|
||||
setAction(withActionTitle) { action?.invoke() }
|
||||
}
|
||||
}.show()
|
||||
} else {
|
||||
if (vectorPreferences.failFast()) {
|
||||
error("No CoordinatorLayout to display this snackbar!")
|
||||
} else {
|
||||
Timber.w("No CoordinatorLayout to display this snackbar!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,117 +18,67 @@ package im.vector.app.core.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import timber.log.Timber
|
||||
|
||||
// Android M permission request code management
|
||||
private const val PERMISSIONS_GRANTED = true
|
||||
private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
|
||||
|
||||
// Permission bit
|
||||
private const val PERMISSION_BYPASSED = 0x0
|
||||
const val PERMISSION_CAMERA = 0x1
|
||||
private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
|
||||
private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
|
||||
private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
|
||||
private const val PERMISSION_READ_EXTERNAL_STORAGE = 0x1 shl 4
|
||||
|
||||
// Permissions sets
|
||||
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
|
||||
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
|
||||
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
||||
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
||||
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_READING_FILES = PERMISSION_READ_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
|
||||
val PERMISSIONS_FOR_AUDIO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO)
|
||||
val PERMISSIONS_FOR_VIDEO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
||||
val PERMISSIONS_FOR_TAKING_PHOTO = listOf(Manifest.permission.CAMERA)
|
||||
val PERMISSIONS_FOR_MEMBERS_SEARCH = listOf(Manifest.permission.READ_CONTACTS)
|
||||
val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA)
|
||||
val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS)
|
||||
|
||||
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
||||
val PERMISSIONS_EMPTY = emptyList<String>()
|
||||
|
||||
// Request code to ask permission to the system (arbitrary values)
|
||||
const val PERMISSION_REQUEST_CODE = 567
|
||||
const val PERMISSION_REQUEST_CODE_LAUNCH_CAMERA = 568
|
||||
const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA = 569
|
||||
const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA = 570
|
||||
const val PERMISSION_REQUEST_CODE_AUDIO_CALL = 571
|
||||
const val PERMISSION_REQUEST_CODE_VIDEO_CALL = 572
|
||||
const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
|
||||
const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
|
||||
const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
|
||||
const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
|
||||
const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
|
||||
// This is not ideal to store the value like that, but it works
|
||||
private var permissionDialogDisplayed = false
|
||||
|
||||
/**
|
||||
* Log the used permissions statuses.
|
||||
* First boolean is true if all permissions have been granted
|
||||
* Second boolean is true if the permission is denied forever AND the permission request has not been displayed.
|
||||
* So when the user does not grant the permission and check the box do not ask again, this boolean will be false.
|
||||
* Only useful if the first boolean is false
|
||||
*/
|
||||
fun logPermissionStatuses(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val permissions = listOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_CONTACTS)
|
||||
fun ComponentActivity.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit)
|
||||
: ActivityResultLauncher<Array<String>> {
|
||||
return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||
onPermissionResult(result, lambda)
|
||||
}
|
||||
}
|
||||
|
||||
Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
|
||||
fun Fragment.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit): ActivityResultLauncher<Array<String>> {
|
||||
return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||
onPermissionResult(result, lambda)
|
||||
}
|
||||
}
|
||||
|
||||
for (permission in permissions) {
|
||||
Timber.v(("Status of [$permission] : " +
|
||||
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission)) {
|
||||
"PERMISSION_GRANTED"
|
||||
} else {
|
||||
"PERMISSION_DENIED"
|
||||
}))
|
||||
private fun onPermissionResult(result: Map<String, Boolean>, lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit) {
|
||||
if (result.keys.all { result[it] == true }) {
|
||||
lambda(true, /* not used */ false)
|
||||
} else {
|
||||
if (permissionDialogDisplayed) {
|
||||
// A permission dialog has been displayed, so even if the user has checked the do not ask again button, we do
|
||||
// not tell the user to open the app settings
|
||||
lambda(false, false)
|
||||
} else {
|
||||
// No dialog has been displayed, so tell the user to go to the system setting
|
||||
lambda(false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher<Array<String>> {
|
||||
return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||
allGranted.invoke(result.keys.all { result[it] == true })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See [.checkPermissions]
|
||||
*
|
||||
* @param permissionsToBeGrantedBitMap
|
||||
* @param activity
|
||||
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
|
||||
*/
|
||||
fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
activity: Activity,
|
||||
requestCode: Int,
|
||||
@StringRes rationaleMessage: Int = 0): Boolean {
|
||||
return checkPermissions(permissionsToBeGrantedBitMap, activity, null, requestCode, rationaleMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* See [.checkPermissions]
|
||||
*
|
||||
* @param permissionsToBeGrantedBitMap
|
||||
* @param activityResultLauncher from the calling fragment that is requesting the permissions
|
||||
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
|
||||
*/
|
||||
fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
activity: Activity,
|
||||
activityResultLauncher: ActivityResultLauncher<Array<String>>,
|
||||
@StringRes rationaleMessage: Int = 0): Boolean {
|
||||
return checkPermissions(permissionsToBeGrantedBitMap, activity, activityResultLauncher, 0, rationaleMessage)
|
||||
// Reset
|
||||
permissionDialogDisplayed = false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,145 +94,65 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
* If a permission was already denied by the user, a popup is displayed to
|
||||
* explain why vector needs the corresponding permission.
|
||||
*
|
||||
* @param permissionsToBeGrantedBitMap the permissions bit map to be granted
|
||||
* @param activity the calling Activity that is requesting the permissions (or fragment parent)
|
||||
* @param activityResultLauncher from the calling fragment that is requesting the permissions
|
||||
* @param permissionsToBeGranted the permissions to be granted
|
||||
* @param activity the calling Activity that is requesting the permissions (or fragment parent)
|
||||
* @param activityResultLauncher from the calling fragment/Activity that is requesting the permissions
|
||||
* @param rationaleMessage message to be displayed BEFORE requesting for the permission
|
||||
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
|
||||
*/
|
||||
private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
activity: Activity,
|
||||
activityResultLauncher: ActivityResultLauncher<Array<String>>?,
|
||||
requestCode: Int,
|
||||
@StringRes rationaleMessage: Int
|
||||
): Boolean {
|
||||
var isPermissionGranted = false
|
||||
fun checkPermissions(permissionsToBeGranted: List<String>,
|
||||
activity: Activity,
|
||||
activityResultLauncher: ActivityResultLauncher<Array<String>>,
|
||||
@StringRes rationaleMessage: Int = 0): Boolean {
|
||||
// retrieve the permissions to be granted according to the permission list
|
||||
val missingPermissions = permissionsToBeGranted.filter { permission ->
|
||||
ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
|
||||
isPermissionGranted = true
|
||||
} else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_TAKING_PHOTO != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_MEMBERS_SEARCH != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_READING_FILES != permissionsToBeGrantedBitMap) {
|
||||
Timber.w("## checkPermissions(): permissions to be granted are not supported")
|
||||
isPermissionGranted = false
|
||||
} else {
|
||||
val permissionListAlreadyDenied = ArrayList<String>()
|
||||
val permissionsListToBeGranted = ArrayList<String>()
|
||||
var isRequestPermissionRequired = false
|
||||
return if (missingPermissions.isNotEmpty()) {
|
||||
permissionDialogDisplayed = !permissionsDeniedPermanently(missingPermissions, activity)
|
||||
|
||||
// retrieve the permissions to be granted according to the request code bit map
|
||||
if (PERMISSION_CAMERA == permissionsToBeGrantedBitMap and PERMISSION_CAMERA) {
|
||||
val permissionType = Manifest.permission.CAMERA
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
if (PERMISSION_RECORD_AUDIO == permissionsToBeGrantedBitMap and PERMISSION_RECORD_AUDIO) {
|
||||
val permissionType = Manifest.permission.RECORD_AUDIO
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
if (PERMISSION_WRITE_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_WRITE_EXTERNAL_STORAGE) {
|
||||
val permissionType = Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
if (PERMISSION_READ_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_READ_EXTERNAL_STORAGE) {
|
||||
val permissionType = Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
// the contact book access is requested for any android platforms
|
||||
// for android M, we use the system preferences
|
||||
// for android < M, we use a dedicated settings
|
||||
if (PERMISSION_READ_CONTACTS == permissionsToBeGrantedBitMap and PERMISSION_READ_CONTACTS) {
|
||||
val permissionType = Manifest.permission.READ_CONTACTS
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
} else {
|
||||
// TODO uncomment
|
||||
/*if (!ContactsManager.getInstance().isContactBookAccessRequested) {
|
||||
isRequestPermissionRequired = true
|
||||
permissionsListToBeGranted.add(permissionType)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
// if some permissions were already denied: display a dialog to the user before asking again.
|
||||
if (permissionListAlreadyDenied.isNotEmpty() && rationaleMessage != 0) {
|
||||
// display the dialog with the info text
|
||||
if (rationaleMessage != 0 && permissionDialogDisplayed) {
|
||||
// display the dialog with the info text. Do not do it if no system dialog will
|
||||
// be displayed
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.permissions_rationale_popup_title)
|
||||
.setMessage(rationaleMessage)
|
||||
.setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (permissionsListToBeGranted.isNotEmpty()) {
|
||||
activityResultLauncher
|
||||
?.launch(permissionsListToBeGranted.toTypedArray())
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
|
||||
}
|
||||
}
|
||||
activityResultLauncher.launch(missingPermissions.toTypedArray())
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
// some permissions are not granted, ask permissions
|
||||
if (isRequestPermissionRequired) {
|
||||
val permissionsArrayToBeGranted = permissionsListToBeGranted.toTypedArray()
|
||||
|
||||
// for android < M, we use a custom dialog to request the contacts book access.
|
||||
if (permissionsListToBeGranted.contains(Manifest.permission.READ_CONTACTS)
|
||||
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
TODO()
|
||||
/*
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setTitle(R.string.permissions_rationale_popup_title)
|
||||
.setMessage(R.string.permissions_msg_contacts_warning_other_androids)
|
||||
// gives the contacts book access
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
ContactsManager.getInstance().setIsContactBookAccessAllowed(true)
|
||||
fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
|
||||
}
|
||||
}
|
||||
// or reject it
|
||||
.setNegativeButton(R.string.no) { _, _ ->
|
||||
ContactsManager.getInstance().setIsContactBookAccessAllowed(false)
|
||||
fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
*/
|
||||
} else {
|
||||
activityResultLauncher
|
||||
?.launch(permissionsArrayToBeGranted)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// permissions were granted, start now.
|
||||
isPermissionGranted = true
|
||||
}
|
||||
activityResultLauncher.launch(missingPermissions.toTypedArray())
|
||||
}
|
||||
false
|
||||
} else {
|
||||
// permissions were granted, start now.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
return isPermissionGranted
|
||||
/**
|
||||
* To be call after the permission request
|
||||
*
|
||||
* @param permissionsToBeGranted the permissions to be granted
|
||||
* @param activity the calling Activity that is requesting the permissions (or fragment parent)
|
||||
*
|
||||
* @return true if one of the permission has been denied and the user check the do not ask again checkbox
|
||||
*/
|
||||
private fun permissionsDeniedPermanently(permissionsToBeGranted: List<String>,
|
||||
activity: Activity): Boolean {
|
||||
return permissionsToBeGranted
|
||||
.filter { permission ->
|
||||
ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
|
||||
}
|
||||
.any { permission ->
|
||||
// If shouldShowRequestPermissionRationale() returns true, it means that the user as denied the permission, but not permanently.
|
||||
// If it return false, it mean that the user as denied permanently the permission
|
||||
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission).not()
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) {
|
||||
@ -291,50 +161,13 @@ fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used in [.checkPermissions] to populate the list of the
|
||||
* permissions to be granted (permissionsListToBeGrantedOut) and the list of the permissions already denied (permissionAlreadyDeniedListOut).
|
||||
*
|
||||
* @param activity calling activity
|
||||
* @param permissionAlreadyDeniedListOut list to be updated with the permissions already denied by the user
|
||||
* @param permissionsListToBeGrantedOut list to be updated with the permissions to be granted
|
||||
* @param permissionType the permission to be checked
|
||||
* @return true if the permission requires to be granted, false otherwise
|
||||
*/
|
||||
private fun updatePermissionsToBeGranted(activity: Activity,
|
||||
permissionAlreadyDeniedListOut: MutableList<String>,
|
||||
permissionsListToBeGrantedOut: MutableList<String>,
|
||||
permissionType: String): Boolean {
|
||||
var isRequestPermissionRequested = false
|
||||
|
||||
// add permission to be granted
|
||||
permissionsListToBeGrantedOut.add(permissionType)
|
||||
|
||||
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(activity.applicationContext, permissionType)) {
|
||||
isRequestPermissionRequested = true
|
||||
|
||||
// add permission to the ones that were already asked to the user
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissionType)) {
|
||||
permissionAlreadyDeniedListOut.add(permissionType)
|
||||
}
|
||||
}
|
||||
return isRequestPermissionRequested
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all permissions are granted, false if not or if permission request has been cancelled
|
||||
*/
|
||||
fun allGranted(grantResults: IntArray): Boolean {
|
||||
if (grantResults.isEmpty()) {
|
||||
// A cancellation occurred
|
||||
return false
|
||||
}
|
||||
|
||||
var granted = true
|
||||
|
||||
grantResults.forEach {
|
||||
granted = granted && PackageManager.PERMISSION_GRANTED == it
|
||||
}
|
||||
|
||||
return granted
|
||||
fun FragmentActivity.onPermissionDeniedDialog(@StringRes rationaleMessage: Int) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.missing_permissions_title)
|
||||
.setMessage(rationaleMessage)
|
||||
.setPositiveButton(R.string.open_settings) { _, _ ->
|
||||
openAppSettingsPage(this)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ class AttachmentTypeSelectorView(context: Context,
|
||||
/**
|
||||
* The all possible types to pick with their required permissions.
|
||||
*/
|
||||
enum class Type(val permissionsBit: Int) {
|
||||
enum class Type(val permissions: List<String>) {
|
||||
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
|
||||
GALLERY(PERMISSIONS_EMPTY),
|
||||
FILE(PERMISSIONS_EMPTY),
|
||||
|
@ -40,8 +40,8 @@ import im.vector.app.core.di.ScreenComponent
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
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.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.databinding.ActivityCallBinding
|
||||
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
|
||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||
@ -139,11 +139,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
.disposeOnDestroy()
|
||||
|
||||
if (callArgs.isVideoCall) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, CAPTURE_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_camera_and_audio)) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_camera_and_audio)) {
|
||||
start()
|
||||
}
|
||||
} else {
|
||||
if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, CAPTURE_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_record_audio)) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_record_audio)) {
|
||||
start()
|
||||
}
|
||||
}
|
||||
@ -298,9 +298,8 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
|
||||
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
|
||||
if (allGranted) {
|
||||
start()
|
||||
} else {
|
||||
// TODO display something
|
||||
@ -370,8 +369,6 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CAPTURE_PERMISSION_REQUEST_CODE = 1
|
||||
private const val EXTRA_MODE = "EXTRA_MODE"
|
||||
private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG"
|
||||
|
||||
|
@ -38,11 +38,9 @@ import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.core.platform.WaitingViewData
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
|
||||
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_READ_CONTACTS
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||
import im.vector.app.features.contactsbook.ContactsBookViewState
|
||||
@ -52,7 +50,6 @@ import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewState
|
||||
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
import java.net.HttpURLConnection
|
||||
@ -111,35 +108,31 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
|
||||
}
|
||||
|
||||
private fun openAddByQrCode() {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA, 0)) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
|
||||
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPhoneBook() {
|
||||
// Check permission first
|
||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
|
||||
this,
|
||||
PERMISSION_REQUEST_CODE_READ_CONTACTS,
|
||||
0)) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionReadContactLauncher)) {
|
||||
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (allGranted(grantResults)) {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||
} else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
||||
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||
}
|
||||
} else {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||
} else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
||||
}
|
||||
private val permissionReadContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||
} else if (deniedPermanently) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
||||
}
|
||||
}
|
||||
|
||||
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||
} else if (deniedPermanently) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
||||
import im.vector.app.features.userdirectory.PendingSelection
|
||||
@ -44,9 +45,11 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
|
||||
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
startCamera()
|
||||
} else if (deniedPermanently) {
|
||||
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,14 @@ import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.parentFragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
|
||||
import im.vector.app.features.crypto.verification.VerificationAction
|
||||
@ -79,9 +81,11 @@ class VerificationChooseMethodFragment @Inject constructor(
|
||||
state.pendingRequest.invoke()?.transactionId ?: ""))
|
||||
}
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
doOpenQRCodeScanner()
|
||||
} else if (deniedPermanently) {
|
||||
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,7 @@ import im.vector.app.core.utils.copyToClipboard
|
||||
import im.vector.app.core.utils.createJSonViewerStyleProvider
|
||||
import im.vector.app.core.utils.createUIHandler
|
||||
import im.vector.app.core.utils.isValidUrl
|
||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||
import im.vector.app.core.utils.openUrlInExternalBrowser
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.core.utils.saveMedia
|
||||
@ -1062,14 +1063,16 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
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)
|
||||
if (deniedPermanently) {
|
||||
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||
}
|
||||
cleanUpAfterPermissionNotGranted()
|
||||
}
|
||||
}
|
||||
@ -1738,13 +1741,16 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
sharedActionViewModel.pendingAction?.let {
|
||||
handleActions(it)
|
||||
sharedActionViewModel.pendingAction = null
|
||||
}
|
||||
} else {
|
||||
if (deniedPermanently) {
|
||||
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||
}
|
||||
cleanUpAfterPermissionNotGranted()
|
||||
}
|
||||
}
|
||||
@ -1977,7 +1983,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
|
||||
// AttachmentTypeSelectorView.Callback
|
||||
|
||||
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
val pendingType = attachmentsHelper.pendingType
|
||||
if (pendingType != null) {
|
||||
@ -1985,12 +1991,15 @@ class RoomDetailFragment @Inject constructor(
|
||||
launchAttachmentProcess(pendingType)
|
||||
}
|
||||
} else {
|
||||
if (deniedPermanently) {
|
||||
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||
}
|
||||
cleanUpAfterPermissionNotGranted()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
|
||||
if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
|
||||
if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) {
|
||||
launchAttachmentProcess(type)
|
||||
} else {
|
||||
attachmentsHelper.pendingType = type
|
||||
|
@ -73,7 +73,8 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut
|
||||
}
|
||||
epoxyModel.getEventIds().forEach { eventId ->
|
||||
adapterPositionMapping[eventId] = index
|
||||
appendReadMarker = epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker
|
||||
appendReadMarker = appendReadMarker
|
||||
|| (epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker)
|
||||
}
|
||||
}
|
||||
if (epoxyModel is DaySeparatorItem) {
|
||||
|
@ -21,7 +21,6 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@ -33,9 +32,9 @@ import im.vector.app.core.extensions.addFragmentToBackstack
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.core.platform.WaitingViewData
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
|
||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||
@ -118,22 +117,16 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
|
||||
|
||||
private fun openPhoneBook() {
|
||||
// Check permission first
|
||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
|
||||
this,
|
||||
PERMISSION_REQUEST_CODE_READ_CONTACTS,
|
||||
0)) {
|
||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionContactLauncher)) {
|
||||
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (allGranted(grantResults)) {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
|
||||
private val permissionContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||
} else if (deniedPermanently) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ class ScanUserCodeFragment @Inject constructor()
|
||||
}
|
||||
}
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, _ ->
|
||||
if (allGranted) {
|
||||
startCamera()
|
||||
} else {
|
||||
@ -112,7 +112,7 @@ class ScanUserCodeFragment @Inject constructor()
|
||||
super.onResume()
|
||||
// Register ourselves as a handler for scan results.
|
||||
views.userCodeScannerView.setResultHandler(this)
|
||||
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) {
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
@ -43,11 +43,11 @@ class ShowUserCodeFragment @Inject constructor(
|
||||
|
||||
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
|
||||
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
doOpenQRCodeScanner()
|
||||
} else {
|
||||
sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted)
|
||||
sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted(deniedPermanently))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,6 @@ sealed class UserCodeActions : VectorViewModelAction {
|
||||
data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions()
|
||||
data class DecodedQRCode(val code: String) : UserCodeActions()
|
||||
data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
|
||||
object CameraPermissionNotGranted : UserCodeActions()
|
||||
data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeActions()
|
||||
object ShareByText : UserCodeActions()
|
||||
}
|
||||
|
@ -81,13 +81,17 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||
|
||||
sharedViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
|
||||
UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
|
||||
UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
|
||||
is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
|
||||
is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
|
||||
UserCodeShareViewEvents.CameraPermissionNotGranted -> onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||
else -> {
|
||||
UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
|
||||
UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
|
||||
UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
|
||||
is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
|
||||
is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
|
||||
is UserCodeShareViewEvents.CameraPermissionNotGranted -> {
|
||||
if (it.deniedPermanently) {
|
||||
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,6 @@ sealed class UserCodeShareViewEvents : VectorViewEvents {
|
||||
object HideWaitingScreen : UserCodeShareViewEvents()
|
||||
data class ToastMessage(val message: String) : UserCodeShareViewEvents()
|
||||
data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents()
|
||||
object CameraPermissionNotGranted : UserCodeShareViewEvents()
|
||||
data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeShareViewEvents()
|
||||
data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents()
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class UserCodeSharedViewModel @AssistedInject constructor(
|
||||
is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) }
|
||||
is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
|
||||
is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
|
||||
UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted)
|
||||
is UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted(action.deniedPermanently))
|
||||
UserCodeActions.ShareByText -> handleShareByText()
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,10 @@
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp" />
|
||||
android:layout_height="40dp"
|
||||
app:tabPaddingEnd="0dp"
|
||||
app:tabPaddingStart="0dp" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
@ -386,12 +386,16 @@
|
||||
<string name="reset">Reset</string>
|
||||
<string name="start_chatting">Start Chatting</string>
|
||||
|
||||
<!-- Permissions denied forever -->
|
||||
<string name="denied_permission_generic">Some permissions are missing to perform this action, please grant the permissions from the system settings.</string>
|
||||
<string name="denied_permission_camera">To perform this action, please grant the Camera permission from the system settings.</string>
|
||||
|
||||
<!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
|
||||
<string name="ongoing_conference_call">Ongoing conference call.\nJoin as %1$s or %2$s</string>
|
||||
<string name="ongoing_conference_call_voice">Voice</string>
|
||||
<string name="ongoing_conference_call_video">Video</string>
|
||||
<string name="cannot_start_call">Cannot start the call, please try later</string>
|
||||
<string name="missing_permissions_title">Missing permissions</string>
|
||||
<string name="missing_permissions_warning">"Due to missing permissions, some features may be missing…</string>
|
||||
<string name="missing_permissions_error">"Due to missing permissions, this action is not possible.</string>
|
||||
<string name="missing_permissions_to_start_conf_call">You need permission to invite to start a conference in this room</string>
|
||||
|
Loading…
Reference in New Issue
Block a user