mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-02 20:26:47 +01:00
Merge pull request #437 from vector-im/feature/create_direct_room
Feature/create direct room
This commit is contained in:
commit
ab25980c4e
@ -2,7 +2,7 @@ Changes in RiotX 0.3.0 (2019-XX-XX)
|
|||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
-
|
- Create Direct Room flow
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
- UI for pending edits (#193)
|
- UI for pending edits (#193)
|
||||||
|
@ -38,6 +38,8 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
// Paging
|
||||||
|
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.MainThreadDisposable
|
import io.reactivex.android.MainThreadDisposable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
private class LiveDataObservable<T>(
|
private class LiveDataObservable<T>(
|
||||||
private val liveData: LiveData<T>,
|
private val liveData: LiveData<T>,
|
||||||
@ -57,5 +59,5 @@ private class LiveDataObservable<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||||
return LiveDataObservable(this)
|
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||||
}
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import io.reactivex.CompletableEmitter
|
||||||
|
import io.reactivex.SingleEmitter
|
||||||
|
|
||||||
|
internal class MatrixCallbackCompletable<T>(private val completableEmitter: CompletableEmitter) : MatrixCallback<T> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: T) {
|
||||||
|
completableEmitter.onComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
completableEmitter.onError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Cancelable.toCompletable(completableEmitter: CompletableEmitter) {
|
||||||
|
completableEmitter.setCancellable {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import io.reactivex.SingleEmitter
|
||||||
|
|
||||||
|
internal class MatrixCallbackSingle<T>(private val singleEmitter: SingleEmitter<T>) : MatrixCallback<T> {
|
||||||
|
|
||||||
|
override fun onSuccess(data: T) {
|
||||||
|
singleEmitter.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
singleEmitter.onError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Cancelable.toSingle(singleEmitter: SingleEmitter<T>) {
|
||||||
|
singleEmitter.setCancellable {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
}
|
@ -21,24 +21,28 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
|||||||
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.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.Single
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||||
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
|
return room.liveRoomSummary().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
|
return room.getRoomMemberIdsLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||||
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation())
|
return room.getEventSummaryLive(eventId).asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||||
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation())
|
return room.liveTimeLineEvent(eventId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadRoomMembersIfNeeded(): Single<Boolean> = Single.create {
|
||||||
|
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,30 +16,51 @@
|
|||||||
|
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import androidx.paging.PagedList
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
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.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.Single
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||||
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
|
return session.liveRoomSummaries().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||||
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
|
return session.liveGroupSummaries().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
return session.syncState().asObservable().observeOn(Schedulers.computation())
|
return session.syncState().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): Observable<List<Pusher>> {
|
fun livePushers(): Observable<List<Pusher>> {
|
||||||
return session.livePushers().asObservable().observeOn(Schedulers.computation())
|
return session.livePushers().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveUsers(): Observable<List<User>> {
|
||||||
|
return session.liveUsers().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||||
|
return session.livePagedUsers(filter).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
|
||||||
|
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchUsersDirectory(search: String,
|
||||||
|
limit: Int,
|
||||||
|
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
|
||||||
|
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ object MatrixPatterns {
|
|||||||
// regex pattern to find matrix user ids in a string.
|
// regex pattern to find matrix user ids in a string.
|
||||||
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
||||||
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
||||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// regex pattern to find room ids in a string.
|
// regex pattern to find room ids in a string.
|
||||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.pushrules
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
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
|
||||||
|
|
||||||
interface PushRuleService {
|
interface PushRuleService {
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ interface PushRuleService {
|
|||||||
|
|
||||||
//TODO update rule
|
//TODO update rule
|
||||||
|
|
||||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>)
|
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun addPushRuleListener(listener: PushRuleListener)
|
fun addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
|
@ -30,20 +30,17 @@ interface RoomDirectoryService {
|
|||||||
/**
|
/**
|
||||||
* Get rooms from directory
|
* Get rooms from directory
|
||||||
*/
|
*/
|
||||||
fun getPublicRooms(server: String?,
|
fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||||
publicRoomsParams: PublicRoomsParams,
|
|
||||||
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join a room by id
|
* Join a room by id
|
||||||
*/
|
*/
|
||||||
fun joinRoom(roomId: String,
|
fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the overall metadata about protocols supported by the homeserver.
|
* Fetches the overall metadata about protocols supported by the homeserver.
|
||||||
* Includes both the available protocols and all fields required for queries against each protocol.
|
* Includes both the available protocols and all fields required for queries against each protocol.
|
||||||
*/
|
*/
|
||||||
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>)
|
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
|
||||||
|
|
||||||
}
|
}
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
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.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||||
@ -27,10 +28,9 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
|||||||
interface RoomService {
|
interface RoomService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a room
|
* Create a room asynchronously
|
||||||
*/
|
*/
|
||||||
fun createRoom(createRoomParams: CreateRoomParams,
|
fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
|
||||||
callback: MatrixCallback<String>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a room from a roomId
|
* Get a room from a roomId
|
||||||
|
@ -30,7 +30,7 @@ interface MembershipService {
|
|||||||
* This methods load all room members if it was done yet.
|
* This methods load all room members if it was done yet.
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun loadRoomMembersIfNeeded(): Cancelable
|
fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Boolean>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the roomMember with userId or null.
|
* Return the roomMember with userId or null.
|
||||||
@ -52,16 +52,16 @@ interface MembershipService {
|
|||||||
/**
|
/**
|
||||||
* Invite a user in the room
|
* Invite a user in the room
|
||||||
*/
|
*/
|
||||||
fun invite(userId: String, callback: MatrixCallback<Unit>)
|
fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the room, or accept an invitation.
|
* Join the room, or accept an invitation.
|
||||||
*/
|
*/
|
||||||
fun join(callback: MatrixCallback<Unit>)
|
fun join(callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leave the room, or reject an invitation.
|
* Leave the room, or reject an invitation.
|
||||||
*/
|
*/
|
||||||
fun leave(callback: MatrixCallback<Unit>)
|
fun leave(callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
}
|
}
|
@ -17,7 +17,10 @@
|
|||||||
package im.vector.matrix.android.api.session.user
|
package im.vector.matrix.android.api.session.user
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get users. It's implemented at the session level.
|
* This interface defines methods to get users. It's implemented at the session level.
|
||||||
@ -31,11 +34,34 @@ interface UserService {
|
|||||||
*/
|
*/
|
||||||
fun getUser(userId: String): User?
|
fun getUser(userId: String): User?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search list of users on server directory.
|
||||||
|
* @param search the searched term
|
||||||
|
* @param limit the max number of users to return
|
||||||
|
* @param excludedUserIds the user ids to filter from the search
|
||||||
|
* @param callback the async callback
|
||||||
|
* @return Cancelable
|
||||||
|
*/
|
||||||
|
fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>, callback: MatrixCallback<List<User>>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe a live user from a userId
|
* Observe a live user from a userId
|
||||||
* @param userId the userId to look for.
|
* @param userId the userId to look for.
|
||||||
* @return a Livedata of user with userId
|
* @return a Livedata of user with userId
|
||||||
*/
|
*/
|
||||||
fun observeUser(userId: String): LiveData<User?>
|
fun liveUser(userId: String): LiveData<User?>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe a live list of users sorted alphabetically
|
||||||
|
* @return a Livedata of users
|
||||||
|
*/
|
||||||
|
fun liveUsers(): LiveData<List<User>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
|
* @return a Livedata of users
|
||||||
|
*/
|
||||||
|
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
|
||||||
|
|
||||||
}
|
}
|
@ -19,14 +19,17 @@ package im.vector.matrix.android.internal.database
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import io.realm.*
|
import io.realm.*
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val THREAD_NAME = "REALM_QUERY_LATCH"
|
private const val THREAD_NAME = "REALM_QUERY_LATCH"
|
||||||
|
|
||||||
class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
|
class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
|
||||||
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {
|
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {
|
||||||
|
|
||||||
fun await() {
|
@Throws(InterruptedException::class)
|
||||||
|
fun await(timeout: Long = Long.MAX_VALUE, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) {
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val handlerThread = HandlerThread(THREAD_NAME + hashCode())
|
val handlerThread = HandlerThread(THREAD_NAME + hashCode())
|
||||||
handlerThread.start()
|
handlerThread.start()
|
||||||
@ -46,8 +49,13 @@ class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConf
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
handler.post(runnable)
|
handler.post(runnable)
|
||||||
latch.await()
|
try {
|
||||||
handlerThread.quit()
|
latch.await(timeout, timeUnit)
|
||||||
|
} catch (exception: InterruptedException) {
|
||||||
|
throw exception
|
||||||
|
} finally {
|
||||||
|
handlerThread.quit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||||||
var joinedMembersCount: Int? = 0,
|
var joinedMembersCount: Int? = 0,
|
||||||
var invitedMembersCount: Int? = 0,
|
var invitedMembersCount: Int? = 0,
|
||||||
var isDirect: Boolean = false,
|
var isDirect: Boolean = false,
|
||||||
|
var directUserId: String? = null,
|
||||||
var otherMemberIds: RealmList<String> = RealmList(),
|
var otherMemberIds: RealmList<String> = RealmList(),
|
||||||
var notificationCount: Int = 0,
|
var notificationCount: Int = 0,
|
||||||
var highlightCount: Int = 0,
|
var highlightCount: Int = 0,
|
||||||
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
|||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.RealmResults
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
|
internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
|
||||||
@ -29,3 +30,20 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n
|
|||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> {
|
||||||
|
return RoomSummaryEntity.where(realm)
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
.findAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun RoomSummaryEntity.Companion.isDirect(realm: Realm, roomId: String): Boolean {
|
||||||
|
return RoomSummaryEntity.where(realm)
|
||||||
|
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
.findAll()
|
||||||
|
.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,11 +65,12 @@ internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm:
|
|||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
includesSending: Boolean,
|
||||||
includedTypes: List<String> = emptyList(),
|
includedTypes: List<String> = emptyList(),
|
||||||
excludedTypes: List<String> = emptyList()): TimelineEventEntity? {
|
excludedTypes: List<String> = emptyList()): TimelineEventEntity? {
|
||||||
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
||||||
val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) {
|
val eventList = if (includesSending && roomEntity.sendingTimelineEvents.isNotEmpty()) {
|
||||||
roomEntity.sendingTimelineEvents
|
roomEntity.sendingTimelineEvents
|
||||||
} else {
|
} else {
|
||||||
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents
|
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents
|
||||||
|
@ -19,11 +19,11 @@ package im.vector.matrix.android.internal.session
|
|||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoModule
|
import im.vector.matrix.android.internal.crypto.CryptoModule
|
||||||
import im.vector.matrix.android.internal.di.MatrixComponent
|
import im.vector.matrix.android.internal.di.MatrixComponent
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
||||||
import im.vector.matrix.android.internal.session.cache.CacheModule
|
import im.vector.matrix.android.internal.session.cache.CacheModule
|
||||||
import im.vector.matrix.android.internal.session.content.ContentModule
|
import im.vector.matrix.android.internal.session.content.ContentModule
|
||||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||||
@ -46,20 +46,21 @@ import im.vector.matrix.android.internal.session.user.UserModule
|
|||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
|
||||||
@Component(dependencies = [MatrixComponent::class],
|
@Component(dependencies = [MatrixComponent::class],
|
||||||
modules = [
|
modules = [
|
||||||
SessionModule::class,
|
SessionModule::class,
|
||||||
RoomModule::class,
|
RoomModule::class,
|
||||||
SyncModule::class,
|
SyncModule::class,
|
||||||
SignOutModule::class,
|
SignOutModule::class,
|
||||||
GroupModule::class,
|
GroupModule::class,
|
||||||
UserModule::class,
|
UserModule::class,
|
||||||
FilterModule::class,
|
FilterModule::class,
|
||||||
GroupModule::class,
|
GroupModule::class,
|
||||||
ContentModule::class,
|
ContentModule::class,
|
||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
PushersModule::class
|
PushersModule::class,
|
||||||
]
|
AccountDataModule::class
|
||||||
|
]
|
||||||
)
|
)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal interface SessionComponent {
|
internal interface SessionComponent {
|
||||||
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.pushrules.Action
|
|||||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
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.internal.database.mapper.PushRulesMapper
|
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
|
||||||
import im.vector.matrix.android.internal.database.model.PushRulesEntity
|
import im.vector.matrix.android.internal.database.model.PushRulesEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -80,8 +81,8 @@ internal class DefaultPushRuleService @Inject constructor(
|
|||||||
return contentRules + overrideRules + roomRules + senderRules + underrideRules
|
return contentRules + overrideRules + roomRules + senderRules + underrideRules
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>) {
|
override fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
updatePushRuleEnableStatusTask
|
return updatePushRuleEnableStatusTask
|
||||||
.configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled))
|
.configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled))
|
||||||
// TODO Fetch the rules
|
// TODO Fetch the rules
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
|
@ -44,15 +44,15 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinRoom(roomId: String, callback: MatrixCallback<Unit>) {
|
override fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
joinRoomTask
|
return joinRoomTask
|
||||||
.configureWith(JoinRoomTask.Params(roomId))
|
.configureWith(JoinRoomTask.Params(roomId))
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>) {
|
override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable {
|
||||||
getThirdPartyProtocolsTask
|
return getThirdPartyProtocolsTask
|
||||||
.toConfigurableTask()
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.room.Room
|
|||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
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.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
@ -40,8 +41,8 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
|||||||
private val roomFactory: RoomFactory,
|
private val roomFactory: RoomFactory,
|
||||||
private val taskExecutor: TaskExecutor) : RoomService {
|
private val taskExecutor: TaskExecutor) : RoomService {
|
||||||
|
|
||||||
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>) {
|
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
|
||||||
createRoomTask
|
return createRoomTask
|
||||||
.configureWith(createRoomParams)
|
.configureWith(createRoomParams)
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
@ -62,7 +62,9 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
membership: Membership? = null,
|
membership: Membership? = null,
|
||||||
roomSummary: RoomSyncSummary? = null,
|
roomSummary: RoomSyncSummary? = null,
|
||||||
unreadNotifications: RoomSyncUnreadNotifications? = null) {
|
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
||||||
|
isDirect: Boolean? = null,
|
||||||
|
directUserId: String? = null) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
|
|
||||||
@ -85,7 +87,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
|
|||||||
roomSummaryEntity.membership = membership
|
roomSummaryEntity.membership = membership
|
||||||
}
|
}
|
||||||
|
|
||||||
val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
|
val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, includedTypes = PREVIEWABLE_TYPES)
|
||||||
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
|
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
|
||||||
|
|
||||||
val otherRoomMembers = RoomMembers(realm, roomId)
|
val otherRoomMembers = RoomMembers(realm, roomId)
|
||||||
@ -95,6 +97,10 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
|
|||||||
.asSequence()
|
.asSequence()
|
||||||
.map { it.stateKey }
|
.map { it.stateKey }
|
||||||
|
|
||||||
|
if (isDirect != null) {
|
||||||
|
roomSummaryEntity.isDirect = isDirect
|
||||||
|
roomSummaryEntity.directUserId = directUserId
|
||||||
|
}
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
||||||
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
|
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
|
||||||
|
@ -17,22 +17,33 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.create
|
package im.vector.matrix.android.internal.session.room.create
|
||||||
|
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
||||||
import im.vector.matrix.android.internal.database.RealmQueryLatch
|
import im.vector.matrix.android.internal.database.RealmQueryLatch
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
|
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface CreateRoomTask : Task<CreateRoomParams, String>
|
internal interface CreateRoomTask : Task<CreateRoomParams, String>
|
||||||
|
|
||||||
internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: RoomAPI,
|
internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: RoomAPI,
|
||||||
|
private val monarchy: Monarchy,
|
||||||
|
private val directChatsHelper: DirectChatsHelper,
|
||||||
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
|
private val readMarkersTask: SetReadMarkersTask,
|
||||||
@SessionDatabase private val realmConfiguration: RealmConfiguration) : CreateRoomTask {
|
@SessionDatabase private val realmConfiguration: RealmConfiguration) : CreateRoomTask {
|
||||||
|
|
||||||
|
|
||||||
@ -41,17 +52,50 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
|
|||||||
apiCall = roomAPI.createRoom(params)
|
apiCall = roomAPI.createRoom(params)
|
||||||
}.flatMap { createRoomResponse ->
|
}.flatMap { createRoomResponse ->
|
||||||
val roomId = createRoomResponse.roomId!!
|
val roomId = createRoomResponse.roomId!!
|
||||||
|
|
||||||
// TODO Maybe do the same code for join room request ?
|
|
||||||
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
||||||
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
||||||
realm.where(RoomEntity::class.java)
|
realm.where(RoomEntity::class.java)
|
||||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||||
}
|
}
|
||||||
|
Try {
|
||||||
rql.await()
|
rql.await(timeout = 20L, timeUnit = TimeUnit.SECONDS)
|
||||||
|
roomId
|
||||||
return Try.just(roomId)
|
}
|
||||||
|
}.flatMap { roomId ->
|
||||||
|
if (params.isDirect()) {
|
||||||
|
handleDirectChatCreation(params, roomId)
|
||||||
|
} else {
|
||||||
|
Try.just(roomId)
|
||||||
|
}
|
||||||
|
}.flatMap { roomId ->
|
||||||
|
setReadMarkers(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String): Try<String> {
|
||||||
|
val otherUserId = params.getFirstInvitedUserId()
|
||||||
|
?: return Try.raise(IllegalStateException("You can't create a direct room without an invitedUser"))
|
||||||
|
|
||||||
|
return monarchy.tryTransactionSync { realm ->
|
||||||
|
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
|
||||||
|
this.directUserId = otherUserId
|
||||||
|
this.isDirect = true
|
||||||
|
}
|
||||||
|
}.flatMap {
|
||||||
|
val directChats = directChatsHelper.getDirectChats()
|
||||||
|
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
|
||||||
|
}.flatMap {
|
||||||
|
Try.just(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun setReadMarkers(roomId: String): Try<String> {
|
||||||
|
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
|
||||||
|
return readMarkersTask
|
||||||
|
.execute(setReadMarkerParams)
|
||||||
|
.flatMap {
|
||||||
|
Try.just(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,11 @@ internal class DefaultMembershipService @Inject constructor(private val roomId:
|
|||||||
private val leaveRoomTask: LeaveRoomTask
|
private val leaveRoomTask: LeaveRoomTask
|
||||||
) : MembershipService {
|
) : MembershipService {
|
||||||
|
|
||||||
override fun loadRoomMembersIfNeeded(): Cancelable {
|
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Boolean>): Cancelable {
|
||||||
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
|
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
|
||||||
return loadRoomMembersTask.configureWith(params).executeBy(taskExecutor)
|
return loadRoomMembersTask.configureWith(params)
|
||||||
|
.dispatchTo(matrixCallback)
|
||||||
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomMember(userId: String): RoomMember? {
|
override fun getRoomMember(userId: String): RoomMember? {
|
||||||
@ -73,23 +75,23 @@ internal class DefaultMembershipService @Inject constructor(private val roomId:
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invite(userId: String, callback: MatrixCallback<Unit>) {
|
override fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = InviteTask.Params(roomId, userId)
|
val params = InviteTask.Params(roomId, userId)
|
||||||
inviteTask.configureWith(params)
|
return inviteTask.configureWith(params)
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun join(callback: MatrixCallback<Unit>) {
|
override fun join(callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = JoinRoomTask.Params(roomId)
|
val params = JoinRoomTask.Params(roomId)
|
||||||
joinTask.configureWith(params)
|
return joinTask.configureWith(params)
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun leave(callback: MatrixCallback<Unit>) {
|
override fun leave(callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = LeaveRoomTask.Params(roomId)
|
val params = LeaveRoomTask.Params(roomId)
|
||||||
leaveRoomTask.configureWith(params)
|
return leaveRoomTask.configureWith(params)
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
|
|||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import io.realm.RealmResults
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,10 +80,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||||||
|
|
||||||
val roomMembers = RoomMembers(realm, roomId)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
val loadedMembers = roomMembers.queryRoomMembersEvent().findAll()
|
val loadedMembers = roomMembers.queryRoomMembersEvent().findAll()
|
||||||
val otherMembersSubset = loadedMembers.where()
|
|
||||||
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
|
|
||||||
.limit(3)
|
|
||||||
.findAll()
|
|
||||||
|
|
||||||
if (roomEntity?.membership == Membership.INVITE) {
|
if (roomEntity?.membership == Membership.INVITE) {
|
||||||
val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst()
|
val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst()
|
||||||
@ -97,23 +93,29 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||||||
} else {
|
} else {
|
||||||
context.getString(R.string.room_displayname_room_invite)
|
context.getString(R.string.room_displayname_room_invite)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (roomEntity?.membership == Membership.JOIN) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
val memberIds: List<String> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
val otherMembersSubset: List<EventEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
||||||
roomSummary.heroes
|
roomSummary.heroes.mapNotNull {
|
||||||
|
roomMembers.getStateEvent(it)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
otherMembersSubset.mapNotNull { it.stateKey }
|
loadedMembers.where()
|
||||||
|
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
|
||||||
|
.limit(3)
|
||||||
|
.findAll()
|
||||||
}
|
}
|
||||||
name = when (memberIds.size) {
|
val otherMembersCount = roomMembers.getNumberOfMembers() - 1
|
||||||
|
name = when (otherMembersCount) {
|
||||||
0 -> context.getString(R.string.room_displayname_empty_room)
|
0 -> context.getString(R.string.room_displayname_empty_room)
|
||||||
1 -> resolveRoomMember(otherMembersSubset[0], roomMembers)
|
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
||||||
2 -> context.getString(R.string.room_displayname_two_members,
|
2 -> context.getString(R.string.room_displayname_two_members,
|
||||||
resolveRoomMember(otherMembersSubset[0], roomMembers),
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
resolveRoomMember(otherMembersSubset[1], roomMembers)
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
|
||||||
)
|
)
|
||||||
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
|
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1,
|
roomMembers.getNumberOfJoinedMembers() - 1,
|
||||||
resolveRoomMember(otherMembersSubset[0], roomMembers),
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1)
|
roomMembers.getNumberOfJoinedMembers() - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,8 +124,8 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||||||
return name ?: roomId
|
return name ?: roomId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveRoomMember(eventEntity: EventEntity?,
|
private fun resolveRoomMemberName(eventEntity: EventEntity?,
|
||||||
roomMembers: RoomMembers): String? {
|
roomMembers: RoomMembers): String? {
|
||||||
if (eventEntity == null) return null
|
if (eventEntity == null) return null
|
||||||
val roomMember = eventEntity.toRoomMember() ?: return null
|
val roomMember = eventEntity.toRoomMember() ?: return null
|
||||||
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
|
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
|
||||||
|
@ -42,12 +42,16 @@ internal class RoomMembers(private val realm: Realm,
|
|||||||
RoomSummaryEntity.where(realm, roomId).findFirst()
|
RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(userId: String): RoomMember? {
|
fun getStateEvent(userId: String): EventEntity? {
|
||||||
return EventEntity
|
return EventEntity
|
||||||
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
||||||
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||||
.equalTo(EventEntityFields.STATE_KEY, userId)
|
.equalTo(EventEntityFields.STATE_KEY, userId)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(userId: String): RoomMember? {
|
||||||
|
return getStateEvent(userId)
|
||||||
?.let {
|
?.let {
|
||||||
it.asDomain().content?.toModel<RoomMember>()
|
it.asDomain().content?.toModel<RoomMember>()
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,16 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.membership.joining
|
package im.vector.matrix.android.internal.session.room.membership.joining
|
||||||
|
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.internal.database.RealmQueryLatch
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
||||||
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
|
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
||||||
@ -29,12 +35,32 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI) : JoinRoomTask {
|
internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI,
|
||||||
|
private val readMarkersTask: SetReadMarkersTask,
|
||||||
|
@SessionDatabase private val realmConfiguration: RealmConfiguration) : JoinRoomTask {
|
||||||
|
|
||||||
override suspend fun execute(params: JoinRoomTask.Params): Try<Unit> {
|
override suspend fun execute(params: JoinRoomTask.Params): Try<Unit> {
|
||||||
return executeRequest {
|
return executeRequest<Unit> {
|
||||||
apiCall = roomAPI.join(params.roomId, HashMap())
|
apiCall = roomAPI.join(params.roomId, HashMap())
|
||||||
|
}.flatMap {
|
||||||
|
val roomId = params.roomId
|
||||||
|
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
||||||
|
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
||||||
|
realm.where(RoomEntity::class.java)
|
||||||
|
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||||
|
}
|
||||||
|
Try {
|
||||||
|
rql.await(20L, TimeUnit.SECONDS)
|
||||||
|
roomId
|
||||||
|
}
|
||||||
|
}.flatMap { roomId ->
|
||||||
|
setReadMarkers(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun setReadMarkers(roomId: String): Try<Unit> {
|
||||||
|
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
|
||||||
|
return readMarkersTask.execute(setReadMarkerParams)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,11 @@ import im.vector.matrix.android.api.auth.data.Credentials
|
|||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
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.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultReadService @Inject constructor(private val roomId: String,
|
internal class DefaultReadService @Inject constructor(private val roomId: String,
|
||||||
@ -39,9 +36,7 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||||||
private val credentials: Credentials) : ReadService {
|
private val credentials: Credentials) : ReadService {
|
||||||
|
|
||||||
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
||||||
//TODO shouldn't it be latest synced event?
|
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
|
||||||
val latestEvent = getLatestEvent()
|
|
||||||
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId)
|
|
||||||
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
|
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,9 +50,6 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||||||
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
|
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLatestEvent(): TimelineEventEntity? {
|
|
||||||
return monarchy.fetchCopied { TimelineEventEntity.latestEvent(it, roomId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEventRead(eventId: String): Boolean {
|
override fun isEventRead(eventId: String): Boolean {
|
||||||
var isEventRead = false
|
var isEventRead = false
|
||||||
|
@ -20,7 +20,6 @@ import arrow.core.Try
|
|||||||
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.auth.data.Credentials
|
||||||
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.model.EventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
@ -33,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
|
|||||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
||||||
|
import io.realm.Realm
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -40,8 +40,9 @@ internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> {
|
|||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val fullyReadEventId: String?,
|
val markAllAsRead: Boolean = false,
|
||||||
val readReceiptEventId: String?
|
val fullyReadEventId: String? = null,
|
||||||
|
val readReceiptEventId: String? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,21 +56,35 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||||||
|
|
||||||
override suspend fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
|
override suspend fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
|
||||||
val markers = HashMap<String, String>()
|
val markers = HashMap<String, String>()
|
||||||
if (params.fullyReadEventId != null) {
|
val fullyReadEventId: String?
|
||||||
if (LocalEchoEventFactory.isLocalEchoId(params.fullyReadEventId)) {
|
val readReceiptEventId: String?
|
||||||
|
|
||||||
|
if (params.markAllAsRead) {
|
||||||
|
val latestSyncedEventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
|
TimelineEventEntity.latestEvent(realm, roomId = params.roomId, includesSending = false)?.eventId
|
||||||
|
}
|
||||||
|
fullyReadEventId = latestSyncedEventId
|
||||||
|
readReceiptEventId = latestSyncedEventId
|
||||||
|
} else {
|
||||||
|
fullyReadEventId = params.fullyReadEventId
|
||||||
|
readReceiptEventId = params.readReceiptEventId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullyReadEventId != null) {
|
||||||
|
if (LocalEchoEventFactory.isLocalEchoId(fullyReadEventId)) {
|
||||||
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
|
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
|
||||||
} else {
|
} else {
|
||||||
markers[READ_MARKER] = params.fullyReadEventId
|
markers[READ_MARKER] = fullyReadEventId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (params.readReceiptEventId != null
|
if (readReceiptEventId != null
|
||||||
&& !isEventRead(params.roomId, params.readReceiptEventId)) {
|
&& !isEventRead(params.roomId, readReceiptEventId)) {
|
||||||
|
|
||||||
if (LocalEchoEventFactory.isLocalEchoId(params.readReceiptEventId)) {
|
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
|
||||||
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
|
Timber.w("Can't set read receipt for local event ${params.fullyReadEventId}")
|
||||||
} else {
|
} else {
|
||||||
updateNotificationCountIfNecessary(params.roomId, params.readReceiptEventId)
|
updateNotificationCountIfNecessary(params.roomId, readReceiptEventId)
|
||||||
markers[READ_RECEIPT] = params.readReceiptEventId
|
markers[READ_RECEIPT] = readReceiptEventId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (markers.isEmpty()) {
|
return if (markers.isEmpty()) {
|
||||||
@ -83,10 +98,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||||||
|
|
||||||
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
||||||
monarchy.tryTransactionAsync { realm ->
|
monarchy.tryTransactionAsync { realm ->
|
||||||
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId)?.eventId == eventId
|
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == eventId
|
||||||
if (isLatestReceived) {
|
if (isLatestReceived) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: return@tryTransactionAsync
|
?: return@tryTransactionAsync
|
||||||
roomSummary.notificationCount = 0
|
roomSummary.notificationCount = 0
|
||||||
roomSummary.highlightCount = 0
|
roomSummary.highlightCount = 0
|
||||||
}
|
}
|
||||||
@ -97,13 +112,13 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||||||
var isEventRead = false
|
var isEventRead = false
|
||||||
monarchy.doWithRealm {
|
monarchy.doWithRealm {
|
||||||
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||||
?: Int.MAX_VALUE
|
?: Int.MAX_VALUE
|
||||||
isEventRead = eventToCheckIndex <= readReceiptIndex
|
isEventRead = eventToCheckIndex <= readReceiptIndex
|
||||||
}
|
}
|
||||||
return isEventRead
|
return isEventRead
|
||||||
|
@ -18,27 +18,41 @@ package im.vector.matrix.android.internal.session.sync
|
|||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.R
|
import im.vector.matrix.android.R
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
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.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
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.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.CryptoManager
|
||||||
import im.vector.matrix.android.internal.database.helper.*
|
import im.vector.matrix.android.internal.database.helper.add
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
||||||
|
import im.vector.matrix.android.internal.database.helper.updateSenderDataFor
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
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.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
|
import im.vector.matrix.android.internal.database.query.isDirect
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
import im.vector.matrix.android.internal.session.mapWithProgress
|
import im.vector.matrix.android.internal.session.mapWithProgress
|
||||||
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
|
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
|
||||||
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
|
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||||
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
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.session.sync.model.*
|
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomSync
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncAccountData
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
@ -55,6 +69,9 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
private val tokenStore: SyncTokenStore,
|
private val tokenStore: SyncTokenStore,
|
||||||
private val pushRuleService: DefaultPushRuleService,
|
private val pushRuleService: DefaultPushRuleService,
|
||||||
private val processForPushTask: ProcessEventForPushTask,
|
private val processForPushTask: ProcessEventForPushTask,
|
||||||
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
|
private val credentials: Credentials,
|
||||||
|
private val directChatsHelper: DirectChatsHelper,
|
||||||
private val taskExecutor: TaskExecutor) {
|
private val taskExecutor: TaskExecutor) {
|
||||||
|
|
||||||
sealed class HandlingStrategy {
|
sealed class HandlingStrategy {
|
||||||
@ -118,7 +135,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
Timber.v("Handle join sync for room $roomId")
|
Timber.v("Handle join sync for room $roomId")
|
||||||
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
|
|
||||||
if (roomEntity.membership == Membership.INVITE) {
|
if (roomEntity.membership == Membership.INVITE) {
|
||||||
roomEntity.chunks.deleteAllFromRealm()
|
roomEntity.chunks.deleteAllFromRealm()
|
||||||
@ -128,7 +145,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
// State event
|
// State event
|
||||||
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
||||||
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
|
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val untimelinedStateIndex = minStateIndex + 1
|
val untimelinedStateIndex = minStateIndex + 1
|
||||||
roomSync.state.events.forEach { event ->
|
roomSync.state.events.forEach { event ->
|
||||||
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
||||||
@ -169,13 +186,27 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
InvitedRoomSync): RoomEntity {
|
InvitedRoomSync): RoomEntity {
|
||||||
Timber.v("Handle invited sync for room $roomId")
|
Timber.v("Handle invited sync for room $roomId")
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
roomEntity.membership = Membership.INVITE
|
roomEntity.membership = Membership.INVITE
|
||||||
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
|
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
|
||||||
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
|
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
|
||||||
roomEntity.addOrUpdate(chunkEntity)
|
roomEntity.addOrUpdate(chunkEntity)
|
||||||
}
|
}
|
||||||
roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
|
val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(credentials.userId)
|
||||||
|
val inviterId = myUserStateEvent?.sender
|
||||||
|
val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
|
||||||
|
val isDirect = myUserRoomMember?.isDirect
|
||||||
|
if (isDirect == true && inviterId != null) {
|
||||||
|
val isAlreadyDirect = RoomSummaryEntity.isDirect(realm, roomId)
|
||||||
|
if (!isAlreadyDirect) {
|
||||||
|
val directChatsMap = directChatsHelper.getDirectChats(include = Pair(inviterId, roomId))
|
||||||
|
val updateUserAccountParams = UpdateUserAccountDataTask.DirectChatParams(
|
||||||
|
directMessages = directChatsMap
|
||||||
|
)
|
||||||
|
updateUserAccountDataTask.configureWith(updateUserAccountParams).executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roomSummaryUpdater.update(realm, roomId, Membership.INVITE, isDirect = isDirect, directUserId = inviterId)
|
||||||
return roomEntity
|
return roomEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +214,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
roomSync: RoomSync): RoomEntity {
|
roomSync: RoomSync): RoomEntity {
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
|
|
||||||
roomEntity.membership = Membership.LEAVE
|
roomEntity.membership = Membership.LEAVE
|
||||||
roomEntity.chunks.deleteAllFromRealm()
|
roomEntity.chunks.deleteAllFromRealm()
|
||||||
|
@ -19,8 +19,8 @@ package im.vector.matrix.android.internal.session.sync
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.query.getDirectRooms
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
|
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
|
||||||
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync
|
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -37,19 +37,22 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) {
|
private fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) {
|
||||||
val newDirectRoomIds = directMessages.content.values.flatten()
|
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
|
|
||||||
val oldDirectRooms = RoomSummaryEntity.where(realm)
|
val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
|
||||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
oldDirectRooms.forEach {
|
||||||
.findAll()
|
it.isDirect = false
|
||||||
oldDirectRooms.forEach { it.isDirect = false }
|
it.directUserId = null
|
||||||
|
}
|
||||||
newDirectRoomIds.forEach { roomId ->
|
directMessages.content.forEach {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val userId = it.key
|
||||||
if (roomSummaryEntity != null) {
|
it.value.forEach { roomId ->
|
||||||
roomSummaryEntity.isDirect = true
|
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
realm.insertOrUpdate(roomSummaryEntity)
|
if (roomSummaryEntity != null) {
|
||||||
|
roomSummaryEntity.isDirect = true
|
||||||
|
roomSummaryEntity.directUserId = userId
|
||||||
|
realm.insertOrUpdate(roomSummaryEntity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,18 +18,46 @@ package im.vector.matrix.android.internal.session.user
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
|
import androidx.paging.DataSource
|
||||||
|
import androidx.paging.LivePagedListBuilder
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveData
|
import im.vector.matrix.android.internal.database.RealmLiveData
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.UserEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy) : UserService {
|
internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy,
|
||||||
|
private val searchUserTask: SearchUserTask,
|
||||||
|
private val taskExecutor: TaskExecutor) : UserService {
|
||||||
|
|
||||||
|
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
|
||||||
|
monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm.where(UserEntity::class.java)
|
||||||
|
.isNotEmpty(UserEntityFields.USER_ID)
|
||||||
|
.sort(UserEntityFields.DISPLAY_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
|
||||||
|
realmDataSourceFactory.map {
|
||||||
|
it.asDomain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
|
||||||
|
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
|
||||||
|
}
|
||||||
|
|
||||||
override fun getUser(userId: String): User? {
|
override fun getUser(userId: String): User? {
|
||||||
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
||||||
@ -38,7 +66,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
|||||||
return userEntity.asDomain()
|
return userEntity.asDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun observeUser(userId: String): LiveData<User?> {
|
override fun liveUser(userId: String): LiveData<User?> {
|
||||||
val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
||||||
UserEntity.where(realm, userId)
|
UserEntity.where(realm, userId)
|
||||||
}
|
}
|
||||||
@ -48,4 +76,45 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
|||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun liveUsers(): LiveData<List<User>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
realm.where(UserEntity::class.java)
|
||||||
|
.isNotEmpty(UserEntityFields.USER_ID)
|
||||||
|
.sort(UserEntityFields.DISPLAY_NAME)
|
||||||
|
},
|
||||||
|
{ it.asDomain() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun livePagedUsers(filter: String?): LiveData<PagedList<User>> {
|
||||||
|
realmDataSourceFactory.updateQuery { realm ->
|
||||||
|
val query = realm.where(UserEntity::class.java)
|
||||||
|
if (filter.isNullOrEmpty()) {
|
||||||
|
query.isNotEmpty(UserEntityFields.USER_ID)
|
||||||
|
} else {
|
||||||
|
query
|
||||||
|
.beginGroup()
|
||||||
|
.contains(UserEntityFields.DISPLAY_NAME, filter)
|
||||||
|
.or()
|
||||||
|
.contains(UserEntityFields.USER_ID, filter)
|
||||||
|
.endGroup()
|
||||||
|
}
|
||||||
|
query.sort(UserEntityFields.DISPLAY_NAME)
|
||||||
|
}
|
||||||
|
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun searchUsersDirectory(search: String,
|
||||||
|
limit: Int,
|
||||||
|
excludedUserIds: Set<String>,
|
||||||
|
callback: MatrixCallback<List<User>>): Cancelable {
|
||||||
|
val params = SearchUserTask.Params(limit, search, excludedUserIds)
|
||||||
|
return searchUserTask
|
||||||
|
.configureWith(params)
|
||||||
|
.dispatchTo(callback)
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.user
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants.URI_API_PREFIX_PATH_R0
|
||||||
|
import im.vector.matrix.android.internal.session.user.model.SearchUsersParams
|
||||||
|
import im.vector.matrix.android.internal.session.user.model.SearchUsersRequestResponse
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
internal interface SearchUserAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a user search.
|
||||||
|
*
|
||||||
|
* @param searchUsersParams the search params.
|
||||||
|
*/
|
||||||
|
@POST(URI_API_PREFIX_PATH_R0 + "user_directory/search")
|
||||||
|
fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersRequestResponse>
|
||||||
|
}
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.user
|
|||||||
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.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||||
|
|
||||||
@ -29,9 +30,13 @@ internal object UserEntityFactory {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val roomMember = event.content.toModel<RoomMember>() ?: return null
|
val roomMember = event.content.toModel<RoomMember>() ?: return null
|
||||||
|
// We only use JOIN and INVITED memberships to create User data
|
||||||
|
if (roomMember.membership != Membership.JOIN && roomMember.membership != Membership.INVITE) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return UserEntity(event.stateKey ?: "",
|
return UserEntity(event.stateKey ?: "",
|
||||||
roomMember.displayName ?: "",
|
roomMember.displayName ?: "",
|
||||||
roomMember.avatarUrl ?: ""
|
roomMember.avatarUrl ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,12 +18,31 @@ package im.vector.matrix.android.internal.session.user
|
|||||||
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.sync.SyncAPI
|
||||||
|
import im.vector.matrix.android.internal.session.user.model.DefaultSearchUserTask
|
||||||
|
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
internal abstract class UserModule {
|
internal abstract class UserModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@Provides
|
||||||
|
@JvmStatic
|
||||||
|
@SessionScope
|
||||||
|
fun providesSearchUserAPI(retrofit: Retrofit): SearchUserAPI {
|
||||||
|
return retrofit.create(SearchUserAPI::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUserService(userService: DefaultUserService): UserService
|
abstract fun bindUserService(userService: DefaultUserService): UserService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSearchUserTask(searchUserTask: DefaultSearchUserTask): SearchUserTask
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.user.accountdata
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.PUT
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
interface AccountDataAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set some account_data for the client.
|
||||||
|
*
|
||||||
|
* @param userId the user id
|
||||||
|
* @param type the type
|
||||||
|
* @param params the put params
|
||||||
|
*/
|
||||||
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}")
|
||||||
|
fun setAccountData(@Path("userId") userId: String, @Path("type") type: String, @Body params: Any): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a bearer token from the homeserver that the user can
|
||||||
|
* present to a third party in order to prove their ownership
|
||||||
|
* of the Matrix account they are logged into.
|
||||||
|
*
|
||||||
|
* @param userId the user id
|
||||||
|
* @param body the body content
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
|
||||||
|
fun openIdToken(@Path("userId") userId: String, @Body body: Map<Any, Any>): Call<Map<Any, Any>>
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.user.accountdata
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class AccountDataModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
fun providesAccountDataAPI(retrofit: Retrofit): AccountDataAPI {
|
||||||
|
return retrofit.create(AccountDataAPI::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAcountDataTask): UpdateUserAccountDataTask
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.user.accountdata
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.getDirectRooms
|
||||||
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class DirectChatsHelper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
|
||||||
|
|
||||||
|
fun getDirectChats(include: Pair<String, String>? = null, filterRoomId: String? = null): Map<String, List<String>> {
|
||||||
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
|
val currentDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
|
||||||
|
val directChatsMap = mutableMapOf<String, MutableList<String>>()
|
||||||
|
for (directRoom in currentDirectRooms) {
|
||||||
|
if (directRoom.roomId == filterRoomId) continue
|
||||||
|
val directUserId = directRoom.directUserId ?: continue
|
||||||
|
directChatsMap.getOrPut(directUserId, { arrayListOf() }).apply {
|
||||||
|
add(directRoom.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (include != null) {
|
||||||
|
directChatsMap.getOrPut(include.first, { arrayListOf() }).apply {
|
||||||
|
if (contains(include.second)) {
|
||||||
|
Timber.v("Direct chats already include room ${include.second} with user ${include.first}")
|
||||||
|
} else {
|
||||||
|
add(include.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
directChatsMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.user.accountdata
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.UserAccountData
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Params, Unit> {
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
val type: String
|
||||||
|
fun getData(): Any
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DirectChatParams(override val type: String = UserAccountData.TYPE_DIRECT_MESSAGES,
|
||||||
|
private val directMessages: Map<String, List<String>>
|
||||||
|
) : Params {
|
||||||
|
|
||||||
|
override fun getData(): Any {
|
||||||
|
return directMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultUpdateUserAcountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
||||||
|
private val credentials: Credentials) : UpdateUserAccountDataTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: UpdateUserAccountDataTask.Params): Try<Unit> {
|
||||||
|
|
||||||
|
return executeRequest {
|
||||||
|
apiCall = accountDataApi.setAccountData(credentials.userId, params.type, params.getData())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.user.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchUser(
|
||||||
|
@Json(name = "user_id") val userId: String,
|
||||||
|
@Json(name = "display_name") val displayName: String? = null,
|
||||||
|
@Json(name = "avatar_url") val avatarUrl: String? = null
|
||||||
|
)
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.user.model
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.user.SearchUserAPI
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SearchUserTask : Task<SearchUserTask.Params, List<User>> {
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val limit: Int,
|
||||||
|
val search: String,
|
||||||
|
val excludedUserIds: Set<String>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSearchUserTask @Inject constructor(private val searchUserAPI: SearchUserAPI) : SearchUserTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: SearchUserTask.Params): Try<List<User>> {
|
||||||
|
return executeRequest<SearchUsersRequestResponse> {
|
||||||
|
apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit))
|
||||||
|
}.map { response ->
|
||||||
|
response.users.map {
|
||||||
|
User(it.userId, it.displayName, it.avatarUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.user.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing an user search parameters
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchUsersParams(
|
||||||
|
// the searched term
|
||||||
|
@Json(name = "search_term") val searchTerm: String,
|
||||||
|
// set a limit to the request response
|
||||||
|
@Json(name = "limit") val limit: Int
|
||||||
|
)
|
@ -0,0 +1,14 @@
|
|||||||
|
package im.vector.matrix.android.internal.session.user.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing an users search response
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SearchUsersRequestResponse(
|
||||||
|
@Json(name = "limited") val limited: Boolean = false,
|
||||||
|
@Json(name = "results") val users: List<SearchUser> = emptyList()
|
||||||
|
)
|
||||||
|
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.util
|
package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,3 +50,10 @@ fun convertFromUTF8(s: String): String? {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String?.firstLetterOfDisplayName(): String {
|
||||||
|
if (this.isNullOrEmpty()) return ""
|
||||||
|
val isUserId = MatrixPatterns.isUserId(this)
|
||||||
|
val firstLetterIndex = if (isUserId) 1 else 0
|
||||||
|
return this[firstLetterIndex].toString().toUpperCase()
|
||||||
|
}
|
@ -148,7 +148,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
def epoxy_version = "3.3.0"
|
def epoxy_version = "3.7.0"
|
||||||
def arrow_version = "0.8.2"
|
def arrow_version = "0.8.2"
|
||||||
def coroutines_version = "1.0.1"
|
def coroutines_version = "1.0.1"
|
||||||
def markwon_version = '3.0.0'
|
def markwon_version = '3.0.0'
|
||||||
@ -193,11 +193,15 @@ dependencies {
|
|||||||
|
|
||||||
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
||||||
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
||||||
|
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
|
||||||
implementation 'com.airbnb.android:mvrx:1.0.1'
|
implementation 'com.airbnb.android:mvrx:1.0.1'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||||
|
|
||||||
|
// Paging
|
||||||
|
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||||
|
|
||||||
// Functional Programming
|
// Functional Programming
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
|
|
||||||
@ -206,7 +210,7 @@ dependencies {
|
|||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha07'
|
implementation 'com.google.android.material:material:1.1.0-alpha08'
|
||||||
implementation 'me.gujun.android:span:1.7'
|
implementation 'me.gujun.android:span:1.7'
|
||||||
implementation "ru.noties.markwon:core:$markwon_version"
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
implementation "ru.noties.markwon:html:$markwon_version"
|
implementation "ru.noties.markwon:html:$markwon_version"
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
<activity android:name=".features.home.room.filtered.FilteredRoomsActivity" />
|
<activity android:name=".features.home.room.filtered.FilteredRoomsActivity" />
|
||||||
<activity android:name=".features.home.room.detail.RoomDetailActivity" />
|
<activity android:name=".features.home.room.detail.RoomDetailActivity" />
|
||||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||||
|
<activity android:name=".features.home.createdirect.CreateDirectRoomActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
|
@ -36,6 +36,9 @@ import im.vector.riotx.features.home.HomeActivity
|
|||||||
import im.vector.riotx.features.home.HomeDetailFragment
|
import im.vector.riotx.features.home.HomeDetailFragment
|
||||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||||
import im.vector.riotx.features.home.HomeModule
|
import im.vector.riotx.features.home.HomeModule
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
||||||
import im.vector.riotx.features.home.group.GroupListFragment
|
import im.vector.riotx.features.home.group.GroupListFragment
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
||||||
@ -45,6 +48,7 @@ import im.vector.riotx.features.invite.VectorInviteView
|
|||||||
import im.vector.riotx.features.login.LoginActivity
|
import im.vector.riotx.features.login.LoginActivity
|
||||||
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
||||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||||
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import im.vector.riotx.features.rageshake.BugReportActivity
|
import im.vector.riotx.features.rageshake.BugReportActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.RageShake
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
@ -73,6 +77,8 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun rageShake(): RageShake
|
fun rageShake(): RageShake
|
||||||
|
|
||||||
|
fun navigator(): Navigator
|
||||||
|
|
||||||
fun inject(activity: HomeActivity)
|
fun inject(activity: HomeActivity)
|
||||||
|
|
||||||
fun inject(roomDetailFragment: RoomDetailFragment)
|
fun inject(roomDetailFragment: RoomDetailFragment)
|
||||||
@ -153,6 +159,12 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun inject(pushGatewaysFragment: PushGatewaysFragment)
|
fun inject(pushGatewaysFragment: PushGatewaysFragment)
|
||||||
|
|
||||||
|
fun inject(createDirectRoomKnownUsersFragment: CreateDirectRoomKnownUsersFragment)
|
||||||
|
|
||||||
|
fun inject(createDirectRoomDirectoryUsersFragment: CreateDirectRoomDirectoryUsersFragment)
|
||||||
|
|
||||||
|
fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(vectorComponent: VectorComponent,
|
fun create(vectorComponent: VectorComponent,
|
||||||
|
@ -30,6 +30,9 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsVie
|
|||||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||||
import im.vector.riotx.features.home.*
|
import im.vector.riotx.features.home.*
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.group.GroupListViewModel
|
import im.vector.riotx.features.home.group.GroupListViewModel
|
||||||
import im.vector.riotx.features.home.group.GroupListViewModel_AssistedFactory
|
import im.vector.riotx.features.home.group.GroupListViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
|
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
|
||||||
@ -116,6 +119,11 @@ interface ViewModelModule {
|
|||||||
@ViewModelKey(ConfigurationViewModel::class)
|
@ViewModelKey(ConfigurationViewModel::class)
|
||||||
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(CreateDirectRoomNavigationViewModel::class)
|
||||||
|
fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future.
|
* Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future.
|
||||||
*/
|
*/
|
||||||
@ -168,6 +176,9 @@ interface ViewModelModule {
|
|||||||
@Binds
|
@Binds
|
||||||
fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
|
fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindCreateDirectRoomViewModelFactory(factory: CreateDirectRoomViewModel_AssistedFactory): CreateDirectRoomViewModel.Factory
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
|
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
|
||||||
|
|
||||||
|
@ -23,13 +23,16 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
|
||||||
fun EditText.setupAsSearch() {
|
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
|
||||||
|
@DrawableRes clearIconRes: Int = R.drawable.ic_x_green) {
|
||||||
|
|
||||||
addTextChangedListener(object : TextWatcher {
|
addTextChangedListener(object : TextWatcher {
|
||||||
override fun afterTextChanged(editable: Editable?) {
|
override fun afterTextChanged(editable: Editable?) {
|
||||||
val clearIcon = if (editable?.isNotEmpty() == true) R.drawable.ic_clear_white else 0
|
val clearIcon = if (editable?.isNotEmpty() == true) clearIconRes else 0
|
||||||
setCompoundDrawablesWithIntrinsicBounds(0, 0, clearIcon, 0)
|
setCompoundDrawablesWithIntrinsicBounds(searchIconRes, 0, clearIcon, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.riotx.core.extensions
|
|||||||
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import im.vector.riotx.core.utils.FirstThrottler
|
import im.vector.riotx.core.utils.FirstThrottler
|
||||||
import im.vector.riotx.core.utils.EventObserver
|
import im.vector.riotx.core.utils.EventObserver
|
||||||
@ -44,3 +45,7 @@ inline fun <T> LiveData<LiveEvent<T>>.observeEventFirstThrottle(owner: Lifecycle
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> MutableLiveData<LiveEvent<T>>.postLiveEvent(content: T) {
|
||||||
|
this.postValue(LiveEvent(content))
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.riotx.core.mvrx
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
|
||||||
abstract class NavigationViewModel<NavigationClass> : ViewModel() {
|
abstract class NavigationViewModel<NavigationClass> : ViewModel() {
|
||||||
@ -29,6 +30,6 @@ abstract class NavigationViewModel<NavigationClass> : ViewModel() {
|
|||||||
|
|
||||||
|
|
||||||
fun goTo(navigation: NavigationClass) {
|
fun goTo(navigation: NavigationClass) {
|
||||||
_navigateTo.postValue(LiveEvent(navigation))
|
_navigateTo.postLiveEvent(navigation)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.core.platform
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ScrollView
|
||||||
|
|
||||||
|
import im.vector.riotx.R
|
||||||
|
|
||||||
|
private const val DEFAULT_MAX_HEIGHT = 200
|
||||||
|
|
||||||
|
class MaxHeightScrollView : ScrollView {
|
||||||
|
|
||||||
|
var maxHeight: Int = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||||
|
if (!isInEditMode) {
|
||||||
|
init(context, attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||||
|
if (!isInEditMode) {
|
||||||
|
init(context, attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
if (!isInEditMode) {
|
||||||
|
init(context, attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init(context: Context, attrs: AttributeSet?) {
|
||||||
|
if (attrs != null) {
|
||||||
|
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView)
|
||||||
|
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, DEFAULT_MAX_HEIGHT)
|
||||||
|
styledAttrs.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
val newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
|
||||||
|
super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ package im.vector.riotx.core.platform
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
@ -46,6 +47,7 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
|
|||||||
|
|
||||||
@Inject lateinit var session: Session
|
@Inject lateinit var session: Session
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
session = injector.session()
|
session = injector.session()
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import androidx.annotation.*
|
|||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
@ -40,6 +41,7 @@ import im.vector.riotx.R
|
|||||||
import im.vector.riotx.core.di.*
|
import im.vector.riotx.core.di.*
|
||||||
import im.vector.riotx.core.utils.toast
|
import im.vector.riotx.core.utils.toast
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import im.vector.riotx.features.rageshake.BugReportActivity
|
import im.vector.riotx.features.rageshake.BugReportActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.RageShake
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
@ -70,6 +72,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
|
|||||||
private lateinit var configurationViewModel: ConfigurationViewModel
|
private lateinit var configurationViewModel: ConfigurationViewModel
|
||||||
protected lateinit var bugReporter: BugReporter
|
protected lateinit var bugReporter: BugReporter
|
||||||
private lateinit var rageShake: RageShake
|
private lateinit var rageShake: RageShake
|
||||||
|
protected lateinit var navigator: Navigator
|
||||||
|
|
||||||
private var unBinder: Unbinder? = null
|
private var unBinder: Unbinder? = null
|
||||||
|
|
||||||
@ -121,6 +124,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
|
|||||||
configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java)
|
configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java)
|
||||||
bugReporter = screenComponent.bugReporter()
|
bugReporter = screenComponent.bugReporter()
|
||||||
rageShake = screenComponent.rageShake()
|
rageShake = screenComponent.rageShake()
|
||||||
|
navigator = screenComponent.navigator()
|
||||||
configurationViewModel.activityRestarter.observe(this, Observer {
|
configurationViewModel.activityRestarter.observe(this, Observer {
|
||||||
if (!it.hasBeenHandled) {
|
if (!it.hasBeenHandled) {
|
||||||
// Recreate the Activity because configuration has changed
|
// Recreate the Activity because configuration has changed
|
||||||
@ -262,6 +266,24 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
|
|||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean {
|
||||||
|
// if (fm.backStackEntryCount == 0)
|
||||||
|
// return false
|
||||||
|
|
||||||
|
val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed()
|
||||||
|
for (f in reverseOrder) {
|
||||||
|
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager)
|
||||||
|
if (handledByChildFragments) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val backPressable = f as OnBackPressed
|
||||||
|
if (backPressable.onBackPressed()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* PROTECTED METHODS
|
* PROTECTED METHODS
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
@ -65,7 +65,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasScreen
|
|||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
|
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
|
||||||
navigator = vectorBaseActivity.getVectorComponent().navigator()
|
navigator = screenComponent.navigator()
|
||||||
viewModelFactory = screenComponent.viewModelFactory()
|
viewModelFactory = screenComponent.viewModelFactory()
|
||||||
injectWith(injector())
|
injectWith(injector())
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
|
@ -16,20 +16,36 @@
|
|||||||
|
|
||||||
package im.vector.riotx.core.platform
|
package im.vector.riotx.core.platform
|
||||||
|
|
||||||
import com.airbnb.mvrx.BaseMvRxViewModel
|
import com.airbnb.mvrx.*
|
||||||
import com.airbnb.mvrx.MvRxState
|
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
import im.vector.riotx.BuildConfig
|
import im.vector.riotx.BuildConfig
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
abstract class VectorViewModel<S : MvRxState>(initialState: S)
|
abstract class VectorViewModel<S : MvRxState>(initialState: S)
|
||||||
: BaseMvRxViewModel<S>(initialState, false) {
|
: BaseMvRxViewModel<S>(initialState, false) {
|
||||||
|
|
||||||
protected val cancelableBag = CancelableBag()
|
/**
|
||||||
|
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
|
||||||
override fun onCleared() {
|
* so you can use this in a switchMap or a flatMap
|
||||||
super.onCleared()
|
*/
|
||||||
cancelableBag.cancel()
|
fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> {
|
||||||
|
setState { stateReducer(Loading()) }
|
||||||
|
return this.map { Success(it) as Async<T> }
|
||||||
|
.onErrorReturn { Fail(it) }
|
||||||
|
.doOnSuccess { setState { stateReducer(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
|
||||||
|
* so you can use this in a switchMap or a flatMap
|
||||||
|
*/
|
||||||
|
fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> {
|
||||||
|
setState { stateReducer(Loading()) }
|
||||||
|
return this.map { Success(it) as Async<T> }
|
||||||
|
.onErrorReturn { Fail(it) }
|
||||||
|
.doOnNext { setState { stateReducer(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.core.utils
|
||||||
|
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
import io.reactivex.internal.functions.Functions
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
fun <T> Single<T>.subscribeLogError(): Disposable {
|
||||||
|
return subscribe(Functions.emptyConsumer(), Consumer { Timber.e(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Completable.subscribeLogError(): Disposable {
|
||||||
|
return subscribe({}, { Timber.e(it) })
|
||||||
|
}
|
@ -43,6 +43,7 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
|
|||||||
@Inject lateinit var keysBackupSettingsViewModelFactory: KeysBackupSettingsViewModel.Factory
|
@Inject lateinit var keysBackupSettingsViewModelFactory: KeysBackupSettingsViewModel.Factory
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
super.injectWith(injector)
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,10 +26,10 @@ import com.amulyakhare.textdrawable.TextDrawable
|
|||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.DrawableImageViewTarget
|
import com.bumptech.glide.request.target.DrawableImageViewTarget
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
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.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
@ -41,7 +41,7 @@ import javax.inject.Inject
|
|||||||
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
|
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
|
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val THUMBNAIL_SIZE = 250
|
private const val THUMBNAIL_SIZE = 250
|
||||||
@ -92,9 +92,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||||||
return if (text.isEmpty()) {
|
return if (text.isEmpty()) {
|
||||||
TextDrawable.builder().buildRound("", avatarColor)
|
TextDrawable.builder().buildRound("", avatarColor)
|
||||||
} else {
|
} else {
|
||||||
val isUserId = MatrixPatterns.isUserId(text)
|
val firstLetter = text.firstLetterOfDisplayName()
|
||||||
val firstLetterIndex = if (isUserId) 1 else 0
|
|
||||||
val firstLetter = text[firstLetterIndex].toString().toUpperCase()
|
|
||||||
TextDrawable.builder()
|
TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
.bold()
|
.bold()
|
||||||
|
@ -65,7 +65,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
@Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory
|
@Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory
|
||||||
@Inject lateinit var homeNavigator: HomeNavigator
|
@Inject lateinit var homeNavigator: HomeNavigator
|
||||||
@Inject lateinit var navigator: Navigator
|
|
||||||
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
||||||
@Inject lateinit var pushManager: PushersManager
|
@Inject lateinit var pushManager: PushersManager
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@ -214,23 +213,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean {
|
|
||||||
// if (fm.backStackEntryCount == 0)
|
|
||||||
// return false
|
|
||||||
|
|
||||||
val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed()
|
|
||||||
for (f in reverseOrder) {
|
|
||||||
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager)
|
|
||||||
if (handledByChildFragments) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
val backPressable = f as OnBackPressed
|
|
||||||
if (backPressable.onBackPressed()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -73,21 +73,21 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||||||
.subscribe { list ->
|
.subscribe { list ->
|
||||||
list.let { summaries ->
|
list.let { summaries ->
|
||||||
val peopleNotifications = summaries
|
val peopleNotifications = summaries
|
||||||
.filter { it.isDirect }
|
.filter { it.isDirect }
|
||||||
.map { it.notificationCount }
|
.map { it.notificationCount }
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
?.sumBy { i -> i }
|
?.sumBy { i -> i }
|
||||||
?: 0
|
?: 0
|
||||||
val peopleHasHighlight = summaries
|
val peopleHasHighlight = summaries
|
||||||
.filter { it.isDirect }
|
.filter { it.isDirect }
|
||||||
.any { it.highlightCount > 0 }
|
.any { it.highlightCount > 0 }
|
||||||
|
|
||||||
val roomsNotifications = summaries
|
val roomsNotifications = summaries
|
||||||
.filter { !it.isDirect }
|
.filter { !it.isDirect }
|
||||||
.map { it.notificationCount }
|
.map { it.notificationCount }
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
?.sumBy { i -> i }
|
?.sumBy { i -> i }
|
||||||
?: 0
|
?: 0
|
||||||
val roomsHasHighlight = summaries
|
val roomsHasHighlight = summaries
|
||||||
.filter { !it.isDirect }
|
.filter { !it.isDirect }
|
||||||
.any { it.highlightCount > 0 }
|
.any { it.highlightCount > 0 }
|
||||||
|
@ -51,8 +51,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
|
|||||||
val groupListFragment = GroupListFragment.newInstance()
|
val groupListFragment = GroupListFragment.newInstance()
|
||||||
replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer)
|
replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer)
|
||||||
}
|
}
|
||||||
|
session.liveUser(session.myUserId).observeK(this) { user ->
|
||||||
session.observeUser(session.myUserId).observeK(this) { user ->
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
|
avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
|
||||||
homeDrawerUsernameView.text = user.displayName
|
homeDrawerUsernameView.text = user.displayName
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
|
sealed class CreateDirectRoomActions {
|
||||||
|
|
||||||
|
object CreateRoomAndInviteSelectedUsers : CreateDirectRoomActions()
|
||||||
|
data class FilterKnownUsers(val value: String) : CreateDirectRoomActions()
|
||||||
|
data class SearchDirectoryUsers(val value: String) : CreateDirectRoomActions()
|
||||||
|
object ClearFilterKnownUsers : CreateDirectRoomActions()
|
||||||
|
data class SelectUser(val user: User) : CreateDirectRoomActions()
|
||||||
|
data class RemoveSelectedUser(val user: User) : CreateDirectRoomActions()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.viewModel
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.extensions.addFragment
|
||||||
|
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
|
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
||||||
|
import im.vector.riotx.core.platform.WaitingViewData
|
||||||
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
|
sealed class Navigation {
|
||||||
|
object UsersDirectory : Navigation()
|
||||||
|
object Close : Navigation()
|
||||||
|
object Previous : Navigation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
||||||
|
lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel
|
||||||
|
@Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
|
||||||
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
super.injectWith(injector)
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
toolbar.visibility = View.GONE
|
||||||
|
navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java)
|
||||||
|
navigationViewModel.navigateTo.observeEvent(this) { navigation ->
|
||||||
|
when (navigation) {
|
||||||
|
is Navigation.UsersDirectory -> addFragmentToBackstack(CreateDirectRoomDirectoryUsersFragment(), R.id.container)
|
||||||
|
Navigation.Close -> finish()
|
||||||
|
Navigation.Previous -> onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(CreateDirectRoomKnownUsersFragment(), R.id.container)
|
||||||
|
}
|
||||||
|
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
|
||||||
|
renderCreateAndInviteState(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCreateAndInviteState(state: Async<String>) {
|
||||||
|
when (state) {
|
||||||
|
is Loading -> renderCreationLoading()
|
||||||
|
is Success -> renderCreationSuccess(state())
|
||||||
|
is Fail -> renderCreationFailure(state.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCreationLoading() {
|
||||||
|
updateWaitingView(WaitingViewData(getString(R.string.creating_direct_room)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCreationFailure(error: Throwable) {
|
||||||
|
hideWaitingView()
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setMessage(errorFormatter.toHumanReadable(error))
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCreationSuccess(roomId: String?) {
|
||||||
|
// Navigate to freshly created room
|
||||||
|
if (roomId != null) {
|
||||||
|
navigator.openRoom(this, roomId)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getIntent(context: Context): Intent {
|
||||||
|
return Intent(context, CreateDirectRoomActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
|
import im.vector.riotx.core.extensions.setupAsSearch
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), DirectoryUsersController.Callback {
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users
|
||||||
|
|
||||||
|
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
||||||
|
|
||||||
|
@Inject lateinit var directRoomController: DirectoryUsersController
|
||||||
|
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
navigationViewModel = ViewModelProviders.of(requireActivity(), viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java)
|
||||||
|
setupRecyclerView()
|
||||||
|
setupSearchByMatrixIdView()
|
||||||
|
setupCloseView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
recyclerView.setHasFixedSize(true)
|
||||||
|
directRoomController.callback = this
|
||||||
|
recyclerView.setController(directRoomController)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSearchByMatrixIdView() {
|
||||||
|
createDirectRoomSearchById.setupAsSearch(searchIconRes = 0)
|
||||||
|
createDirectRoomSearchById
|
||||||
|
.textChanges()
|
||||||
|
.subscribe {
|
||||||
|
viewModel.handle(CreateDirectRoomActions.SearchDirectoryUsers(it.toString()))
|
||||||
|
}
|
||||||
|
.disposeOnDestroy()
|
||||||
|
createDirectRoomSearchById.requestFocus()
|
||||||
|
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||||
|
imm?.showSoftInput(createDirectRoomSearchById, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCloseView() {
|
||||||
|
createDirectRoomClose.setOnClickListener {
|
||||||
|
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
directRoomController.setData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(user: User) {
|
||||||
|
view?.hideKeyboard()
|
||||||
|
viewModel.handle(CreateDirectRoomActions.SelectUser(user))
|
||||||
|
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retryDirectoryUsersRequest() {
|
||||||
|
val currentSearch = createDirectRoomSearchById.text.toString()
|
||||||
|
viewModel.handle(CreateDirectRoomActions.SearchDirectoryUsers(currentSearch))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.ScrollView
|
||||||
|
import androidx.core.view.size
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
|
import im.vector.riotx.core.extensions.setupAsSearch
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.DimensionUtils
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CreateDirectRoomKnownUsersFragment : VectorBaseFragment(), KnownUsersController.Callback {
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_create_direct_room
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.vector_create_direct_room
|
||||||
|
|
||||||
|
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
||||||
|
|
||||||
|
@Inject lateinit var directRoomController: KnownUsersController
|
||||||
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
navigationViewModel = ViewModelProviders.of(requireActivity(), viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java)
|
||||||
|
vectorBaseActivity.setSupportActionBar(createDirectRoomToolbar)
|
||||||
|
setupRecyclerView()
|
||||||
|
setupFilterView()
|
||||||
|
setupAddByMatrixIdView()
|
||||||
|
setupCloseView()
|
||||||
|
viewModel.selectUserEvent.observeEvent(this) {
|
||||||
|
updateChipsView(it)
|
||||||
|
}
|
||||||
|
viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) {
|
||||||
|
renderSelectedUsers(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
withState(viewModel) {
|
||||||
|
val createMenuItem = menu.findItem(R.id.action_create_direct_room)
|
||||||
|
val showMenuItem = it.selectedUsers.isNotEmpty()
|
||||||
|
createMenuItem.setVisible(showMenuItem)
|
||||||
|
}
|
||||||
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.action_create_direct_room -> {
|
||||||
|
viewModel.handle(CreateDirectRoomActions.CreateRoomAndInviteSelectedUsers)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAddByMatrixIdView() {
|
||||||
|
addByMatrixId.setOnClickListener {
|
||||||
|
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.UsersDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
recyclerView.setHasFixedSize(true)
|
||||||
|
// Don't activate animation as we might have way to much item animation when filtering
|
||||||
|
recyclerView.itemAnimator = null
|
||||||
|
directRoomController.callback = this
|
||||||
|
recyclerView.setController(directRoomController)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFilterView() {
|
||||||
|
createDirectRoomFilter
|
||||||
|
.textChanges()
|
||||||
|
.startWith(createDirectRoomFilter.text)
|
||||||
|
.subscribe { text ->
|
||||||
|
val filterValue = text.trim()
|
||||||
|
val action = if (filterValue.isBlank()) {
|
||||||
|
CreateDirectRoomActions.ClearFilterKnownUsers
|
||||||
|
} else {
|
||||||
|
CreateDirectRoomActions.FilterKnownUsers(filterValue.toString())
|
||||||
|
}
|
||||||
|
viewModel.handle(action)
|
||||||
|
}
|
||||||
|
.disposeOnDestroy()
|
||||||
|
|
||||||
|
createDirectRoomFilter.setupAsSearch()
|
||||||
|
createDirectRoomFilter.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCloseView() {
|
||||||
|
createDirectRoomClose.setOnClickListener {
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
directRoomController.setData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateChipsView(data: SelectUserAction) {
|
||||||
|
if (data.isAdded) {
|
||||||
|
addChipToGroup(data.user, chipGroup)
|
||||||
|
} else {
|
||||||
|
if (chipGroup.size > data.index) {
|
||||||
|
chipGroup.removeViewAt(data.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderSelectedUsers(selectedUsers: Set<User>) {
|
||||||
|
vectorBaseActivity.invalidateOptionsMenu()
|
||||||
|
if (selectedUsers.isNotEmpty() && chipGroup.size == 0) {
|
||||||
|
selectedUsers.forEach { addChipToGroup(it, chipGroup) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addChipToGroup(user: User, chipGroup: ChipGroup) {
|
||||||
|
val chip = Chip(requireContext())
|
||||||
|
chip.setChipBackgroundColorResource(android.R.color.transparent)
|
||||||
|
chip.chipStrokeWidth = DimensionUtils.dpToPx(1, requireContext()).toFloat()
|
||||||
|
chip.text = if (user.displayName.isNullOrBlank()) user.userId else user.displayName
|
||||||
|
chip.isClickable = true
|
||||||
|
chip.isCheckable = false
|
||||||
|
chip.isCloseIconVisible = true
|
||||||
|
chipGroup.addView(chip)
|
||||||
|
chip.setOnCloseIconClickListener {
|
||||||
|
viewModel.handle(CreateDirectRoomActions.RemoveSelectedUser(user))
|
||||||
|
}
|
||||||
|
chipGroupScrollView.post {
|
||||||
|
chipGroupScrollView.fullScroll(ScrollView.FOCUS_DOWN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(user: User) {
|
||||||
|
view?.hideKeyboard()
|
||||||
|
viewModel.handle(CreateDirectRoomActions.SelectUser(user))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_create_direct_room_letter_header)
|
||||||
|
abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel<CreateDirectRoomLetterHeaderItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute var letter: String = ""
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.letterView.text = letter
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val letterView by bind<TextView>(R.id.createDirectRoomLetterView)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import im.vector.riotx.core.mvrx.NavigationViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CreateDirectRoomNavigationViewModel @Inject constructor(): NavigationViewModel<CreateDirectRoomActivity.Navigation>()
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_create_direct_room_user)
|
||||||
|
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
@EpoxyAttribute var name: String? = null
|
||||||
|
@EpoxyAttribute var userId: String = ""
|
||||||
|
@EpoxyAttribute var avatarUrl: String? = null
|
||||||
|
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||||
|
@EpoxyAttribute var selected: Boolean = false
|
||||||
|
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.view.setOnClickListener(clickListener)
|
||||||
|
// If name is empty, use userId as name and force it being centered
|
||||||
|
if (name.isNullOrEmpty()) {
|
||||||
|
holder.userIdView.visibility = View.GONE
|
||||||
|
holder.nameView.text = userId
|
||||||
|
} else {
|
||||||
|
holder.userIdView.visibility = View.VISIBLE
|
||||||
|
holder.nameView.text = name
|
||||||
|
holder.userIdView.text = userId
|
||||||
|
}
|
||||||
|
renderSelection(holder, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderSelection(holder: Holder, isSelected: Boolean) {
|
||||||
|
if (isSelected) {
|
||||||
|
holder.avatarCheckedImageView.visibility = View.VISIBLE
|
||||||
|
val backgroundColor = ContextCompat.getColor(holder.view.context, R.color.riotx_accent)
|
||||||
|
val backgroundDrawable = TextDrawable.builder().buildRound("", backgroundColor)
|
||||||
|
holder.avatarImageView.setImageDrawable(backgroundDrawable)
|
||||||
|
} else {
|
||||||
|
holder.avatarCheckedImageView.visibility = View.GONE
|
||||||
|
avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val userIdView by bind<TextView>(R.id.createDirectRoomUserID)
|
||||||
|
val nameView by bind<TextView>(R.id.createDirectRoomUserName)
|
||||||
|
val avatarImageView by bind<ImageView>(R.id.createDirectRoomUserAvatar)
|
||||||
|
val avatarCheckedImageView by bind<ImageView>(R.id.createDirectRoomUserAvatarChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import arrow.core.Option
|
||||||
|
import com.airbnb.mvrx.*
|
||||||
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.functions.BiFunction
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
private typealias KnowUsersFilter = String
|
||||||
|
private typealias DirectoryUsersSearch = String
|
||||||
|
|
||||||
|
data class SelectUserAction(
|
||||||
|
val user: User,
|
||||||
|
val isAdded: Boolean,
|
||||||
|
val index: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
|
initialState: CreateDirectRoomViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<CreateDirectRoomViewState>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
||||||
|
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||||
|
|
||||||
|
private val _selectUserEvent = MutableLiveData<LiveEvent<SelectUserAction>>()
|
||||||
|
val selectUserEvent: LiveData<LiveEvent<SelectUserAction>>
|
||||||
|
get() = _selectUserEvent
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: CreateDirectRoomViewState): CreateDirectRoomViewModel? {
|
||||||
|
val activity: CreateDirectRoomActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||||
|
return activity.createDirectRoomViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeKnownUsers()
|
||||||
|
observeDirectoryUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handle(action: CreateDirectRoomActions) {
|
||||||
|
when (action) {
|
||||||
|
is CreateDirectRoomActions.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers()
|
||||||
|
is CreateDirectRoomActions.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value))
|
||||||
|
is CreateDirectRoomActions.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
|
||||||
|
is CreateDirectRoomActions.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value)
|
||||||
|
is CreateDirectRoomActions.SelectUser -> handleSelectUser(action)
|
||||||
|
is CreateDirectRoomActions.RemoveSelectedUser -> handleRemoveSelectedUser(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRoomAndInviteSelectedUsers() = withState { currentState ->
|
||||||
|
val isDirect = currentState.selectedUsers.size == 1
|
||||||
|
val roomParams = CreateRoomParams().apply {
|
||||||
|
invitedUserIds = ArrayList(currentState.selectedUsers.map { it.userId })
|
||||||
|
if (isDirect) {
|
||||||
|
setDirectMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.rx()
|
||||||
|
.createRoom(roomParams)
|
||||||
|
.execute {
|
||||||
|
copy(createAndInviteState = it)
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState { state ->
|
||||||
|
val index = state.selectedUsers.indexOfFirst { it.userId == action.user.userId }
|
||||||
|
val selectedUsers = state.selectedUsers.minus(action.user)
|
||||||
|
setState { copy(selectedUsers = selectedUsers) }
|
||||||
|
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, false, index))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSelectUser(action: CreateDirectRoomActions.SelectUser) = withState { state ->
|
||||||
|
//Reset the filter asap
|
||||||
|
directoryUsersSearch.accept("")
|
||||||
|
val isAddOperation: Boolean
|
||||||
|
val selectedUsers: Set<User>
|
||||||
|
val indexOfUser = state.selectedUsers.indexOfFirst { it.userId == action.user.userId }
|
||||||
|
val changeIndex: Int
|
||||||
|
if (indexOfUser == -1) {
|
||||||
|
changeIndex = state.selectedUsers.size
|
||||||
|
selectedUsers = state.selectedUsers.plus(action.user)
|
||||||
|
isAddOperation = true
|
||||||
|
} else {
|
||||||
|
changeIndex = indexOfUser
|
||||||
|
selectedUsers = state.selectedUsers.minus(action.user)
|
||||||
|
isAddOperation = false
|
||||||
|
}
|
||||||
|
setState { copy(selectedUsers = selectedUsers) }
|
||||||
|
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, isAddOperation, changeIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeDirectoryUsers() {
|
||||||
|
directoryUsersSearch
|
||||||
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.switchMapSingle { search ->
|
||||||
|
val stream = if (search.isBlank()) {
|
||||||
|
Single.just(emptyList())
|
||||||
|
} else {
|
||||||
|
session.rx()
|
||||||
|
.searchUsersDirectory(search, 50, emptySet())
|
||||||
|
.map { users ->
|
||||||
|
users.sortedBy { it.displayName.firstLetterOfDisplayName() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.toAsync {
|
||||||
|
copy(directoryUsers = it, directorySearchTerm = search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribe()
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeKnownUsers() {
|
||||||
|
knownUsersFilter
|
||||||
|
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.switchMap {
|
||||||
|
session.rx().livePagedUsers(it.orNull())
|
||||||
|
}
|
||||||
|
.execute { async ->
|
||||||
|
copy(
|
||||||
|
knownUsers = async,
|
||||||
|
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import arrow.core.Option
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
|
data class CreateDirectRoomViewState(
|
||||||
|
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
||||||
|
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||||
|
val selectedUsers: Set<User> = emptySet(),
|
||||||
|
val createAndInviteState: Async<String> = Uninitialized,
|
||||||
|
val directorySearchTerm: String = "",
|
||||||
|
val filterKnownUsersValue: Option<String> = Option.empty()
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
enum class DisplayMode {
|
||||||
|
KNOWN_USERS,
|
||||||
|
DIRECTORY_USERS
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
import com.airbnb.mvrx.*
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||||
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
|
import im.vector.riotx.core.epoxy.noResultItem
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||||
|
|
||||||
|
private var state: CreateDirectRoomViewState? = null
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setData(state: CreateDirectRoomViewState) {
|
||||||
|
this.state = state
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun buildModels() {
|
||||||
|
val currentState = state ?: return
|
||||||
|
val hasSearch = currentState.directorySearchTerm.isNotBlank()
|
||||||
|
val asyncUsers = currentState.directoryUsers
|
||||||
|
when (asyncUsers) {
|
||||||
|
is Uninitialized -> renderEmptyState(false)
|
||||||
|
is Loading -> renderLoading()
|
||||||
|
is Success -> renderSuccess(asyncUsers(), currentState.selectedUsers.map { it.userId }, hasSearch)
|
||||||
|
is Fail -> renderFailure(asyncUsers.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderLoading() {
|
||||||
|
loadingItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderFailure(failure: Throwable) {
|
||||||
|
errorWithRetryItem {
|
||||||
|
id("error")
|
||||||
|
text(errorFormatter.toHumanReadable(failure))
|
||||||
|
listener { callback?.retryDirectoryUsersRequest() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderSuccess(users: List<User>,
|
||||||
|
selectedUsers: List<String>,
|
||||||
|
hasSearch: Boolean) {
|
||||||
|
if (users.isEmpty()) {
|
||||||
|
renderEmptyState(hasSearch)
|
||||||
|
} else {
|
||||||
|
renderUsers(users, selectedUsers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
|
||||||
|
for (user in users) {
|
||||||
|
if (user.userId == session.myUserId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val isSelected = selectedUsers.contains(user.userId)
|
||||||
|
createDirectRoomUserItem {
|
||||||
|
id(user.userId)
|
||||||
|
selected(isSelected)
|
||||||
|
userId(user.userId)
|
||||||
|
name(user.displayName)
|
||||||
|
avatarUrl(user.avatarUrl)
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
clickListener { _ ->
|
||||||
|
callback?.onItemClick(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderEmptyState(hasSearch: Boolean) {
|
||||||
|
val noResultRes = if (hasSearch) {
|
||||||
|
R.string.no_result_placeholder
|
||||||
|
} else {
|
||||||
|
R.string.direct_room_start_search
|
||||||
|
}
|
||||||
|
noResultItem {
|
||||||
|
id("noResult")
|
||||||
|
text(stringProvider.getString(noResultRes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onItemClick(user: User)
|
||||||
|
fun retryDirectoryUsersRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* 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.riotx.features.home.createdirect
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.internal.util.createUIHandler
|
||||||
|
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.EmptyItem_
|
||||||
|
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||||
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
|
import im.vector.riotx.core.epoxy.noResultItem
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val stringProvider: StringProvider) : PagedListEpoxyController<User>(
|
||||||
|
modelBuildingHandler = createUIHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var selectedUsers: List<String> = emptyList()
|
||||||
|
private var users: Async<List<User>> = Uninitialized
|
||||||
|
private var isFiltering: Boolean = false
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setData(state: CreateDirectRoomViewState) {
|
||||||
|
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
|
||||||
|
val newSelection = state.selectedUsers.map { it.userId }
|
||||||
|
this.users = state.knownUsers
|
||||||
|
if (newSelection != selectedUsers) {
|
||||||
|
this.selectedUsers = newSelection
|
||||||
|
requestForcedModelBuild()
|
||||||
|
}
|
||||||
|
submitList(state.knownUsers())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: User?): EpoxyModel<*> {
|
||||||
|
return if (item == null) {
|
||||||
|
EmptyItem_().id(currentPosition)
|
||||||
|
} else {
|
||||||
|
val isSelected = selectedUsers.contains(item.userId)
|
||||||
|
CreateDirectRoomUserItem_()
|
||||||
|
.id(item.userId)
|
||||||
|
.selected(isSelected)
|
||||||
|
.userId(item.userId)
|
||||||
|
.name(item.displayName)
|
||||||
|
.avatarUrl(item.avatarUrl)
|
||||||
|
.avatarRenderer(avatarRenderer)
|
||||||
|
.clickListener { _ ->
|
||||||
|
callback?.onItemClick(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||||
|
if (users is Incomplete) {
|
||||||
|
renderLoading()
|
||||||
|
} else if (models.isEmpty()) {
|
||||||
|
renderEmptyState()
|
||||||
|
} else {
|
||||||
|
var lastFirstLetter: String? = null
|
||||||
|
for (model in models) {
|
||||||
|
if (model is CreateDirectRoomUserItem) {
|
||||||
|
if (model.userId == session.myUserId) continue
|
||||||
|
val currentFirstLetter = model.name.firstLetterOfDisplayName()
|
||||||
|
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
||||||
|
lastFirstLetter = currentFirstLetter
|
||||||
|
|
||||||
|
CreateDirectRoomLetterHeaderItem_()
|
||||||
|
.id(currentFirstLetter)
|
||||||
|
.letter(currentFirstLetter)
|
||||||
|
.addIf(showLetter, this)
|
||||||
|
|
||||||
|
model.addTo(this)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderLoading() {
|
||||||
|
loadingItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderEmptyState() {
|
||||||
|
noResultItem {
|
||||||
|
id("noResult")
|
||||||
|
text(stringProvider.getString(R.string.direct_room_no_known_users))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onItemClick(user: User)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.Session
|
|||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
@ -67,7 +68,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
|||||||
private fun observeSelectionState() {
|
private fun observeSelectionState() {
|
||||||
selectSubscribe(GroupListViewState::selectedGroup) {
|
selectSubscribe(GroupListViewState::selectedGroup) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
_openGroupLiveData.postValue(LiveEvent(it))
|
_openGroupLiveData.postLiveEvent(it)
|
||||||
val optionGroup = Option.fromNullable(it)
|
val optionGroup = Option.fromNullable(it)
|
||||||
selectedGroupHolder.post(optionGroup)
|
selectedGroupHolder.post(optionGroup)
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,12 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.intent.getFilenameFromUri
|
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
import im.vector.riotx.core.utils.subscribeLogError
|
||||||
import im.vector.riotx.features.command.CommandParser
|
import im.vector.riotx.features.command.CommandParser
|
||||||
import im.vector.riotx.features.command.ParsedCommand
|
import im.vector.riotx.features.command.ParsedCommand
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||||
@ -95,7 +97,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
observeRoomSummary()
|
observeRoomSummary()
|
||||||
observeEventDisplayedActions()
|
observeEventDisplayedActions()
|
||||||
observeInvitationState()
|
observeInvitationState()
|
||||||
cancelableBag += room.loadRoomMembersIfNeeded()
|
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||||
timeline.start()
|
timeline.start()
|
||||||
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
||||||
}
|
}
|
||||||
@ -167,62 +169,62 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
is ParsedCommand.ErrorNotACommand -> {
|
is ParsedCommand.ErrorNotACommand -> {
|
||||||
// Send the text message to the room
|
// Send the text message to the room
|
||||||
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorSyntax -> {
|
is ParsedCommand.ErrorSyntax -> {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorEmptySlashCommand -> {
|
is ParsedCommand.ErrorEmptySlashCommand -> {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown("/"))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))
|
||||||
}
|
}
|
||||||
is ParsedCommand.Invite -> {
|
is ParsedCommand.Invite -> {
|
||||||
handleInviteSlashCommand(slashCommandResult)
|
handleInviteSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SetUserPowerLevel -> {
|
is ParsedCommand.SetUserPowerLevel -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ClearScalarToken -> {
|
is ParsedCommand.ClearScalarToken -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SetMarkdown -> {
|
is ParsedCommand.SetMarkdown -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.UnbanUser -> {
|
is ParsedCommand.UnbanUser -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.BanUser -> {
|
is ParsedCommand.BanUser -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.KickUser -> {
|
is ParsedCommand.KickUser -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.JoinRoom -> {
|
is ParsedCommand.JoinRoom -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.PartRoom -> {
|
is ParsedCommand.PartRoom -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendEmote -> {
|
is ParsedCommand.SendEmote -> {
|
||||||
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeTopic -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(slashCommandResult)
|
handleChangeTopicSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeDisplayName -> {
|
is ParsedCommand.ChangeDisplayName -> {
|
||||||
// TODO
|
// TODO
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,12 +241,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
} else {
|
} else {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val existingBody = messageContent?.body ?: ""
|
val existingBody = messageContent?.body ?: ""
|
||||||
if (existingBody != action.text) {
|
if (existingBody != action.text) {
|
||||||
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
||||||
?: "", messageContent?.type
|
?: "", messageContent?.type
|
||||||
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("Same message content, do not send edition")
|
Timber.w("Same message content, do not send edition")
|
||||||
}
|
}
|
||||||
@ -254,12 +256,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
sendMode = SendMode.REGULAR
|
sendMode = SendMode.REGULAR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||||
}
|
}
|
||||||
is SendMode.QUOTE -> {
|
is SendMode.QUOTE -> {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val textMsg = messageContent?.body
|
val textMsg = messageContent?.body
|
||||||
|
|
||||||
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
||||||
@ -279,7 +281,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
sendMode = SendMode.REGULAR
|
sendMode = SendMode.REGULAR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||||
}
|
}
|
||||||
is SendMode.REPLY -> {
|
is SendMode.REPLY -> {
|
||||||
state.sendMode.timelineEvent.let {
|
state.sendMode.timelineEvent.let {
|
||||||
@ -289,7 +291,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
sendMode = SendMode.REGULAR
|
sendMode = SendMode.REGULAR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -318,29 +320,29 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
|
||||||
|
|
||||||
room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
|
room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultOk))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultError(failure)))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
|
||||||
|
|
||||||
room.invite(invite.userId, object : MatrixCallback<Unit> {
|
room.invite(invite.userId, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultOk))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultError(failure)))
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -452,19 +454,19 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(),
|
action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
object : MatrixCallback<File> {
|
object : MatrixCallback<File> {
|
||||||
override fun onSuccess(data: File) {
|
override fun onSuccess(data: File) {
|
||||||
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState(
|
_downloadedFileEvent.postLiveEvent(DownloadFileState(
|
||||||
action.messageFileContent.getMimeType(),
|
action.messageFileContent.getMimeType(),
|
||||||
data,
|
data,
|
||||||
null
|
null
|
||||||
)))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState(
|
_downloadedFileEvent.postLiveEvent(DownloadFileState(
|
||||||
action.messageFileContent.getMimeType(),
|
action.messageFileContent.getMimeType(),
|
||||||
null,
|
null,
|
||||||
failure
|
failure
|
||||||
)))
|
))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -493,7 +495,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_navigateToEvent.postValue(LiveEvent(targetEventId))
|
_navigateToEvent.postLiveEvent(targetEventId)
|
||||||
} else {
|
} else {
|
||||||
// change timeline
|
// change timeline
|
||||||
timeline.dispose()
|
timeline.dispose()
|
||||||
@ -518,7 +520,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_navigateToEvent.postValue(LiveEvent(targetEventId))
|
_navigateToEvent.postLiveEvent(targetEventId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun createDirectChat() {
|
override fun createDirectChat() {
|
||||||
vectorBaseActivity.notImplemented("creating direct chat")
|
navigator.openCreateDirectRoom(requireActivity())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
@ -253,7 +253,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
|||||||
return super.onBackPressed()
|
return super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomSummaryController.Callback **************************************************************
|
// RoomSummaryController.Callback **************************************************************
|
||||||
|
|
||||||
override fun onRoomSelected(room: RoomSummary) {
|
override fun onRoomSelected(room: RoomSummary) {
|
||||||
roomListViewModel.accept(RoomListActions.SelectRoom(room))
|
roomListViewModel.accept(RoomListActions.SelectRoom(room))
|
||||||
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.Session
|
|||||||
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.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
import im.vector.riotx.features.home.HomeRoomListObservableStore
|
import im.vector.riotx.features.home.HomeRoomListObservableStore
|
||||||
@ -142,7 +143,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
|||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// Notify the user
|
// Notify the user
|
||||||
_invitationAnswerErrorLiveData.postValue(LiveEvent(failure))
|
_invitationAnswerErrorLiveData.postLiveEvent(failure)
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
@ -178,7 +179,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
|||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// Notify the user
|
// Notify the user
|
||||||
_invitationAnswerErrorLiveData.postValue(LiveEvent(failure))
|
_invitationAnswerErrorLiveData.postLiveEvent(failure)
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -25,6 +25,7 @@ import im.vector.riotx.core.utils.toast
|
|||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||||
import im.vector.riotx.features.debug.DebugMenuActivity
|
import im.vector.riotx.features.debug.DebugMenuActivity
|
||||||
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
@ -68,6 +69,11 @@ class DefaultNavigator @Inject constructor() : Navigator {
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openCreateDirectRoom(context: Context) {
|
||||||
|
val intent = CreateDirectRoomActivity.getIntent(context)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun openRoomsFiltering(context: Context) {
|
override fun openRoomsFiltering(context: Context) {
|
||||||
val intent = FilteredRoomsActivity.newIntent(context)
|
val intent = FilteredRoomsActivity.newIntent(context)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
@ -29,6 +29,8 @@ interface Navigator {
|
|||||||
|
|
||||||
fun openCreateRoom(context: Context, initialName: String = "")
|
fun openCreateRoom(context: Context, initialName: String = "")
|
||||||
|
|
||||||
|
fun openCreateDirectRoom(context: Context)
|
||||||
|
|
||||||
fun openRoomDirectory(context: Context, initialFilter: String = "")
|
fun openRoomDirectory(context: Context, initialFilter: String = "")
|
||||||
|
|
||||||
fun openRoomsFiltering(context: Context)
|
fun openRoomsFiltering(context: Context)
|
||||||
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms
|
|||||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -207,7 +208,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// Notify the user
|
// Notify the user
|
||||||
_joinRoomErrorLiveData.postValue(LiveEvent(failure))
|
_joinRoomErrorLiveData.postLiveEvent(failure)
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
144
vector/src/main/res/layout/fragment_create_direct_room.xml
Normal file
144
vector/src/main/res/layout/fragment_create_direct_room.xml
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/createDirectRoomToolbar"
|
||||||
|
style="@style/VectorToolbarStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/createDirectRoomClose"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_x_18dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/createDirectRoomTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/fab_menu_create_chat"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/createDirectRoomClose"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.MaxHeightScrollView
|
||||||
|
android:id="@+id/chipGroupScrollView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomToolbar"
|
||||||
|
app:maxHeight="64dp">
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/chipGroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:lineSpacing="2dp" />
|
||||||
|
|
||||||
|
</im.vector.riotx.core.platform.MaxHeightScrollView>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/createDirectRoomFilter"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:background="@null"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/direct_room_filter_hint"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:maxHeight="80dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollView" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/createDirectRoomFilterDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="?attr/vctr_list_divider_color"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomFilter" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/addByMatrixId"
|
||||||
|
style="@style/VectorButtonStyleFlat"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:minHeight="@dimen/layout_touch_size"
|
||||||
|
android:text="@string/add_by_matrix_id"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:icon="@drawable/ic_plus_circle"
|
||||||
|
app:iconPadding="13dp"
|
||||||
|
app:iconTint="@color/riotx_accent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/createDirectRoomFilterDivider" />
|
||||||
|
|
||||||
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:fastScrollEnabled="true"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/addByMatrixId"
|
||||||
|
tools:listitem="@layout/item_create_direct_room_user" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
@ -0,0 +1,108 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/createRoomToolbar"
|
||||||
|
style="@style/VectorToolbarStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/createDirectRoomClose"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_x_18dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/createDirectRoomTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/direct_chats_header"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/createDirectRoomClose"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/createDirectRoomSearchByIdContainer"
|
||||||
|
style="@style/VectorTextInputLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createRoomToolbar">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/createDirectRoomSearchById"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/add_by_matrix_id" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/createDirectRoomFilterDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="?attr/vctr_list_divider_color"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomSearchByIdContainer" />
|
||||||
|
|
||||||
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fastScrollEnabled="true"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomFilterDivider"
|
||||||
|
tools:listitem="@layout/item_create_direct_room_user" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/createDirectRoomLetterView"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textColor="?attr/riotx_text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="normal"
|
||||||
|
tools:text="C" />
|
72
vector/src/main/res/layout/item_create_direct_room_user.xml
Normal file
72
vector/src/main/res/layout/item_create_direct_room_user.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/createDirectRoomUserAvatarContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/createDirectRoomUserAvatar"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/createDirectRoomUserAvatarChecked"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_material_done"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:visibility="visible" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/createDirectRoomUserName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/createDirectRoomUserID"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/createDirectRoomUserAvatarContainer"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/createDirectRoomUserID"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/createDirectRoomUserName"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomUserName"
|
||||||
|
tools:text="Blabla" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
10
vector/src/main/res/menu/vector_create_direct_room.xml
Executable file
10
vector/src/main/res/menu/vector_create_direct_room.xml
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_create_direct_room"
|
||||||
|
android:title="@string/create_room_action_create"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
</menu>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="MaxHeightScrollView">
|
||||||
|
<attr name="maxHeight" format="dimension" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
@ -2,5 +2,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Strings not defined in Riot -->
|
<!-- Strings not defined in Riot -->
|
||||||
|
<string name="add_by_matrix_id">Add by matrix ID</string>
|
||||||
|
<string name="creating_direct_room">"Creating room…"</string>
|
||||||
|
<string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string>
|
||||||
|
<string name="direct_room_start_search">"Start typing to get results"</string>
|
||||||
|
<string name="direct_room_filter_hint">"Filter by username or ID…"</string>
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user