forked from GitHub-Mirror/riotX-android
Media: start to play with uploading media
This commit is contained in:
@ -0,0 +1,240 @@
|
||||
/*
|
||||
*
|
||||
* * 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.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Browser
|
||||
import android.provider.MediaStore
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.riotredesign.BuildConfig
|
||||
import im.vector.riotredesign.R
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Open a url in the internet browser of the system
|
||||
*/
|
||||
fun openUrlInExternalBrowser(context: Context, url: String?) {
|
||||
url?.let {
|
||||
openUrlInExternalBrowser(context, Uri.parse(it))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a uri in the internet browser of the system
|
||||
*/
|
||||
fun openUrlInExternalBrowser(context: Context, uri: Uri?) {
|
||||
uri?.let {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, it).apply {
|
||||
putExtra(Browser.EXTRA_APPLICATION_ID, context.packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
context.startActivity(browserIntent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
context.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open sound recorder external application
|
||||
*/
|
||||
fun openSoundRecorder(activity: Activity, requestCode: Int) {
|
||||
val recordSoundIntent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
|
||||
|
||||
// Create chooser
|
||||
val chooserIntent = Intent.createChooser(recordSoundIntent, activity.getString(R.string.go_on_with))
|
||||
|
||||
try {
|
||||
activity.startActivityForResult(chooserIntent, requestCode)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open file selection activity
|
||||
*/
|
||||
fun openFileSelection(activity: Activity,
|
||||
fragment: Fragment?,
|
||||
allowMultipleSelection: Boolean,
|
||||
requestCode: Int) {
|
||||
val fileIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultipleSelection)
|
||||
}
|
||||
|
||||
fileIntent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
fileIntent.type = "*/*"
|
||||
|
||||
try {
|
||||
fragment
|
||||
?.startActivityForResult(fileIntent, requestCode)
|
||||
?: run {
|
||||
activity.startActivityForResult(fileIntent, requestCode)
|
||||
}
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open external video recorder
|
||||
*/
|
||||
fun openVideoRecorder(activity: Activity, requestCode: Int) {
|
||||
val captureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
|
||||
|
||||
// lowest quality
|
||||
captureIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0)
|
||||
|
||||
try {
|
||||
activity.startActivityForResult(captureIntent, requestCode)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open external camera
|
||||
* @return the latest taken picture camera uri
|
||||
*/
|
||||
fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): String? {
|
||||
val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||
|
||||
// the following is a fix for buggy 2.x devices
|
||||
val date = Date()
|
||||
val formatter = SimpleDateFormat("yyyyMMddHHmmss", Locale.US)
|
||||
val values = ContentValues()
|
||||
values.put(MediaStore.Images.Media.TITLE, titlePrefix + formatter.format(date))
|
||||
// The Galaxy S not only requires the name of the file to output the image to, but will also not
|
||||
// set the mime type of the picture it just took (!!!). We assume that the Galaxy S takes image/jpegs
|
||||
// so the attachment uploader doesn't freak out about there being no mimetype in the content database.
|
||||
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
||||
var dummyUri: Uri? = null
|
||||
try {
|
||||
dummyUri = activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
|
||||
if (null == dummyUri) {
|
||||
Timber.e("Cannot use the external storage media to save image")
|
||||
}
|
||||
} catch (uoe: UnsupportedOperationException) {
|
||||
Timber.e(uoe, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI " +
|
||||
"no SD card? Attempting to insert into device storage.")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI. $e")
|
||||
}
|
||||
|
||||
if (null == dummyUri) {
|
||||
try {
|
||||
dummyUri = activity.contentResolver.insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values)
|
||||
if (null == dummyUri) {
|
||||
Timber.e("Cannot use the internal storage to save media to save image")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Unable to insert camera URI into internal storage. Giving up. $e")
|
||||
}
|
||||
}
|
||||
|
||||
if (dummyUri != null) {
|
||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, dummyUri)
|
||||
Timber.d("trying to take a photo on " + dummyUri.toString())
|
||||
} else {
|
||||
Timber.d("trying to take a photo with no predefined uri")
|
||||
}
|
||||
|
||||
// Store the dummy URI which will be set to a placeholder location. When all is lost on Samsung devices,
|
||||
// this will point to the data we're looking for.
|
||||
// Because Activities tend to use a single MediaProvider for all their intents, this field will only be the
|
||||
// *latest* TAKE_PICTURE Uri. This is deemed acceptable as the normal flow is to create the intent then immediately
|
||||
// fire it, meaning onActivityResult/getUri will be the next thing called, not another createIntentFor.
|
||||
val result = if (dummyUri == null) null else dummyUri.toString()
|
||||
|
||||
try {
|
||||
activity.startActivityForResult(captureIntent, requestCode)
|
||||
|
||||
return result
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email to address with optional subject and message
|
||||
*/
|
||||
fun sendMailTo(address: String, subject: String? = null, message: String? = null, activity: Activity) {
|
||||
val intent = Intent(Intent.ACTION_SENDTO, Uri.fromParts(
|
||||
"mailto", address, null))
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, message)
|
||||
|
||||
try {
|
||||
activity.startActivity(intent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an arbitrary uri
|
||||
*/
|
||||
fun openUri(activity: Activity, uri: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
|
||||
|
||||
try {
|
||||
activity.startActivity(intent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send media to a third party application.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param savedMediaPath the media path
|
||||
* @param mimeType the media mime type.
|
||||
*/
|
||||
fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) {
|
||||
val file = File(savedMediaPath)
|
||||
val uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileProvider", file)
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, mimeType)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
|
||||
try {
|
||||
activity.startActivity(intent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
activity.toast(R.string.error_no_external_application_found)
|
||||
}
|
||||
}
|
@ -16,12 +16,14 @@
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import com.jaiselrahman.filepicker.model.MediaFile
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
|
||||
sealed class RoomDetailActions {
|
||||
|
||||
data class SendMessage(val text: String) : RoomDetailActions()
|
||||
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
|
||||
object IsDisplayed : RoomDetailActions()
|
||||
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
||||
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
@ -26,6 +28,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.jaiselrahman.filepicker.activity.FilePickerActivity
|
||||
import com.jaiselrahman.filepicker.config.Configurations
|
||||
import com.jaiselrahman.filepicker.model.MediaFile
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.dialogs.DialogListItem
|
||||
@ -33,11 +38,7 @@ 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.core.utils.*
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.HomeModule
|
||||
import im.vector.riotredesign.features.home.HomePermalinkHandler
|
||||
@ -51,6 +52,7 @@ import org.koin.android.ext.android.inject
|
||||
import org.koin.android.scope.ext.android.bindScope
|
||||
import org.koin.android.scope.ext.android.getOrCreateScope
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
@Parcelize
|
||||
@ -60,6 +62,10 @@ data class RoomDetailArgs(
|
||||
) : Parcelable
|
||||
|
||||
|
||||
private const val CAMERA_VALUE_TITLE = "attachment"
|
||||
private const val REQUEST_FILES_REQUEST_CODE = 0
|
||||
private const val TAKE_IMAGE_REQUEST_CODE = 1
|
||||
|
||||
class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
|
||||
companion object {
|
||||
@ -91,6 +97,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
roomDetailViewModel.subscribe { renderState(it) }
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
when (requestCode) {
|
||||
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
|
||||
@ -150,7 +166,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
*/
|
||||
|
||||
// Send sticker
|
||||
items.add(DialogListItem.SendSticker)
|
||||
//items.add(DialogListItem.SendSticker)
|
||||
// Camera
|
||||
|
||||
//if (PreferencesManager.useNativeCamera(this)) {
|
||||
@ -161,18 +177,24 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
// }
|
||||
val adapter = DialogSendItemAdapter(requireContext(), items)
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setAdapter(adapter, { dialog, which ->
|
||||
onSendChoiceClicked(items[which])
|
||||
})
|
||||
.setAdapter(adapter) { _, position ->
|
||||
onSendChoiceClicked(items[position])
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
|
||||
Timber.v("On send choice clicked: $dialogListItem")
|
||||
when (dialogListItem) {
|
||||
is DialogListItem.SendFile -> {
|
||||
//launchFileSelectionIntent()
|
||||
val intent = Intent(requireContext(), FilePickerActivity::class.java)
|
||||
intent.putExtra(FilePickerActivity.CONFIGS, Configurations.Builder()
|
||||
.setCheckPermission(true)
|
||||
.setSkipZeroSizeFiles(true)
|
||||
.build())
|
||||
startActivityForResult(intent, REQUEST_FILES_REQUEST_CODE)
|
||||
}
|
||||
is DialogListItem.SendVoice -> {
|
||||
//launchAudioRecorderIntent()
|
||||
@ -184,7 +206,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
// launchCamera()
|
||||
}
|
||||
is DialogListItem.TakePhoto -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
|
||||
// launchNativeCamera()
|
||||
openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE)
|
||||
}
|
||||
is DialogListItem.TakeVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
|
||||
// launchNativeVideoRecorder()
|
||||
@ -192,6 +214,11 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMediaIntent(data: Intent) {
|
||||
val files: ArrayList<MediaFile> = data.getParcelableArrayListExtra(FilePickerActivity.MEDIA_FILES)
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMedia(files))
|
||||
}
|
||||
|
||||
private fun renderState(state: RoomDetailViewState) {
|
||||
renderRoomSummary(state)
|
||||
timelineEventController.setTimeline(state.timeline)
|
||||
|
@ -22,6 +22,7 @@ import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.session.room.media.MediaAttachment
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
||||
@ -64,6 +65,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||
fun process(action: RoomDetailActions) {
|
||||
when (action) {
|
||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailActions.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
||||
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
||||
is RoomDetailActions.LoadMore -> handleLoadMore(action)
|
||||
@ -76,6 +78,25 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
|
||||
}
|
||||
|
||||
private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
|
||||
val attachment = action.mediaFiles.firstOrNull()
|
||||
?.let {
|
||||
MediaAttachment(
|
||||
it.size,
|
||||
it.duration,
|
||||
it.date,
|
||||
it.height,
|
||||
it.width,
|
||||
it.name,
|
||||
it.thumbnail,
|
||||
it.path,
|
||||
it.mimeType
|
||||
)
|
||||
}
|
||||
?: return
|
||||
room.sendMedia(attachment, callback = object : MatrixCallback<Event> {})
|
||||
}
|
||||
|
||||
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
||||
displayedEventsObservable.accept(action)
|
||||
}
|
||||
|
@ -53,7 +53,9 @@ object MediaContentRenderer {
|
||||
val resolvedUrl = when (mode) {
|
||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
} ?: return
|
||||
}
|
||||
//Fallback to base url
|
||||
?: data.url
|
||||
|
||||
GlideApp
|
||||
.with(imageView)
|
||||
@ -68,8 +70,8 @@ object MediaContentRenderer {
|
||||
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
||||
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
imageView.showImage(
|
||||
Uri.parse(thumbnail),
|
||||
Uri.parse(fullSize)
|
||||
Uri.parse(thumbnail ?: data.url),
|
||||
Uri.parse(fullSize ?: data.url)
|
||||
)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user