mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-10 10:17:42 +03:00
Merge branch 'release/0.10.0'
This commit is contained in:
commit
902a9aa243
137 changed files with 2083 additions and 674 deletions
17
CHANGES.md
17
CHANGES.md
|
@ -1,3 +1,20 @@
|
||||||
|
Changes in RiotX 0.10.0 (2019-12-10)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Breadcrumbs: switch from one room to another quickly (#571)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- When automardown is ON, pills are sent as MD in body (#739)
|
||||||
|
- "ban" event are not rendered correctly (#716)
|
||||||
|
- Fix crash when rotating screen in Room timeline
|
||||||
|
|
||||||
Changes in RiotX 0.9.1 (2019-12-05)
|
Changes in RiotX 0.9.1 (2019-12-05)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ Client request the sign-up flows, once the homeserver is chosen by the user and
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We get the flows with a 401, which also means the the registration is possible on this homeserver.
|
We get the flows with a 401, which also means that the registration is possible on this homeserver.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
|
@ -57,8 +57,9 @@ class RxRoom(private val room: Room) {
|
||||||
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
fun joinRoom(reason: String? = null,
|
||||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||||
|
room.join(reason, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||||
|
|
|
@ -38,6 +38,10 @@ class RxSession(private val session: Session) {
|
||||||
return session.liveGroupSummaries().asObservable()
|
return session.liveGroupSummaries().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||||
|
return session.liveBreadcrumbs().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
return session.syncState().asObservable()
|
return session.syncState().asObservable()
|
||||||
}
|
}
|
||||||
|
@ -72,8 +76,10 @@ class RxSession(private val session: Session) {
|
||||||
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinRoom(roomId: String, viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
fun joinRoom(roomId: String,
|
||||||
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
reason: String? = null,
|
||||||
|
viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||||
|
session.joinRoom(roomId, reason, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
sealed class LoginFlowResult {
|
sealed class LoginFlowResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val loginFlowResponse: LoginFlowResponse,
|
val loginFlowResponse: LoginFlowResponse,
|
||||||
val isLoginAndRegistrationSupported: Boolean
|
val isLoginAndRegistrationSupported: Boolean,
|
||||||
|
val homeServerUrl: String
|
||||||
) : LoginFlowResult()
|
) : LoginFlowResult()
|
||||||
|
|
||||||
object OutdatedHomeserver : LoginFlowResult()
|
object OutdatedHomeserver : LoginFlowResult()
|
||||||
|
|
|
@ -36,7 +36,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||||
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
||||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody"))
|
||||||
|
|
||||||
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ interface ContentUploadStateTracker {
|
||||||
|
|
||||||
fun untrack(key: String, updateListener: UpdateListener)
|
fun untrack(key: String, updateListener: UpdateListener)
|
||||||
|
|
||||||
|
fun clear()
|
||||||
|
|
||||||
interface UpdateListener {
|
interface UpdateListener {
|
||||||
fun onUpdate(state: State)
|
fun onUpdate(state: State)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,16 @@ interface RoomDirectoryService {
|
||||||
/**
|
/**
|
||||||
* Get rooms from directory
|
* Get rooms from directory
|
||||||
*/
|
*/
|
||||||
fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
fun getPublicRooms(server: String?,
|
||||||
|
publicRoomsParams: PublicRoomsParams,
|
||||||
|
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join a room by id
|
* Join a room by id
|
||||||
*/
|
*/
|
||||||
fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable
|
fun joinRoom(roomId: String,
|
||||||
|
reason: String? = null,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the overall metadata about protocols supported by the homeserver.
|
* Fetches the overall metadata about protocols supported by the homeserver.
|
||||||
|
|
|
@ -30,14 +30,17 @@ interface RoomService {
|
||||||
/**
|
/**
|
||||||
* Create a room asynchronously
|
* Create a room asynchronously
|
||||||
*/
|
*/
|
||||||
fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable
|
fun createRoom(createRoomParams: CreateRoomParams,
|
||||||
|
callback: MatrixCallback<String>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join a room by id
|
* Join a room by id
|
||||||
* @param roomId the roomId of the room to join
|
* @param roomId the roomId of the room to join
|
||||||
|
* @param reason optional reason for joining the room
|
||||||
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
|
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
|
||||||
*/
|
*/
|
||||||
fun joinRoom(roomId: String,
|
fun joinRoom(roomId: String,
|
||||||
|
reason: String? = null,
|
||||||
viaServers: List<String> = emptyList(),
|
viaServers: List<String> = emptyList(),
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
@ -54,8 +57,21 @@ interface RoomService {
|
||||||
*/
|
*/
|
||||||
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a live list of Breadcrumbs
|
||||||
|
* @return the [LiveData] of [RoomSummary]
|
||||||
|
*/
|
||||||
|
fun liveBreadcrumbs(): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the Matrix SDK that a room is displayed.
|
||||||
|
* The SDK will update the breadcrumbs in the user account data
|
||||||
|
*/
|
||||||
|
fun onRoomDisplayed(roomId: String): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all rooms as read
|
* Mark all rooms as read
|
||||||
*/
|
*/
|
||||||
fun markAllAsRead(roomIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
|
fun markAllAsRead(roomIds: List<String>,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,16 +52,21 @@ interface MembershipService {
|
||||||
/**
|
/**
|
||||||
* Invite a user in the room
|
* Invite a user in the room
|
||||||
*/
|
*/
|
||||||
fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable
|
fun invite(userId: String,
|
||||||
|
reason: String? = null,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the room, or accept an invitation.
|
* Join the room, or accept an invitation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun join(viaServers: List<String> = emptyList(), callback: MatrixCallback<Unit>): Cancelable
|
fun join(reason: String? = null,
|
||||||
|
viaServers: List<String> = emptyList(),
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leave the room, or reject an invitation.
|
* Leave the room, or reject an invitation.
|
||||||
*/
|
*/
|
||||||
fun leave(callback: MatrixCallback<Unit>): Cancelable
|
fun leave(reason: String? = null,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,13 @@ import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class RoomMember(
|
data class RoomMember(
|
||||||
@Json(name = "membership") val membership: Membership,
|
@Json(name = "membership") val membership: Membership,
|
||||||
|
@Json(name = "reason") val reason: String? = null,
|
||||||
@Json(name = "displayname") val displayName: String? = null,
|
@Json(name = "displayname") val displayName: String? = null,
|
||||||
@Json(name = "avatar_url") val avatarUrl: String? = null,
|
@Json(name = "avatar_url") val avatarUrl: String? = null,
|
||||||
@Json(name = "is_direct") val isDirect: Boolean = false,
|
@Json(name = "is_direct") val isDirect: Boolean = false,
|
||||||
@Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null,
|
@Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null,
|
||||||
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
|
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
|
||||||
)
|
) {
|
||||||
|
val safeReason
|
||||||
|
get() = reason?.takeIf { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ interface RelationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a reaction (emoji) to the targetedEvent.
|
* Sends a reaction (emoji) to the targetedEvent.
|
||||||
|
* It has no effect if the user has already added the same reaction to the event.
|
||||||
* @param targetEventId the id of the event being reacted
|
* @param targetEventId the id of the event being reacted
|
||||||
* @param reaction the reaction (preferably emoji)
|
* @param reaction the reaction (preferably emoji)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.Versions
|
import im.vector.matrix.android.api.auth.data.Versions
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
|
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||||
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
|
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
|
||||||
import im.vector.matrix.android.internal.auth.registration.*
|
import im.vector.matrix.android.internal.auth.registration.*
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
@ -31,6 +32,12 @@ import retrofit2.http.*
|
||||||
*/
|
*/
|
||||||
internal interface AuthAPI {
|
internal interface AuthAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Riot config file
|
||||||
|
*/
|
||||||
|
@GET("config.json")
|
||||||
|
fun getRiotConfig(): Call<RiotConfig>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the version information of the homeserver
|
* Get the version information of the homeserver
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,16 +16,19 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.api.auth.data.*
|
import im.vector.matrix.android.api.auth.data.*
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
|
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
||||||
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
||||||
|
@ -40,6 +43,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
private val okHttpClient: Lazy<OkHttpClient>,
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
@ -84,7 +88,12 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
{
|
{
|
||||||
if (it is LoginFlowResult.Success) {
|
if (it is LoginFlowResult.Success) {
|
||||||
// The homeserver exists and up to date, keep the config
|
// The homeserver exists and up to date, keep the config
|
||||||
pendingSessionData = PendingSessionData(homeServerConnectionConfig)
|
// Homeserver url may have been changed, if it was a Riot url
|
||||||
|
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||||
|
)
|
||||||
|
|
||||||
|
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||||
}
|
}
|
||||||
callback.onSuccess(it)
|
callback.onSuccess(it)
|
||||||
|
@ -97,20 +106,71 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
.toCancelable()
|
.toCancelable()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) {
|
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
|
return withContext(coroutineDispatchers.io) {
|
||||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
// First check the homeserver version
|
// First check the homeserver version
|
||||||
val versions = executeRequest<Versions> {
|
runCatching {
|
||||||
|
executeRequest<Versions> {
|
||||||
apiCall = authAPI.versions()
|
apiCall = authAPI.versions()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.map { versions ->
|
||||||
|
// Ok, it seems that the homeserver url is valid
|
||||||
|
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
it
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (it is Failure.OtherServerError
|
||||||
|
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||||
|
// It's maybe a Riot url?
|
||||||
|
getRiotLoginFlowInternal(homeServerConnectionConfig)
|
||||||
|
} else {
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (versions.isSupportedBySdk()) {
|
private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
|
// Ok, try to get the config.json file of a RiotWeb client
|
||||||
|
val riotConfig = executeRequest<RiotConfig> {
|
||||||
|
apiCall = authAPI.getRiotConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) {
|
||||||
|
// Ok, good sign, we got a default hs url
|
||||||
|
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl)
|
||||||
|
)
|
||||||
|
|
||||||
|
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||||
|
|
||||||
|
val versions = executeRequest<Versions> {
|
||||||
|
apiCall = newAuthAPI.versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl)
|
||||||
|
} else {
|
||||||
|
// Config exists, but there is no default homeserver url (ex: https://riot.im/app)
|
||||||
|
throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||||
|
return if (versions.isSupportedBySdk()) {
|
||||||
// Get the login flow
|
// Get the login flow
|
||||||
val loginFlowResponse = executeRequest<LoginFlowResponse> {
|
val loginFlowResponse = executeRequest<LoginFlowResponse> {
|
||||||
apiCall = authAPI.getLoginFlows()
|
apiCall = authAPI.getLoginFlows()
|
||||||
}
|
}
|
||||||
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk())
|
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
||||||
} else {
|
} else {
|
||||||
// Not supported
|
// Not supported
|
||||||
LoginFlowResult.OutdatedHomeserver
|
LoginFlowResult.OutdatedHomeserver
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.auth.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RiotConfig(
|
||||||
|
// There are plenty of other elements in the file config.json of a RiotWeb client, but for the moment only one is interesting
|
||||||
|
// Ex: "brand", "branding", etc.
|
||||||
|
@Json(name = "default_hs_url")
|
||||||
|
val defaultHomeServerUrl: String?
|
||||||
|
)
|
|
@ -19,16 +19,14 @@ package im.vector.matrix.android.internal.crypto.store.db.query
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity
|
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a room
|
* Get or create a room
|
||||||
*/
|
*/
|
||||||
internal fun CryptoRoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): CryptoRoomEntity {
|
internal fun CryptoRoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): CryptoRoomEntity {
|
||||||
return getById(realm, roomId)
|
return getById(realm, roomId) ?: realm.createObject(roomId)
|
||||||
?: let {
|
|
||||||
realm.createObject(CryptoRoomEntity::class.java, roomId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,18 +20,20 @@ import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey
|
import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a device info
|
* Get or create a device info
|
||||||
*/
|
*/
|
||||||
internal fun DeviceInfoEntity.Companion.getOrCreate(realm: Realm, userId: String, deviceId: String): DeviceInfoEntity {
|
internal fun DeviceInfoEntity.Companion.getOrCreate(realm: Realm, userId: String, deviceId: String): DeviceInfoEntity {
|
||||||
|
val key = DeviceInfoEntity.createPrimaryKey(userId, deviceId)
|
||||||
|
|
||||||
return realm.where<DeviceInfoEntity>()
|
return realm.where<DeviceInfoEntity>()
|
||||||
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, key)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
?: let {
|
?: realm.createObject<DeviceInfoEntity>(key)
|
||||||
realm.createObject(DeviceInfoEntity::class.java, DeviceInfoEntity.createPrimaryKey(userId, deviceId)).apply {
|
.apply {
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.store.db.query
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
|
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,9 +29,7 @@ internal fun UserEntity.Companion.getOrCreate(realm: Realm, userId: String): Use
|
||||||
return realm.where<UserEntity>()
|
return realm.where<UserEntity>()
|
||||||
.equalTo(UserEntityFields.USER_ID, userId)
|
.equalTo(UserEntityFields.USER_ID, userId)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
?: let {
|
?: realm.createObject(userId)
|
||||||
realm.createObject(UserEntity::class.java, userId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
internal open class BreadcrumbsEntity(
|
||||||
|
var recentRoomIds: RealmList<String> = RealmList()
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -38,7 +38,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||||
var readMarkerId: String? = null,
|
var readMarkerId: String? = null,
|
||||||
var hasUnreadMessages: Boolean = false,
|
var hasUnreadMessages: Boolean = false,
|
||||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
var tags: RealmList<RoomTagEntity> = RealmList(),
|
||||||
var userDrafts: UserDraftsEntity? = null
|
var userDrafts: UserDraftsEntity? = null,
|
||||||
|
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
@ -59,5 +60,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||||
versioningStateStr = value.name
|
versioningStateStr = value.name
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object
|
companion object {
|
||||||
|
const val NOT_IN_BREADCRUMBS = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule
|
||||||
SyncEntity::class,
|
SyncEntity::class,
|
||||||
UserEntity::class,
|
UserEntity::class,
|
||||||
IgnoredUserEntity::class,
|
IgnoredUserEntity::class,
|
||||||
|
BreadcrumbsEntity::class,
|
||||||
EventAnnotationsSummaryEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
EditAggregatedSummaryEntity::class,
|
EditAggregatedSummaryEntity::class,
|
||||||
|
|
|
@ -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.matrix.android.internal.database.query
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun BreadcrumbsEntity.Companion.get(realm: Realm): BreadcrumbsEntity? {
|
||||||
|
return realm.where<BreadcrumbsEntity>().findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BreadcrumbsEntity.Companion.getOrCreate(realm: Realm): BreadcrumbsEntity {
|
||||||
|
return get(realm) ?: realm.createObject()
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadMarkerEntityFields
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<ReadMarkerEntity> {
|
internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<ReadMarkerEntity> {
|
||||||
|
@ -28,6 +29,5 @@ internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): Rea
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ReadMarkerEntity.Companion.getOrCreate(realm: Realm, roomId: String): ReadMarkerEntity {
|
internal fun ReadMarkerEntity.Companion.getOrCreate(realm: Realm, roomId: String): ReadMarkerEntity {
|
||||||
return where(realm, roomId).findFirst()
|
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||||
?: realm.createObject(ReadMarkerEntity::class.java, roomId)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntityFields
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
|
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
|
||||||
|
@ -44,7 +45,8 @@ internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId
|
||||||
|
|
||||||
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
|
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
|
||||||
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
||||||
?: realm.createObject(ReadReceiptEntity::class.java, buildPrimaryKey(roomId, userId)).apply {
|
?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId))
|
||||||
|
.apply {
|
||||||
this.roomId = roomId
|
this.roomId = roomId
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ 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.RealmResults
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
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> {
|
||||||
|
@ -32,8 +33,7 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomSummaryEntity {
|
internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomSummaryEntity {
|
||||||
return where(realm, roomId).findFirst()
|
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||||
?: realm.createObject(RoomSummaryEntity::class.java, roomId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> {
|
internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
|
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
|
||||||
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
|
||||||
|
@ -34,6 +35,7 @@ object MoshiProvider {
|
||||||
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
|
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
|
||||||
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
|
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
|
||||||
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
|
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
|
||||||
|
.registerSubtype(UserAccountDataBreadcrumbs::class.java, UserAccountData.TYPE_BREADCRUMBS)
|
||||||
)
|
)
|
||||||
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
||||||
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
|
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import com.squareup.moshi.JsonDataException
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import com.squareup.moshi.JsonEncodingException
|
||||||
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
|
@ -106,6 +107,9 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
|
||||||
} catch (ex: JsonDataException) {
|
} catch (ex: JsonDataException) {
|
||||||
// This is not a MatrixError
|
// This is not a MatrixError
|
||||||
Timber.w("The error returned by the server is not a MatrixError")
|
Timber.w("The error returned by the server is not a MatrixError")
|
||||||
|
} catch (ex: JsonEncodingException) {
|
||||||
|
// This is not a MatrixError, HTML code?
|
||||||
|
Timber.w("The error returned by the server is not a MatrixError, probably HTML string")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Failure.OtherServerError(errorBodyStr, httpCode)
|
return Failure.OtherServerError(errorBodyStr, httpCode)
|
||||||
|
|
|
@ -42,6 +42,10 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
listeners.clear()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun setFailure(key: String, throwable: Throwable) {
|
internal fun setFailure(key: String, throwable: Throwable) {
|
||||||
val failure = ContentUploadStateTracker.State.Failure(throwable)
|
val failure = ContentUploadStateTracker.State.Failure(throwable)
|
||||||
updateState(key, failure)
|
updateState(key, failure)
|
||||||
|
|
|
@ -44,9 +44,9 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinRoom(roomId: String, callback: MatrixCallback<Unit>): Cancelable {
|
override fun joinRoom(roomId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
return joinRoomTask
|
return joinRoomTask
|
||||||
.configureWith(JoinRoomTask.Params(roomId)) {
|
.configureWith(JoinRoomTask.Params(roomId, reason)) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||||
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 io.realm.Realm
|
import io.realm.Realm
|
||||||
|
@ -43,6 +44,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
private val createRoomTask: CreateRoomTask,
|
private val createRoomTask: CreateRoomTask,
|
||||||
private val joinRoomTask: JoinRoomTask,
|
private val joinRoomTask: JoinRoomTask,
|
||||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||||
|
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||||
private val roomFactory: RoomFactory,
|
private val roomFactory: RoomFactory,
|
||||||
private val taskExecutor: TaskExecutor) : RoomService {
|
private val taskExecutor: TaskExecutor) : RoomService {
|
||||||
|
|
||||||
|
@ -75,9 +77,28 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
override fun liveBreadcrumbs(): LiveData<List<RoomSummary>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
RoomSummaryEntity.where(realm)
|
||||||
|
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||||
|
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||||
|
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||||
|
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||||
|
},
|
||||||
|
{ roomSummaryMapper.map(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRoomDisplayed(roomId: String): Cancelable {
|
||||||
|
return updateBreadcrumbsTask
|
||||||
|
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun joinRoom(roomId: String, reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
return joinRoomTask
|
return joinRoomTask
|
||||||
.configureWith(JoinRoomTask.Params(roomId, viaServers)) {
|
.configureWith(JoinRoomTask.Params(roomId, reason, viaServers)) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
|
|
@ -217,7 +217,7 @@ internal interface RoomAPI {
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/join")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/join")
|
||||||
fun join(@Path("roomId") roomId: String,
|
fun join(@Path("roomId") roomId: String,
|
||||||
@Query("server_name") viaServers: List<String>,
|
@Query("server_name") viaServers: List<String>,
|
||||||
@Body params: Map<String, String>): Call<Unit>
|
@Body params: Map<String, String?>): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leave the given room.
|
* Leave the given room.
|
||||||
|
@ -227,7 +227,7 @@ internal interface RoomAPI {
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave")
|
||||||
fun leave(@Path("roomId") roomId: String,
|
fun leave(@Path("roomId") roomId: String,
|
||||||
@Body params: Map<String, String>): Call<Unit>
|
@Body params: Map<String, String?>): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room.
|
* Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room.
|
||||||
|
|
|
@ -83,8 +83,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable {
|
override fun invite(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = InviteTask.Params(roomId, userId)
|
val params = InviteTask.Params(roomId, userId, reason)
|
||||||
return inviteTask
|
return inviteTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
@ -92,8 +92,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun join(viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
override fun join(reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = JoinRoomTask.Params(roomId, viaServers)
|
val params = JoinRoomTask.Params(roomId, reason, viaServers)
|
||||||
return joinTask
|
return joinTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
@ -101,8 +101,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun leave(callback: MatrixCallback<Unit>): Cancelable {
|
override fun leave(reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = LeaveRoomTask.Params(roomId)
|
val params = LeaveRoomTask.Params(roomId, reason)
|
||||||
return leaveRoomTask
|
return leaveRoomTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
|
|
@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class InviteBody(
|
data class InviteBody(
|
||||||
@Json(name = "user_id") val userId: String
|
@Json(name = "user_id") val userId: String,
|
||||||
|
@Json(name = "reason") val reason: String?
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,8 @@ import javax.inject.Inject
|
||||||
internal interface InviteTask : Task<InviteTask.Params, Unit> {
|
internal interface InviteTask : Task<InviteTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val userId: String
|
val userId: String,
|
||||||
|
val reason: String?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAP
|
||||||
|
|
||||||
override suspend fun execute(params: InviteTask.Params) {
|
override suspend fun execute(params: InviteTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest {
|
||||||
val body = InviteBody(params.userId)
|
val body = InviteBody(params.userId, params.reason)
|
||||||
apiCall = roomAPI.invite(params.roomId, body)
|
apiCall = roomAPI.invite(params.roomId, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import javax.inject.Inject
|
||||||
internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
val reason: String?,
|
||||||
val viaServers: List<String> = emptyList()
|
val viaServers: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -43,7 +44,7 @@ internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: Room
|
||||||
|
|
||||||
override suspend fun execute(params: JoinRoomTask.Params) {
|
override suspend fun execute(params: JoinRoomTask.Params) {
|
||||||
executeRequest<Unit> {
|
executeRequest<Unit> {
|
||||||
apiCall = roomAPI.join(params.roomId, params.viaServers, HashMap())
|
apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason))
|
||||||
}
|
}
|
||||||
val roomId = params.roomId
|
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)
|
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
||||||
|
|
|
@ -23,7 +23,8 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
|
internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String
|
val roomId: String,
|
||||||
|
val reason: String?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: Roo
|
||||||
|
|
||||||
override suspend fun execute(params: LeaveRoomTask.Params) {
|
override suspend fun execute(params: LeaveRoomTask.Params) {
|
||||||
return executeRequest {
|
return executeRequest {
|
||||||
apiCall = roomAPI.leave(params.roomId, HashMap())
|
apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,13 @@ import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||||
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.EventAnnotationsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
|
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
|
||||||
|
@ -44,6 +47,7 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEvent
|
||||||
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.CancelableWork
|
import im.vector.matrix.android.internal.util.CancelableWork
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -54,6 +58,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||||
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor)
|
private val taskExecutor: TaskExecutor)
|
||||||
: RelationService {
|
: RelationService {
|
||||||
|
@ -64,11 +69,27 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
|
override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
|
||||||
|
return if (monarchy
|
||||||
|
.fetchCopyMap(
|
||||||
|
{ realm ->
|
||||||
|
TimelineEventEntity.where(realm, roomId, targetEventId).findFirst()
|
||||||
|
},
|
||||||
|
{ entity, _ ->
|
||||||
|
timelineEventMapper.map(entity)
|
||||||
|
})
|
||||||
|
?.annotations
|
||||||
|
?.reactionsSummary
|
||||||
|
.orEmpty()
|
||||||
|
.none { it.addedByMe && it.key == reaction }) {
|
||||||
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
val sendRelationWork = createSendEventWork(event, true)
|
val sendRelationWork = createSendEventWork(event, true)
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
|
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
|
||||||
return CancelableWork(context, sendRelationWork.id)
|
CancelableWork(context, sendRelationWork.id)
|
||||||
|
} else {
|
||||||
|
Timber.w("Reaction already added")
|
||||||
|
NoOpCancellable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun undoReaction(targetEventId: String, reaction: String): Cancelable {
|
override fun undoReaction(targetEventId: String, reaction: String): Cancelable {
|
||||||
|
|
|
@ -78,7 +78,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
val htmlText = renderer.render(document)
|
val htmlText = renderer.render(document)
|
||||||
|
|
||||||
if (isFormattedTextPertinent(source, htmlText)) {
|
if (isFormattedTextPertinent(source, htmlText)) {
|
||||||
return TextContent(source, htmlText)
|
return TextContent(text.toString(), htmlText)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Try to detect pills
|
// Try to detect pills
|
||||||
|
|
|
@ -289,6 +289,9 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addListener(listener: Timeline.Listener) = synchronized(listeners) {
|
override fun addListener(listener: Timeline.Listener) = synchronized(listeners) {
|
||||||
|
if (listeners.contains(listener)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
listeners.add(listener).also {
|
listeners.add(listener).also {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.SaveBreadcrumbsTask
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask
|
import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
@ -44,6 +45,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
private val savePushRulesTask: SavePushRulesTask,
|
private val savePushRulesTask: SavePushRulesTask,
|
||||||
private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
|
private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
|
||||||
|
private val saveBreadcrumbsTask: SaveBreadcrumbsTask,
|
||||||
private val taskExecutor: TaskExecutor) {
|
private val taskExecutor: TaskExecutor) {
|
||||||
|
|
||||||
suspend fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) {
|
suspend fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) {
|
||||||
|
@ -52,6 +54,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||||
is UserAccountDataDirectMessages -> handleDirectChatRooms(it)
|
is UserAccountDataDirectMessages -> handleDirectChatRooms(it)
|
||||||
is UserAccountDataPushRules -> handlePushRules(it)
|
is UserAccountDataPushRules -> handlePushRules(it)
|
||||||
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it)
|
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it)
|
||||||
|
is UserAccountDataBreadcrumbs -> handleBreadcrumbs(it)
|
||||||
is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}")
|
is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}")
|
||||||
else -> error("Missing code here!")
|
else -> error("Missing code here!")
|
||||||
}
|
}
|
||||||
|
@ -130,4 +133,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
// TODO If not initial sync, we should execute a init sync
|
// TODO If not initial sync, we should execute a init sync
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleBreadcrumbs(userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) {
|
||||||
|
saveBreadcrumbsTask
|
||||||
|
.configureWith(SaveBreadcrumbsTask.Params(userAccountDataBreadcrumbs.content.recentRoomIds))
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ internal abstract class UserAccountData {
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list"
|
const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list"
|
||||||
const val TYPE_DIRECT_MESSAGES = "m.direct"
|
const val TYPE_DIRECT_MESSAGES = "m.direct"
|
||||||
|
const val TYPE_BREADCRUMBS = "im.vector.setting.breadcrumbs" // Was previously "im.vector.riot.breadcrumb_rooms"
|
||||||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||||
const val TYPE_WIDGETS = "m.widgets"
|
const val TYPE_WIDGETS = "m.widgets"
|
||||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||||
|
|
|
@ -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.sync.model.accountdata
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class UserAccountDataBreadcrumbs(
|
||||||
|
@Json(name = "type") override val type: String = TYPE_BREADCRUMBS,
|
||||||
|
@Json(name = "content") val content: BreadcrumbsContent
|
||||||
|
) : UserAccountData()
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class BreadcrumbsContent(
|
||||||
|
@Json(name = "recent_rooms") val recentRoomIds: List<String> = emptyList()
|
||||||
|
)
|
|
@ -35,5 +35,11 @@ internal abstract class AccountDataModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask
|
abstract fun bindUpdateUserAccountDataTask(task: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSaveBreadcrumbsTask(task: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUpdateBreadcrumsTask(task: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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 com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
|
||||||
|
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.query.getOrCreate
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
|
import io.realm.RealmList
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the Breadcrumbs roomId list in DB, either from the sync, or updated locally
|
||||||
|
*/
|
||||||
|
internal interface SaveBreadcrumbsTask : Task<SaveBreadcrumbsTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val recentRoomIds: List<String>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSaveBreadcrumbsTask @Inject constructor(
|
||||||
|
private val monarchy: Monarchy
|
||||||
|
) : SaveBreadcrumbsTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: SaveBreadcrumbsTask.Params) {
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
|
// Get or create a breadcrumbs entity
|
||||||
|
val entity = BreadcrumbsEntity.getOrCreate(realm)
|
||||||
|
|
||||||
|
// And save the new received list
|
||||||
|
entity.recentRoomIds = RealmList<String>().apply { addAll(params.recentRoomIds) }
|
||||||
|
|
||||||
|
// Update the room summaries
|
||||||
|
// Reset all the indexes...
|
||||||
|
RoomSummaryEntity.where(realm)
|
||||||
|
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||||
|
.findAll()
|
||||||
|
.forEach {
|
||||||
|
it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and apply new indexes
|
||||||
|
params.recentRoomIds.forEachIndexed { index, roomId ->
|
||||||
|
RoomSummaryEntity.where(realm, roomId)
|
||||||
|
.findFirst()
|
||||||
|
?.breadcrumbsIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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 com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.get
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
// Use the same arbitrary value than Riot-Web
|
||||||
|
private const val MAX_BREADCRUMBS_ROOMS_NUMBER = 20
|
||||||
|
|
||||||
|
internal interface UpdateBreadcrumbsTask : Task<UpdateBreadcrumbsTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val newTopRoomId: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultUpdateBreadcrumbsTask @Inject constructor(
|
||||||
|
private val saveBreadcrumbsTask: SaveBreadcrumbsTask,
|
||||||
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
|
private val monarchy: Monarchy
|
||||||
|
) : UpdateBreadcrumbsTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: UpdateBreadcrumbsTask.Params) {
|
||||||
|
val newBreadcrumbs =
|
||||||
|
// Get the breadcrumbs entity, if any
|
||||||
|
monarchy.fetchCopied { BreadcrumbsEntity.get(it) }
|
||||||
|
?.recentRoomIds
|
||||||
|
?.apply {
|
||||||
|
// Modify the list to add the newTopRoomId first
|
||||||
|
// Ensure the newTopRoomId is not already in the list
|
||||||
|
remove(params.newTopRoomId)
|
||||||
|
// Add the newTopRoomId at first position
|
||||||
|
add(0, params.newTopRoomId)
|
||||||
|
}
|
||||||
|
?.take(MAX_BREADCRUMBS_ROOMS_NUMBER)
|
||||||
|
?: listOf(params.newTopRoomId)
|
||||||
|
|
||||||
|
// Update the DB locally, do not wait for the sync
|
||||||
|
saveBreadcrumbsTask.execute(SaveBreadcrumbsTask.Params(newBreadcrumbs))
|
||||||
|
|
||||||
|
// FIXME It can remove the previous breadcrumbs, if not synced yet
|
||||||
|
// And update account data
|
||||||
|
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.BreadcrumbsParams(
|
||||||
|
breadcrumbsContent = BreadcrumbsContent(newBreadcrumbs)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -38,6 +39,15 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
||||||
return directMessages
|
return directMessages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class BreadcrumbsParams(override val type: String = UserAccountData.TYPE_BREADCRUMBS,
|
||||||
|
private val breadcrumbsContent: BreadcrumbsContent
|
||||||
|
) : Params {
|
||||||
|
|
||||||
|
override fun getData(): Any {
|
||||||
|
return breadcrumbsContent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
||||||
|
|
|
@ -2,8 +2,19 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="notice_room_invite_no_invitee_with_reason">%1$s\'s invitation. Reason: %2$s</string>
|
||||||
|
<string name="notice_room_invite_with_reason">%1$s invited %2$s. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_invite_you_with_reason">%1$s invited you. Reason: %2$s</string>
|
||||||
|
<string name="notice_room_join_with_reason">%1$s joined. Reason: %2$s</string>
|
||||||
|
<string name="notice_room_leave_with_reason">%1$s left. Reason: %2$s</string>
|
||||||
|
<string name="notice_room_reject_with_reason">%1$s rejected the invitation. Reason: %2$s</string>
|
||||||
|
<string name="notice_room_kick_with_reason">%1$s kicked %2$s. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_unban_with_reason">%1$s unbanned %2$s. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_ban_with_reason">%1$s banned %2$s. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_invite_with_reason">%1$s sent an invitation to %2$s to join the room. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_revoked_invite_with_reason">%1$s revoked the invitation for %2$s to join the room. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_registered_invite_with_reason">%1$s accepted the invitation for %2$s. Reason: %3$s</string>
|
||||||
|
<string name="notice_room_withdraw_with_reason">%1$s withdrew %2$s\'s invitation. Reason: %3$s</string>
|
||||||
|
|
||||||
<string name="no_network_indicator">There is no network connection right now</string>
|
<string name="no_network_indicator">There is no network connection right now</string>
|
||||||
</resources>
|
</resources>
|
|
@ -82,3 +82,6 @@ layout_constraintLeft_
|
||||||
### Will crash on API < 21. Use ?colorAccent instead
|
### Will crash on API < 21. Use ?colorAccent instead
|
||||||
\?android:colorAccent
|
\?android:colorAccent
|
||||||
\?android:attr/colorAccent
|
\?android:attr/colorAccent
|
||||||
|
|
||||||
|
### Use androidx.recyclerview.widget.RecyclerView because EpoxyRecyclerViews add behavior we do not want to
|
||||||
|
<com\.airbnb\.epoxy\.EpoxyRecyclerView
|
||||||
|
|
|
@ -15,8 +15,8 @@ androidExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 9
|
ext.versionMinor = 10
|
||||||
ext.versionPatch = 1
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
|
|
@ -20,16 +20,22 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import im.vector.matrix.android.api.crypto.getAllVerificationEmojis
|
import im.vector.matrix.android.api.crypto.getAllVerificationEmojis
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.*
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
|
|
||||||
class DebugSasEmojiActivity : AppCompatActivity() {
|
class DebugSasEmojiActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.fragment_generic_recycler_epoxy)
|
setContentView(R.layout.fragment_generic_recycler)
|
||||||
|
|
||||||
val controller = SasEmojiController()
|
val controller = SasEmojiController()
|
||||||
epoxyRecyclerView.setController(controller)
|
recyclerView.configureWith(controller)
|
||||||
controller.setData(SasState(getAllVerificationEmojis()))
|
controller.setData(SasState(getAllVerificationEmojis()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ import im.vector.riotx.core.di.DaggerVectorComponent
|
||||||
import im.vector.riotx.core.di.HasVectorInjector
|
import im.vector.riotx.core.di.HasVectorInjector
|
||||||
import im.vector.riotx.core.di.VectorComponent
|
import im.vector.riotx.core.di.VectorComponent
|
||||||
import im.vector.riotx.core.extensions.configureAndStart
|
import im.vector.riotx.core.extensions.configureAndStart
|
||||||
import im.vector.riotx.core.utils.initKnownEmojiHashSet
|
import im.vector.riotx.core.rx.setupRxPlugin
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
|
@ -55,8 +55,7 @@ import im.vector.riotx.features.version.VersionProvider
|
||||||
import im.vector.riotx.push.fcm.FcmHelper
|
import im.vector.riotx.push.fcm.FcmHelper
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.*
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
||||||
|
@ -79,14 +78,13 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
lateinit var vectorComponent: VectorComponent
|
lateinit var vectorComponent: VectorComponent
|
||||||
private var fontThreadHandler: Handler? = null
|
private var fontThreadHandler: Handler? = null
|
||||||
|
|
||||||
// var slowMode = false
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
appContext = this
|
appContext = this
|
||||||
vectorComponent = DaggerVectorComponent.factory().create(this)
|
vectorComponent = DaggerVectorComponent.factory().create(this)
|
||||||
vectorComponent.inject(this)
|
vectorComponent.inject(this)
|
||||||
vectorUncaughtExceptionHandler.activate(this)
|
vectorUncaughtExceptionHandler.activate(this)
|
||||||
|
setupRxPlugin()
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
|
@ -138,7 +136,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
})
|
})
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
|
||||||
// This should be done as early as possible
|
// This should be done as early as possible
|
||||||
initKnownEmojiHashSet(appContext)
|
// initKnownEmojiHashSet(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
||||||
|
|
|
@ -33,10 +33,12 @@ import im.vector.riotx.features.home.LoadingFragment
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
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.breadcrumbs.BreadcrumbsFragment
|
||||||
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.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.login.*
|
import im.vector.riotx.features.login.*
|
||||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||||
|
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||||
|
@ -249,4 +251,14 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(PublicRoomsFragment::class)
|
@FragmentKey(PublicRoomsFragment::class)
|
||||||
fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment
|
fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(BreadcrumbsFragment::class)
|
||||||
|
fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(EmojiChooserFragment::class)
|
||||||
|
fun bindEmojiChooserFragment(fragment: EmojiChooserFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedVie
|
||||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomSharedActionViewModel
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomSharedActionViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||||
import im.vector.riotx.features.login.LoginSharedActionViewModel
|
import im.vector.riotx.features.login.LoginSharedActionViewModel
|
||||||
|
@ -118,4 +119,9 @@ interface ViewModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(LoginSharedActionViewModel::class)
|
@ViewModelKey(LoginSharedActionViewModel::class)
|
||||||
fun bindLoginSharedActionViewModel(viewModel: LoginSharedActionViewModel): ViewModel
|
fun bindLoginSharedActionViewModel(viewModel: LoginSharedActionViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(RoomDetailSharedActionViewModel::class)
|
||||||
|
fun bindRoomDetailSharedActionViewModel(viewModel: RoomDetailSharedActionViewModel): ViewModel
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import java.net.HttpURLConnection
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -76,6 +77,15 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is Failure.OtherServerError -> {
|
||||||
|
when (throwable.httpCode) {
|
||||||
|
HttpURLConnection.HTTP_NOT_FOUND ->
|
||||||
|
// homeserver not found
|
||||||
|
stringProvider.getString(R.string.login_error_no_homeserver_found)
|
||||||
|
else ->
|
||||||
|
throwable.localizedMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> throwable.localizedMessage
|
else -> throwable.localizedMessage
|
||||||
}
|
}
|
||||||
?: stringProvider.getString(R.string.unknown_error)
|
?: stringProvider.getString(R.string.unknown_error)
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.extensions
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a Vertical LinearLayout Manager to the recyclerView and set the adapter from the epoxy controller
|
||||||
|
*/
|
||||||
|
fun RecyclerView.configureWith(epoxyController: EpoxyController,
|
||||||
|
itemAnimator: RecyclerView.ItemAnimator? = null,
|
||||||
|
showDivider: Boolean = false,
|
||||||
|
hasFixedSize: Boolean = true) {
|
||||||
|
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||||
|
itemAnimator?.let { this.itemAnimator = it }
|
||||||
|
if (showDivider) {
|
||||||
|
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||||
|
}
|
||||||
|
setHasFixedSize(hasFixedSize)
|
||||||
|
adapter = epoxyController.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To call from Fragment.onDestroyView()
|
||||||
|
*/
|
||||||
|
fun RecyclerView.cleanup() {
|
||||||
|
adapter = null
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
View.inflate(context, R.layout.view_state, this)
|
View.inflate(context, R.layout.view_state, this)
|
||||||
layoutParams = LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||||
errorRetryView.setOnClickListener {
|
errorRetryView.setOnClickListener {
|
||||||
eventCallback?.onRetryClicked()
|
eventCallback?.onRetryClicked()
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
||||||
injectWith(screenComponent)
|
injectWith(screenComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun injectWith(screenComponent: ScreenComponent) = Unit
|
protected open fun injectWith(injector: ScreenComponent) = Unit
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
mvrxViewIdProperty.restoreFrom(savedInstanceState)
|
mvrxViewIdProperty.restoreFrom(savedInstanceState)
|
||||||
|
|
|
@ -135,6 +135,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
restorables.forEach { it.onSaveInstanceState(outState) }
|
restorables.forEach { it.onSaveInstanceState(outState) }
|
||||||
|
restorables.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||||
|
|
35
vector/src/main/java/im/vector/riotx/core/rx/Rx.kt
Normal file
35
vector/src/main/java/im/vector/riotx/core/rx/Rx.kt
Normal file
|
@ -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.riotx.core.rx
|
||||||
|
|
||||||
|
import im.vector.riotx.BuildConfig
|
||||||
|
import io.reactivex.plugins.RxJavaPlugins
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure unhandled Rx error does not crash the app in production
|
||||||
|
*/
|
||||||
|
fun setupRxPlugin() {
|
||||||
|
RxJavaPlugins.setErrorHandler { throwable ->
|
||||||
|
Timber.e(throwable, "RxError")
|
||||||
|
|
||||||
|
// Avoid crash in production
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,13 +16,6 @@
|
||||||
|
|
||||||
package im.vector.riotx.core.utils
|
package im.vector.riotx.core.utils
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import im.vector.riotx.R
|
|
||||||
import im.vector.riotx.features.reactions.EmojiDataSource
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
|
private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
|
||||||
|
@ -49,6 +42,7 @@ private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
|
||||||
"|\uD83C\uDCCF\uFE0F?" +
|
"|\uD83C\uDCCF\uFE0F?" +
|
||||||
"|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
|
"|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
|
||||||
|
|
||||||
|
/*
|
||||||
// A hashset from all supported emoji
|
// A hashset from all supported emoji
|
||||||
private var knownEmojiSet: HashSet<String>? = null
|
private var knownEmojiSet: HashSet<String>? = null
|
||||||
|
|
||||||
|
@ -56,7 +50,7 @@ fun initKnownEmojiHashSet(context: Context, done: (() -> Unit)? = null) {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
|
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
|
||||||
val moshi = Moshi.Builder().build()
|
val moshi = Moshi.Builder().build()
|
||||||
val jsonAdapter = moshi.adapter(EmojiDataSource.EmojiData::class.java)
|
val jsonAdapter = moshi.adapter(EmojiData::class.java)
|
||||||
val inputAsString = input.bufferedReader().use { it.readText() }
|
val inputAsString = input.bufferedReader().use { it.readText() }
|
||||||
val source = jsonAdapter.fromJson(inputAsString)
|
val source = jsonAdapter.fromJson(inputAsString)
|
||||||
knownEmojiSet = HashSet<String>().also {
|
knownEmojiSet = HashSet<String>().also {
|
||||||
|
@ -77,6 +71,7 @@ fun isSingleEmoji(string: String): Boolean {
|
||||||
}
|
}
|
||||||
return knownEmojiSet?.contains(string) ?: false
|
return knownEmojiSet?.contains(string) ?: false
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if a string contains emojis.
|
* Test if a string contains emojis.
|
||||||
|
|
|
@ -27,16 +27,19 @@ import im.vector.riotx.R
|
||||||
enum class Command(val command: String, val parameters: String, @StringRes val description: Int) {
|
enum class Command(val command: String, val parameters: String, @StringRes val description: Int) {
|
||||||
EMOTE("/me", "<message>", R.string.command_description_emote),
|
EMOTE("/me", "<message>", R.string.command_description_emote),
|
||||||
BAN_USER("/ban", "<user-id> [reason]", R.string.command_description_ban_user),
|
BAN_USER("/ban", "<user-id> [reason]", R.string.command_description_ban_user),
|
||||||
UNBAN_USER("/unban", "<user-id>", R.string.command_description_unban_user),
|
UNBAN_USER("/unban", "<user-id> [reason]", R.string.command_description_unban_user),
|
||||||
SET_USER_POWER_LEVEL("/op", "<user-id> [<power-level>]", R.string.command_description_op_user),
|
SET_USER_POWER_LEVEL("/op", "<user-id> [<power-level>]", R.string.command_description_op_user),
|
||||||
RESET_USER_POWER_LEVEL("/deop", "<user-id>", R.string.command_description_deop_user),
|
RESET_USER_POWER_LEVEL("/deop", "<user-id>", R.string.command_description_deop_user),
|
||||||
INVITE("/invite", "<user-id>", R.string.command_description_invite_user),
|
INVITE("/invite", "<user-id> [reason]", R.string.command_description_invite_user),
|
||||||
JOIN_ROOM("/join", "<room-alias>", R.string.command_description_join_room),
|
JOIN_ROOM("/join", "<room-alias> [reason]", R.string.command_description_join_room),
|
||||||
PART("/part", "<room-alias>", R.string.command_description_part_room),
|
PART("/part", "<room-alias> [reason]", R.string.command_description_part_room),
|
||||||
TOPIC("/topic", "<topic>", R.string.command_description_topic),
|
TOPIC("/topic", "<topic>", R.string.command_description_topic),
|
||||||
KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user),
|
KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user),
|
||||||
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
|
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
|
||||||
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
|
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
|
||||||
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
||||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler);
|
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler);
|
||||||
|
|
||||||
|
val length
|
||||||
|
get() = command.length + 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,29 +81,52 @@ object CommandParser {
|
||||||
ParsedCommand.SendEmote(message)
|
ParsedCommand.SendEmote(message)
|
||||||
}
|
}
|
||||||
Command.JOIN_ROOM.command -> {
|
Command.JOIN_ROOM.command -> {
|
||||||
val roomAlias = textMessage.substring(Command.JOIN_ROOM.command.length).trim()
|
if (messageParts.size >= 2) {
|
||||||
|
val roomAlias = messageParts[1]
|
||||||
|
|
||||||
if (roomAlias.isNotEmpty()) {
|
if (roomAlias.isNotEmpty()) {
|
||||||
ParsedCommand.JoinRoom(roomAlias)
|
ParsedCommand.JoinRoom(
|
||||||
|
roomAlias,
|
||||||
|
textMessage.substring(Command.JOIN_ROOM.length + roomAlias.length)
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.PART.command -> {
|
Command.PART.command -> {
|
||||||
val roomAlias = textMessage.substring(Command.PART.command.length).trim()
|
if (messageParts.size >= 2) {
|
||||||
|
val roomAlias = messageParts[1]
|
||||||
|
|
||||||
if (roomAlias.isNotEmpty()) {
|
if (roomAlias.isNotEmpty()) {
|
||||||
ParsedCommand.PartRoom(roomAlias)
|
ParsedCommand.PartRoom(
|
||||||
|
roomAlias,
|
||||||
|
textMessage.substring(Command.PART.length + roomAlias.length)
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.PART)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.PART)
|
ParsedCommand.ErrorSyntax(Command.PART)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.INVITE.command -> {
|
Command.INVITE.command -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
ParsedCommand.Invite(userId)
|
ParsedCommand.Invite(
|
||||||
|
userId,
|
||||||
|
textMessage.substring(Command.INVITE.length + userId.length)
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.INVITE)
|
ParsedCommand.ErrorSyntax(Command.INVITE)
|
||||||
}
|
}
|
||||||
|
@ -114,12 +137,14 @@ object CommandParser {
|
||||||
Command.KICK_USER.command -> {
|
Command.KICK_USER.command -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
|
||||||
val reason = textMessage.substring(Command.KICK_USER.command.length
|
|
||||||
+ 1
|
|
||||||
+ userId.length).trim()
|
|
||||||
|
|
||||||
ParsedCommand.KickUser(userId, reason)
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
|
ParsedCommand.KickUser(
|
||||||
|
userId,
|
||||||
|
textMessage.substring(Command.KICK_USER.length + userId.length)
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.KICK_USER)
|
ParsedCommand.ErrorSyntax(Command.KICK_USER)
|
||||||
}
|
}
|
||||||
|
@ -130,12 +155,14 @@ object CommandParser {
|
||||||
Command.BAN_USER.command -> {
|
Command.BAN_USER.command -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
|
||||||
val reason = textMessage.substring(Command.BAN_USER.command.length
|
|
||||||
+ 1
|
|
||||||
+ userId.length).trim()
|
|
||||||
|
|
||||||
ParsedCommand.BanUser(userId, reason)
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
|
ParsedCommand.BanUser(
|
||||||
|
userId,
|
||||||
|
textMessage.substring(Command.BAN_USER.length + userId.length)
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.BAN_USER)
|
ParsedCommand.ErrorSyntax(Command.BAN_USER)
|
||||||
}
|
}
|
||||||
|
@ -144,11 +171,16 @@ object CommandParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.UNBAN_USER.command -> {
|
Command.UNBAN_USER.command -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
ParsedCommand.UnbanUser(userId)
|
ParsedCommand.UnbanUser(
|
||||||
|
userId,
|
||||||
|
textMessage.substring(Command.UNBAN_USER.length + userId.length)
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
|
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,14 +34,14 @@ sealed class ParsedCommand {
|
||||||
// Valid commands:
|
// Valid commands:
|
||||||
|
|
||||||
class SendEmote(val message: CharSequence) : ParsedCommand()
|
class SendEmote(val message: CharSequence) : ParsedCommand()
|
||||||
class BanUser(val userId: String, val reason: String) : ParsedCommand()
|
class BanUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||||
class UnbanUser(val userId: String) : ParsedCommand()
|
class UnbanUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||||
class SetUserPowerLevel(val userId: String, val powerLevel: Int) : ParsedCommand()
|
class SetUserPowerLevel(val userId: String, val powerLevel: Int) : ParsedCommand()
|
||||||
class Invite(val userId: String) : ParsedCommand()
|
class Invite(val userId: String, val reason: String?) : ParsedCommand()
|
||||||
class JoinRoom(val roomAlias: String) : ParsedCommand()
|
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
|
||||||
class PartRoom(val roomAlias: String) : ParsedCommand()
|
class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
|
||||||
class ChangeTopic(val topic: String) : ParsedCommand()
|
class ChangeTopic(val topic: String) : ParsedCommand()
|
||||||
class KickUser(val userId: String, val reason: String) : ParsedCommand()
|
class KickUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||||
class ChangeDisplayName(val displayName: String) : ParsedCommand()
|
class ChangeDisplayName(val displayName: String) : ParsedCommand()
|
||||||
class SetMarkdown(val enable: Boolean) : ParsedCommand()
|
class SetMarkdown(val enable: Boolean) : ParsedCommand()
|
||||||
object ClearScalarToken : ParsedCommand()
|
object ClearScalarToken : ParsedCommand()
|
||||||
|
|
|
@ -21,6 +21,8 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||||
|
@ -37,12 +39,16 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
keysBackupSettingsRecyclerView.configureWith(keysBackupSettingsRecyclerViewController)
|
||||||
keysBackupSettingsRecyclerView.setController(keysBackupSettingsRecyclerViewController)
|
|
||||||
|
|
||||||
keysBackupSettingsRecyclerViewController.listener = this
|
keysBackupSettingsRecyclerViewController.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
keysBackupSettingsRecyclerViewController.listener = null
|
||||||
|
keysBackupSettingsRecyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
keysBackupSettingsRecyclerViewController.setData(state)
|
keysBackupSettingsRecyclerViewController.setData(state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationItemView
|
import com.google.android.material.bottomnavigation.BottomNavigationItemView
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
||||||
import im.vector.matrix.android.api.session.Session
|
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -46,7 +45,6 @@ private const val INDEX_PEOPLE = 1
|
||||||
private const val INDEX_ROOMS = 2
|
private const val INDEX_ROOMS = 2
|
||||||
|
|
||||||
class HomeDetailFragment @Inject constructor(
|
class HomeDetailFragment @Inject constructor(
|
||||||
private val session: Session,
|
|
||||||
val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
|
val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
|
||||||
private val avatarRenderer: AvatarRenderer
|
private val avatarRenderer: AvatarRenderer
|
||||||
) : VectorBaseFragment(), KeysBackupBanner.Delegate {
|
) : VectorBaseFragment(), KeysBackupBanner.Delegate {
|
||||||
|
@ -56,9 +54,7 @@ class HomeDetailFragment @Inject constructor(
|
||||||
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
||||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||||
|
|
||||||
override fun getLayoutResId(): Int {
|
override fun getLayoutResId() = R.layout.fragment_home_detail
|
||||||
return R.layout.fragment_home_detail
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
|
@ -23,9 +23,7 @@ import com.airbnb.mvrx.withState
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.*
|
||||||
import im.vector.riotx.core.extensions.setupAsSearch
|
|
||||||
import im.vector.riotx.core.extensions.showKeyboard
|
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
|
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -48,10 +46,15 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
directRoomController.callback = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
recyclerView.setHasFixedSize(true)
|
|
||||||
directRoomController.callback = this
|
directRoomController.callback = this
|
||||||
recyclerView.setController(directRoomController)
|
recyclerView.configureWith(directRoomController)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSearchByMatrixIdView() {
|
private fun setupSearchByMatrixIdView() {
|
||||||
|
|
|
@ -31,9 +31,7 @@ import com.google.android.material.chip.ChipGroup
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.*
|
||||||
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.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
|
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
|
||||||
|
@ -67,6 +65,12 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
knownUsersController.callback = null
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
withState(viewModel) {
|
withState(viewModel) {
|
||||||
val createMenuItem = menu.findItem(R.id.action_create_direct_room)
|
val createMenuItem = menu.findItem(R.id.action_create_direct_room)
|
||||||
|
@ -94,11 +98,10 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
recyclerView.setHasFixedSize(true)
|
|
||||||
// Don't activate animation as we might have way to much item animation when filtering
|
// Don't activate animation as we might have way to much item animation when filtering
|
||||||
recyclerView.itemAnimator = null
|
recyclerView.itemAnimator = null
|
||||||
knownUsersController.callback = this
|
knownUsersController.callback = this
|
||||||
recyclerView.setController(knownUsersController)
|
recyclerView.configureWith(knownUsersController)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFilterView() {
|
private fun setupFilterView() {
|
||||||
|
|
|
@ -23,11 +23,13 @@ import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.extensions.observeEvent
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
import im.vector.riotx.core.platform.StateView
|
import im.vector.riotx.core.platform.StateView
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
|
||||||
import im.vector.riotx.features.home.HomeActivitySharedAction
|
import im.vector.riotx.features.home.HomeActivitySharedAction
|
||||||
|
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
||||||
import kotlinx.android.synthetic.main.fragment_group_list.*
|
import kotlinx.android.synthetic.main.fragment_group_list.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -45,14 +47,20 @@ class GroupListFragment @Inject constructor(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||||
groupController.callback = this
|
groupController.callback = this
|
||||||
stateView.contentView = groupListEpoxyRecyclerView
|
stateView.contentView = groupListView
|
||||||
groupListEpoxyRecyclerView.setController(groupController)
|
groupListView.configureWith(groupController)
|
||||||
viewModel.subscribe { renderState(it) }
|
viewModel.subscribe { renderState(it) }
|
||||||
viewModel.openGroupLiveData.observeEvent(this) {
|
viewModel.openGroupLiveData.observeEvent(this) {
|
||||||
sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
groupController.callback = null
|
||||||
|
groupListView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderState(state: GroupListViewState) {
|
private fun renderState(state: GroupListViewState) {
|
||||||
when (state.asyncGroups) {
|
when (state.asyncGroups) {
|
||||||
is Incomplete -> stateView.state = StateView.State.Loading
|
is Incomplete -> stateView.state = StateView.State.Loading
|
||||||
|
|
|
@ -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.riotx.features.home.room.breadcrumbs
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
|
|
||||||
|
private const val ANIM_DURATION_IN_MILLIS = 200L
|
||||||
|
|
||||||
|
class BreadcrumbsAnimator : DefaultItemAnimator() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
addDuration = ANIM_DURATION_IN_MILLIS
|
||||||
|
removeDuration = ANIM_DURATION_IN_MILLIS
|
||||||
|
moveDuration = ANIM_DURATION_IN_MILLIS
|
||||||
|
changeDuration = ANIM_DURATION_IN_MILLIS
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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.room.breadcrumbs
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BreadcrumbsController @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer
|
||||||
|
) : EpoxyController() {
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
private var viewState: BreadcrumbsViewState? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
||||||
|
// It avoids to build the whole list of breadcrumbs on the main thread.
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(viewState: BreadcrumbsViewState) {
|
||||||
|
this.viewState = viewState
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels() {
|
||||||
|
val safeViewState = viewState ?: return
|
||||||
|
|
||||||
|
// An empty breadcrumbs list can only be temporary because when entering in a room,
|
||||||
|
// this one is added to the breadcrumbs
|
||||||
|
|
||||||
|
safeViewState.asyncBreadcrumbs.invoke()
|
||||||
|
?.forEach {
|
||||||
|
breadcrumbsItem {
|
||||||
|
id(it.roomId)
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
roomId(it.roomId)
|
||||||
|
roomName(it.displayName)
|
||||||
|
avatarUrl(it.avatarUrl)
|
||||||
|
unreadNotificationCount(it.notificationCount)
|
||||||
|
showHighlighted(it.highlightCount > 0)
|
||||||
|
hasUnreadMessage(it.hasUnreadMessages)
|
||||||
|
hasDraft(it.userDrafts.isNotEmpty())
|
||||||
|
itemClickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { _ ->
|
||||||
|
listener?.onBreadcrumbClicked(it.roomId)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onBreadcrumbClicked(roomId: String)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.room.breadcrumbs
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailSharedAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||||
|
import kotlinx.android.synthetic.main.fragment_breadcrumbs.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BreadcrumbsFragment @Inject constructor(
|
||||||
|
private val breadcrumbsController: BreadcrumbsController,
|
||||||
|
val breadcrumbsViewModelFactory: BreadcrumbsViewModel.Factory
|
||||||
|
) : VectorBaseFragment(), BreadcrumbsController.Listener {
|
||||||
|
|
||||||
|
private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel
|
||||||
|
private val breadcrumbsViewModel: BreadcrumbsViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_breadcrumbs
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupRecyclerView()
|
||||||
|
sharedActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
||||||
|
|
||||||
|
breadcrumbsViewModel.subscribe { renderState(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
breadcrumbsRecyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
breadcrumbsRecyclerView.configureWith(breadcrumbsController, BreadcrumbsAnimator(), hasFixedSize = false)
|
||||||
|
breadcrumbsController.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderState(state: BreadcrumbsViewState) {
|
||||||
|
breadcrumbsController.update(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BreadcrumbsController.Listener **************************************************************
|
||||||
|
|
||||||
|
override fun onBreadcrumbClicked(roomId: String) {
|
||||||
|
sharedActionViewModel.post(RoomDetailSharedAction.SwitchToRoom(roomId))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.home.room.breadcrumbs
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
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
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_breadcrumbs)
|
||||||
|
abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
@EpoxyAttribute lateinit var roomId: String
|
||||||
|
@EpoxyAttribute lateinit var roomName: CharSequence
|
||||||
|
@EpoxyAttribute var avatarUrl: String? = null
|
||||||
|
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||||
|
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||||
|
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||||
|
@EpoxyAttribute var hasDraft: Boolean = false
|
||||||
|
@EpoxyAttribute var itemClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.rootView.setOnClickListener(itemClickListener)
|
||||||
|
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
|
||||||
|
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
|
||||||
|
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
|
||||||
|
holder.draftIndentIndicator.isVisible = hasDraft
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.breadcrumbsUnreadCounterBadgeView)
|
||||||
|
val unreadIndentIndicator by bind<View>(R.id.breadcrumbsUnreadIndicator)
|
||||||
|
val draftIndentIndicator by bind<View>(R.id.breadcrumbsDraftBadge)
|
||||||
|
val avatarImageView by bind<ImageView>(R.id.breadcrumbsImageView)
|
||||||
|
val rootView by bind<ViewGroup>(R.id.breadcrumbsRoot)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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.room.breadcrumbs
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.platform.EmptyAction
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
|
class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<BreadcrumbsViewState, EmptyAction>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: BreadcrumbsViewState): BreadcrumbsViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<BreadcrumbsViewModel, BreadcrumbsViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: BreadcrumbsViewState): BreadcrumbsViewModel? {
|
||||||
|
val fragment: BreadcrumbsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.breadcrumbsViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeBreadcrumbs()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: EmptyAction) {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
|
private fun observeBreadcrumbs() {
|
||||||
|
session.rx()
|
||||||
|
.liveBreadcrumbs()
|
||||||
|
.observeOn(Schedulers.computation())
|
||||||
|
.execute { asyncBreadcrumbs ->
|
||||||
|
copy(asyncBreadcrumbs = asyncBreadcrumbs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.room.breadcrumbs
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
data class BreadcrumbsViewState(
|
||||||
|
val asyncBreadcrumbs: Async<List<RoomSummary>> = Uninitialized
|
||||||
|
) : MvRxState
|
|
@ -20,17 +20,25 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.replaceFragment
|
import im.vector.riotx.core.extensions.replaceFragment
|
||||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||||
|
import kotlinx.android.synthetic.main.activity_room_detail.*
|
||||||
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
||||||
|
|
||||||
class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
override fun getLayoutRes() = R.layout.activity_room_detail
|
||||||
return R.layout.activity_room_detail
|
|
||||||
}
|
private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel
|
||||||
|
|
||||||
|
// Simple filter
|
||||||
|
private var currentRoomId: String? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -38,14 +46,57 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
|
val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
|
||||||
?: return
|
?: return
|
||||||
|
currentRoomId = roomDetailArgs.roomId
|
||||||
replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs)
|
replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs)
|
||||||
|
replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
||||||
|
|
||||||
|
sharedActionViewModel
|
||||||
|
.observe()
|
||||||
|
.subscribe { sharedAction ->
|
||||||
|
when (sharedAction) {
|
||||||
|
is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnDestroy()
|
||||||
|
|
||||||
|
drawerLayout.addDrawerListener(drawerListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun switchToRoom(switchToRoom: RoomDetailSharedAction.SwitchToRoom) {
|
||||||
|
drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
// Do not replace the Fragment if it's the same roomId
|
||||||
|
if (currentRoomId != switchToRoom.roomId) {
|
||||||
|
currentRoomId = switchToRoom.roomId
|
||||||
|
replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, RoomDetailArgs(switchToRoom.roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
drawerLayout.removeDrawerListener(drawerListener)
|
||||||
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun configure(toolbar: Toolbar) {
|
override fun configure(toolbar: Toolbar) {
|
||||||
configureToolbar(toolbar)
|
configureToolbar(toolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||||
|
override fun onDrawerStateChanged(newState: Int) {
|
||||||
|
hideKeyboard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||||
|
drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
} else {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
|
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
|
||||||
|
|
|
@ -46,6 +46,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||||
|
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.*
|
||||||
import com.github.piasy.biv.BigImageViewer
|
import com.github.piasy.biv.BigImageViewer
|
||||||
import com.github.piasy.biv.loader.ImageLoader
|
import com.github.piasy.biv.loader.ImageLoader
|
||||||
|
@ -69,10 +70,7 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.dialogs.withColoredButton
|
import im.vector.riotx.core.dialogs.withColoredButton
|
||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.*
|
||||||
import im.vector.riotx.core.extensions.observeEvent
|
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
|
||||||
import im.vector.riotx.core.extensions.showKeyboard
|
|
||||||
import im.vector.riotx.core.files.addEntryToDownloadManager
|
import im.vector.riotx.core.files.addEntryToDownloadManager
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
@ -193,6 +191,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||||
private lateinit var layoutManager: LinearLayoutManager
|
private lateinit var layoutManager: LinearLayoutManager
|
||||||
|
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||||
|
|
||||||
private lateinit var attachmentsHelper: AttachmentsHelper
|
private lateinit var attachmentsHelper: AttachmentsHelper
|
||||||
private lateinit var keyboardStateUtils: KeyboardStateUtils
|
private lateinit var keyboardStateUtils: KeyboardStateUtils
|
||||||
|
|
||||||
|
@ -286,13 +286,16 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
timelineEventController.callback = null
|
||||||
|
timelineEventController.removeModelBuildListener(modelBuildListener)
|
||||||
|
modelBuildListener = null
|
||||||
|
debouncer.cancelAll()
|
||||||
|
recyclerView.cleanup()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
recyclerView.adapter = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
||||||
debouncer.cancelAll()
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,11 +450,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
|
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
REACTION_SELECT_REQUEST_CODE -> {
|
REACTION_SELECT_REQUEST_CODE -> {
|
||||||
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
|
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
|
||||||
?: return
|
|
||||||
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
|
|
||||||
?: return
|
|
||||||
// TODO check if already reacted with that?
|
|
||||||
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
|
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,13 +469,14 @@ class RoomDetailFragment @Inject constructor(
|
||||||
recyclerView.layoutManager = layoutManager
|
recyclerView.layoutManager = layoutManager
|
||||||
recyclerView.itemAnimator = null
|
recyclerView.itemAnimator = null
|
||||||
recyclerView.setHasFixedSize(true)
|
recyclerView.setHasFixedSize(true)
|
||||||
timelineEventController.addModelBuildListener {
|
modelBuildListener = OnModelBuildFinishedListener {
|
||||||
it.dispatchTo(stateRestorer)
|
it.dispatchTo(stateRestorer)
|
||||||
it.dispatchTo(scrollOnNewMessageCallback)
|
it.dispatchTo(scrollOnNewMessageCallback)
|
||||||
it.dispatchTo(scrollOnHighlightedEventCallback)
|
it.dispatchTo(scrollOnHighlightedEventCallback)
|
||||||
updateJumpToReadMarkerViewVisibility()
|
updateJumpToReadMarkerViewVisibility()
|
||||||
updateJumpToBottomViewVisibility()
|
updateJumpToBottomViewVisibility()
|
||||||
}
|
}
|
||||||
|
timelineEventController.addModelBuildListener(modelBuildListener)
|
||||||
recyclerView.adapter = timelineEventController.adapter
|
recyclerView.adapter = timelineEventController.adapter
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
@ -521,7 +521,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateJumpToReadMarkerViewVisibility() = jumpToReadMarkerView.post {
|
private fun updateJumpToReadMarkerViewVisibility() {
|
||||||
|
jumpToReadMarkerView?.post {
|
||||||
withState(roomDetailViewModel) {
|
withState(roomDetailViewModel) {
|
||||||
val showJumpToUnreadBanner = when (it.unreadState) {
|
val showJumpToUnreadBanner = when (it.unreadState) {
|
||||||
UnreadState.Unknown,
|
UnreadState.Unknown,
|
||||||
|
@ -544,6 +545,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
|
jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateJumpToBottomViewVisibility() {
|
private fun updateJumpToBottomViewVisibility() {
|
||||||
debouncer.debounce("jump_to_bottom_visibility", 250, Runnable {
|
debouncer.debounce("jump_to_bottom_visibility", 250, Runnable {
|
||||||
|
@ -1181,7 +1183,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
&& userId == session.myUserId) {
|
&& userId == session.myUserId) {
|
||||||
// Empty composer, current user: start an emote
|
// Empty composer, current user: start an emote
|
||||||
composerLayout.composerEditText.setText(Command.EMOTE.command + " ")
|
composerLayout.composerEditText.setText(Command.EMOTE.command + " ")
|
||||||
composerLayout.composerEditText.setSelection(Command.EMOTE.command.length + 1)
|
composerLayout.composerEditText.setSelection(Command.EMOTE.length)
|
||||||
} else {
|
} else {
|
||||||
val roomMember = roomDetailViewModel.getMember(userId)
|
val roomMember = roomDetailViewModel.getMember(userId)
|
||||||
// TODO move logic outside of fragment
|
// TODO move logic outside of fragment
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.room.detail
|
||||||
|
|
||||||
|
import im.vector.riotx.core.platform.VectorSharedAction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported navigation actions for [RoomDetailActivity]
|
||||||
|
*/
|
||||||
|
sealed class RoomDetailSharedAction : VectorSharedAction {
|
||||||
|
data class SwitchToRoom(val roomId: String) : RoomDetailSharedAction()
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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.room.detail
|
||||||
|
|
||||||
|
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity shared view model
|
||||||
|
*/
|
||||||
|
class RoomDetailSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<RoomDetailSharedAction>()
|
|
@ -143,6 +143,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
timeline.addListener(this)
|
timeline.addListener(this)
|
||||||
timeline.start()
|
timeline.start()
|
||||||
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
||||||
|
|
||||||
|
// Inform the SDK that the room is displayed
|
||||||
|
session.onRoomDisplayed(initialState.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomDetailAction) {
|
override fun handle(action: RoomDetailAction) {
|
||||||
|
@ -197,9 +200,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
invisibleEventsObservable.accept(action)
|
invisibleEventsObservable.accept(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMember(userId: String) : RoomMember? {
|
fun getMember(userId: String): RoomMember? {
|
||||||
return room.getRoomMember(userId)
|
return room.getRoomMember(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a send mode to a draft and save the draft
|
* Convert a send mode to a draft and save the draft
|
||||||
*/
|
*/
|
||||||
|
@ -263,7 +267,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session.rx()
|
session.rx()
|
||||||
.joinRoom(roomId, viaServer)
|
.joinRoom(roomId, viaServers = viaServer)
|
||||||
.map { roomId }
|
.map { roomId }
|
||||||
.execute {
|
.execute {
|
||||||
copy(tombstoneEventHandling = it)
|
copy(tombstoneEventHandling = it)
|
||||||
|
@ -484,7 +488,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
||||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||||
|
|
||||||
room.invite(invite.userId, object : MatrixCallback<Unit> {
|
room.invite(invite.userId, invite.reason, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
|
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
|
||||||
}
|
}
|
||||||
|
@ -550,7 +554,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRejectInvite() {
|
private fun handleRejectInvite() {
|
||||||
room.leave(object : MatrixCallback<Unit> {})
|
room.leave(null, object : MatrixCallback<Unit> {})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAcceptInvite() {
|
private fun handleAcceptInvite() {
|
||||||
|
@ -859,7 +863,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
timeline.dispose()
|
timeline.dispose()
|
||||||
timeline.removeAllListeners()
|
timeline.removeListener(this)
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
|
@ -29,6 +28,8 @@ import com.airbnb.mvrx.MvRx
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
@ -52,8 +53,8 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
|
private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
@ -64,12 +65,16 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
recyclerView.configureWith(epoxyController, hasFixedSize = false)
|
||||||
recyclerView.adapter = epoxyController.adapter
|
|
||||||
bottomSheetTitle.text = getString(R.string.seen_by)
|
bottomSheetTitle.text = getString(R.string.seen_by)
|
||||||
epoxyController.setData(displayReadReceiptArgs.readReceipts)
|
epoxyController.setData(displayReadReceiptArgs.readReceipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
// we are not using state for this one as it's static, so no need to override invalidate()
|
// we are not using state for this one as it's static, so no need to override invalidate()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.VisibilityState
|
import com.airbnb.epoxy.VisibilityState
|
||||||
import im.vector.matrix.android.api.session.Session
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
@ -44,7 +43,7 @@ import org.threeten.bp.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||||
private val session: Session,
|
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||||
private val timelineItemFactory: TimelineItemFactory,
|
private val timelineItemFactory: TimelineItemFactory,
|
||||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||||
private val mergedHeaderItemFactory: MergedHeaderItemFactory,
|
private val mergedHeaderItemFactory: MergedHeaderItemFactory,
|
||||||
|
@ -209,6 +208,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
timelineMediaSizeProvider.recyclerView = recyclerView
|
timelineMediaSizeProvider.recyclerView = recyclerView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
timelineMediaSizeProvider.recyclerView = null
|
||||||
|
contentUploadStateTrackerBinder.clear()
|
||||||
|
timeline?.removeListener(this)
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildModels() {
|
override fun buildModels() {
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
showingForwardLoader = LoadingItem_()
|
showingForwardLoader = LoadingItem_()
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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.room.detail.timeline.action
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
|
|
||||||
|
private const val ANIM_DURATION_IN_MILLIS = 300L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We only want to animate the expand of the "Report content" submenu
|
||||||
|
*/
|
||||||
|
class MessageActionsAnimator : DefaultItemAnimator() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
addDuration = ANIM_DURATION_IN_MILLIS
|
||||||
|
removeDuration = 0
|
||||||
|
moveDuration = 0
|
||||||
|
changeDuration = 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,6 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
|
@ -27,6 +26,8 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -48,8 +49,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
@ -61,13 +62,17 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false)
|
||||||
recyclerView.adapter = messageActionsEpoxyController.adapter
|
|
||||||
// Disable item animation
|
// Disable item animation
|
||||||
recyclerView.itemAnimator = null
|
recyclerView.itemAnimator = null
|
||||||
messageActionsEpoxyController.listener = this
|
messageActionsEpoxyController.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onUrlClicked(url: String): Boolean {
|
override fun onUrlClicked(url: String): Boolean {
|
||||||
sharedActionViewModel.post(EventSharedAction.OnUrlClicked(url))
|
sharedActionViewModel.post(EventSharedAction.OnUrlClicked(url))
|
||||||
// Always consume
|
// Always consume
|
||||||
|
@ -83,6 +88,10 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
|
||||||
override fun didSelectMenuAction(eventAction: EventSharedAction) {
|
override fun didSelectMenuAction(eventAction: EventSharedAction) {
|
||||||
if (eventAction is EventSharedAction.ReportContent) {
|
if (eventAction is EventSharedAction.ReportContent) {
|
||||||
// Toggle report menu
|
// Toggle report menu
|
||||||
|
// Enable item animation
|
||||||
|
if (recyclerView.itemAnimator == null) {
|
||||||
|
recyclerView.itemAnimator = MessageActionsAnimator()
|
||||||
|
}
|
||||||
viewModel.handle(MessageActionsAction.ToggleReportMenu)
|
viewModel.handle(MessageActionsAction.ToggleReportMenu)
|
||||||
} else {
|
} else {
|
||||||
sharedActionViewModel.post(eventAction)
|
sharedActionViewModel.post(eventAction)
|
||||||
|
|
|
@ -19,9 +19,6 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
|
@ -30,6 +27,8 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
@ -54,8 +53,8 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer)
|
ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
@ -66,13 +65,18 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
recyclerView.adapter = epoxyController.adapter
|
recyclerView.configureWith(
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
epoxyController,
|
||||||
val dividerItemDecoration = DividerItemDecoration(requireContext(), LinearLayout.VERTICAL)
|
showDivider = true,
|
||||||
recyclerView.addItemDecoration(dividerItemDecoration)
|
hasFixedSize = false)
|
||||||
bottomSheetTitle.text = context?.getString(R.string.message_edits)
|
bottomSheetTitle.text = context?.getString(R.string.message_edits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
epoxyController.setData(it)
|
epoxyController.setData(it)
|
||||||
super.invalidate()
|
super.invalidate()
|
||||||
|
|
|
@ -28,17 +28,16 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.core.extensions.localDateTime
|
import im.vector.riotx.core.extensions.localDateTime
|
||||||
import im.vector.riotx.core.ui.list.genericFooterItem
|
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||||
import im.vector.riotx.core.ui.list.genericItem
|
import im.vector.riotx.core.ui.list.genericItem
|
||||||
import im.vector.riotx.core.ui.list.genericItemHeader
|
import im.vector.riotx.core.ui.list.genericItemHeader
|
||||||
import im.vector.riotx.core.ui.list.genericLoaderItem
|
import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||||
import im.vector.riotx.core.date.VectorDateFormatter
|
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import name.fraser.neil.plaintext.diff_match_patch
|
import name.fraser.neil.plaintext.diff_match_patch
|
||||||
import timber.log.Timber
|
import java.util.*
|
||||||
import java.util.Calendar
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Epoxy controller for edit history list
|
* Epoxy controller for edit history list
|
||||||
|
@ -104,9 +103,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
||||||
?: nContent.first
|
?: nContent.first
|
||||||
val dmp = diff_match_patch()
|
val dmp = diff_match_patch()
|
||||||
val diff = dmp.diff_main(nextBody.toString(), body.toString())
|
val diff = dmp.diff_main(nextBody.toString(), body.toString())
|
||||||
Timber.e("#### Diff: $diff")
|
|
||||||
dmp.diff_cleanupSemantic(diff)
|
dmp.diff_cleanupSemantic(diff)
|
||||||
Timber.e("#### Diff: $diff")
|
|
||||||
spannedDiff = span {
|
spannedDiff = span {
|
||||||
diff.map {
|
diff.map {
|
||||||
when (it.operation) {
|
when (it.operation) {
|
||||||
|
|
|
@ -19,14 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.format
|
||||||
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.*
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomJoinRules
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomNameContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -36,7 +29,7 @@ import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder,
|
class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder,
|
||||||
private val stringProvider: StringProvider) {
|
private val sp: StringProvider) {
|
||||||
|
|
||||||
fun format(timelineEvent: TimelineEvent): CharSequence? {
|
fun format(timelineEvent: TimelineEvent): CharSequence? {
|
||||||
return when (val type = timelineEvent.root.getClearType()) {
|
return when (val type = timelineEvent.root.getClearType()) {
|
||||||
|
@ -84,36 +77,35 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
|
private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
|
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
|
||||||
return if (content.name.isNullOrBlank()) {
|
return if (content.name.isNullOrBlank()) {
|
||||||
stringProvider.getString(R.string.notice_room_name_removed, senderName)
|
sp.getString(R.string.notice_room_name_removed, senderName)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.notice_room_name_changed, senderName, content.name)
|
sp.getString(R.string.notice_room_name_changed, senderName, content.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatRoomTombstoneEvent(senderName: String?): CharSequence? {
|
private fun formatRoomTombstoneEvent(senderName: String?): CharSequence? {
|
||||||
return stringProvider.getString(R.string.notice_room_update, senderName)
|
return sp.getString(R.string.notice_room_update, senderName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? {
|
private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val content = event.getClearContent().toModel<RoomTopicContent>() ?: return null
|
val content = event.getClearContent().toModel<RoomTopicContent>() ?: return null
|
||||||
return if (content.topic.isNullOrEmpty()) {
|
return if (content.topic.isNullOrEmpty()) {
|
||||||
stringProvider.getString(R.string.notice_room_topic_removed, senderName)
|
sp.getString(R.string.notice_room_topic_removed, senderName)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.notice_room_topic_changed, senderName, content.topic)
|
sp.getString(R.string.notice_room_topic_changed, senderName, content.topic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
|
||||||
?: return null
|
|
||||||
|
|
||||||
val formattedVisibility = when (historyVisibility) {
|
val formattedVisibility = when (historyVisibility) {
|
||||||
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
|
RoomHistoryVisibility.SHARED -> sp.getString(R.string.notice_room_visibility_shared)
|
||||||
RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited)
|
RoomHistoryVisibility.INVITED -> sp.getString(R.string.notice_room_visibility_invited)
|
||||||
RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined)
|
RoomHistoryVisibility.JOINED -> sp.getString(R.string.notice_room_visibility_joined)
|
||||||
RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable)
|
RoomHistoryVisibility.WORLD_READABLE -> sp.getString(R.string.notice_room_visibility_world_readable)
|
||||||
}
|
}
|
||||||
return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility)
|
return sp.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatCallEvent(event: Event, senderName: String?): CharSequence? {
|
private fun formatCallEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
|
@ -122,13 +114,13 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
val content = event.getClearContent().toModel<CallInviteContent>() ?: return null
|
val content = event.getClearContent().toModel<CallInviteContent>() ?: return null
|
||||||
val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO
|
val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO
|
||||||
return if (isVideoCall) {
|
return if (isVideoCall) {
|
||||||
stringProvider.getString(R.string.notice_placed_video_call, senderName)
|
sp.getString(R.string.notice_placed_video_call, senderName)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.notice_placed_voice_call, senderName)
|
sp.getString(R.string.notice_placed_voice_call, senderName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName)
|
EventType.CALL_ANSWER == event.type -> sp.getString(R.string.notice_answered_call, senderName)
|
||||||
EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName)
|
EventType.CALL_HANGUP == event.type -> sp.getString(R.string.notice_ended_call, senderName)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,12 +142,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
if (eventContent?.displayName != prevEventContent?.displayName) {
|
if (eventContent?.displayName != prevEventContent?.displayName) {
|
||||||
val displayNameText = when {
|
val displayNameText = when {
|
||||||
prevEventContent?.displayName.isNullOrEmpty() ->
|
prevEventContent?.displayName.isNullOrEmpty() ->
|
||||||
stringProvider.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName)
|
sp.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName)
|
||||||
eventContent?.displayName.isNullOrEmpty() ->
|
eventContent?.displayName.isNullOrEmpty() ->
|
||||||
stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
|
sp.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
|
||||||
else ->
|
else ->
|
||||||
stringProvider.getString(R.string.notice_display_name_changed_from,
|
sp.getString(R.string.notice_display_name_changed_from, event.senderId, prevEventContent?.displayName, eventContent?.displayName)
|
||||||
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
|
|
||||||
}
|
}
|
||||||
displayText.append(displayNameText)
|
displayText.append(displayNameText)
|
||||||
}
|
}
|
||||||
|
@ -163,64 +154,87 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
if (eventContent?.avatarUrl != prevEventContent?.avatarUrl) {
|
if (eventContent?.avatarUrl != prevEventContent?.avatarUrl) {
|
||||||
val displayAvatarText = if (displayText.isNotEmpty()) {
|
val displayAvatarText = if (displayText.isNotEmpty()) {
|
||||||
displayText.append(" ")
|
displayText.append(" ")
|
||||||
stringProvider.getString(R.string.notice_avatar_changed_too)
|
sp.getString(R.string.notice_avatar_changed_too)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.notice_avatar_url_changed, senderName)
|
sp.getString(R.string.notice_avatar_url_changed, senderName)
|
||||||
}
|
}
|
||||||
displayText.append(displayAvatarText)
|
displayText.append(displayAvatarText)
|
||||||
}
|
}
|
||||||
if (displayText.isEmpty()) {
|
if (displayText.isEmpty()) {
|
||||||
displayText.append(
|
displayText.append(
|
||||||
stringProvider.getString(R.string.notice_member_no_changes, senderName)
|
sp.getString(R.string.notice_member_no_changes, senderName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return displayText.toString()
|
return displayText.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
|
private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
|
||||||
val senderDisplayName = senderName ?: event.senderId
|
val senderDisplayName = senderName ?: event.senderId ?: ""
|
||||||
val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: ""
|
val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: ""
|
||||||
return when {
|
return when (eventContent?.membership) {
|
||||||
Membership.INVITE == eventContent?.membership -> {
|
Membership.INVITE -> {
|
||||||
val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId
|
val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId
|
||||||
when {
|
when {
|
||||||
eventContent.thirdPartyInvite != null -> {
|
eventContent.thirdPartyInvite != null -> {
|
||||||
val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid
|
val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey
|
||||||
?: event.stateKey
|
val threePidDisplayName = eventContent.thirdPartyInvite?.displayName ?: ""
|
||||||
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
|
eventContent.safeReason?.let { reason ->
|
||||||
userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName)
|
sp.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, threePidDisplayName)
|
||||||
}
|
}
|
||||||
event.stateKey == selfUserId ->
|
event.stateKey == selfUserId ->
|
||||||
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_invite_you, senderDisplayName)
|
||||||
event.stateKey.isNullOrEmpty() ->
|
event.stateKey.isNullOrEmpty() ->
|
||||||
stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_invite_no_invitee, senderDisplayName)
|
||||||
else ->
|
else ->
|
||||||
stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_invite_with_reason, senderDisplayName, targetDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Membership.JOIN == eventContent?.membership ->
|
Membership.JOIN ->
|
||||||
stringProvider.getString(R.string.notice_room_join, senderDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
Membership.LEAVE == eventContent?.membership ->
|
sp.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_join, senderDisplayName)
|
||||||
|
Membership.LEAVE ->
|
||||||
// 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked
|
// 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked
|
||||||
return if (event.senderId == event.stateKey) {
|
if (event.senderId == event.stateKey) {
|
||||||
if (prevEventContent?.membership == Membership.INVITE) {
|
if (prevEventContent?.membership == Membership.INVITE) {
|
||||||
stringProvider.getString(R.string.notice_room_reject, senderDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_reject, senderDisplayName)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.notice_room_leave, senderDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_leave, senderDisplayName)
|
||||||
}
|
}
|
||||||
} else if (prevEventContent?.membership == Membership.INVITE) {
|
} else if (prevEventContent?.membership == Membership.INVITE) {
|
||||||
stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName)
|
||||||
} else if (prevEventContent?.membership == Membership.JOIN) {
|
} else if (prevEventContent?.membership == Membership.JOIN) {
|
||||||
stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName)
|
||||||
} else if (prevEventContent?.membership == Membership.BAN) {
|
} else if (prevEventContent?.membership == Membership.BAN) {
|
||||||
stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName)
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
Membership.BAN == eventContent?.membership ->
|
Membership.BAN ->
|
||||||
stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName)
|
eventContent.safeReason?.let {
|
||||||
Membership.KNOCK == eventContent?.membership ->
|
sp.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, it)
|
||||||
stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName)
|
} ?: sp.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName)
|
||||||
|
Membership.KNOCK ->
|
||||||
|
eventContent.safeReason?.let { reason ->
|
||||||
|
sp.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason)
|
||||||
|
} ?: sp.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,8 +242,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
private fun formatJoinRulesEvent(event: Event, senderName: String?): CharSequence? {
|
private fun formatJoinRulesEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val content = event.getClearContent().toModel<RoomJoinRulesContent>() ?: return null
|
val content = event.getClearContent().toModel<RoomJoinRulesContent>() ?: return null
|
||||||
return when (content.joinRules) {
|
return when (content.joinRules) {
|
||||||
RoomJoinRules.INVITE -> stringProvider.getString(R.string.room_join_rules_invite, senderName)
|
RoomJoinRules.INVITE -> sp.getString(R.string.room_join_rules_invite, senderName)
|
||||||
RoomJoinRules.PUBLIC -> stringProvider.getString(R.string.room_join_rules_public, senderName)
|
RoomJoinRules.PUBLIC -> sp.getString(R.string.room_join_rules_public, senderName)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,14 @@ import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
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.di.ScreenScope
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.utils.TextUtils
|
import im.vector.riotx.core.utils.TextUtils
|
||||||
import im.vector.riotx.features.ui.getMessageTextColor
|
import im.vector.riotx.features.ui.getMessageTextColor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ScreenScope
|
||||||
class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
private val errorFormatter: ErrorFormatter) {
|
private val errorFormatter: ErrorFormatter) {
|
||||||
|
@ -40,7 +42,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
|
||||||
fun bind(eventId: String,
|
fun bind(eventId: String,
|
||||||
isLocalFile: Boolean,
|
isLocalFile: Boolean,
|
||||||
progressLayout: ViewGroup) {
|
progressLayout: ViewGroup) {
|
||||||
activeSessionHolder.getActiveSession().also { session ->
|
activeSessionHolder.getSafeActiveSession()?.also { session ->
|
||||||
val uploadStateTracker = session.contentUploadProgressTracker()
|
val uploadStateTracker = session.contentUploadProgressTracker()
|
||||||
val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider, errorFormatter)
|
val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider, errorFormatter)
|
||||||
updateListeners[eventId] = updateListener
|
updateListeners[eventId] = updateListener
|
||||||
|
@ -49,13 +51,19 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unbind(eventId: String) {
|
fun unbind(eventId: String) {
|
||||||
activeSessionHolder.getActiveSession().also { session ->
|
activeSessionHolder.getSafeActiveSession()?.also { session ->
|
||||||
val uploadStateTracker = session.contentUploadProgressTracker()
|
val uploadStateTracker = session.contentUploadProgressTracker()
|
||||||
updateListeners[eventId]?.also {
|
updateListeners[eventId]?.also {
|
||||||
uploadStateTracker.untrack(eventId, it)
|
uploadStateTracker.untrack(eventId, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()?.also {
|
||||||
|
it.contentUploadProgressTracker().clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
|
private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
|
||||||
|
|
|
@ -19,11 +19,12 @@ package im.vector.riotx.features.home.room.detail.timeline.helper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import im.vector.riotx.core.di.ScreenScope
|
import im.vector.riotx.core.di.ScreenScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ScreenScope
|
@ScreenScope
|
||||||
class TimelineMediaSizeProvider @Inject constructor() {
|
class TimelineMediaSizeProvider @Inject constructor() {
|
||||||
|
|
||||||
lateinit var recyclerView: RecyclerView
|
var recyclerView: RecyclerView? = null
|
||||||
private var cachedSize: Pair<Int, Int>? = null
|
private var cachedSize: Pair<Int, Int>? = null
|
||||||
|
|
||||||
fun getMaxSize(): Pair<Int, Int> {
|
fun getMaxSize(): Pair<Int, Int> {
|
||||||
|
@ -31,17 +32,17 @@ class TimelineMediaSizeProvider @Inject constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeMaxSize(): Pair<Int, Int> {
|
private fun computeMaxSize(): Pair<Int, Int> {
|
||||||
val width = recyclerView.width
|
val width = recyclerView?.width ?: 0
|
||||||
val height = recyclerView.height
|
val height = recyclerView?.height ?: 0
|
||||||
val maxImageWidth: Int
|
val maxImageWidth: Int
|
||||||
val maxImageHeight: Int
|
val maxImageHeight: Int
|
||||||
// landscape / portrait
|
// landscape / portrait
|
||||||
if (width < height) {
|
if (width < height) {
|
||||||
maxImageWidth = Math.round(width * 0.7f)
|
maxImageWidth = (width * 0.7f).roundToInt()
|
||||||
maxImageHeight = Math.round(height * 0.5f)
|
maxImageHeight = (height * 0.5f).roundToInt()
|
||||||
} else {
|
} else {
|
||||||
maxImageWidth = Math.round(width * 0.5f)
|
maxImageWidth = (width * 0.5f).roundToInt()
|
||||||
maxImageHeight = Math.round(height * 0.7f)
|
maxImageHeight = (height * 0.7f).roundToInt()
|
||||||
}
|
}
|
||||||
return Pair(maxImageWidth, maxImageHeight)
|
return Pair(maxImageWidth, maxImageHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
|
@ -29,6 +28,8 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
@ -49,8 +50,8 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
@Inject lateinit var epoxyController: ViewReactionsEpoxyController
|
@Inject lateinit var epoxyController: ViewReactionsEpoxyController
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
@ -61,11 +62,15 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
recyclerView.configureWith(epoxyController, hasFixedSize = false)
|
||||||
recyclerView.adapter = epoxyController.adapter
|
|
||||||
bottomSheetTitle.text = context?.getString(R.string.reactions)
|
bottomSheetTitle.text = context?.getString(R.string.reactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
epoxyController.setData(it)
|
epoxyController.setData(it)
|
||||||
super.invalidate()
|
super.invalidate()
|
||||||
|
|
|
@ -36,9 +36,7 @@ class FilteredRoomsActivity : VectorBaseActivity() {
|
||||||
return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomListFragment
|
return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomListFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
override fun getLayoutRes() = R.layout.activity_filtered_rooms
|
||||||
return R.layout.activity_filtered_rooms
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.*
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
@ -35,13 +36,13 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.platform.OnBackPressed
|
import im.vector.riotx.core.platform.OnBackPressed
|
||||||
import im.vector.riotx.core.platform.StateView
|
import im.vector.riotx.core.platform.StateView
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
|
||||||
import im.vector.riotx.features.home.RoomListDisplayMode
|
import im.vector.riotx.features.home.RoomListDisplayMode
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||||
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.list.widget.FabMenuView
|
import im.vector.riotx.features.home.room.list.widget.FabMenuView
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
|
@ -65,6 +66,7 @@ class RoomListFragment @Inject constructor(
|
||||||
|
|
||||||
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
||||||
|
|
||||||
|
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||||
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
||||||
private val roomListParams: RoomListParams by args()
|
private val roomListParams: RoomListParams by args()
|
||||||
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
|
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
|
||||||
|
@ -118,8 +120,12 @@ class RoomListFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
roomController.removeModelBuildListener(modelBuildListener)
|
||||||
|
modelBuildListener = null
|
||||||
|
roomListView.cleanup()
|
||||||
|
roomController.listener = null
|
||||||
|
createChatFabMenu.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
roomListView.adapter = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openSelectedRoom(event: RoomListViewEvents.SelectRoom) {
|
private fun openSelectedRoom(event: RoomListViewEvents.SelectRoom) {
|
||||||
|
@ -198,7 +204,8 @@ class RoomListFragment @Inject constructor(
|
||||||
roomListView.layoutManager = layoutManager
|
roomListView.layoutManager = layoutManager
|
||||||
roomListView.itemAnimator = RoomListAnimator()
|
roomListView.itemAnimator = RoomListAnimator()
|
||||||
roomController.listener = this
|
roomController.listener = this
|
||||||
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
|
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||||
|
roomController.addModelBuildListener(modelBuildListener)
|
||||||
roomListView.adapter = roomController.adapter
|
roomListView.adapter = roomController.adapter
|
||||||
stateView.contentView = roomListView
|
stateView.contentView = roomListView
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getRoom(roomId)?.join(emptyList(), object : MatrixCallback<Unit> {
|
session.getRoom(roomId)?.join(callback = object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||||
// Instead, we wait for the room to be joined
|
// Instead, we wait for the room to be joined
|
||||||
|
@ -158,7 +158,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getRoom(roomId)?.leave(object : MatrixCallback<Unit> {
|
session.getRoom(roomId)?.leave(null, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
// We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data.
|
// We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data.
|
||||||
// Instead, we wait for the room to be rejected
|
// Instead, we wait for the room to be rejected
|
||||||
|
@ -197,7 +197,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
|
private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
|
||||||
session.getRoom(action.roomId)?.leave(object : MatrixCallback<Unit> {
|
session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
||||||
// It avoids to build the the whole list of rooms on the main thread.
|
// It avoids to build the whole list of rooms on the main thread.
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
|
@ -29,6 +28,8 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
@ -56,8 +57,8 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
|
||||||
|
|
||||||
override val showExpanded = true
|
override val showExpanded = true
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
@ -69,13 +70,17 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
recyclerView.configureWith(roomListActionsEpoxyController, hasFixedSize = false)
|
||||||
recyclerView.adapter = roomListActionsEpoxyController.adapter
|
|
||||||
// Disable item animation
|
// Disable item animation
|
||||||
recyclerView.itemAnimator = null
|
recyclerView.itemAnimator = null
|
||||||
roomListActionsEpoxyController.listener = this
|
roomListActionsEpoxyController.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
roomListActionsEpoxyController.setData(it)
|
roomListActionsEpoxyController.setData(it)
|
||||||
super.invalidate()
|
super.invalidate()
|
||||||
|
|
|
@ -539,7 +539,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||||
homeServerUrl = action.homeServerUrl,
|
homeServerUrl = data.homeServerUrl,
|
||||||
loginMode = loginMode,
|
loginMode = loginMode,
|
||||||
loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList()
|
loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,6 +24,8 @@ import butterknife.OnClick
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.utils.openUrlInExternalBrowser
|
import im.vector.riotx.core.utils.openUrlInExternalBrowser
|
||||||
import im.vector.riotx.features.login.AbstractLoginFragment
|
import im.vector.riotx.features.login.AbstractLoginFragment
|
||||||
import im.vector.riotx.features.login.LoginAction
|
import im.vector.riotx.features.login.LoginAction
|
||||||
|
@ -55,8 +57,7 @@ class LoginTermsFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
loginTermsPolicyList.configureWith(policyController)
|
||||||
loginTermsPolicyList.setController(policyController)
|
|
||||||
policyController.listener = this
|
policyController.listener = this
|
||||||
|
|
||||||
val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
|
val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
|
||||||
|
@ -69,6 +70,12 @@ class LoginTermsFragment @Inject constructor(
|
||||||
loginTermsViewState = LoginTermsViewState(list)
|
loginTermsViewState = LoginTermsViewState(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
loginTermsPolicyList.cleanup()
|
||||||
|
policyController.listener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderState() {
|
private fun renderState() {
|
||||||
policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked)
|
policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked)
|
||||||
|
|
||||||
|
|
|
@ -74,14 +74,14 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||||
private fun handleJoinRoom(roomId: String) {
|
private fun handleJoinRoom(roomId: String) {
|
||||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
session.getRoom(roomId)
|
session.getRoom(roomId)
|
||||||
?.join(emptyList(), object : MatrixCallback<Unit> {})
|
?.join(callback = object : MatrixCallback<Unit> {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRejectRoom(roomId: String) {
|
private fun handleRejectRoom(roomId: String) {
|
||||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
session.getRoom(roomId)
|
session.getRoom(roomId)
|
||||||
?.leave(object : MatrixCallback<Unit> {})
|
?.leave(callback = object : MatrixCallback<Unit> {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,18 @@ package im.vector.riotx.features.reactions
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.lifecycle.observe
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import kotlinx.android.synthetic.main.emoji_chooser_fragment.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
|
class EmojiChooserFragment @Inject constructor(
|
||||||
|
private val emojiRecyclerAdapter: EmojiRecyclerAdapter
|
||||||
|
) : VectorBaseFragment(),
|
||||||
|
EmojiRecyclerAdapter.InteractionListener,
|
||||||
|
ReactionClickListener {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.emoji_chooser_fragment
|
override fun getLayoutResId() = R.layout.emoji_chooser_fragment
|
||||||
|
|
||||||
|
@ -31,10 +37,29 @@ class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
|
viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
|
||||||
viewModel.initWithContext(context!!)
|
|
||||||
(view as? RecyclerView)?.let {
|
emojiRecyclerAdapter.reactionClickListener = this
|
||||||
it.adapter = viewModel.adapter
|
emojiRecyclerAdapter.interactionListener = this
|
||||||
it.adapter?.notifyDataSetChanged()
|
|
||||||
|
emojiRecyclerView.adapter = emojiRecyclerAdapter
|
||||||
|
|
||||||
|
viewModel.moveToSection.observe(viewLifecycleOwner) { section ->
|
||||||
|
emojiRecyclerAdapter.scrollToSection(section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun firstVisibleSectionChange(section: Int) {
|
||||||
|
viewModel.setCurrentSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReactionSelected(reaction: String) {
|
||||||
|
viewModel.onReactionSelected(reaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
emojiRecyclerView.cleanup()
|
||||||
|
emojiRecyclerAdapter.reactionClickListener = null
|
||||||
|
emojiRecyclerAdapter.interactionListener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.reactions
|
package im.vector.riotx.features.reactions
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
@ -23,36 +22,26 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class EmojiChooserViewModel @Inject constructor() : ViewModel() {
|
class EmojiChooserViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
var adapter: EmojiRecyclerAdapter? = null
|
|
||||||
val emojiSourceLiveData: MutableLiveData<EmojiDataSource> = MutableLiveData()
|
|
||||||
|
|
||||||
val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||||
var selectedReaction: String? = null
|
var selectedReaction: String? = null
|
||||||
var eventId: String? = null
|
var eventId: String? = null
|
||||||
|
|
||||||
val currentSection: MutableLiveData<Int> = MutableLiveData()
|
val currentSection: MutableLiveData<Int> = MutableLiveData()
|
||||||
|
val moveToSection: MutableLiveData<Int> = MutableLiveData()
|
||||||
|
|
||||||
var reactionClickListener = object : ReactionClickListener {
|
fun onReactionSelected(reaction: String) {
|
||||||
override fun onReactionSelected(reaction: String) {
|
|
||||||
selectedReaction = reaction
|
selectedReaction = reaction
|
||||||
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun initWithContext(context: Context) {
|
// Called by the Fragment, when the List is scrolled
|
||||||
// TODO load async
|
fun setCurrentSection(section: Int) {
|
||||||
val emojiDataSource = EmojiDataSource(context)
|
|
||||||
emojiSourceLiveData.value = emojiDataSource
|
|
||||||
adapter = EmojiRecyclerAdapter(emojiDataSource, reactionClickListener)
|
|
||||||
adapter?.interactionListener = object : EmojiRecyclerAdapter.InteractionListener {
|
|
||||||
override fun firstVisibleSectionChange(section: Int) {
|
|
||||||
currentSection.value = section
|
currentSection.value = section
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun scrollToSection(sectionIndex: Int) {
|
// Called by the Activity, when a tab item is clicked
|
||||||
adapter?.scrollToSection(sectionIndex)
|
fun scrollToSection(section: Int) {
|
||||||
|
moveToSection.value = section
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.reactions
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import im.vector.riotx.R
|
|
||||||
|
|
||||||
class EmojiDataSource(val context: Context) {
|
|
||||||
|
|
||||||
var rawData: EmojiData? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
val jsonAdapter = moshi.adapter(EmojiData::class.java)
|
|
||||||
val inputAsString = input.bufferedReader().use { it.readText() }
|
|
||||||
this.rawData = jsonAdapter.fromJson(inputAsString)
|
|
||||||
// this.rawData = mb.fr(InputStreamReader(it), EmojiData::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class EmojiData(val categories: List<EmojiCategory>,
|
|
||||||
val emojis: Map<String, EmojiItem>,
|
|
||||||
val aliases: Map<String, String>)
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class EmojiCategory(val id: String, val name: String, val emojis: List<String>)
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class EmojiItem(
|
|
||||||
@Json(name = "a") val name: String,
|
|
||||||
@Json(name = "b") val unicode: String,
|
|
||||||
@Json(name = "j") val keywords: List<String>?,
|
|
||||||
val k: List<String>?) {
|
|
||||||
|
|
||||||
var _emojiText: String? = null
|
|
||||||
|
|
||||||
fun emojiString() : String {
|
|
||||||
if (_emojiText == null) {
|
|
||||||
val utf8Text = unicode.split("-").joinToString("") { "\\u$it" } // "\u0048\u0065\u006C\u006C\u006F World"
|
|
||||||
_emojiText = fromUnicode(utf8Text)
|
|
||||||
}
|
|
||||||
return _emojiText!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromUnicode(unicode: String): String {
|
|
||||||
val str = unicode.replace("\\", "")
|
|
||||||
val arr = str.split("u".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
|
||||||
val text = StringBuffer()
|
|
||||||
for (i in 1 until arr.size) {
|
|
||||||
val hexVal = Integer.parseInt(arr[i], 16)
|
|
||||||
text.append(Character.toChars(hexVal))
|
|
||||||
}
|
|
||||||
return text.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// name: 'a',
|
|
||||||
// unified: 'b',
|
|
||||||
// non_qualified: 'c',
|
|
||||||
// has_img_apple: 'd',
|
|
||||||
// has_img_google: 'e',
|
|
||||||
// has_img_twitter: 'f',
|
|
||||||
// has_img_emojione: 'g',
|
|
||||||
// has_img_facebook: 'h',
|
|
||||||
// has_img_messenger: 'i',
|
|
||||||
// keywords: 'j',
|
|
||||||
// sheet: 'k',
|
|
||||||
// emoticons: 'l',
|
|
||||||
// text: 'm',
|
|
||||||
// short_names: 'n',
|
|
||||||
// added_in: 'o',
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue