diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 79021902c4..f58f0b87ae 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -587,6 +587,16 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { } } + fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) { + coordinatorLayout?.let { + Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply { + withActionTitle?.let { + setAction(withActionTitle, { action?.invoke() }) + } + }.show() + } + } + /* ========================================================================================== * User Consent * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index 44fc6afa4e..f3ac8d9c47 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -19,8 +19,11 @@ package im.vector.app.core.utils import android.Manifest import android.app.Activity import android.content.Context +import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Build +import android.provider.Settings import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -30,6 +33,8 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import im.vector.app.R +import im.vector.app.core.platform.VectorBaseActivity +import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber // Android M permission request code management @@ -284,6 +289,19 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int, return isPermissionGranted } +fun VectorBaseActivity.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) { + showSnackbar(getString(rationaleMessage), R.string.settings) { + tryOrNull { + startActivity( + Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + data = Uri.fromParts("package", this@onPermissionDeniedSnackbar.packageName, null) + }) + } + } +} + /** * 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). diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 95351afec8..b4c278116b 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -22,7 +22,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -43,6 +42,7 @@ 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.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel import im.vector.app.features.userdirectory.UserListFragment @@ -132,9 +132,10 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java) } } else { - Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { - finish() + onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code) + } else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { + onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact) } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt index 60354db9c6..782d7e1c04 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt @@ -16,10 +16,13 @@ package im.vector.app.features.usercode +import android.Manifest import android.app.Activity +import android.content.pm.PackageManager import android.os.Bundle import android.view.View import android.widget.Toast +import androidx.core.content.ContextCompat import com.airbnb.mvrx.activityViewModel import com.google.zxing.Result import com.google.zxing.ResultMetadataType @@ -60,6 +63,9 @@ class ScanUserCodeFragment @Inject constructor() private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> if (allGranted) { startCamera() + } else { + // For now just go back + sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) } } @@ -90,12 +96,18 @@ class ScanUserCodeFragment @Inject constructor() } } + override fun onStart() { + super.onStart() + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + startCamera() + } + } + override fun onResume() { super.onResume() // Register ourselves as a handler for scan results. userCodeScannerView.setResultHandler(this) - // Start camera on resume - if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) { startCamera() } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt index e812ca31bb..532557af84 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt @@ -23,6 +23,10 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide 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.registerForPermissionsResult +import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.features.home.AvatarRenderer import kotlinx.android.synthetic.main.fragment_user_code_show.* import javax.inject.Inject @@ -35,13 +39,38 @@ class ShowUserCodeFragment @Inject constructor( val sharedViewModel: UserCodeSharedViewModel by activityViewModel() + private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + doOpenQRCodeScanner() + } else { + sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) showUserCodeClose.debouncedClicks { sharedViewModel.handle(UserCodeActions.DismissAction) } showUserCodeScanButton.debouncedClicks { - doOpenQRCodeScanner() + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + doOpenQRCodeScanner() + } + } + shareByText.debouncedClicks { + sharedViewModel.handle(UserCodeActions.ShareByText) + } + + sharedViewModel.observeViewEvents { + if (it is UserCodeShareViewEvents.SharePlainText) { + startSharePlainTextIntent( + fragment = this, + activityResultLauncher = null, + chooserTitle = it.title, + text = it.text, + extraTitle = it.richPlainText + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt index 0611e0f8c3..3411fe3d7f 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt @@ -24,4 +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() + object ShareByText : UserCodeActions() } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 0e7efa5b92..149caaba8f 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -32,6 +32,7 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.features.matrixto.MatrixToBottomSheet import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_simple.* @@ -78,13 +79,15 @@ class UserCodeActivity sharedViewModel.observeViewEvents { when (it) { - is UserCodeShareViewEvents.InviteFriend -> TODO() - UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) - UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true - UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false - is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() - is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId) - }.exhaustive + UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) + UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true + UserCodeShareViewEvents.HideWaitingScreen -> 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 -> { + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt index 26fcffadd2..67a1ab8a6c 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt @@ -19,10 +19,11 @@ package im.vector.app.features.usercode import im.vector.app.core.platform.VectorViewEvents sealed class UserCodeShareViewEvents : VectorViewEvents { - data class InviteFriend(val permalink: String) : UserCodeShareViewEvents() object Dismiss : UserCodeShareViewEvents() object ShowWaitingScreen : UserCodeShareViewEvents() object HideWaitingScreen : UserCodeShareViewEvents() data class ToastMessage(val message: String) : UserCodeShareViewEvents() data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents() + object CameraPermissionNotGranted : UserCodeShareViewEvents() + data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 1d1283c269..b456f24972 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -71,12 +71,6 @@ class UserCodeSharedViewModel @AssistedInject constructor( } } - private fun handleInviteFriend() { - session.permalinkService().createPermalink(initialState.userId)?.let { permalink -> - _viewEvents.post(UserCodeShareViewEvents.InviteFriend(permalink)) - } - } - @AssistedInject.Factory interface Factory { fun create(initialState: UserCodeState, args: UserCodeActivity.Args): UserCodeSharedViewModel @@ -84,10 +78,23 @@ class UserCodeSharedViewModel @AssistedInject constructor( override fun handle(action: UserCodeActions) { when (action) { - UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) - is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } - is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) - is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted) + UserCodeActions.ShareByText -> handleShareByText() + } + } + + private fun handleShareByText() { + session.permalinkService().createPermalink(session.myUserId)?.let { permalink -> + val text = stringProvider.getString(R.string.invite_friends_text, permalink) + _viewEvents.post(UserCodeShareViewEvents.SharePlainText( + text, + stringProvider.getString(R.string.invite_friends), + stringProvider.getString(R.string.invite_friends_rich_title) + )) } } diff --git a/vector/src/main/res/layout/activity.xml b/vector/src/main/res/layout/activity.xml index b5203cd589..9e56d9e605 100644 --- a/vector/src/main/res/layout/activity.xml +++ b/vector/src/main/res/layout/activity.xml @@ -1,26 +1,32 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/vector_coordinator_layout" android:layout_width="match_parent" android:layout_height="match_parent"> - <androidx.appcompat.widget.Toolbar - android:id="@+id/toolbar" - style="@style/VectorToolbarStyle" + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:elevation="4dp" - app:layout_constraintTop_toTopOf="parent" /> + android:layout_height="match_parent"> - <androidx.fragment.app.FragmentContainerView - android:id="@+id/container" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" /> + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + style="@style/VectorToolbarStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:elevation="4dp" + app:layout_constraintTop_toTopOf="parent" /> - <include layout="@layout/merge_overlay_waiting_view"/> + <androidx.fragment.app.FragmentContainerView + android:id="@+id/container" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar" /> -</androidx.constraintlayout.widget.ConstraintLayout> + <include layout="@layout/merge_overlay_waiting_view" /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_user_code_show.xml b/vector/src/main/res/layout/fragment_user_code_show.xml index 92c40b1eb2..ca65c64f19 100644 --- a/vector/src/main/res/layout/fragment_user_code_show.xml +++ b/vector/src/main/res/layout/fragment_user_code_show.xml @@ -49,6 +49,19 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/showUserCodeClose" + app:layout_constraintEnd_toStartOf="@id/shareByText" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/shareByText" + android:layout_width="@dimen/layout_touch_size" + android:layout_height="@dimen/layout_touch_size" + android:scaleType="center" + android:src="@drawable/ic_share" + app:tint="@color/riotx_accent" + android:layout_marginEnd="@dimen/layout_horizontal_margin" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b6f2e15ab2..d32dd86a19 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -425,6 +425,8 @@ <string name="permissions_msg_contacts_warning_other_androids">Element can check your address book to find other Matrix users based on their email and phone numbers.\n\nDo you agree to share your address book for this purpose?</string> <string name="permissions_action_not_performed_missing_permissions">Sorry. Action not performed, due to missing permissions</string> + <string name="permissions_denied_qr_code">To scan a QR code, allow Camera permission to take a picture</string> + <string name="permissions_denied_add_contact">To check your address book, allow Contact permission.</string> <!-- medias slider string --> <string name="media_slider_saved">Saved</string> diff --git a/vector/src/main/res/values/style_snackbar.xml b/vector/src/main/res/values/style_snackbar.xml index 7e0dbc0e0b..5f1412e0b4 100644 --- a/vector/src/main/res/values/style_snackbar.xml +++ b/vector/src/main/res/values/style_snackbar.xml @@ -5,7 +5,9 @@ <item name="android:background">@color/notification_accent_color</item> </style> - <style name="VectorSnackBarButton" parent="@style/Widget.MaterialComponents.Button" /> + <style name="VectorSnackBarButton" parent="@style/Widget.MaterialComponents.Button"> + <item name="android:textColor">@color/white</item> + </style> <style name="VectorSnackBarText" parent="@style/Widget.MaterialComponents.Snackbar.TextView"> <item name="android:textColor">@color/white</item>