Media: grab some code from Riot legacy
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
internal abstract class DialogAdapter(context: Context) : ArrayAdapter<DialogListItem>(context, R.layout.item_dialog) {
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var view = convertView
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(context).inflate(R.layout.item_dialog, parent, false)
|
||||
view.tag = DialogListItemHolder(view)
|
||||
}
|
||||
(view!!.tag as DialogListItemHolder).let {
|
||||
it.icon.setImageResource(getItem(position).iconRes)
|
||||
it.text.setText(getItem(position).titleRes)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.riotredesign.core.dialogs.DialogAdapter
|
||||
import im.vector.riotredesign.core.dialogs.DialogListItem
|
||||
|
||||
internal class DialogCallAdapter(context: Context) : DialogAdapter(context) {
|
||||
|
||||
init {
|
||||
add(DialogListItem.StartVoiceCall)
|
||||
add(DialogListItem.StartVideoCall)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
internal sealed class DialogListItem(@DrawableRes val iconRes: Int,
|
||||
@StringRes val titleRes: Int) {
|
||||
|
||||
object StartVoiceCall : DialogListItem(R.drawable.voice_call_green, R.string.action_voice_call)
|
||||
object StartVideoCall : DialogListItem(R.drawable.video_call_green, R.string.action_video_call)
|
||||
|
||||
object SendFile : DialogListItem(R.drawable.ic_material_file, R.string.option_send_files)
|
||||
object SendVoice : DialogListItem(R.drawable.vector_micro_green, R.string.option_send_voice)
|
||||
object SendSticker : DialogListItem(R.drawable.ic_send_sticker, R.string.option_send_sticker)
|
||||
object TakePhoto : DialogListItem(R.drawable.ic_material_camera, R.string.option_take_photo)
|
||||
object TakeVideo : DialogListItem(R.drawable.ic_material_videocam, R.string.option_take_video)
|
||||
object TakePhotoVideo : DialogListItem(R.drawable.ic_material_camera, R.string.option_take_photo_video)
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
class DialogListItemHolder(view: View) {
|
||||
|
||||
@BindView(R.id.adapter_item_dialog_icon)
|
||||
lateinit var icon: ImageView
|
||||
|
||||
@BindView(R.id.adapter_item_dialog_text)
|
||||
lateinit var text: TextView
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, view)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import im.vector.riotredesign.core.platform.Restorable
|
||||
import timber.log.Timber
|
||||
|
||||
private const val KEY_DIALOG_IS_DISPLAYED = "DialogLocker.KEY_DIALOG_IS_DISPLAYED"
|
||||
|
||||
/**
|
||||
* Class to avoid displaying twice the same dialog
|
||||
*/
|
||||
class DialogLocker() : Restorable {
|
||||
|
||||
private var isDialogDisplayed: Boolean = false
|
||||
|
||||
private fun unlock() {
|
||||
isDialogDisplayed = false
|
||||
}
|
||||
|
||||
private fun lock() {
|
||||
isDialogDisplayed = true
|
||||
}
|
||||
|
||||
fun displayDialog(builder: () -> AlertDialog.Builder): AlertDialog? {
|
||||
return if (isDialogDisplayed) {
|
||||
Timber.w("Filtered dialog request")
|
||||
null
|
||||
} else {
|
||||
builder
|
||||
.invoke()
|
||||
.create()
|
||||
.apply {
|
||||
setOnShowListener { lock() }
|
||||
setOnCancelListener { unlock() }
|
||||
setOnDismissListener { unlock() }
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(KEY_DIALOG_IS_DISPLAYED, isDialogDisplayed)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
|
||||
isDialogDisplayed = savedInstanceState?.getBoolean(KEY_DIALOG_IS_DISPLAYED, false) == true
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.riotredesign.core.dialogs.DialogAdapter
|
||||
import im.vector.riotredesign.core.dialogs.DialogListItem
|
||||
|
||||
internal class DialogSendItemAdapter(context: Context, items: MutableList<DialogListItem>) : DialogAdapter(context) {
|
||||
|
||||
init {
|
||||
addAll(items)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
class ExportKeysDialog {
|
||||
|
||||
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
|
||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.encryption_export_room_keys)
|
||||
.setView(dialogLayout)
|
||||
|
||||
val passPhrase1EditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
|
||||
val passPhrase2EditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_confirm_passphrase_edit_text)
|
||||
val passPhrase2Til = dialogLayout.findViewById<TextInputLayout>(R.id.dialog_e2e_keys_confirm_passphrase_til)
|
||||
val exportButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_export_button)
|
||||
val textWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
when {
|
||||
TextUtils.isEmpty(passPhrase1EditText.text) -> {
|
||||
exportButton.isEnabled = false
|
||||
passPhrase2Til.error = null
|
||||
}
|
||||
TextUtils.equals(passPhrase1EditText.text, passPhrase2EditText.text) -> {
|
||||
exportButton.isEnabled = true
|
||||
passPhrase2Til.error = null
|
||||
}
|
||||
else -> {
|
||||
exportButton.isEnabled = false
|
||||
passPhrase2Til.error = activity.getString(R.string.passphrase_passphrase_does_not_match)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
passPhrase1EditText.addTextChangedListener(textWatcher)
|
||||
passPhrase2EditText.addTextChangedListener(textWatcher)
|
||||
|
||||
val exportDialog = builder.show()
|
||||
|
||||
exportButton.setOnClickListener {
|
||||
exportKeyDialogListener.onPassphrase(passPhrase1EditText.text.toString())
|
||||
|
||||
exportDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface ExportKeyDialogListener {
|
||||
fun onPassphrase(passphrase: String)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotredesign.core.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.riotredesign.R
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
|
||||
private const val LOG_TAG = "PermissionUtils"
|
||||
|
||||
// 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
|
||||
|
||||
// Permissions sets
|
||||
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA or PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
|
||||
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
||||
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
||||
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
|
||||
private const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
||||
|
||||
// Request code to ask permission to the system (arbitrary values)
|
||||
const val PERMISSION_REQUEST_CODE = 567
|
||||
const val PERMISSION_REQUEST_CODE_LAUNCH_CAMERA = 568
|
||||
const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA = 569
|
||||
const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA = 570
|
||||
const val PERMISSION_REQUEST_CODE_AUDIO_CALL = 571
|
||||
const val PERMISSION_REQUEST_CODE_VIDEO_CALL = 572
|
||||
const val PERMISSION_REQUEST_CODE_EXPORT_KEYS = 573
|
||||
const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
|
||||
|
||||
/**
|
||||
* Log the used permissions statuses.
|
||||
*/
|
||||
fun logPermissionStatuses(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val permissions = Arrays.asList(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_CONTACTS)
|
||||
|
||||
Timber.d("## logPermissionStatuses() : log the permissions status used by the app")
|
||||
|
||||
for (permission in permissions) {
|
||||
Timber.d(("Status of [$permission] : " +
|
||||
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission))
|
||||
"PERMISSION_GRANTED"
|
||||
else
|
||||
"PERMISSION_DENIED"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 = PERMISSION_REQUEST_CODE): Boolean {
|
||||
return checkPermissions(permissionsToBeGrantedBitMap, activity, null, requestCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* See [.checkPermissions]
|
||||
*
|
||||
* @param permissionsToBeGrantedBitMap
|
||||
* @param fragment
|
||||
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
|
||||
*/
|
||||
fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
fragment: Fragment,
|
||||
requestCode: Int = PERMISSION_REQUEST_CODE): Boolean {
|
||||
return checkPermissions(permissionsToBeGrantedBitMap, fragment.activity, fragment, requestCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the permissions provided in the list are granted.
|
||||
* This is an asynchronous method if permissions are requested, the final response
|
||||
* is provided in onRequestPermissionsResult(). In this case checkPermissions()
|
||||
* returns false.
|
||||
* <br></br>If checkPermissions() returns true, the permissions were already granted.
|
||||
* The permissions to be granted are given as bit map in permissionsToBeGrantedBitMap (ex: [.PERMISSIONS_FOR_TAKING_PHOTO]).
|
||||
* <br></br>permissionsToBeGrantedBitMap is passed as the request code in onRequestPermissionsResult().
|
||||
*
|
||||
*
|
||||
* If a permission was already denied by the user, a popup is displayed to
|
||||
* explain why vector needs the corresponding permission.
|
||||
*
|
||||
* @param permissionsToBeGrantedBitMap the permissions bit map to be granted
|
||||
* @param activity the calling Activity that is requesting the permissions (or fragment parent)
|
||||
* @param fragment the calling fragment that is requesting the permissions
|
||||
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
|
||||
*/
|
||||
private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
activity: Activity?,
|
||||
fragment: Fragment?,
|
||||
requestCode: Int): Boolean {
|
||||
var isPermissionGranted = false
|
||||
|
||||
// sanity check
|
||||
if (null == activity) {
|
||||
Timber.w("## checkPermissions(): invalid input data")
|
||||
isPermissionGranted = false
|
||||
} else if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
|
||||
isPermissionGranted = true
|
||||
} else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_TAKING_PHOTO != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_MEMBERS_SEARCH != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap) {
|
||||
Timber.w("## checkPermissions(): permissions to be granted are not supported")
|
||||
isPermissionGranted = false
|
||||
} else {
|
||||
val permissionListAlreadyDenied = ArrayList<String>()
|
||||
val permissionsListToBeGranted = ArrayList<String>()
|
||||
var isRequestPermissionRequired = false
|
||||
var explanationMessage = ""
|
||||
|
||||
// retrieve the permissions to be granted according to the request code bit map
|
||||
if (PERMISSION_CAMERA == permissionsToBeGrantedBitMap and PERMISSION_CAMERA) {
|
||||
val permissionType = Manifest.permission.CAMERA
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
if (PERMISSION_RECORD_AUDIO == permissionsToBeGrantedBitMap and PERMISSION_RECORD_AUDIO) {
|
||||
val permissionType = Manifest.permission.RECORD_AUDIO
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
if (PERMISSION_WRITE_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_WRITE_EXTERNAL_STORAGE) {
|
||||
val permissionType = Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
// 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.isEmpty()) {
|
||||
if (permissionsToBeGrantedBitMap == PERMISSIONS_FOR_VIDEO_IP_CALL || permissionsToBeGrantedBitMap == PERMISSIONS_FOR_AUDIO_IP_CALL) {
|
||||
// Permission request for VOIP call
|
||||
if (permissionListAlreadyDenied.contains(Manifest.permission.CAMERA)
|
||||
&& permissionListAlreadyDenied.contains(Manifest.permission.RECORD_AUDIO)) {
|
||||
// Both missing
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_camera_and_audio)
|
||||
} else if (permissionListAlreadyDenied.contains(Manifest.permission.RECORD_AUDIO)) {
|
||||
// Audio missing
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_record_audio)
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_record_audio_explanation)
|
||||
} else if (permissionListAlreadyDenied.contains(Manifest.permission.CAMERA)) {
|
||||
// Camera missing
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_camera)
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_camera_explanation)
|
||||
}
|
||||
} else {
|
||||
permissionListAlreadyDenied.forEach {
|
||||
when (it) {
|
||||
Manifest.permission.CAMERA -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_camera)
|
||||
}
|
||||
Manifest.permission.RECORD_AUDIO -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_record_audio)
|
||||
}
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_storage)
|
||||
}
|
||||
Manifest.permission.READ_CONTACTS -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_contacts)
|
||||
}
|
||||
else -> Timber.d("## checkPermissions(): already denied permission not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display the dialog with the info text
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.permissions_rationale_popup_title)
|
||||
.setMessage(explanationMessage)
|
||||
.setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (!permissionsListToBeGranted.isEmpty()) {
|
||||
fragment?.requestPermissions(permissionsListToBeGranted.toTypedArray(), requestCode)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
// some permissions are not granted, ask permissions
|
||||
if (isRequestPermissionRequired) {
|
||||
val permissionsArrayToBeGranted = permissionsListToBeGranted.toTypedArray()
|
||||
|
||||
// for android < M, we use a custom dialog to request the contacts book access.
|
||||
/*
|
||||
if (permissionsListToBeGranted.contains(Manifest.permission.READ_CONTACTS)
|
||||
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
AlertDialog.Builder(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 {
|
||||
fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
|
||||
}
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
// permissions were granted, start now.
|
||||
isPermissionGranted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isPermissionGranted
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method used in [.checkPermissions] to populate the list of the
|
||||
* permissions to be granted (permissionsListToBeGranted_out) and the list of the permissions already denied (permissionAlreadyDeniedList_out).
|
||||
*
|
||||
* @param activity calling activity
|
||||
* @param permissionAlreadyDeniedList_out list to be updated with the permissions already denied by the user
|
||||
* @param permissionsListToBeGranted_out list to be updated with the permissions to be granted
|
||||
* @param permissionType the permission to be checked
|
||||
* @return true if the permission requires to be granted, false otherwise
|
||||
*/
|
||||
private fun updatePermissionsToBeGranted(activity: Activity,
|
||||
permissionAlreadyDeniedList_out: MutableList<String>,
|
||||
permissionsListToBeGranted_out: MutableList<String>,
|
||||
permissionType: String): Boolean {
|
||||
var isRequestPermissionRequested = false
|
||||
|
||||
// add permission to be granted
|
||||
permissionsListToBeGranted_out.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)) {
|
||||
permissionAlreadyDeniedList_out.add(permissionType)
|
||||
}
|
||||
}
|
||||
return isRequestPermissionRequested
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to process [.PERMISSIONS_FOR_AUDIO_IP_CALL]
|
||||
* on onRequestPermissionsResult() methods.
|
||||
*
|
||||
* @param context App context
|
||||
* @param grantResults permissions granted results
|
||||
* @return true if audio IP call is permitted, false otherwise
|
||||
*/
|
||||
fun onPermissionResultAudioIpCall(context: Context, grantResults: IntArray): Boolean {
|
||||
val arePermissionsGranted = allGranted(grantResults)
|
||||
|
||||
if (!arePermissionsGranted) {
|
||||
Toast.makeText(context, R.string.permissions_action_not_performed_missing_permissions, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
return arePermissionsGranted
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to process [.PERMISSIONS_FOR_VIDEO_IP_CALL]
|
||||
* on onRequestPermissionsResult() methods.
|
||||
* For video IP calls, record audio and camera permissions are both mandatory.
|
||||
*
|
||||
* @param context App context
|
||||
* @param grantResults permissions granted results
|
||||
* @return true if video IP call is permitted, false otherwise
|
||||
*/
|
||||
fun onPermissionResultVideoIpCall(context: Context, grantResults: IntArray): Boolean {
|
||||
val arePermissionsGranted = allGranted(grantResults)
|
||||
|
||||
if (!arePermissionsGranted) {
|
||||
Toast.makeText(context, R.string.permissions_action_not_performed_missing_permissions, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
return arePermissionsGranted
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all permissions are granted, false if not or if permission request has been cancelled
|
||||
*/
|
||||
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
|
||||
}
|
|
@ -21,15 +21,23 @@ import android.os.Parcelable
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.dialogs.DialogListItem
|
||||
import im.vector.riotredesign.core.dialogs.DialogSendItemAdapter
|
||||
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotredesign.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA
|
||||
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA
|
||||
import im.vector.riotredesign.core.utils.checkPermissions
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.HomeModule
|
||||
import im.vector.riotredesign.features.home.HomePermalinkHandler
|
||||
|
@ -79,6 +87,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||
setupRecyclerView()
|
||||
setupToolbar()
|
||||
setupSendButton()
|
||||
setupAttachmentButton()
|
||||
roomDetailViewModel.subscribe { renderState(it) }
|
||||
}
|
||||
|
||||
|
@ -128,6 +137,61 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupAttachmentButton() {
|
||||
attachmentButton.setOnClickListener {
|
||||
val items = ArrayList<DialogListItem>()
|
||||
// Send file
|
||||
items.add(DialogListItem.SendFile)
|
||||
// Send voice
|
||||
/*
|
||||
if (PreferencesManager.isSendVoiceFeatureEnabled(this)) {
|
||||
items.add(DialogListItem.SendVoice.INSTANCE)
|
||||
}
|
||||
*/
|
||||
|
||||
// Send sticker
|
||||
items.add(DialogListItem.SendSticker)
|
||||
// Camera
|
||||
|
||||
//if (PreferencesManager.useNativeCamera(this)) {
|
||||
items.add(DialogListItem.TakePhoto)
|
||||
items.add(DialogListItem.TakeVideo)
|
||||
//} else {
|
||||
// items.add(DialogListItem.TakePhotoVideo.INSTANCE)
|
||||
// }
|
||||
val adapter = DialogSendItemAdapter(requireContext(), items)
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setAdapter(adapter, { dialog, which ->
|
||||
onSendChoiceClicked(items[which])
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
|
||||
when (dialogListItem) {
|
||||
is DialogListItem.SendFile -> {
|
||||
//launchFileSelectionIntent()
|
||||
}
|
||||
is DialogListItem.SendVoice -> {
|
||||
//launchAudioRecorderIntent()
|
||||
}
|
||||
is DialogListItem.SendSticker -> {
|
||||
//startStickerPickerActivity()
|
||||
}
|
||||
is DialogListItem.TakePhotoVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||
// launchCamera()
|
||||
}
|
||||
is DialogListItem.TakePhoto -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
|
||||
// launchNativeCamera()
|
||||
}
|
||||
is DialogListItem.TakeVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
|
||||
// launchNativeVideoRecorder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(state: RoomDetailViewState) {
|
||||
renderRoomSummary(state)
|
||||
timelineEventController.setTimeline(state.timeline)
|
||||
|
|
BIN
vector/src/main/res/drawable-hdpi/ic_attach_file_white.png
Normal file
After Width: | Height: | Size: 394 B |
BIN
vector/src/main/res/drawable-mdpi/ic_attach_file_white.png
Normal file
After Width: | Height: | Size: 285 B |
BIN
vector/src/main/res/drawable-mdpi/ic_material_camera.png
Executable file
After Width: | Height: | Size: 539 B |
BIN
vector/src/main/res/drawable-mdpi/ic_material_file.png
Executable file
After Width: | Height: | Size: 545 B |
BIN
vector/src/main/res/drawable-mdpi/ic_material_videocam.png
Executable file
After Width: | Height: | Size: 3 KiB |
BIN
vector/src/main/res/drawable-mdpi/ic_send_sticker.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
vector/src/main/res/drawable-mdpi/vector_micro_green.png
Executable file
After Width: | Height: | Size: 399 B |
BIN
vector/src/main/res/drawable-mdpi/video_call_black.png
Executable file
After Width: | Height: | Size: 224 B |
BIN
vector/src/main/res/drawable-mdpi/video_call_green.png
Executable file
After Width: | Height: | Size: 298 B |
BIN
vector/src/main/res/drawable-mdpi/voice_call_black.png
Executable file
After Width: | Height: | Size: 574 B |
BIN
vector/src/main/res/drawable-mdpi/voice_call_end_fushia.png
Executable file
After Width: | Height: | Size: 331 B |
BIN
vector/src/main/res/drawable-mdpi/voice_call_green.png
Executable file
After Width: | Height: | Size: 432 B |
BIN
vector/src/main/res/drawable-mdpi/voice_call_start_green.png
Executable file
After Width: | Height: | Size: 684 B |
BIN
vector/src/main/res/drawable-xhdpi/ic_attach_file_white.png
Normal file
After Width: | Height: | Size: 507 B |
BIN
vector/src/main/res/drawable-xxhdpi/ic_attach_file_white.png
Normal file
After Width: | Height: | Size: 809 B |
BIN
vector/src/main/res/drawable-xxxhdpi/ic_attach_file_white.png
Normal file
After Width: | Height: | Size: 1 KiB |
62
vector/src/main/res/layout/dialog_export_e2e_keys.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/layout_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?dialogPreferredPadding"
|
||||
android:paddingLeft="?dialogPreferredPadding"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/encryption_export_notice"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColorHint="?attr/vctr_default_text_hint_color">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/dialog_e2e_keys_passphrase_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_create_passphrase"
|
||||
android:inputType="textPassword"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/dialog_e2e_keys_confirm_passphrase_til"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textColorHint="?attr/vctr_default_text_hint_color"
|
||||
app:errorEnabled="true">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/dialog_e2e_keys_confirm_passphrase_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/passphrase_confirm_passphrase"
|
||||
android:inputType="textPassword"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_e2e_keys_export_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:enabled="false"
|
||||
android:text="@string/encryption_export_export" />
|
||||
</LinearLayout>
|
|
@ -93,6 +93,17 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/attachmentButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/sendButton"
|
||||
android:layout_toLeftOf="@id/sendButton"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:src="@drawable/ic_attach_file_white"
|
||||
android:tint="?attr/colorAccent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="48dp"
|
||||
|
@ -111,8 +122,8 @@
|
|||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/sendButton"
|
||||
android:layout_toLeftOf="@id/sendButton"
|
||||
android:layout_toStartOf="@id/attachmentButton"
|
||||
android:layout_toLeftOf="@id/attachmentButton"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="@string/room_message_placeholder_not_encrypted"
|
||||
|
|
53
vector/src/main/res/layout/item_dialog.xml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2019 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.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<!-- Do not use drawableStart for icon size and for RTL -->
|
||||
<ImageView
|
||||
android:id="@+id/adapter_item_dialog_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:rotationY="@integer/rtl_mirror_flip"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/video_call_green" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/adapter_item_dialog_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/adapter_item_dialog_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/action_video_call" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|