Start sending message : introduce WorkManager. WIP - have to clean

This commit is contained in:
ganfra 2018-11-08 19:08:14 +01:00
parent b2bb89ac94
commit f050574728
19 changed files with 207 additions and 11 deletions

View File

@ -50,6 +50,13 @@ class RoomDetailFragment : RiotFragment() {
room.loadRoomMembersIfNeeded() room.loadRoomMembersIfNeeded()
room.liveTimeline().observe(this, Observer { renderEvents(it) }) room.liveTimeline().observe(this, Observer { renderEvents(it) })
room.roomSummary.observe(this, Observer { renderRoomSummary(it) }) room.roomSummary.observe(this, Observer { renderRoomSummary(it) })
sendButton.setOnClickListener {
val textMessage = composerEditText.text.toString()
if (textMessage.isNotBlank()) {
composerEditText.text = null
room.sendTextMessage(textMessage)
}
}
} }


private fun setupToolbar() { private fun setupToolbar() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

View File

@ -73,9 +73,53 @@
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/composerDivider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" /> app:layout_constraintTop_toBottomOf="@id/toolbar" />


<View
android:id="@+id/composerDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/pale_grey"
app:layout_constraintBottom_toTopOf="@+id/composerLayout" />

<RelativeLayout
android:id="@+id/composerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<ImageButton
android:id="@+id/sendButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_send_white"
android:tint="?attr/colorAccent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />

<EditText
android:id="@+id/composerEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/sendButton"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:hint="Send a message"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="16dp"
android:textSize="14sp" />

</RelativeLayout>

</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -75,6 +75,9 @@ dependencies {
// Paging // Paging
implementation "android.arch.paging:runtime:1.0.1" implementation "android.arch.paging:runtime:1.0.1"


// Work
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha10"

// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
implementation "io.arrow-kt:arrow-instances-core:$arrow_version" implementation "io.arrow-kt:arrow-instances-core:$arrow_version"

View File

@ -6,12 +6,14 @@ import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.legacy.util.JsonUtils import im.vector.matrix.android.internal.legacy.util.JsonUtils


typealias Content = Map<String, Any>

@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Event( data class Event(
@Json(name = "type") val type: String, @Json(name = "type") val type: String,
@Json(name = "event_id") val eventId: String?, @Json(name = "event_id") val eventId: String?,
@Json(name = "content") val content: Map<String, Any>? = null, @Json(name = "content") val content: Content? = null,
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null, @Json(name = "prev_content") val prevContent: Content? = null,
@Json(name = "origin_server_ts") val originServerTs: Long? = null, @Json(name = "origin_server_ts") val originServerTs: Long? = null,
@Json(name = "sender") val sender: String? = null, @Json(name = "sender") val sender: String? = null,
@Json(name = "state_key") val stateKey: String? = null, @Json(name = "state_key") val stateKey: String? = null,
@ -39,7 +41,7 @@ data class Event(
return toModel(prevContent) return toModel(prevContent)
} }


inline fun <reified T> toModel(data: Map<String, Any>?): T? { inline fun <reified T> toModel(data: Content?): T? {
val moshi = MoshiProvider.providesMoshi() val moshi = MoshiProvider.providesMoshi()
val moshiAdapter = moshi.adapter(T::class.java) val moshiAdapter = moshi.adapter(T::class.java)
return moshiAdapter.fromJsonValue(data) return moshiAdapter.fromJsonValue(data)

View File

@ -5,7 +5,7 @@ import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable


interface Room : TimelineHolder { interface Room : TimelineHolder, SendService {


val roomId: String val roomId: String



View File

@ -0,0 +1,10 @@
package im.vector.matrix.android.api.session.room

import im.vector.matrix.android.api.util.Cancelable

interface SendService {

fun sendTextMessage(text: String): Cancelable


}

View File

@ -7,6 +7,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.MyMembership
@ -28,11 +29,11 @@ internal data class DefaultRoom(
override val myMembership: MyMembership override val myMembership: MyMembership
) : Room, KoinComponent { ) : Room, KoinComponent {



private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>() private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>()
private val syncTokenStore by inject<SyncTokenStore>() private val syncTokenStore by inject<SyncTokenStore>()
private val monarchy by inject<Monarchy>() private val monarchy by inject<Monarchy>()
private val timelineHolder by inject<TimelineHolder>(parameters = { parametersOf(roomId) }) private val timelineHolder by inject<TimelineHolder>(parameters = { parametersOf(roomId) })
private val sendService by inject<SendService>(parameters = { parametersOf(roomId) })


override val roomSummary: LiveData<RoomSummary> by lazy { override val roomSummary: LiveData<RoomSummary> by lazy {
val liveData = monarchy val liveData = monarchy
@ -66,4 +67,8 @@ internal data class DefaultRoom(
} }




override fun sendTextMessage(text: String): Cancelable {
return sendService.sendTextMessage(text)
}

} }

View File

@ -1,12 +1,14 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room


import im.vector.matrix.android.api.session.room.model.MessageContent
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
import kotlinx.coroutines.Deferred
import retrofit2.Call import retrofit2.Call
import retrofit2.Response import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.PUT
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query


@ -46,4 +48,20 @@ internal interface RoomAPI {
): Call<RoomMembersResponse> ): Call<RoomMembersResponse>




/**
* Send an event to a room.
*
* @param txId the transaction Id
* @param roomId the room id
* @param eventType the event type
* @param content the event content
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}")
fun send(@Path("txId") txId: String,
@Path("roomId") roomId: String,
@Path("eventType") eventType: String,
@Body content: MessageContent
): Call<SendResponse>


} }

View File

@ -1,12 +1,13 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room


import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder
import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import org.koin.core.parameter.parametersOf
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module import org.koin.dsl.module.Module
import org.koin.dsl.module.module import org.koin.dsl.module.module
@ -31,14 +32,16 @@ class RoomModule : Module {
PaginationRequest(get(), get(), get(), get()) PaginationRequest(get(), get(), get(), get())
} }



factory { factory {
val roomId: String = it[0] val roomId: String = it[0]
TimelineBoundaryCallback(roomId, get(), get(), Executors.newSingleThreadExecutor()) val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), Executors.newSingleThreadExecutor())
DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback) as TimelineHolder
} }


factory { factory {
val roomId: String = it[0] val roomId: String = it[0]
DefaultTimelineHolder(roomId, get(), get(parameters = { parametersOf(roomId) })) as TimelineHolder DefaultSendService(roomId) as SendService
} }


}.invoke() }.invoke()

View File

@ -0,0 +1,45 @@
package im.vector.matrix.android.internal.session.room.send

import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.util.CancelableWork
import java.util.concurrent.TimeUnit

private const val SEND_WORK_NAME = "SEND_WORK_NAME"

internal class DefaultSendService(private val roomId: String) : SendService {

private val sendConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

override fun sendTextMessage(text: String): Cancelable {

val data = mapOf(
"roomId" to roomId,
"text" to text
)
val workData = Data.Builder().putAll(data).build()

val sendWork = OneTimeWorkRequestBuilder<SendContentWorker>()
.setConstraints(sendConstraints)
.setInputData(workData)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.build()

val work = WorkManager.getInstance()
.beginUniqueWork(SEND_WORK_NAME, ExistingWorkPolicy.APPEND, sendWork)
.enqueue()

return CancelableWork(work)

}

}

View File

@ -0,0 +1,37 @@
package im.vector.matrix.android.internal.session.room.send

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.MessageContent
import im.vector.matrix.android.api.session.room.model.MessageType
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject

internal class SendContentWorker(context: Context, params: WorkerParameters)
: Worker(context, params), KoinComponent {

private val roomAPI by inject<RoomAPI>()

override fun doWork(): Result {

val roomId = inputData.getString("roomId")
val text = inputData.getString("text")

val fakeId = roomId + "-" + System.currentTimeMillis()

if (roomId == null || text == null) {
return Result.FAILURE
}

val result = executeRequest<SendResponse> {
apiCall = roomAPI.send(fakeId, roomId, EventType.MESSAGE, MessageContent(MessageType.MSGTYPE_TEXT, text))
}
return result.fold({ Result.RETRY }, { Result.SUCCESS })
}


}

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.internal.session.room.send

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
internal data class SendResponse(
@Json(name = "event_id") val eventId: String
)

View File

@ -0,0 +1,13 @@
package im.vector.matrix.android.internal.util

import com.google.common.util.concurrent.ListenableFuture
import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.Job

internal class CancelableWork(private val work: ListenableFuture<Void>) : Cancelable {

override fun cancel() {
work.cancel(true)
}

}