mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 01:45:52 +03:00
Merge branch 'develop' into feature/ons/voice_message
This commit is contained in:
commit
9df874c975
29 changed files with 423 additions and 348 deletions
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
|
|
@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
|
||||||
|
|
||||||
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
|
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
|
||||||
|
|
||||||
// Sign userSigningKey with master
|
// Sign selfSigningKey with master
|
||||||
val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
|
||||||
.key(sskPublicKey)
|
.key(sskPublicKey)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
<activity android:name=".features.debug.TestLinkifyActivity" />
|
<activity android:name=".features.debug.TestLinkifyActivity" />
|
||||||
|
<activity android:name=".features.debug.DebugPermissionActivity" />
|
||||||
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
|
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,8 @@ import im.vector.app.core.di.ScreenComponent
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
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.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.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import im.vector.app.databinding.ActivityDebugMenuBinding
|
import im.vector.app.databinding.ActivityDebugMenuBinding
|
||||||
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
|
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.DebugVectorTextViewDarkActivity
|
||||||
import im.vector.lib.ui.styles.debug.DebugVectorTextViewLightActivity
|
import im.vector.lib.ui.styles.debug.DebugVectorTextViewLightActivity
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
|
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
|
||||||
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -115,6 +113,9 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
|
||||||
}
|
}
|
||||||
views.debugTestCrash.setOnClickListener { testCrash() }
|
views.debugTestCrash.setOnClickListener { testCrash() }
|
||||||
views.debugScanQrCode.setOnClickListener { scanQRCode() }
|
views.debugScanQrCode.setOnClickListener { scanQRCode() }
|
||||||
|
views.debugPermission.setOnClickListener {
|
||||||
|
startActivity(Intent(this, DebugPermissionActivity::class.java))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderQrCode(text: String) {
|
private fun renderQrCode(text: String) {
|
||||||
|
@ -217,15 +218,13 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scanQRCode() {
|
private fun scanQRCode() {
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
|
||||||
doScanQRCode()
|
doScanQRCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
if (allGranted) {
|
||||||
|
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
|
|
||||||
doScanQRCode()
|
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"
|
android:layout_height="200dp"
|
||||||
tools:src="@drawable/ic_qr_code_add" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</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.resources.ColorProvider
|
||||||
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.checkPermissions
|
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.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.features.media.createUCropWithDefaultSettings
|
import im.vector.app.features.media.createUCropWithDefaultSettings
|
||||||
import im.vector.lib.multipicker.MultiPicker
|
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 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) {
|
if (allGranted) {
|
||||||
doOpenCamera()
|
doOpenCamera()
|
||||||
|
} else if (deniedPermanently) {
|
||||||
|
activity.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ class GalleryOrCameraDialogHelper(
|
||||||
|
|
||||||
private fun onAvatarTypeSelected(type: Type) {
|
private fun onAvatarTypeSelected(type: Type) {
|
||||||
when (type) {
|
when (type) {
|
||||||
Type.Camera ->
|
Type.Camera ->
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
|
||||||
doOpenCamera()
|
doOpenCamera()
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import androidx.annotation.MainThread
|
||||||
import androidx.annotation.MenuRes
|
import androidx.annotation.MenuRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -42,6 +41,7 @@ import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.bumptech.glide.util.Util
|
import com.bumptech.glide.util.Util
|
||||||
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.jakewharton.rxbinding3.view.clicks
|
import com.jakewharton.rxbinding3.view.clicks
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
|
@ -82,14 +82,13 @@ import im.vector.app.receivers.DebugReceiver
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
import org.matrix.android.sdk.api.failure.GlobalError
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* View
|
* View
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
@ -596,12 +595,19 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
|
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
|
||||||
getCoordinatorLayout()?.let {
|
val coordinatorLayout = getCoordinatorLayout()
|
||||||
Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
|
if (coordinatorLayout != null) {
|
||||||
|
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).apply {
|
||||||
withActionTitle?.let {
|
withActionTitle?.let {
|
||||||
setAction(withActionTitle, { action?.invoke() })
|
setAction(withActionTitle) { action?.invoke() }
|
||||||
}
|
}
|
||||||
}.show()
|
}.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.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import androidx.activity.ComponentActivity
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
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
|
// Permissions sets
|
||||||
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
|
val PERMISSIONS_FOR_AUDIO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO)
|
||||||
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
val PERMISSIONS_FOR_VIDEO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
||||||
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
|
val PERMISSIONS_FOR_TAKING_PHOTO = listOf(Manifest.permission.CAMERA)
|
||||||
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
|
val PERMISSIONS_FOR_MEMBERS_SEARCH = listOf(Manifest.permission.READ_CONTACTS)
|
||||||
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA)
|
||||||
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS)
|
||||||
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
|
|
||||||
|
|
||||||
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
val PERMISSIONS_EMPTY = emptyList<String>()
|
||||||
|
|
||||||
// Request code to ask permission to the system (arbitrary values)
|
// This is not ideal to store the value like that, but it works
|
||||||
const val PERMISSION_REQUEST_CODE = 567
|
private var permissionDialogDisplayed = false
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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) {
|
fun ComponentActivity.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
: ActivityResultLauncher<Array<String>> {
|
||||||
val permissions = listOf(
|
return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||||
Manifest.permission.CAMERA,
|
onPermissionResult(result, lambda)
|
||||||
Manifest.permission.RECORD_AUDIO,
|
}
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
}
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.READ_CONTACTS)
|
|
||||||
|
|
||||||
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) {
|
private fun onPermissionResult(result: Map<String, Boolean>, lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit) {
|
||||||
Timber.v(("Status of [$permission] : " +
|
if (result.keys.all { result[it] == true }) {
|
||||||
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission)) {
|
lambda(true, /* not used */ false)
|
||||||
"PERMISSION_GRANTED"
|
} else {
|
||||||
} else {
|
if (permissionDialogDisplayed) {
|
||||||
"PERMISSION_DENIED"
|
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// Reset
|
||||||
|
permissionDialogDisplayed = false
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,145 +94,65 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||||
* If a permission was already denied by the user, a popup is displayed to
|
* If a permission was already denied by the user, a popup is displayed to
|
||||||
* explain why vector needs the corresponding permission.
|
* explain why vector needs the corresponding permission.
|
||||||
*
|
*
|
||||||
* @param permissionsToBeGrantedBitMap the permissions bit map to be granted
|
* @param permissionsToBeGranted the permissions 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 activityResultLauncher from the calling fragment that is requesting the permissions
|
* @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)
|
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
|
||||||
*/
|
*/
|
||||||
private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
fun checkPermissions(permissionsToBeGranted: List<String>,
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
activityResultLauncher: ActivityResultLauncher<Array<String>>?,
|
activityResultLauncher: ActivityResultLauncher<Array<String>>,
|
||||||
requestCode: Int,
|
@StringRes rationaleMessage: Int = 0): Boolean {
|
||||||
@StringRes rationaleMessage: Int
|
// retrieve the permissions to be granted according to the permission list
|
||||||
): Boolean {
|
val missingPermissions = permissionsToBeGranted.filter { permission ->
|
||||||
var isPermissionGranted = false
|
ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
|
||||||
|
}
|
||||||
|
|
||||||
// sanity check
|
return if (missingPermissions.isNotEmpty()) {
|
||||||
if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
|
permissionDialogDisplayed = !permissionsDeniedPermanently(missingPermissions, activity)
|
||||||
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
|
|
||||||
|
|
||||||
// retrieve the permissions to be granted according to the request code bit map
|
if (rationaleMessage != 0 && permissionDialogDisplayed) {
|
||||||
if (PERMISSION_CAMERA == permissionsToBeGrantedBitMap and PERMISSION_CAMERA) {
|
// display the dialog with the info text. Do not do it if no system dialog will
|
||||||
val permissionType = Manifest.permission.CAMERA
|
// be displayed
|
||||||
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
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
MaterialAlertDialogBuilder(activity)
|
||||||
.setTitle(R.string.permissions_rationale_popup_title)
|
.setTitle(R.string.permissions_rationale_popup_title)
|
||||||
.setMessage(rationaleMessage)
|
.setMessage(rationaleMessage)
|
||||||
.setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
if (permissionsListToBeGranted.isNotEmpty()) {
|
activityResultLauncher.launch(missingPermissions.toTypedArray())
|
||||||
activityResultLauncher
|
|
||||||
?.launch(permissionsListToBeGranted.toTypedArray())
|
|
||||||
?: run {
|
|
||||||
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
// some permissions are not granted, ask permissions
|
// some permissions are not granted, ask permissions
|
||||||
if (isRequestPermissionRequired) {
|
activityResultLauncher.launch(missingPermissions.toTypedArray())
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) {
|
fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) {
|
||||||
|
@ -291,50 +161,13 @@ fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun FragmentActivity.onPermissionDeniedDialog(@StringRes rationaleMessage: Int) {
|
||||||
* Helper method used in [.checkPermissions] to populate the list of the
|
MaterialAlertDialogBuilder(this)
|
||||||
* permissions to be granted (permissionsListToBeGrantedOut) and the list of the permissions already denied (permissionAlreadyDeniedListOut).
|
.setTitle(R.string.missing_permissions_title)
|
||||||
*
|
.setMessage(rationaleMessage)
|
||||||
* @param activity calling activity
|
.setPositiveButton(R.string.open_settings) { _, _ ->
|
||||||
* @param permissionAlreadyDeniedListOut list to be updated with the permissions already denied by the user
|
openAppSettingsPage(this)
|
||||||
* @param permissionsListToBeGrantedOut list to be updated with the permissions to be granted
|
}
|
||||||
* @param permissionType the permission to be checked
|
.setNegativeButton(R.string.cancel, null)
|
||||||
* @return true if the permission requires to be granted, false otherwise
|
.show()
|
||||||
*/
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,7 +206,7 @@ class AttachmentTypeSelectorView(context: Context,
|
||||||
/**
|
/**
|
||||||
* The all possible types to pick with their required permissions.
|
* 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),
|
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
|
||||||
GALLERY(PERMISSIONS_EMPTY),
|
GALLERY(PERMISSIONS_EMPTY),
|
||||||
FILE(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.platform.VectorBaseActivity
|
||||||
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.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.databinding.ActivityCallBinding
|
import im.vector.app.databinding.ActivityCallBinding
|
||||||
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
|
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
|
||||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||||
|
@ -139,11 +139,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
|
|
||||||
if (callArgs.isVideoCall) {
|
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()
|
start()
|
||||||
}
|
}
|
||||||
} else {
|
} 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()
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,9 +298,8 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
if (allGranted) {
|
||||||
if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
|
|
||||||
start()
|
start()
|
||||||
} else {
|
} else {
|
||||||
// TODO display something
|
// TODO display something
|
||||||
|
@ -370,8 +369,6 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val CAPTURE_PERMISSION_REQUEST_CODE = 1
|
|
||||||
private const val EXTRA_MODE = "EXTRA_MODE"
|
private const val EXTRA_MODE = "EXTRA_MODE"
|
||||||
private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG"
|
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.platform.WaitingViewData
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
|
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.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.checkPermissions
|
||||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
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.ContactsBookFragment
|
||||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||||
import im.vector.app.features.contactsbook.ContactsBookViewState
|
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.UserListSharedActionViewModel
|
||||||
import im.vector.app.features.userdirectory.UserListViewModel
|
import im.vector.app.features.userdirectory.UserListViewModel
|
||||||
import im.vector.app.features.userdirectory.UserListViewState
|
import im.vector.app.features.userdirectory.UserListViewState
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
@ -111,35 +108,31 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openAddByQrCode() {
|
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)
|
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openPhoneBook() {
|
private fun openPhoneBook() {
|
||||||
// Check permission first
|
// Check permission first
|
||||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
|
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionReadContactLauncher)) {
|
||||||
this,
|
|
||||||
PERMISSION_REQUEST_CODE_READ_CONTACTS,
|
|
||||||
0)) {
|
|
||||||
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
|
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
private val permissionReadContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
if (allGranted) {
|
||||||
if (allGranted(grantResults)) {
|
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
} else if (deniedPermanently) {
|
||||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
||||||
} else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
}
|
||||||
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
}
|
||||||
}
|
|
||||||
} else {
|
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
|
if (allGranted) {
|
||||||
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
|
||||||
} else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
} else if (deniedPermanently) {
|
||||||
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
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.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.checkPermissions
|
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.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
||||||
import im.vector.app.features.userdirectory.PendingSelection
|
import im.vector.app.features.userdirectory.PendingSelection
|
||||||
|
@ -44,9 +45,11 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
|
||||||
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
|
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
startCamera()
|
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.fragmentViewModel
|
||||||
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.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.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.checkPermissions
|
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.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
|
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
|
||||||
import im.vector.app.features.crypto.verification.VerificationAction
|
import im.vector.app.features.crypto.verification.VerificationAction
|
||||||
|
@ -79,9 +81,11 @@ class VerificationChooseMethodFragment @Inject constructor(
|
||||||
state.pendingRequest.invoke()?.transactionId ?: ""))
|
state.pendingRequest.invoke()?.transactionId ?: ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
doOpenQRCodeScanner()
|
doOpenQRCodeScanner()
|
||||||
|
} else if (deniedPermanently) {
|
||||||
|
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@ 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.onPermissionDeniedDialog
|
||||||
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.registerForPermissionsResult
|
||||||
import im.vector.app.core.utils.saveMedia
|
import im.vector.app.core.utils.saveMedia
|
||||||
|
@ -1107,14 +1108,16 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
|
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
|
||||||
roomDetailViewModel.pendingAction = null
|
roomDetailViewModel.pendingAction = null
|
||||||
roomDetailViewModel.handle(it)
|
roomDetailViewModel.handle(it)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
context?.toast(R.string.permissions_action_not_performed_missing_permissions)
|
if (deniedPermanently) {
|
||||||
|
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||||
|
}
|
||||||
cleanUpAfterPermissionNotGranted()
|
cleanUpAfterPermissionNotGranted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1800,13 +1803,16 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
sharedActionViewModel.pendingAction?.let {
|
sharedActionViewModel.pendingAction?.let {
|
||||||
handleActions(it)
|
handleActions(it)
|
||||||
sharedActionViewModel.pendingAction = null
|
sharedActionViewModel.pendingAction = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (deniedPermanently) {
|
||||||
|
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||||
|
}
|
||||||
cleanUpAfterPermissionNotGranted()
|
cleanUpAfterPermissionNotGranted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2039,7 +2045,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
// AttachmentTypeSelectorView.Callback
|
// AttachmentTypeSelectorView.Callback
|
||||||
|
|
||||||
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
val pendingType = attachmentsHelper.pendingType
|
val pendingType = attachmentsHelper.pendingType
|
||||||
if (pendingType != null) {
|
if (pendingType != null) {
|
||||||
|
@ -2047,12 +2053,15 @@ class RoomDetailFragment @Inject constructor(
|
||||||
launchAttachmentProcess(pendingType)
|
launchAttachmentProcess(pendingType)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (deniedPermanently) {
|
||||||
|
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
|
||||||
|
}
|
||||||
cleanUpAfterPermissionNotGranted()
|
cleanUpAfterPermissionNotGranted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
|
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
|
||||||
if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
|
if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) {
|
||||||
launchAttachmentProcess(type)
|
launchAttachmentProcess(type)
|
||||||
} else {
|
} else {
|
||||||
attachmentsHelper.pendingType = type
|
attachmentsHelper.pendingType = type
|
||||||
|
|
|
@ -73,7 +73,8 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut
|
||||||
}
|
}
|
||||||
epoxyModel.getEventIds().forEach { eventId ->
|
epoxyModel.getEventIds().forEach { eventId ->
|
||||||
adapterPositionMapping[eventId] = index
|
adapterPositionMapping[eventId] = index
|
||||||
appendReadMarker = epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker
|
appendReadMarker = appendReadMarker
|
||||||
|
|| (epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (epoxyModel is DaySeparatorItem) {
|
if (epoxyModel is DaySeparatorItem) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import com.airbnb.mvrx.MvRx
|
import com.airbnb.mvrx.MvRx
|
||||||
import com.airbnb.mvrx.viewModel
|
import com.airbnb.mvrx.viewModel
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
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.SimpleFragmentActivity
|
||||||
import im.vector.app.core.platform.WaitingViewData
|
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_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.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.core.utils.toast
|
||||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||||
|
@ -118,22 +117,16 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
|
||||||
|
|
||||||
private fun openPhoneBook() {
|
private fun openPhoneBook() {
|
||||||
// Check permission first
|
// Check permission first
|
||||||
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
|
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionContactLauncher)) {
|
||||||
this,
|
|
||||||
PERMISSION_REQUEST_CODE_READ_CONTACTS,
|
|
||||||
0)) {
|
|
||||||
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
|
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
private val permissionContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
if (allGranted) {
|
||||||
if (allGranted(grantResults)) {
|
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
} else if (deniedPermanently) {
|
||||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ class ScanUserCodeFragment @Inject constructor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, _ ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
startCamera()
|
startCamera()
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,7 +112,7 @@ class ScanUserCodeFragment @Inject constructor()
|
||||||
super.onResume()
|
super.onResume()
|
||||||
// Register ourselves as a handler for scan results.
|
// Register ourselves as a handler for scan results.
|
||||||
views.userCodeScannerView.setResultHandler(this)
|
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()
|
startCamera()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,11 +43,11 @@ class ShowUserCodeFragment @Inject constructor(
|
||||||
|
|
||||||
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
|
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
|
||||||
|
|
||||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
doOpenQRCodeScanner()
|
doOpenQRCodeScanner()
|
||||||
} else {
|
} 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 SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions()
|
||||||
data class DecodedQRCode(val code: String) : UserCodeActions()
|
data class DecodedQRCode(val code: String) : UserCodeActions()
|
||||||
data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
|
data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
|
||||||
object CameraPermissionNotGranted : UserCodeActions()
|
data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeActions()
|
||||||
object ShareByText : UserCodeActions()
|
object ShareByText : UserCodeActions()
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,13 +81,17 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||||
|
|
||||||
sharedViewModel.observeViewEvents {
|
sharedViewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
|
UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
|
||||||
UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
|
UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
|
||||||
UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
|
UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
|
||||||
is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
|
is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
|
||||||
is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
|
is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
|
||||||
UserCodeShareViewEvents.CameraPermissionNotGranted -> onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
is UserCodeShareViewEvents.CameraPermissionNotGranted -> {
|
||||||
else -> {
|
if (it.deniedPermanently) {
|
||||||
|
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,6 @@ sealed class UserCodeShareViewEvents : VectorViewEvents {
|
||||||
object HideWaitingScreen : UserCodeShareViewEvents()
|
object HideWaitingScreen : UserCodeShareViewEvents()
|
||||||
data class ToastMessage(val message: String) : UserCodeShareViewEvents()
|
data class ToastMessage(val message: String) : UserCodeShareViewEvents()
|
||||||
data class NavigateToRoom(val roomId: 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()
|
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.SwitchMode -> setState { copy(mode = action.mode) }
|
||||||
is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
|
is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
|
||||||
is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
|
is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
|
||||||
UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted)
|
is UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted(action.deniedPermanently))
|
||||||
UserCodeActions.ShareByText -> handleShareByText()
|
UserCodeActions.ShareByText -> handleShareByText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,10 @@
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/tabs"
|
||||||
android:layout_width="match_parent"
|
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>
|
</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="reset">Reset</string>
|
||||||
<string name="start_chatting">Start Chatting</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 -->
|
<!-- 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">Ongoing conference call.\nJoin as %1$s or %2$s</string>
|
||||||
<string name="ongoing_conference_call_voice">Voice</string>
|
<string name="ongoing_conference_call_voice">Voice</string>
|
||||||
<string name="ongoing_conference_call_video">Video</string>
|
<string name="ongoing_conference_call_video">Video</string>
|
||||||
<string name="cannot_start_call">Cannot start the call, please try later</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_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_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>
|
<string name="missing_permissions_to_start_conf_call">You need permission to invite to start a conference in this room</string>
|
||||||
|
|
Loading…
Reference in a new issue