diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index de59e1fa..e57bc19d 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index bb4d617f..b9211003 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -50,6 +50,13 @@ class RoomDetailFragment : RiotFragment() { room.loadRoomMembersIfNeeded() room.liveTimeline().observe(this, Observer { renderEvents(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() { diff --git a/app/src/main/res/drawable-hdpi/ic_send_white.png b/app/src/main/res/drawable-hdpi/ic_send_white.png new file mode 100644 index 00000000..f133cdbe Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_send_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_send_white.png b/app/src/main/res/drawable-mdpi/ic_send_white.png new file mode 100644 index 00000000..34e49af7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_send_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_send_white.png b/app/src/main/res/drawable-xhdpi/ic_send_white.png new file mode 100644 index 00000000..e5f9ba41 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_send_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_send_white.png b/app/src/main/res/drawable-xxhdpi/ic_send_white.png new file mode 100644 index 00000000..0ba718b6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_send_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_send_white.png b/app/src/main/res/drawable-xxxhdpi/ic_send_white.png new file mode 100644 index 00000000..f02b6453 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_send_white.png differ diff --git a/app/src/main/res/layout/fragment_room_detail.xml b/app/src/main/res/layout/fragment_room_detail.xml index 0f22a314..b34ff8ed 100644 --- a/app/src/main/res/layout/fragment_room_detail.xml +++ b/app/src/main/res/layout/fragment_room_detail.xml @@ -73,9 +73,53 @@ android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/composerDivider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbar" /> + + + + + + + + + + \ No newline at end of file diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 5b5d5c7a..8438f451 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -75,6 +75,9 @@ dependencies { // Paging implementation "android.arch.paging:runtime:1.0.1" + // Work + implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha10" + // FP implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-instances-core:$arrow_version" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 2763506b..62a24bc8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -6,12 +6,14 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.legacy.util.JsonUtils +typealias Content = Map + @JsonClass(generateAdapter = true) data class Event( @Json(name = "type") val type: String, @Json(name = "event_id") val eventId: String?, - @Json(name = "content") val content: Map? = null, - @Json(name = "prev_content") val prevContent: Map? = null, + @Json(name = "content") val content: Content? = null, + @Json(name = "prev_content") val prevContent: Content? = null, @Json(name = "origin_server_ts") val originServerTs: Long? = null, @Json(name = "sender") val sender: String? = null, @Json(name = "state_key") val stateKey: String? = null, @@ -39,7 +41,7 @@ data class Event( return toModel(prevContent) } - inline fun toModel(data: Map?): T? { + inline fun toModel(data: Content?): T? { val moshi = MoshiProvider.providesMoshi() val moshiAdapter = moshi.adapter(T::class.java) return moshiAdapter.fromJsonValue(data) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index ac58a11b..412131f1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -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.util.Cancelable -interface Room : TimelineHolder { +interface Room : TimelineHolder, SendService { val roomId: String diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt new file mode 100644 index 00000000..d4b085cb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt @@ -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 + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index c8bb8881..1388c141 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -7,6 +7,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback 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.SendService 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.MyMembership @@ -28,11 +29,11 @@ internal data class DefaultRoom( override val myMembership: MyMembership ) : Room, KoinComponent { - private val loadRoomMembersRequest by inject() private val syncTokenStore by inject() private val monarchy by inject() private val timelineHolder by inject(parameters = { parametersOf(roomId) }) + private val sendService by inject(parameters = { parametersOf(roomId) }) override val roomSummary: LiveData by lazy { val liveData = monarchy @@ -66,4 +67,8 @@ internal data class DefaultRoom( } + override fun sendTextMessage(text: String): Cancelable { + return sendService.sendTextMessage(text) + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 9b3d677e..655cd33d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -1,12 +1,14 @@ 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.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 kotlinx.coroutines.Deferred import retrofit2.Call -import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.PUT import retrofit2.http.Path import retrofit2.http.Query @@ -46,4 +48,20 @@ internal interface RoomAPI { ): Call + /** + * 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 + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 6a5538b5..bfd9052e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -1,12 +1,13 @@ 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.internal.session.DefaultSession 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.PaginationRequest 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.module.Module import org.koin.dsl.module.module @@ -31,14 +32,16 @@ class RoomModule : Module { PaginationRequest(get(), get(), get(), get()) } + factory { 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 { val roomId: String = it[0] - DefaultTimelineHolder(roomId, get(), get(parameters = { parametersOf(roomId) })) as TimelineHolder + DefaultSendService(roomId) as SendService } }.invoke() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt new file mode 100644 index 00000000..347445ac --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -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() + .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) + + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendContentWorker.kt new file mode 100644 index 00000000..1f972dfe --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendContentWorker.kt @@ -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() + + 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 { + apiCall = roomAPI.send(fakeId, roomId, EventType.MESSAGE, MessageContent(MessageType.MSGTYPE_TEXT, text)) + } + return result.fold({ Result.RETRY }, { Result.SUCCESS }) + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendResponse.kt new file mode 100644 index 00000000..1f285797 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendResponse.kt @@ -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 +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt new file mode 100644 index 00000000..d78f2045 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt @@ -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) : Cancelable { + + override fun cancel() { + work.cancel(true) + } + +} \ No newline at end of file