Media: grab some code from Riot legacy

This commit is contained in:
ganfra 2019-04-02 15:59:36 +02:00
parent 494d893aa3
commit 96a67a44ac
28 changed files with 910 additions and 2 deletions

View File

@ -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
}

}

View File

@ -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)
}
}

View File

@ -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)

}

View File

@ -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)
}
}

View File

@ -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
}

}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -21,15 +21,23 @@ import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R 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.epoxy.LayoutManagerStateRestorer
import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.ToolbarConfigurable 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.AvatarRenderer
import im.vector.riotredesign.features.home.HomeModule import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomePermalinkHandler import im.vector.riotredesign.features.home.HomePermalinkHandler
@ -79,6 +87,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
setupRecyclerView() setupRecyclerView()
setupToolbar() setupToolbar()
setupSendButton() setupSendButton()
setupAttachmentButton()
roomDetailViewModel.subscribe { renderState(it) } 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) { private fun renderState(state: RoomDetailViewState) {
renderRoomSummary(state) renderRoomSummary(state)
timelineEventController.setTimeline(state.timeline) timelineEventController.setTimeline(state.timeline)

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View 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>

View File

@ -93,6 +93,17 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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 <ImageButton
android:id="@+id/sendButton" android:id="@+id/sendButton"
android:layout_width="48dp" android:layout_width="48dp"
@ -111,8 +122,8 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toStartOf="@id/sendButton" android:layout_toStartOf="@id/attachmentButton"
android:layout_toLeftOf="@id/sendButton" android:layout_toLeftOf="@id/attachmentButton"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:hint="@string/room_message_placeholder_not_encrypted" android:hint="@string/room_message_placeholder_not_encrypted"

View 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>