forked from GitHub-Mirror/riotX-android
Media: start to play with uploading media
This commit is contained in:
parent
96a67a44ac
commit
18591d0287
@ -1,6 +1,7 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'realm-android'
|
apply plugin: 'realm-android'
|
||||||
apply plugin: 'okreplay'
|
apply plugin: 'okreplay'
|
||||||
|
|
||||||
@ -19,6 +20,10 @@ repositories {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room.send
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.session.room.media.MediaAttachment
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to send events in a room. It's implemented at the room level.
|
* This interface defines methods to send events in a room. It's implemented at the room level.
|
||||||
@ -33,5 +34,6 @@ interface SendService {
|
|||||||
*/
|
*/
|
||||||
fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable
|
fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable
|
||||||
|
|
||||||
|
fun sendMedia(attachment: MediaAttachment, callback: MatrixCallback<Event>): Cancelable
|
||||||
|
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
|||||||
|
|
||||||
|
|
||||||
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||||
private const val URI_PREFIX_CONTENT_API = "/_matrix/media/v1/"
|
internal const val URI_PREFIX_CONTENT_API = "/_matrix/media/v1/"
|
||||||
|
|
||||||
internal class DefaultContentUrlResolver(private val homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
internal class DefaultContentUrlResolver(private val homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.matrix.android.internal.session.room
|
package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
|
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
|
||||||
@ -33,7 +32,6 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
|||||||
|
|
||||||
internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
|
internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val credentials: Credentials,
|
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val setReadMarkersTask: SetReadMarkersTask,
|
private val setReadMarkersTask: SetReadMarkersTask,
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.internal.session.room
|
package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.session.DefaultSession
|
import im.vector.matrix.android.internal.session.DefaultSession
|
||||||
|
import im.vector.matrix.android.internal.session.room.media.MediaUploader
|
||||||
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
|
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
|
||||||
@ -61,7 +62,11 @@ class RoomModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get())
|
MediaUploader(get(), get())
|
||||||
|
}
|
||||||
|
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
RoomFactory(get(), get(), get(), get(), get(), get(), get())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.matrix.android.internal.session.room.media
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class MediaAttachment(
|
||||||
|
val size: Long = 0,
|
||||||
|
val duration: Long = 0,
|
||||||
|
val date: Long = 0,
|
||||||
|
val height: Long = 0,
|
||||||
|
val width: Long = 0,
|
||||||
|
val name: String? = null,
|
||||||
|
val thumbnail: Uri? = null,
|
||||||
|
val path: String? = null,
|
||||||
|
val mimeType: String? = null
|
||||||
|
) : Parcelable
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.matrix.android.internal.session.room.media
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.internal.session.content.URI_PREFIX_CONTENT_API
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
|
internal class MediaUploader(private val okHttpClient: OkHttpClient,
|
||||||
|
private val sessionParams: SessionParams) {
|
||||||
|
|
||||||
|
fun uploadFile(attachment: MediaAttachment): Try<String> {
|
||||||
|
if (attachment.path == null || attachment.mimeType == null) {
|
||||||
|
return Try.raise(RuntimeException())
|
||||||
|
}
|
||||||
|
val urlString = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload"
|
||||||
|
val file = File(attachment.path)
|
||||||
|
|
||||||
|
// create RequestBody instance from file
|
||||||
|
val requestFile = RequestBody.create(
|
||||||
|
MediaType.parse(attachment.mimeType),
|
||||||
|
file
|
||||||
|
)
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(urlString)
|
||||||
|
.post(requestFile)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return okHttpClient.newCall(request).execute().use { response ->
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
Try.raise(IOException(""))
|
||||||
|
} else {
|
||||||
|
Try.just(response.message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.matrix.android.internal.session.room.media
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
||||||
|
import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
||||||
|
import org.koin.standalone.inject
|
||||||
|
|
||||||
|
internal class UploadMediaWorker(context: Context, params: WorkerParameters)
|
||||||
|
: Worker(context, params), MatrixKoinComponent {
|
||||||
|
|
||||||
|
private val mediaUploader by inject<MediaUploader>()
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class Params(
|
||||||
|
val attachment: MediaAttachment
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun doWork(): Result {
|
||||||
|
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||||
|
?: return Result.failure()
|
||||||
|
|
||||||
|
return mediaUploader
|
||||||
|
.uploadFile(params.attachment)
|
||||||
|
.fold({ Result.retry() }, { Result.success() })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -25,6 +25,8 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||||||
import im.vector.matrix.android.internal.database.helper.add
|
import im.vector.matrix.android.internal.database.helper.add
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
|
import im.vector.matrix.android.internal.session.room.media.MediaAttachment
|
||||||
|
import im.vector.matrix.android.internal.session.room.media.UploadMediaWorker
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import im.vector.matrix.android.internal.util.CancelableWork
|
import im.vector.matrix.android.internal.util.CancelableWork
|
||||||
import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
||||||
@ -32,39 +34,70 @@ import im.vector.matrix.android.internal.util.tryTransactionAsync
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val SEND_WORK = "SEND_WORK"
|
private const val SEND_WORK = "SEND_WORK"
|
||||||
|
private const val BACKOFF_DELAY = 10_000L
|
||||||
|
private val WORK_CONSTRAINTS = Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
|
.build()
|
||||||
|
|
||||||
internal class DefaultSendService(private val roomId: String,
|
internal class DefaultSendService(private val roomId: String,
|
||||||
private val eventFactory: EventFactory,
|
private val eventFactory: EventFactory,
|
||||||
private val monarchy: Monarchy) : SendService {
|
private val monarchy: Monarchy) : SendService {
|
||||||
|
|
||||||
private val sendConstraints = Constraints.Builder()
|
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
|
override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
|
||||||
val event = eventFactory.createTextEvent(roomId, text)
|
val event = eventFactory.createTextEvent(roomId, text)
|
||||||
|
saveLiveEvent(event)
|
||||||
monarchy.tryTransactionAsync { realm ->
|
val sendWork = createSendEventWork(event)
|
||||||
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
|
|
||||||
?: return@tryTransactionAsync
|
|
||||||
chunkEntity.add(roomId, event, PaginationDirection.FORWARDS)
|
|
||||||
}
|
|
||||||
|
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
|
|
||||||
val workData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
|
||||||
|
|
||||||
val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>()
|
|
||||||
.setConstraints(sendConstraints)
|
|
||||||
.setInputData(workData)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
WorkManager.getInstance()
|
WorkManager.getInstance()
|
||||||
.beginUniqueWork(SEND_WORK, ExistingWorkPolicy.APPEND, sendWork)
|
.beginUniqueWork(SEND_WORK, ExistingWorkPolicy.APPEND, sendWork)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
|
|
||||||
return CancelableWork(sendWork.id)
|
return CancelableWork(sendWork.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendMedia(attachment: MediaAttachment, callback: MatrixCallback<Event>): Cancelable {
|
||||||
|
// Create an event with the media file path
|
||||||
|
val event = eventFactory.createImageEvent(roomId, attachment)
|
||||||
|
saveLiveEvent(event)
|
||||||
|
|
||||||
|
val uploadWork = createUploadMediaWork(attachment)
|
||||||
|
val sendWork = createSendEventWork(event)
|
||||||
|
|
||||||
|
WorkManager.getInstance()
|
||||||
|
.beginUniqueWork(SEND_WORK, ExistingWorkPolicy.APPEND, uploadWork)
|
||||||
|
.then(sendWork)
|
||||||
|
.enqueue()
|
||||||
|
return CancelableWork(sendWork.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveLiveEvent(event: Event) {
|
||||||
|
monarchy.tryTransactionAsync { realm ->
|
||||||
|
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
|
||||||
|
?: return@tryTransactionAsync
|
||||||
|
chunkEntity.add(roomId, event, PaginationDirection.FORWARDS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
||||||
|
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
|
||||||
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
|
return OneTimeWorkRequestBuilder<SendEventWorker>()
|
||||||
|
.setConstraints(WORK_CONSTRAINTS)
|
||||||
|
.setInputData(sendWorkData)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createUploadMediaWork(attachment: MediaAttachment): OneTimeWorkRequest {
|
||||||
|
val uploadMediaWorkerParams = UploadMediaWorker.Params(attachment)
|
||||||
|
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||||
|
|
||||||
|
return OneTimeWorkRequestBuilder<UploadMediaWorker>()
|
||||||
|
.setConstraints(WORK_CONSTRAINTS)
|
||||||
|
.setInputData(uploadWorkData)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,27 +17,45 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.session.room.media.MediaAttachment
|
||||||
|
|
||||||
internal class EventFactory(private val credentials: Credentials) {
|
internal class EventFactory(private val credentials: Credentials) {
|
||||||
|
|
||||||
private val moshi = MoshiProvider.providesMoshi()
|
|
||||||
|
|
||||||
fun createTextEvent(roomId: String, text: String): Event {
|
fun createTextEvent(roomId: String, text: String): Event {
|
||||||
val content = MessageTextContent(type = MessageType.MSGTYPE_TEXT, body = text)
|
val content = MessageTextContent(type = MessageType.MSGTYPE_TEXT, body = text)
|
||||||
|
return createEvent(roomId, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createImageEvent(roomId: String, attachment: MediaAttachment): Event {
|
||||||
|
val content = MessageImageContent(
|
||||||
|
type = MessageType.MSGTYPE_IMAGE,
|
||||||
|
body = attachment.name ?: "image",
|
||||||
|
url = attachment.path
|
||||||
|
)
|
||||||
|
return createEvent(roomId, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateImageEvent(event: Event, url: String): Event {
|
||||||
|
val imageContent = event.content.toModel<MessageImageContent>() ?: return event
|
||||||
|
val updatedContent = imageContent.copy(url = url)
|
||||||
|
return event.copy(content = updatedContent.toContent())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createEvent(roomId: String, content: Any? = null): Event {
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
originServerTs = dummyOriginServerTs(),
|
originServerTs = dummyOriginServerTs(),
|
||||||
sender = credentials.userId,
|
sender = credentials.userId,
|
||||||
eventId = dummyEventId(roomId),
|
eventId = dummyEventId(roomId),
|
||||||
type = EventType.MESSAGE,
|
type = EventType.MESSAGE,
|
||||||
content = toContent(content)
|
content = content.toContent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,13 +66,4 @@ internal class EventFactory(private val credentials: Credentials) {
|
|||||||
private fun dummyEventId(roomId: String): String {
|
private fun dummyEventId(roomId: String): String {
|
||||||
return roomId + "-" + dummyOriginServerTs()
|
return roomId + "-" + dummyOriginServerTs()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private inline fun <reified T> toContent(data: T?): Content? {
|
|
||||||
val moshiAdapter = moshi.adapter(T::class.java)
|
|
||||||
val jsonValue = moshiAdapter.toJsonValue(data)
|
|
||||||
return jsonValue as? Content?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ dependencies {
|
|||||||
def coroutines_version = "1.0.1"
|
def coroutines_version = "1.0.1"
|
||||||
def markwon_version = '3.0.0-SNAPSHOT'
|
def markwon_version = '3.0.0-SNAPSHOT'
|
||||||
def big_image_viewer_version = '1.5.6'
|
def big_image_viewer_version = '1.5.6'
|
||||||
def glide_version = '4.8.0'
|
def glide_version = '4.9.0'
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
@ -209,6 +209,9 @@ dependencies {
|
|||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||||
|
|
||||||
|
implementation 'com.github.jaiselrahman:FilePicker:1.2.0'
|
||||||
|
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
implementation "org.koin:koin-android:$koin_version"
|
implementation "org.koin:koin-android:$koin_version"
|
||||||
implementation "org.koin:koin-android-scope:$koin_version"
|
implementation "org.koin:koin-android-scope:$koin_version"
|
||||||
|
@ -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
|
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.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
sealed class RoomDetailActions {
|
sealed class RoomDetailActions {
|
||||||
|
|
||||||
data class SendMessage(val text: String) : RoomDetailActions()
|
data class SendMessage(val text: String) : RoomDetailActions()
|
||||||
|
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
|
||||||
object IsDisplayed : RoomDetailActions()
|
object IsDisplayed : RoomDetailActions()
|
||||||
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
||||||
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
|
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail
|
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.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -26,6 +28,9 @@ 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 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.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.DialogListItem
|
||||||
@ -33,11 +38,7 @@ 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.*
|
||||||
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
|
||||||
@ -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.bindScope
|
||||||
import org.koin.android.scope.ext.android.getOrCreateScope
|
import org.koin.android.scope.ext.android.getOrCreateScope
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@ -60,6 +62,10 @@ data class RoomDetailArgs(
|
|||||||
) : Parcelable
|
) : 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 {
|
class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -91,6 +97,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||||||
roomDetailViewModel.subscribe { renderState(it) }
|
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() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
|
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
|
||||||
@ -150,7 +166,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Send sticker
|
// Send sticker
|
||||||
items.add(DialogListItem.SendSticker)
|
//items.add(DialogListItem.SendSticker)
|
||||||
// Camera
|
// Camera
|
||||||
|
|
||||||
//if (PreferencesManager.useNativeCamera(this)) {
|
//if (PreferencesManager.useNativeCamera(this)) {
|
||||||
@ -161,18 +177,24 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||||||
// }
|
// }
|
||||||
val adapter = DialogSendItemAdapter(requireContext(), items)
|
val adapter = DialogSendItemAdapter(requireContext(), items)
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setAdapter(adapter, { dialog, which ->
|
.setAdapter(adapter) { _, position ->
|
||||||
onSendChoiceClicked(items[which])
|
onSendChoiceClicked(items[position])
|
||||||
})
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
|
private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
|
||||||
|
Timber.v("On send choice clicked: $dialogListItem")
|
||||||
when (dialogListItem) {
|
when (dialogListItem) {
|
||||||
is DialogListItem.SendFile -> {
|
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 -> {
|
is DialogListItem.SendVoice -> {
|
||||||
//launchAudioRecorderIntent()
|
//launchAudioRecorderIntent()
|
||||||
@ -184,7 +206,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||||||
// launchCamera()
|
// launchCamera()
|
||||||
}
|
}
|
||||||
is DialogListItem.TakePhoto -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
|
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)) {
|
is DialogListItem.TakeVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
|
||||||
// launchNativeVideoRecorder()
|
// 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) {
|
private fun renderState(state: RoomDetailViewState) {
|
||||||
renderRoomSummary(state)
|
renderRoomSummary(state)
|
||||||
timelineEventController.setTimeline(state.timeline)
|
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.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.matrix.rx.rx
|
||||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||||
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
||||||
@ -64,6 +65,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
fun process(action: RoomDetailActions) {
|
fun process(action: RoomDetailActions) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||||
|
is RoomDetailActions.SendMedia -> handleSendMedia(action)
|
||||||
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
||||||
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
||||||
is RoomDetailActions.LoadMore -> handleLoadMore(action)
|
is RoomDetailActions.LoadMore -> handleLoadMore(action)
|
||||||
@ -76,6 +78,25 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
|
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) {
|
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
||||||
displayedEventsObservable.accept(action)
|
displayedEventsObservable.accept(action)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,9 @@ object MediaContentRenderer {
|
|||||||
val resolvedUrl = when (mode) {
|
val resolvedUrl = when (mode) {
|
||||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
||||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
} ?: return
|
}
|
||||||
|
//Fallback to base url
|
||||||
|
?: data.url
|
||||||
|
|
||||||
GlideApp
|
GlideApp
|
||||||
.with(imageView)
|
.with(imageView)
|
||||||
@ -68,8 +70,8 @@ object MediaContentRenderer {
|
|||||||
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
||||||
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
imageView.showImage(
|
imageView.showImage(
|
||||||
Uri.parse(thumbnail),
|
Uri.parse(thumbnail ?: data.url),
|
||||||
Uri.parse(fullSize)
|
Uri.parse(fullSize ?: data.url)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user