mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 01:45:52 +03:00
Merge branch 'release/0.20.0'
This commit is contained in:
commit
d1d79c0191
125 changed files with 4324 additions and 706 deletions
20
CHANGES.md
20
CHANGES.md
|
@ -1,3 +1,23 @@
|
||||||
|
Changes in RiotX 0.20.0 (2020-05-15)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Add Direct Shortcuts (#652)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Invite member(s) to an existing room (#1276)
|
||||||
|
- Improve notification accessibility with ticker text (#1226)
|
||||||
|
- Support homeserver discovery from MXID (DISABLED: waiting for design) (#476)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix | Verify Manually by Text crashes if private SSK not known (#1337)
|
||||||
|
- Sometimes the same device appears twice in the list of devices of a user (#1329)
|
||||||
|
- Random Crashes while doing sth with cross signing keys (#1364)
|
||||||
|
- Crash | crash while restoring key backup (#1366)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- excludedUserIds parameter added to the UserService.getPagedUsersLive() function
|
||||||
|
|
||||||
Changes in RiotX 0.19.0 (2020-05-04)
|
Changes in RiotX 0.19.0 (2020-05-04)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
org.gradle.jvmargs=-Xmx8192m
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
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.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 io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
@ -95,6 +96,10 @@ class RxRoom(private val room: Room) {
|
||||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||||
return room.getLiveRoomNotificationState().asObservable()
|
return room.getLiveRoomNotificationState().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
||||||
|
room.invite(userId, reason, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
|
|
@ -90,8 +90,8 @@ class RxSession(private val session: Session) {
|
||||||
return session.getIgnoredUsersLive().asObservable()
|
return session.getIgnoredUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
fun livePagedUsers(filter: String? = null, excludedUserIds: Set<String>? = null): Observable<PagedList<User>> {
|
||||||
return session.getPagedUsersLive(filter).asObservable()
|
return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||||
|
|
|
@ -19,13 +19,13 @@ package im.vector.matrix.android.internal.crypto.keysbackup
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.common.CommonTestHelper
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
import im.vector.matrix.android.common.CryptoTestData
|
import im.vector.matrix.android.common.CryptoTestData
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||||
*/
|
*/
|
||||||
data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||||
val aliceKeys: List<OlmInboundGroupSessionWrapper>,
|
val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
|
||||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||||
val aliceSession2: Session) {
|
val aliceSession2: Session) {
|
||||||
fun cleanUp(testHelper: CommonTestHelper) {
|
fun cleanUp(testHelper: CommonTestHelper) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
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.auth.wellknown.WellknownResult
|
||||||
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
|
||||||
|
|
||||||
|
@ -30,7 +31,6 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||||
*/
|
*/
|
||||||
interface AuthenticationService {
|
interface AuthenticationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the supported login flows for this homeserver.
|
* Request the supported login flows for this homeserver.
|
||||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||||
|
@ -89,4 +89,20 @@ interface AuthenticationService {
|
||||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
callback: MatrixCallback<Session>): Cancelable
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a wellknown request, using the domain from the matrixId
|
||||||
|
*/
|
||||||
|
fun getWellKnownData(matrixId: String,
|
||||||
|
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate with a matrixId and a password
|
||||||
|
* Usually call this after a successful call to getWellKnownData()
|
||||||
|
*/
|
||||||
|
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
matrixId: String,
|
||||||
|
password: String,
|
||||||
|
initialDeviceName: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,38 @@ import im.vector.matrix.android.internal.util.md5
|
||||||
* This data class hold credentials user data.
|
* This data class hold credentials user data.
|
||||||
* You shouldn't have to instantiate it.
|
* You shouldn't have to instantiate it.
|
||||||
* The access token should be use to authenticate user in all server requests.
|
* The access token should be use to authenticate user in all server requests.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Credentials(
|
data class Credentials(
|
||||||
|
/**
|
||||||
|
* The fully-qualified Matrix ID that has been registered.
|
||||||
|
*/
|
||||||
@Json(name = "user_id") val userId: String,
|
@Json(name = "user_id") val userId: String,
|
||||||
@Json(name = "home_server") val homeServer: String,
|
/**
|
||||||
|
* An access token for the account. This access token can then be used to authorize other requests.
|
||||||
|
*/
|
||||||
@Json(name = "access_token") val accessToken: String,
|
@Json(name = "access_token") val accessToken: String,
|
||||||
|
/**
|
||||||
|
* Not documented
|
||||||
|
*/
|
||||||
@Json(name = "refresh_token") val refreshToken: String?,
|
@Json(name = "refresh_token") val refreshToken: String?,
|
||||||
|
/**
|
||||||
|
* The server_name of the homeserver on which the account has been registered.
|
||||||
|
* @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
|
||||||
|
* if they require it. Note also that homeserver is not spelt this way.
|
||||||
|
*/
|
||||||
|
@Json(name = "home_server") val homeServer: String,
|
||||||
|
/**
|
||||||
|
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
|
||||||
|
*/
|
||||||
@Json(name = "device_id") val deviceId: String?,
|
@Json(name = "device_id") val deviceId: String?,
|
||||||
// Optional data that may contain info to override home server and/or identity server
|
/**
|
||||||
@Json(name = "well_known") val wellKnown: WellKnown? = null
|
* Optional client configuration provided by the server. If present, clients SHOULD use the provided object to
|
||||||
|
* reconfigure themselves, optionally validating the URLs within.
|
||||||
|
* This object takes the same form as the one returned from .well-known autodiscovery.
|
||||||
|
*/
|
||||||
|
@Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun Credentials.sessionId(): String {
|
internal fun Credentials.sessionId(): String {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.api.auth.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a light version of Wellknown model, used for login response
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class DiscoveryInformation(
|
||||||
|
/**
|
||||||
|
* Required. Used by clients to discover homeserver information.
|
||||||
|
*/
|
||||||
|
@Json(name = "m.homeserver")
|
||||||
|
val homeServer: WellKnownBaseConfig? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by clients to discover identity server information.
|
||||||
|
* Note: matrix.org does not send this field
|
||||||
|
*/
|
||||||
|
@Json(name = "m.identity_server")
|
||||||
|
val identityServer: WellKnownBaseConfig? = null
|
||||||
|
)
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
|
@ -52,7 +53,7 @@ data class WellKnown(
|
||||||
val identityServer: WellKnownBaseConfig? = null,
|
val identityServer: WellKnownBaseConfig? = null,
|
||||||
|
|
||||||
@Json(name = "m.integrations")
|
@Json(name = "m.integrations")
|
||||||
val integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
val integrations: JsonDict? = null
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Returns the list of integration managers proposed
|
* Returns the list of integration managers proposed
|
||||||
|
|
|
@ -16,6 +16,6 @@
|
||||||
package im.vector.matrix.android.api.auth.data
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
data class WellKnownManagerConfig(
|
data class WellKnownManagerConfig(
|
||||||
val apiUrl : String,
|
val apiUrl: String,
|
||||||
val uiUrl: String
|
val uiUrl: String
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.api.auth.wellknown
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
|
||||||
|
*/
|
||||||
|
sealed class WellknownResult {
|
||||||
|
/**
|
||||||
|
* The provided matrixId is no valid. Unable to extract a domain name.
|
||||||
|
*/
|
||||||
|
object InvalidMatrixId : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
|
||||||
|
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
|
||||||
|
*/
|
||||||
|
data class Prompt(val homeServerUrl: String,
|
||||||
|
val identityServerUrl: String?,
|
||||||
|
val wellKnown: WellKnown) : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available,
|
||||||
|
* then the client may use other methods of determining the required parameters, such as prompting the user, or using default values.
|
||||||
|
*/
|
||||||
|
object Ignore : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter.
|
||||||
|
*/
|
||||||
|
object FailPrompt : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process.
|
||||||
|
* At this point, valid data was obtained, but no homeserver is available to serve the client.
|
||||||
|
* No further guess should be attempted and the user should make a conscientious decision what to do next.
|
||||||
|
*/
|
||||||
|
object FailError : WellknownResult()
|
||||||
|
}
|
|
@ -61,9 +61,10 @@ interface UserService {
|
||||||
/**
|
/**
|
||||||
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
* @param filter the filter. It will look into userId and displayName.
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
|
* @param excludedUserIds userId list which will be excluded from the result list.
|
||||||
* @return a Livedata of users
|
* @return a Livedata of users
|
||||||
*/
|
*/
|
||||||
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
|
fun getPagedUsersLive(filter: String? = null, excludedUserIds: Set<String>? = null): LiveData<PagedList<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of ignored users
|
* Get list of ignored users
|
||||||
|
|
|
@ -22,6 +22,9 @@ package im.vector.matrix.android.api.session.user.model
|
||||||
*/
|
*/
|
||||||
data class User(
|
data class User(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
|
/**
|
||||||
|
* For usage in UI, consider using [getBestName]
|
||||||
|
*/
|
||||||
val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
val avatarUrl: String? = null
|
val avatarUrl: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -25,6 +25,10 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DefaultDirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DefaultGetWellknownTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
@ -59,14 +63,20 @@ internal abstract class AuthModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
|
abstract fun bindPendingSessionStore(store: RealmPendingSessionStore): PendingSessionStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
abstract fun bindAuthenticationService(service: DefaultAuthenticationService): AuthenticationService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionCreator(sessionCreator: DefaultSessionCreator): SessionCreator
|
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedByS
|
||||||
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
||||||
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.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
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
|
||||||
|
@ -38,11 +39,16 @@ 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
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.exhaustive
|
||||||
import im.vector.matrix.android.internal.util.toCancelable
|
import im.vector.matrix.android.internal.util.toCancelable
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -59,7 +65,10 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val sessionManager: SessionManager,
|
private val sessionManager: SessionManager,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionCreator: SessionCreator,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val pendingSessionStore: PendingSessionStore,
|
||||||
|
private val getWellknownTask: GetWellknownTask,
|
||||||
|
private val directLoginTask: DirectLoginTask,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
) : AuthenticationService {
|
) : AuthenticationService {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||||
|
@ -148,27 +157,71 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
// Ok, try to get the config.json file of a RiotWeb client
|
// Ok, try to get the config.json file of a RiotWeb client
|
||||||
val riotConfig = executeRequest<RiotConfig>(null) {
|
return runCatching {
|
||||||
apiCall = authAPI.getRiotConfig()
|
executeRequest<RiotConfig>(null) {
|
||||||
}
|
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>(null) {
|
|
||||||
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 */)
|
|
||||||
}
|
}
|
||||||
|
.map { riotConfig ->
|
||||||
|
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>(null) {
|
||||||
|
apiCall = newAuthAPI.versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
it
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (it is Failure.OtherServerError
|
||||||
|
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||||
|
// Try with wellknown
|
||||||
|
getWellknownLoginFlowInternal(homeServerConnectionConfig)
|
||||||
|
} else {
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getWellknownLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
|
val domain = homeServerConnectionConfig.homeServerUri.host
|
||||||
|
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
|
||||||
|
// Create a fake userId, for the getWellknown task
|
||||||
|
val fakeUserId = "@alice:$domain"
|
||||||
|
val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId))
|
||||||
|
|
||||||
|
return when (wellknownResult) {
|
||||||
|
is WellknownResult.Prompt -> {
|
||||||
|
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(wellknownResult.homeServerUrl),
|
||||||
|
identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||||
|
|
||||||
|
val versions = executeRequest<Versions>(null) {
|
||||||
|
apiCall = newAuthAPI.versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl)
|
||||||
|
}
|
||||||
|
else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||||
|
@ -260,6 +313,26 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getWellKnownData(matrixId: String, callback: MatrixCallback<WellknownResult>): Cancelable {
|
||||||
|
return getWellknownTask
|
||||||
|
.configureWith(GetWellknownTask.Params(matrixId)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
matrixId: String,
|
||||||
|
password: String,
|
||||||
|
initialDeviceName: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable {
|
||||||
|
return directLoginTask
|
||||||
|
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun createSessionFromSso(credentials: Credentials,
|
private suspend fun createSessionFromSso(credentials: Credentials,
|
||||||
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
||||||
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||||
|
|
|
@ -46,14 +46,14 @@ internal class DefaultSessionCreator @Inject constructor(
|
||||||
val sessionParams = SessionParams(
|
val sessionParams = SessionParams(
|
||||||
credentials = credentials,
|
credentials = credentials,
|
||||||
homeServerConnectionConfig = homeServerConnectionConfig.copy(
|
homeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
homeServerUri = credentials.wellKnown?.homeServer?.baseURL
|
homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL
|
||||||
// remove trailing "/"
|
// remove trailing "/"
|
||||||
?.trim { it == '/' }
|
?.trim { it == '/' }
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
?.also { Timber.d("Overriding homeserver url to $it") }
|
?.also { Timber.d("Overriding homeserver url to $it") }
|
||||||
?.let { Uri.parse(it) }
|
?.let { Uri.parse(it) }
|
||||||
?: homeServerConnectionConfig.homeServerUri,
|
?: homeServerConnectionConfig.homeServerUri,
|
||||||
identityServerUri = credentials.wellKnown?.identityServer?.baseURL
|
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
|
||||||
// remove trailing "/"
|
// remove trailing "/"
|
||||||
?.trim { it == '/' }
|
?.trim { it == '/' }
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.wellknown
|
||||||
|
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
|
||||||
|
data class Params(
|
||||||
|
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
val userId: String,
|
||||||
|
val password: String,
|
||||||
|
val deviceName: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultDirectLoginTask @Inject constructor(
|
||||||
|
@Unauthenticated
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val retrofitFactory: RetrofitFactory,
|
||||||
|
private val sessionCreator: SessionCreator
|
||||||
|
) : DirectLoginTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||||
|
val authAPI = retrofitFactory.create(okHttpClient, params.homeServerConnectionConfig.homeServerUri.toString())
|
||||||
|
.create(AuthAPI::class.java)
|
||||||
|
|
||||||
|
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||||
|
|
||||||
|
val credentials = executeRequest<Credentials>(null) {
|
||||||
|
apiCall = authAPI.login(loginParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.wellknown
|
||||||
|
|
||||||
|
import android.util.MalformedJsonException
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.identity.IdentityPingApi
|
||||||
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.isValidUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.EOFException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
|
internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownResult> {
|
||||||
|
data class Params(
|
||||||
|
val matrixId: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired from AutoDiscovery class from legacy Matrix Android SDK
|
||||||
|
*/
|
||||||
|
internal class DefaultGetWellknownTask @Inject constructor(
|
||||||
|
@Unauthenticated
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val retrofitFactory: RetrofitFactory
|
||||||
|
) : GetWellknownTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
|
||||||
|
if (!MatrixPatterns.isUserId(params.matrixId)) {
|
||||||
|
return WellknownResult.InvalidMatrixId
|
||||||
|
}
|
||||||
|
|
||||||
|
val homeServerDomain = params.matrixId.substringAfter(":")
|
||||||
|
|
||||||
|
return findClientConfig(homeServerDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find client config
|
||||||
|
*
|
||||||
|
* - Do the .well-known request
|
||||||
|
* - validate homeserver url and identity server url if provide in .well-known result
|
||||||
|
* - return action and .well-known data
|
||||||
|
*
|
||||||
|
* @param domain: homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org")
|
||||||
|
*/
|
||||||
|
private suspend fun findClientConfig(domain: String): WellknownResult {
|
||||||
|
val wellKnownAPI = retrofitFactory.create(okHttpClient, "https://dummy.org")
|
||||||
|
.create(WellKnownAPI::class.java)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val wellKnown = executeRequest<WellKnown>(null) {
|
||||||
|
apiCall = wellKnownAPI.getWellKnown(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
val homeServerBaseUrl = wellKnown.homeServer?.baseURL
|
||||||
|
if (homeServerBaseUrl.isNullOrBlank()) {
|
||||||
|
WellknownResult.FailPrompt
|
||||||
|
} else {
|
||||||
|
if (homeServerBaseUrl.isValidUrl()) {
|
||||||
|
// Check that HS is a real one
|
||||||
|
validateHomeServer(homeServerBaseUrl, wellKnown)
|
||||||
|
} else {
|
||||||
|
WellknownResult.FailError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
when (throwable) {
|
||||||
|
is Failure.NetworkConnection -> {
|
||||||
|
WellknownResult.Ignore
|
||||||
|
}
|
||||||
|
is Failure.OtherServerError -> {
|
||||||
|
when (throwable.httpCode) {
|
||||||
|
HttpsURLConnection.HTTP_NOT_FOUND -> WellknownResult.Ignore
|
||||||
|
else -> WellknownResult.FailPrompt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MalformedJsonException, is EOFException -> {
|
||||||
|
WellknownResult.FailPrompt
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if home server is valid, and (if applicable) if identity server is pingable
|
||||||
|
*/
|
||||||
|
private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown): WellknownResult {
|
||||||
|
val capabilitiesAPI = retrofitFactory.create(okHttpClient, homeServerBaseUrl)
|
||||||
|
.create(CapabilitiesAPI::class.java)
|
||||||
|
|
||||||
|
try {
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = capabilitiesAPI.getVersions()
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
return WellknownResult.FailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (wellKnown.identityServer == null) {
|
||||||
|
// No identity server
|
||||||
|
WellknownResult.Prompt(homeServerBaseUrl, null, wellKnown)
|
||||||
|
} else {
|
||||||
|
// if m.identity_server is present it must be valid
|
||||||
|
val identityServerBaseUrl = wellKnown.identityServer.baseURL
|
||||||
|
if (identityServerBaseUrl.isNullOrBlank()) {
|
||||||
|
WellknownResult.FailError
|
||||||
|
} else {
|
||||||
|
if (identityServerBaseUrl.isValidUrl()) {
|
||||||
|
if (validateIdentityServer(identityServerBaseUrl)) {
|
||||||
|
// All is ok
|
||||||
|
WellknownResult.Prompt(homeServerBaseUrl, identityServerBaseUrl, wellKnown)
|
||||||
|
} else {
|
||||||
|
WellknownResult.FailError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WellknownResult.FailError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if identity server is pingable
|
||||||
|
*/
|
||||||
|
private suspend fun validateIdentityServer(identityServerBaseUrl: String): Boolean {
|
||||||
|
val identityPingApi = retrofitFactory.create(okHttpClient, identityServerBaseUrl)
|
||||||
|
.create(IdentityPingApi::class.java)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = identityPingApi.ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get an identity server URL from a home server URL, using a .wellknown request
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
fun getIdentityServer(homeServerUrl: String, callback: ApiCallback<String?>) {
|
||||||
|
if (homeServerUrl.startsWith("https://")) {
|
||||||
|
wellKnownRestClient.getWellKnown(homeServerUrl.substring("https://".length),
|
||||||
|
object : SimpleApiCallback<WellKnown>(callback) {
|
||||||
|
override fun onSuccess(info: WellKnown) {
|
||||||
|
callback.onSuccess(info.identityServer?.baseURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
callback.onUnexpectedError(InvalidParameterException("malformed url"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getServerPreferredIntegrationManagers(homeServerUrl: String, callback: ApiCallback<List<WellKnownManagerConfig>>) {
|
||||||
|
if (homeServerUrl.startsWith("https://")) {
|
||||||
|
wellKnownRestClient.getWellKnown(homeServerUrl.substring("https://".length),
|
||||||
|
object : SimpleApiCallback<WellKnown>(callback) {
|
||||||
|
override fun onSuccess(info: WellKnown) {
|
||||||
|
callback.onSuccess(info.getIntegrationManagers())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
callback.onUnexpectedError(InvalidParameterException("malformed url"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.wellknown
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
internal interface WellKnownAPI {
|
||||||
|
@GET("https://{domain}/.well-known/matrix/client")
|
||||||
|
fun getWellKnown(@Path("domain") domain: String): Call<WellKnown>
|
||||||
|
}
|
|
@ -446,7 +446,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDevices(userId)?.map { it.value }?.sortedBy { it.deviceId } ?: emptyList()
|
return cryptoStore.getUserDeviceList(userId) ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||||
|
|
|
@ -48,7 +48,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
|
|
||||||
if (device.trustLevel != trustLevel) {
|
if (device.trustLevel != trustLevel) {
|
||||||
device.trustLevel = trustLevel
|
device.trustLevel = trustLevel
|
||||||
cryptoStore.storeUserDevice(userId, device)
|
cryptoStore.setDeviceTrust(userId, deviceId, trustLevel.crossSigningVerified, trustLevel.locallyVerified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,14 +164,6 @@ internal interface IMXCryptoStore {
|
||||||
*/
|
*/
|
||||||
fun saveOlmAccount()
|
fun saveOlmAccount()
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a device for a user.
|
|
||||||
*
|
|
||||||
* @param userId the user's id.
|
|
||||||
* @param device the device to store.
|
|
||||||
*/
|
|
||||||
fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a device for a user.
|
* Retrieve a device for a user.
|
||||||
*
|
*
|
||||||
|
@ -415,7 +407,7 @@ internal interface IMXCryptoStore {
|
||||||
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
|
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
|
||||||
|
|
||||||
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)
|
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)
|
||||||
fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean)
|
fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?)
|
||||||
|
|
||||||
fun clearOtherUserTrust()
|
fun clearOtherUserTrust()
|
||||||
|
|
||||||
|
|
|
@ -233,29 +233,6 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
return olmAccount!!
|
return olmAccount!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?) {
|
|
||||||
if (userId == null || deviceInfo == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
|
||||||
val user = UserEntity.getOrCreate(realm, userId)
|
|
||||||
|
|
||||||
// Create device info
|
|
||||||
val deviceInfoEntity = CryptoMapper.mapToEntity(deviceInfo)
|
|
||||||
realm.insertOrUpdate(deviceInfoEntity)
|
|
||||||
// val deviceInfoEntity = DeviceInfoEntity.getOrCreate(it, userId, deviceInfo.deviceId).apply {
|
|
||||||
// deviceId = deviceInfo.deviceId
|
|
||||||
// identityKey = deviceInfo.identityKey()
|
|
||||||
// putDeviceInfo(deviceInfo)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!user.devices.contains(deviceInfoEntity)) {
|
|
||||||
user.devices.add(deviceInfoEntity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
|
override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
|
||||||
return doWithRealm(realmConfiguration) {
|
return doWithRealm(realmConfiguration) {
|
||||||
it.where<DeviceInfoEntity>()
|
it.where<DeviceInfoEntity>()
|
||||||
|
@ -1276,7 +1253,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean) {
|
override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) {
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
doRealmTransaction(realmConfiguration) { realm ->
|
||||||
realm.where(DeviceInfoEntity::class.java)
|
realm.where(DeviceInfoEntity::class.java)
|
||||||
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
||||||
|
@ -1289,7 +1266,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
deviceInfoEntity.trustLevelEntity = it
|
deviceInfoEntity.trustLevelEntity = it
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trustEntity.locallyVerified = locallyVerified
|
locallyVerified?.let { trustEntity.locallyVerified = it }
|
||||||
trustEntity.crossSignedVerified = crossSignedVerified
|
trustEntity.crossSignedVerified = crossSignedVerified
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1429,7 +1406,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
// Just override existing, caller should check and untrust id needed
|
// Just override existing, caller should check and untrust id needed
|
||||||
val existing = CrossSigningInfoEntity.getOrCreate(realm, userId)
|
val existing = CrossSigningInfoEntity.getOrCreate(realm, userId)
|
||||||
existing.crossSigningKeys.forEach { it.deleteFromRealm() }
|
existing.crossSigningKeys.deleteAllFromRealm()
|
||||||
existing.crossSigningKeys.addAll(
|
existing.crossSigningKeys.addAll(
|
||||||
info.crossSigningKeys.map {
|
info.crossSigningKeys.map {
|
||||||
crossSigningKeysMapper.map(it)
|
crossSigningKeysMapper.map(it)
|
||||||
|
|
|
@ -45,7 +45,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
|
|
||||||
// Version 1L added Cross Signing info persistence
|
// Version 1L added Cross Signing info persistence
|
||||||
companion object {
|
companion object {
|
||||||
const val CRYPTO_STORE_SCHEMA_VERSION = 5L
|
const val CRYPTO_STORE_SCHEMA_VERSION = 6L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
@ -56,6 +56,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
if (oldVersion <= 2) migrateTo3(realm)
|
if (oldVersion <= 2) migrateTo3(realm)
|
||||||
if (oldVersion <= 3) migrateTo4(realm)
|
if (oldVersion <= 3) migrateTo4(realm)
|
||||||
if (oldVersion <= 4) migrateTo5(realm)
|
if (oldVersion <= 4) migrateTo5(realm)
|
||||||
|
if (oldVersion <= 5) migrateTo6(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
@ -255,4 +256,22 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixes duplicate devices in UserEntity#devices
|
||||||
|
private fun migrateTo6(realm: DynamicRealm) {
|
||||||
|
val userEntities = realm.where("UserEntity").findAll()
|
||||||
|
userEntities.forEach {
|
||||||
|
try {
|
||||||
|
val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
|
||||||
|
?: return@forEach
|
||||||
|
val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
|
||||||
|
if (distinct.size != deviceList.size) {
|
||||||
|
deviceList.clear()
|
||||||
|
deviceList.addAll(distinct)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.w(failure, "Crypto Data base migration error for migrateTo6")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
internal interface IdentityPingApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
|
* Simple ping call to check if server alive
|
||||||
|
*
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check
|
||||||
|
*
|
||||||
|
* @return 200 in case of success
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_IDENTITY)
|
||||||
|
fun ping(): Call<Unit>
|
||||||
|
}
|
|
@ -26,4 +26,10 @@ internal object NetworkConstants {
|
||||||
// Media
|
// Media
|
||||||
private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media"
|
private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media"
|
||||||
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
||||||
|
|
||||||
|
// Identity server
|
||||||
|
const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/"
|
||||||
|
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/"
|
||||||
|
|
||||||
|
const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.session.filter.FilterModule
|
||||||
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
||||||
import im.vector.matrix.android.internal.session.group.GroupModule
|
import im.vector.matrix.android.internal.session.group.GroupModule
|
||||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
||||||
|
import im.vector.matrix.android.internal.session.openid.OpenIdModule
|
||||||
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
||||||
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
||||||
import im.vector.matrix.android.internal.session.pushers.PushersModule
|
import im.vector.matrix.android.internal.session.pushers.PushersModule
|
||||||
|
@ -70,6 +71,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
PushersModule::class,
|
PushersModule::class,
|
||||||
|
OpenIdModule::class,
|
||||||
AccountDataModule::class,
|
AccountDataModule::class,
|
||||||
ProfileModule::class,
|
ProfileModule::class,
|
||||||
SessionAssistedInjectModule::class,
|
SessionAssistedInjectModule::class,
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.openid
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetOpenIdTokenTask : Task<Unit, RequestOpenIdTokenResponse>
|
||||||
|
|
||||||
|
internal class DefaultGetOpenIdTokenTask @Inject constructor(
|
||||||
|
@UserId private val userId: String,
|
||||||
|
private val openIdAPI: OpenIdAPI,
|
||||||
|
private val eventBus: EventBus) : GetOpenIdTokenTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Unit): RequestOpenIdTokenResponse {
|
||||||
|
return executeRequest(eventBus) {
|
||||||
|
apiCall = openIdAPI.openIdToken(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.openid
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
internal interface OpenIdAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a bearer token from the homeserver that the user can
|
||||||
|
* present to a third party in order to prove their ownership
|
||||||
|
* of the Matrix account they are logged into.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-user-userid-openid-request-token
|
||||||
|
*
|
||||||
|
* @param userId the user id
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
|
||||||
|
fun openIdToken(@Path("userId") userId: String, @Body body: JsonDict = emptyMap()): Call<RequestOpenIdTokenResponse>
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.openid
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class OpenIdModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
fun providesOpenIdAPI(retrofit: Retrofit): OpenIdAPI {
|
||||||
|
return retrofit.create(OpenIdAPI::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetOpenIdTokenTask(task: DefaultGetOpenIdTokenTask): GetOpenIdTokenTask
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.openid
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class RequestOpenIdTokenResponse(
|
||||||
|
/**
|
||||||
|
* Required. An access token the consumer may use to verify the identity of the person who generated the token.
|
||||||
|
* This is given to the federation API GET /openid/userinfo to verify the user's identity.
|
||||||
|
*/
|
||||||
|
@Json(name = "access_token")
|
||||||
|
val openIdToken: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The string "Bearer".
|
||||||
|
*/
|
||||||
|
@Json(name = "token_type")
|
||||||
|
val tokenType: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The homeserver domain the consumer should use when attempting to verify the user's identity.
|
||||||
|
*/
|
||||||
|
@Json(name = "matrix_server_name")
|
||||||
|
val matrixServerName: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The number of seconds before this token expires and a new one must be generated.
|
||||||
|
*/
|
||||||
|
@Json(name = "expires_in")
|
||||||
|
val expiresIn: Int
|
||||||
|
)
|
|
@ -39,6 +39,8 @@ internal class DefaultInviteTask @Inject constructor(
|
||||||
return executeRequest(eventBus) {
|
return executeRequest(eventBus) {
|
||||||
val body = InviteBody(params.userId, params.reason)
|
val body = InviteBody(params.userId, params.reason)
|
||||||
apiCall = roomAPI.invite(params.roomId, body)
|
apiCall = roomAPI.invite(params.roomId, body)
|
||||||
|
isRetryable = true
|
||||||
|
maxRetryCount = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPagedUsersLive(filter: String?): LiveData<PagedList<User>> {
|
override fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
|
||||||
realmDataSourceFactory.updateQuery { realm ->
|
realmDataSourceFactory.updateQuery { realm ->
|
||||||
val query = realm.where(UserEntity::class.java)
|
val query = realm.where(UserEntity::class.java)
|
||||||
if (filter.isNullOrEmpty()) {
|
if (filter.isNullOrEmpty()) {
|
||||||
|
@ -104,6 +104,11 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
.contains(UserEntityFields.USER_ID, filter)
|
.contains(UserEntityFields.USER_ID, filter)
|
||||||
.endGroup()
|
.endGroup()
|
||||||
}
|
}
|
||||||
|
excludedUserIds
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?.let {
|
||||||
|
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
|
||||||
|
}
|
||||||
query.sort(UserEntityFields.DISPLAY_NAME)
|
query.sort(UserEntityFields.DISPLAY_NAME)
|
||||||
}
|
}
|
||||||
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.user
|
package im.vector.matrix.android.internal.session.user
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants.URI_API_PREFIX_PATH_R0
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import im.vector.matrix.android.internal.session.user.model.SearchUsersParams
|
import im.vector.matrix.android.internal.session.user.model.SearchUsersParams
|
||||||
import im.vector.matrix.android.internal.session.user.model.SearchUsersResponse
|
import im.vector.matrix.android.internal.session.user.model.SearchUsersResponse
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
@ -30,6 +30,6 @@ internal interface SearchUserAPI {
|
||||||
*
|
*
|
||||||
* @param searchUsersParams the search params.
|
* @param searchUsersParams the search params.
|
||||||
*/
|
*/
|
||||||
@POST(URI_API_PREFIX_PATH_R0 + "user_directory/search")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search")
|
||||||
fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersResponse>
|
fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
|
||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
@ -34,15 +33,4 @@ interface AccountDataAPI {
|
||||||
*/
|
*/
|
||||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}")
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}")
|
||||||
fun setAccountData(@Path("userId") userId: String, @Path("type") type: String, @Body params: Any): Call<Unit>
|
fun setAccountData(@Path("userId") userId: String, @Path("type") type: String, @Body params: Any): Call<Unit>
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a bearer token from the homeserver that the user can
|
|
||||||
* present to a third party in order to prove their ownership
|
|
||||||
* of the Matrix account they are logged into.
|
|
||||||
*
|
|
||||||
* @param userId the user id
|
|
||||||
* @param body the body content
|
|
||||||
*/
|
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
|
|
||||||
fun openIdToken(@Path("userId") userId: String, @Body body: Map<Any, Any>): Call<Map<Any, Any>>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,12 +14,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedAction
|
import java.net.URL
|
||||||
|
|
||||||
sealed class CreateDirectRoomSharedAction : VectorSharedAction {
|
internal fun String.isValidUrl(): Boolean {
|
||||||
object OpenUsersDirectory : CreateDirectRoomSharedAction()
|
return try {
|
||||||
object Close : CreateDirectRoomSharedAction()
|
URL(this)
|
||||||
object GoBack : CreateDirectRoomSharedAction()
|
true
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -179,8 +179,8 @@
|
||||||
<string name="notice_room_invite_no_invitee_with_reason">%1$s\'s Einladung. Grund: %2$s</string>
|
<string name="notice_room_invite_no_invitee_with_reason">%1$s\'s Einladung. Grund: %2$s</string>
|
||||||
<string name="notice_room_invite_with_reason">%1$s hat %2$s eingeladen. Grund: %3$s</string>
|
<string name="notice_room_invite_with_reason">%1$s hat %2$s eingeladen. Grund: %3$s</string>
|
||||||
<string name="notice_room_invite_you_with_reason">%1$s hat dich eingeladen. Grund: %2$s</string>
|
<string name="notice_room_invite_you_with_reason">%1$s hat dich eingeladen. Grund: %2$s</string>
|
||||||
<string name="notice_room_join_with_reason">%1$s beigetreten. Grund: %2$s</string>
|
<string name="notice_room_join_with_reason">%1$s ist dem Raum beigetreten. Grund: %2$s</string>
|
||||||
<string name="notice_room_leave_with_reason">%1$s ging. Grund: %2$s</string>
|
<string name="notice_room_leave_with_reason">%1$s hat den Raum verlassen. Grund: %2$s</string>
|
||||||
<string name="notice_room_reject_with_reason">%1$s hat die Einladung abgelehnt. Grund: %2$s</string>
|
<string name="notice_room_reject_with_reason">%1$s hat die Einladung abgelehnt. Grund: %2$s</string>
|
||||||
<string name="notice_room_kick_with_reason">%1$s hat %2$s gekickt. Grund: %3$s</string>
|
<string name="notice_room_kick_with_reason">%1$s hat %2$s gekickt. Grund: %3$s</string>
|
||||||
<string name="notice_room_unban_with_reason">%1$s hat Sperre von %2$s aufgehoben. Grund: %3$s</string>
|
<string name="notice_room_unban_with_reason">%1$s hat Sperre von %2$s aufgehoben. Grund: %3$s</string>
|
||||||
|
|
|
@ -3,20 +3,210 @@
|
||||||
<string name="summary_user_sent_image">%1$s sendis bildon.</string>
|
<string name="summary_user_sent_image">%1$s sendis bildon.</string>
|
||||||
<string name="summary_user_sent_sticker">%1$s sendis glumarkon.</string>
|
<string name="summary_user_sent_sticker">%1$s sendis glumarkon.</string>
|
||||||
|
|
||||||
<string name="notice_room_invite_no_invitee">invito de %s</string>
|
<string name="notice_room_invite_no_invitee">Invito de %s</string>
|
||||||
<string name="notice_room_invite">%1$s invitis %2$s</string>
|
<string name="notice_room_invite">%1$s invitis uzanton %2$s</string>
|
||||||
<string name="notice_room_invite_you">%1$s invitis vin</string>
|
<string name="notice_room_invite_you">%1$s invitis vin</string>
|
||||||
<string name="notice_room_join">%1$s alvenis</string>
|
<string name="notice_room_join">%1$s alvenis</string>
|
||||||
<string name="notice_room_leave">%1$s foriris</string>
|
<string name="notice_room_leave">%1$s foriris</string>
|
||||||
<string name="notice_room_reject">%1$s malakceptis la inviton</string>
|
<string name="notice_room_reject">%1$s malakceptis la inviton</string>
|
||||||
<string name="notice_room_kick">%1$s forpelis %2$s</string>
|
<string name="notice_room_kick">%1$s forpelis uzanton %2$s</string>
|
||||||
<string name="notice_room_unban">%1$s malforbaris %2$s</string>
|
<string name="notice_room_unban">%1$s malforbaris uzanton %2$s</string>
|
||||||
<string name="notice_room_ban">%1$s forbaris %2$s</string>
|
<string name="notice_room_ban">%1$s forbaris uzanton %2$s</string>
|
||||||
<string name="notice_room_withdraw">%1$s malinvitis %2$s</string>
|
<string name="notice_room_withdraw">%1$s nuligis inviton por %2$s</string>
|
||||||
<string name="notice_avatar_url_changed">%1$s ŝanĝis sian profilbildon</string>
|
<string name="notice_avatar_url_changed">%1$s ŝanĝis sian profilbildon</string>
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Ne eblas malĉifri: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Ne eblas malĉifri: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">La aparato de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">La aparato de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Respondanta al</string>
|
<string name="message_reply_to_prefix">Responde al</string>
|
||||||
|
|
||||||
|
<string name="summary_message">%1$s: %2$s</string>
|
||||||
|
<string name="notice_display_name_set">%1$s ŝanĝis sian vidigan nomon al %2$s</string>
|
||||||
|
<string name="notice_display_name_changed_from">%1$s ŝanĝis sian vidigan nomon de %2$s al %3$s</string>
|
||||||
|
<string name="notice_display_name_removed">%1$s forigis sian vidigan nomon (%2$s)</string>
|
||||||
|
<string name="notice_room_topic_changed">%1$s ŝanĝis la temon al: %2$s</string>
|
||||||
|
<string name="notice_room_name_changed">%1$s ŝanĝis nomon de la ĉambro al: %2$s</string>
|
||||||
|
<string name="notice_placed_video_call">%s vidvokis.</string>
|
||||||
|
<string name="notice_placed_voice_call">%s voĉvokis.</string>
|
||||||
|
<string name="notice_answered_call">%s respondis la vokon.</string>
|
||||||
|
<string name="notice_ended_call">%s finis la vokon.</string>
|
||||||
|
<string name="notice_made_future_room_visibility">%1$s videbligis estontan historion de ĉambro al %2$s</string>
|
||||||
|
<string name="notice_room_visibility_invited">ĉiuj ĉambranoj, ekde iliaj invitoj.</string>
|
||||||
|
<string name="notice_room_visibility_joined">ĉiuj ĉambranoj, ekde iliaj aliĝoj.</string>
|
||||||
|
<string name="notice_room_visibility_shared">ĉiuj ĉambranoj.</string>
|
||||||
|
<string name="notice_room_visibility_world_readable">ĉiu ajn.</string>
|
||||||
|
<string name="notice_room_visibility_unknown">nekonata (%s).</string>
|
||||||
|
<string name="notice_end_to_end">%1$s ŝaltis tutvojan ĉifradon (%2$s)</string>
|
||||||
|
<string name="notice_room_update">%s gradaltigis la ĉambron.</string>
|
||||||
|
|
||||||
|
<string name="notice_event_redacted">Mesaĝo foriĝis</string>
|
||||||
|
<string name="notice_event_redacted_by">Mesaĝo foriĝis de %1$s</string>
|
||||||
|
<string name="notice_event_redacted_with_reason">Mesaĝo foriĝis [kialo: %1$s]</string>
|
||||||
|
<string name="notice_event_redacted_by_with_reason">Mesaĝo foriĝis de %1$s [kialo: %2$s]</string>
|
||||||
|
<string name="notice_profile_change_redacted">%1$s ĝisdatigis sian profilon %2$s</string>
|
||||||
|
<string name="notice_room_third_party_invite">%1$s sendis aliĝan inviton al %2$s</string>
|
||||||
|
<string name="notice_room_third_party_revoked_invite">%1$s nuligis la aliĝan inviton por %2$s</string>
|
||||||
|
<string name="notice_room_third_party_registered_invite">%1$s akceptis la inviton por %2$s</string>
|
||||||
|
|
||||||
|
<string name="could_not_redact">Ne povis redakti</string>
|
||||||
|
<string name="unable_to_send_message">Ne povas sendi mesaĝon</string>
|
||||||
|
|
||||||
|
<string name="message_failed_to_upload">Malsukcesis alŝuti bildon</string>
|
||||||
|
|
||||||
|
<string name="network_error">Reta eraro</string>
|
||||||
|
<string name="matrix_error">Matrix-eraro</string>
|
||||||
|
|
||||||
|
<string name="room_error_join_failed_empty_room">Nun ne eblas re-aliĝi al malplena ĉambro</string>
|
||||||
|
|
||||||
|
<string name="encrypted_message">Ĉifrita mesaĝo</string>
|
||||||
|
|
||||||
|
<string name="medium_email">Retpoŝtadreso</string>
|
||||||
|
<string name="medium_phone_number">Telefonnumero</string>
|
||||||
|
|
||||||
|
<string name="reply_to_an_image">sendis bildon.</string>
|
||||||
|
<string name="reply_to_a_video">sendis filmon.</string>
|
||||||
|
<string name="reply_to_an_audio_file">sendis sondosieron.</string>
|
||||||
|
<string name="reply_to_a_file">sendis dosieron.</string>
|
||||||
|
|
||||||
|
<string name="room_displayname_invite_from">Invito de %s</string>
|
||||||
|
<string name="room_displayname_room_invite">Ĉambra invito</string>
|
||||||
|
|
||||||
|
<string name="room_displayname_two_members">%1$s kaj %2$s</string>
|
||||||
|
|
||||||
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
|
<item quantity="one">%1$s kaj 1 alia</item>
|
||||||
|
<item quantity="other">%1$s kaj %2$d aliaj</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="room_displayname_empty_room">Malplena ĉambro</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="verification_emoji_dog">Hundo</string>
|
||||||
|
<string name="verification_emoji_cat">Kato</string>
|
||||||
|
<string name="verification_emoji_lion">Leono</string>
|
||||||
|
<string name="verification_emoji_horse">Ĉevalo</string>
|
||||||
|
<string name="verification_emoji_unicorn">Unukorno</string>
|
||||||
|
<string name="verification_emoji_pig">Porko</string>
|
||||||
|
<string name="verification_emoji_elephant">Elefanto</string>
|
||||||
|
<string name="verification_emoji_rabbit">Kuniklo</string>
|
||||||
|
<string name="verification_emoji_panda">Pando</string>
|
||||||
|
<string name="verification_emoji_rooster">Koko</string>
|
||||||
|
<string name="verification_emoji_penguin">Pingveno</string>
|
||||||
|
<string name="verification_emoji_turtle">Testudo</string>
|
||||||
|
<string name="verification_emoji_fish">Fiŝo</string>
|
||||||
|
<string name="verification_emoji_octopus">Polpo</string>
|
||||||
|
<string name="verification_emoji_butterfly">Papilio</string>
|
||||||
|
<string name="verification_emoji_flower">Floro</string>
|
||||||
|
<string name="verification_emoji_tree">Arbo</string>
|
||||||
|
<string name="verification_emoji_cactus">Kakto</string>
|
||||||
|
<string name="verification_emoji_mushroom">Fungo</string>
|
||||||
|
<string name="verification_emoji_globe">Globo</string>
|
||||||
|
<string name="verification_emoji_moon">Luno</string>
|
||||||
|
<string name="verification_emoji_cloud">Nubo</string>
|
||||||
|
<string name="verification_emoji_fire">Fajro</string>
|
||||||
|
<string name="verification_emoji_banana">Banano</string>
|
||||||
|
<string name="verification_emoji_apple">Pomo</string>
|
||||||
|
<string name="verification_emoji_strawberry">Frago</string>
|
||||||
|
<string name="verification_emoji_corn">Maizo</string>
|
||||||
|
<string name="verification_emoji_pizza">Pico</string>
|
||||||
|
<string name="verification_emoji_cake">Kuko</string>
|
||||||
|
<string name="verification_emoji_heart">Koro</string>
|
||||||
|
<string name="verification_emoji_smiley">Mieneto</string>
|
||||||
|
<string name="verification_emoji_robot">Roboto</string>
|
||||||
|
<string name="verification_emoji_hat">Ĉapelo</string>
|
||||||
|
<string name="verification_emoji_glasses">Okulvitroj</string>
|
||||||
|
<string name="verification_emoji_wrench">Boltilo</string>
|
||||||
|
<string name="verification_emoji_santa">Kristnaska viro</string>
|
||||||
|
<string name="verification_emoji_thumbsup">Dikfingro supren</string>
|
||||||
|
<string name="verification_emoji_umbrella">Ombrelo</string>
|
||||||
|
<string name="verification_emoji_hourglass">Sablohorloĝo</string>
|
||||||
|
<string name="verification_emoji_clock">Horloĝo</string>
|
||||||
|
<string name="verification_emoji_gift">Donaco</string>
|
||||||
|
<string name="verification_emoji_lightbulb">Lampo</string>
|
||||||
|
<string name="verification_emoji_book">Libro</string>
|
||||||
|
<string name="verification_emoji_pencil">Grifelo</string>
|
||||||
|
<string name="verification_emoji_paperclip">Paperkuntenilo</string>
|
||||||
|
<string name="verification_emoji_scissors">Tondilo</string>
|
||||||
|
<string name="verification_emoji_lock">Seruro</string>
|
||||||
|
<string name="verification_emoji_key">Ŝlosilo</string>
|
||||||
|
<string name="verification_emoji_hammer">Martelo</string>
|
||||||
|
<string name="verification_emoji_telephone">Telefono</string>
|
||||||
|
<string name="verification_emoji_flag">Flago</string>
|
||||||
|
<string name="verification_emoji_train">Vagonaro</string>
|
||||||
|
<string name="verification_emoji_bicycle">Biciklo</string>
|
||||||
|
<string name="verification_emoji_airplane">Aviadilo</string>
|
||||||
|
<string name="verification_emoji_rocket">Raketo</string>
|
||||||
|
<string name="verification_emoji_trophy">Trofeo</string>
|
||||||
|
<string name="verification_emoji_ball">Pilko</string>
|
||||||
|
<string name="verification_emoji_guitar">Gitaro</string>
|
||||||
|
<string name="verification_emoji_trumpet">Trumpeto</string>
|
||||||
|
<string name="verification_emoji_bell">Sonorilo</string>
|
||||||
|
<string name="verification_emoji_anchor">Ankro</string>
|
||||||
|
<string name="verification_emoji_headphone">Kapaŭdilo</string>
|
||||||
|
<string name="verification_emoji_folder">Dosierujo</string>
|
||||||
|
<string name="verification_emoji_pin">Pinglo</string>
|
||||||
|
|
||||||
|
<string name="initial_sync_start_importing_account">Komenca spegulado:
|
||||||
|
\nEnportante konton…</string>
|
||||||
|
<string name="initial_sync_start_importing_account_crypto">Komenca spegulado:
|
||||||
|
\nEnportante ĉifrilojn</string>
|
||||||
|
<string name="initial_sync_start_importing_account_rooms">Komenca spegulado:
|
||||||
|
\nEnportante ĉambrojn</string>
|
||||||
|
<string name="initial_sync_start_importing_account_joined_rooms">Komenca spegulado:
|
||||||
|
\nEnportante aliĝitajn ĉambrojn</string>
|
||||||
|
<string name="initial_sync_start_importing_account_invited_rooms">Komenca spegulado:
|
||||||
|
\nEnportante ĉambrojn de invitoj</string>
|
||||||
|
<string name="initial_sync_start_importing_account_left_rooms">Komenca spegulado:
|
||||||
|
\nEnportante forlasitajn ĉambrojn</string>
|
||||||
|
<string name="initial_sync_start_importing_account_groups">Komenca spegulado:
|
||||||
|
\nEnportante komunumojn</string>
|
||||||
|
<string name="initial_sync_start_importing_account_data">Komenca spegulado:
|
||||||
|
\nEnportante datumojn de konto</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Sendante mesaĝon…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Vakigi sendan atendovicon</string>
|
||||||
|
|
||||||
|
<string name="notice_requested_voip_conference">%1$s petis grupan vokon</string>
|
||||||
|
<string name="notice_voip_started">Grupa voko komenciĝis</string>
|
||||||
|
<string name="notice_voip_finished">Grupa voko finiĝis</string>
|
||||||
|
|
||||||
|
<string name="notice_avatar_changed_too">(ankaŭ profilbildo ŝanĝiĝis)</string>
|
||||||
|
<string name="notice_room_name_removed">%1$s forigis nomon de la ĉambro</string>
|
||||||
|
<string name="notice_room_topic_removed">%1$s forigis temon de la ĉambro</string>
|
||||||
|
<string name="notice_room_invite_no_invitee_with_reason">Invito de %1$s. Kialo: %2$s</string>
|
||||||
|
<string name="notice_room_invite_with_reason">%1$s invitis uzanton %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_invite_you_with_reason">%1$s invitis vin. Kialo: %2$s</string>
|
||||||
|
<string name="notice_room_join_with_reason">%1$s aliĝis al la ĉambro. Kialo: %2$s</string>
|
||||||
|
<string name="notice_room_leave_with_reason">%1$s foriris de la ĉambro. Kialo: %2$s</string>
|
||||||
|
<string name="notice_room_reject_with_reason">%1$s rifuzis la inviton. Kialo: %2$s</string>
|
||||||
|
<string name="notice_room_kick_with_reason">%1$s forpelis uzanton %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_unban_with_reason">%1$s malforbaris uzanton %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_ban_with_reason">%1$s forbaris uzanton %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_invite_with_reason">%1$s sendis inviton al la ĉambro al %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_revoked_invite_with_reason">%1$s nuligis la inviton al la ĉambro al %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_registered_invite_with_reason">%1$s akceptis la inviton por %2$s. Kialo: %3$s</string>
|
||||||
|
<string name="notice_room_withdraw_with_reason">%1$s nuligis la inviton al %2$s. Kialo: %3$s</string>
|
||||||
|
|
||||||
|
<plurals name="notice_room_aliases_added">
|
||||||
|
<item quantity="one">%1$s aldonis %2$s kiel adreson por ĉi tiu ĉambro.</item>
|
||||||
|
<item quantity="other">%1$s aldonis %2$s kiel adresojn por ĉi tiu ĉambro.</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notice_room_aliases_removed">
|
||||||
|
<item quantity="one">%1$s forigis %2$s kiel adreson por ĉi tiu ĉambro.</item>
|
||||||
|
<item quantity="other">%1$s forigis %2$s kiel adresojn por ĉi tiu ĉambro.</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="notice_room_aliases_added_and_removed">%1$s aldonis %2$s kaj forigis %3$s kiel adresojn por ĉi tiu ĉambro.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_canonical_alias_set">%1$s agordis la ĉefadreson por ĉi tiu ĉambro al %2$s.</string>
|
||||||
|
<string name="notice_room_canonical_alias_unset">%1$s forigis la ĉefadreson de ĉi tiu ĉambro.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_guest_access_can_join">%1$s permesis al gastoj aliĝi al la ĉambro.</string>
|
||||||
|
<string name="notice_room_guest_access_forbidden">%1$s malpermesis al gastoj aliĝi al la ĉambro.</string>
|
||||||
|
|
||||||
|
<string name="notice_end_to_end_ok">%1$s ŝaltis tutvojan ĉifradon.</string>
|
||||||
|
<string name="notice_end_to_end_unknown_algorithm">%1$s ŝaltis tutvojan ĉifradon (kun nerekonita algoritmo %2$s).</string>
|
||||||
|
|
||||||
|
<string name="key_verification_request_fallback_message">%s petas kontrolon de via ŝlosilo, sed via kliento ne subtenas kontrolon de ŝlosiloj en la babilujo. Vi devos uzi malnovecan kontrolon de ŝlosiloj.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
188
matrix-sdk-android/src/main/res/values-et/strings.xml
Normal file
188
matrix-sdk-android/src/main/res/values-et/strings.xml
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="summary_message">%1$s: %2$s</string>
|
||||||
|
<string name="summary_user_sent_image">%1$s saatis pildi.</string>
|
||||||
|
<string name="summary_user_sent_sticker">%1$s saatis kleepsu.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_invite_no_invitee">Kasutaja %s kutse</string>
|
||||||
|
<string name="notice_room_invite">%1$s kutsus kasutajat %2$s</string>
|
||||||
|
<string name="notice_room_invite_you">%1$s kutsus sind</string>
|
||||||
|
<string name="notice_room_join">%1$s liitus jututoaga</string>
|
||||||
|
<string name="notice_room_leave">%1$s lahkus jututoast</string>
|
||||||
|
<string name="notice_room_reject">%1$s lükkas tagasi kutse</string>
|
||||||
|
<string name="notice_room_kick">%1$s müksas kasutajat %2$s</string>
|
||||||
|
<string name="notice_room_withdraw">%1$s võttis tagasi kutse kasutajale %2$s</string>
|
||||||
|
<string name="notice_avatar_url_changed">%1$s muutis oma avatari</string>
|
||||||
|
<string name="notice_display_name_set">%1$s määras oma kuvatavaks nimeks %2$s</string>
|
||||||
|
<string name="notice_display_name_changed_from">%1$s muutis senise kuvatava nime %2$s uueks nimeks %3$s</string>
|
||||||
|
<string name="notice_display_name_removed">%1$s eemaldas oma kuvatava nime (%2$s)</string>
|
||||||
|
<string name="notice_room_topic_changed">%1$s muutis uueks teemaks %2$s</string>
|
||||||
|
<string name="notice_room_name_changed">%1$s muutis jututoa uueks nimeks %2$s</string>
|
||||||
|
<string name="notice_placed_video_call">%s alustas videokõnet.</string>
|
||||||
|
<string name="notice_placed_voice_call">%s alustas häälkõnet.</string>
|
||||||
|
<string name="notice_answered_call">%s vastas kõnele.</string>
|
||||||
|
<string name="notice_ended_call">%s lõpetas kõne.</string>
|
||||||
|
<string name="notice_made_future_room_visibility">%1$s seadistas, et tulevane jututoa ajalugu on nähtav kasutajale %2$s</string>
|
||||||
|
<string name="notice_room_visibility_invited">kõikidele jututoa liikmetele alates kutsumise hetkest.</string>
|
||||||
|
<string name="notice_room_visibility_joined">kõikidele jututoa liikmetele alates liitumise hetkest.</string>
|
||||||
|
<string name="notice_room_visibility_shared">kõikidele jututoa liikmetele.</string>
|
||||||
|
<string name="notice_room_visibility_world_readable">kõikidele.</string>
|
||||||
|
<string name="notice_room_visibility_unknown">teadmata (%s).</string>
|
||||||
|
<string name="notice_end_to_end">%1$s lülitas sisse läbiva krüptimise (%2$s)</string>
|
||||||
|
<string name="notice_room_update">%s uuendas seda jututuba.</string>
|
||||||
|
|
||||||
|
<string name="notice_requested_voip_conference">%1$s saatis VoIP konverentsi kutse</string>
|
||||||
|
<string name="notice_voip_started">VoIP-konverents algas</string>
|
||||||
|
<string name="notice_voip_finished">VoIP-konverents lõppes</string>
|
||||||
|
|
||||||
|
<string name="notice_avatar_changed_too">(samuti sai avatar muudetud)</string>
|
||||||
|
<string name="notice_room_name_removed">%1$s eemaldas jututoa nime</string>
|
||||||
|
<string name="notice_room_topic_removed">%1$s eemaldas jututoa teema</string>
|
||||||
|
<string name="notice_event_redacted">Sõnum on eemaldatud</string>
|
||||||
|
<string name="notice_event_redacted_by">Sõnum on eemaldatud %1$s poolt</string>
|
||||||
|
<string name="notice_event_redacted_with_reason">Sõnum on eemaldatud [põhjus: %1$s]</string>
|
||||||
|
<string name="notice_event_redacted_by_with_reason">Sõnum on eemaldatud %1$s poolt [põhjus: %2$s]</string>
|
||||||
|
<string name="notice_profile_change_redacted">%1$s uuendas oma profiili %2$s</string>
|
||||||
|
<string name="notice_room_third_party_invite">%1$s saatis jututoaga liitumiseks kutse kasutajale %2$s</string>
|
||||||
|
<string name="notice_room_third_party_revoked_invite">%1$s võttis tagasi jututoaga liitumise kutse kasutajalt %2$s</string>
|
||||||
|
<string name="notice_room_third_party_registered_invite">%1$s võttis vastu kutse %2$s nimel</string>
|
||||||
|
|
||||||
|
<string name="notice_crypto_unable_to_decrypt">** Ei õnnestu dekrüptida: %s **</string>
|
||||||
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.</string>
|
||||||
|
|
||||||
|
<string name="message_reply_to_prefix">Vastuseks kasutajale</string>
|
||||||
|
|
||||||
|
<string name="could_not_redact">Ei saanud muuta sõnumit</string>
|
||||||
|
<string name="unable_to_send_message">Sõnumi saatmine ei õnnestunud</string>
|
||||||
|
|
||||||
|
<string name="message_failed_to_upload">Faili üles laadimine ei õnnestunud</string>
|
||||||
|
|
||||||
|
<string name="network_error">Võrguühenduse viga</string>
|
||||||
|
<string name="matrix_error">Matrix\'i viga</string>
|
||||||
|
|
||||||
|
<string name="room_error_join_failed_empty_room">Hetkel ei ole võimalik uuesti liituda tühja jututoaga.</string>
|
||||||
|
|
||||||
|
<string name="encrypted_message">Krüptitud sõnum</string>
|
||||||
|
|
||||||
|
<string name="medium_email">E-posti aadress</string>
|
||||||
|
<string name="medium_phone_number">Telefoninumber</string>
|
||||||
|
|
||||||
|
<string name="reply_to_an_image">saatis pildi.</string>
|
||||||
|
<string name="reply_to_a_video">saatis video.</string>
|
||||||
|
<string name="reply_to_an_audio_file">saatis helifaili.</string>
|
||||||
|
<string name="reply_to_a_file">saatis faili.</string>
|
||||||
|
|
||||||
|
<string name="room_displayname_invite_from">Kutse kasutajalt %s</string>
|
||||||
|
<string name="room_displayname_room_invite">Kutse jututuppa</string>
|
||||||
|
|
||||||
|
<string name="room_displayname_two_members">%1$s ja %2$s</string>
|
||||||
|
|
||||||
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
|
<item quantity="one">%1$s ja üks muu</item>
|
||||||
|
<item quantity="other">%1$s ja %2$d muud</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="room_displayname_empty_room">Tühi jututuba</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="verification_emoji_dog">Koer</string>
|
||||||
|
<string name="verification_emoji_cat">Kass</string>
|
||||||
|
<string name="verification_emoji_lion">Lõvi</string>
|
||||||
|
<string name="verification_emoji_horse">Hobune</string>
|
||||||
|
<string name="verification_emoji_unicorn">Ükssarvik</string>
|
||||||
|
<string name="verification_emoji_pig">Siga</string>
|
||||||
|
<string name="verification_emoji_elephant">Elevant</string>
|
||||||
|
<string name="verification_emoji_rabbit">Jänes</string>
|
||||||
|
<string name="verification_emoji_panda">Panda</string>
|
||||||
|
<string name="verification_emoji_rooster">Kukk</string>
|
||||||
|
<string name="verification_emoji_penguin">Pingviin</string>
|
||||||
|
<string name="verification_emoji_turtle">Kilpkonn</string>
|
||||||
|
<string name="verification_emoji_fish">Kala</string>
|
||||||
|
<string name="verification_emoji_octopus">Kaheksajalg</string>
|
||||||
|
<string name="verification_emoji_butterfly">Liblikas</string>
|
||||||
|
<string name="verification_emoji_flower">Lill</string>
|
||||||
|
<string name="verification_emoji_tree">Puu</string>
|
||||||
|
<string name="verification_emoji_cactus">Kaktus</string>
|
||||||
|
<string name="verification_emoji_mushroom">Seen</string>
|
||||||
|
<string name="verification_emoji_globe">Maakera</string>
|
||||||
|
<string name="verification_emoji_moon">Kuu</string>
|
||||||
|
<string name="verification_emoji_cloud">Pilv</string>
|
||||||
|
<string name="verification_emoji_fire">Tuli</string>
|
||||||
|
<string name="verification_emoji_banana">Banaan</string>
|
||||||
|
<string name="verification_emoji_apple">Õun</string>
|
||||||
|
<string name="verification_emoji_strawberry">Maasikas</string>
|
||||||
|
<string name="verification_emoji_corn">Mais</string>
|
||||||
|
<string name="verification_emoji_pizza">Pitsa</string>
|
||||||
|
<string name="verification_emoji_cake">Kook</string>
|
||||||
|
<string name="verification_emoji_heart">Süda</string>
|
||||||
|
<string name="verification_emoji_smiley">Smaili</string>
|
||||||
|
<string name="verification_emoji_robot">Robot</string>
|
||||||
|
<string name="verification_emoji_hat">Müts</string>
|
||||||
|
<string name="verification_emoji_glasses">Prillid</string>
|
||||||
|
<string name="verification_emoji_wrench">Mutrivõti</string>
|
||||||
|
<string name="verification_emoji_santa">Jõuluvana</string>
|
||||||
|
<string name="verification_emoji_thumbsup">Pöidlad püsti</string>
|
||||||
|
<string name="verification_emoji_umbrella">Vihmavari</string>
|
||||||
|
<string name="verification_emoji_hourglass">Liivakell</string>
|
||||||
|
<string name="verification_emoji_clock">Kell</string>
|
||||||
|
<string name="verification_emoji_gift">Kingitus</string>
|
||||||
|
<string name="verification_emoji_lightbulb">Lambipirn</string>
|
||||||
|
<string name="verification_emoji_book">Raamat</string>
|
||||||
|
<string name="verification_emoji_pencil">Pliiats</string>
|
||||||
|
<string name="verification_emoji_paperclip">Kirjaklamber</string>
|
||||||
|
<string name="verification_emoji_scissors">Käärid</string>
|
||||||
|
<string name="verification_emoji_lock">Lukk</string>
|
||||||
|
<string name="verification_emoji_key">Võti</string>
|
||||||
|
<string name="verification_emoji_hammer">Haamer</string>
|
||||||
|
<string name="verification_emoji_telephone">Telefon</string>
|
||||||
|
<string name="verification_emoji_flag">Lipp</string>
|
||||||
|
<string name="verification_emoji_train">Rong</string>
|
||||||
|
<string name="verification_emoji_bicycle">Jalgratas</string>
|
||||||
|
<string name="verification_emoji_airplane">Lennuk</string>
|
||||||
|
<string name="verification_emoji_rocket">Rakett</string>
|
||||||
|
<string name="verification_emoji_trophy">Auhind</string>
|
||||||
|
<string name="verification_emoji_ball">Pall</string>
|
||||||
|
<string name="verification_emoji_guitar">Kitarr</string>
|
||||||
|
<string name="verification_emoji_trumpet">Trompet</string>
|
||||||
|
<string name="verification_emoji_bell">Kelluke</string>
|
||||||
|
<string name="verification_emoji_anchor">Ankur</string>
|
||||||
|
<string name="verification_emoji_headphone">Kõrvaklapid</string>
|
||||||
|
<string name="verification_emoji_folder">Kaust</string>
|
||||||
|
<string name="verification_emoji_pin">Knopka</string>
|
||||||
|
|
||||||
|
<string name="initial_sync_start_importing_account">Alglaadimine:
|
||||||
|
\nImpordin kontot…</string>
|
||||||
|
<string name="initial_sync_start_importing_account_crypto">Alglaadimine:
|
||||||
|
\nImpordin krüptoseadistusi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_rooms">Alglaadimine:
|
||||||
|
\nImpordin jututubasid</string>
|
||||||
|
<string name="initial_sync_start_importing_account_joined_rooms">Alglaadimine:
|
||||||
|
\nImpordin liitutud jututubasid</string>
|
||||||
|
<string name="initial_sync_start_importing_account_invited_rooms">Alglaadimine:
|
||||||
|
\nImpordin kutsutud jututubasid</string>
|
||||||
|
<string name="initial_sync_start_importing_account_left_rooms">Alglaadimine:
|
||||||
|
\nImpordin lahkutud jututubasid</string>
|
||||||
|
<string name="initial_sync_start_importing_account_groups">Alglaadimine:
|
||||||
|
\nImpordin kogukondi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_data">Alglaadimine:
|
||||||
|
\nImpordin kontoandmeid</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Saadan sõnumit…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Tühjenda saatmisjärjekord</string>
|
||||||
|
|
||||||
|
<string name="notice_room_invite_no_invitee_with_reason">Kasutaja %1$s kutse. Põhjus: %2$s</string>
|
||||||
|
<string name="notice_room_invite_with_reason">%1$s kutsus kasutajat %2$s. Põhjus: %3$s</string>
|
||||||
|
<string name="notice_room_invite_you_with_reason">%1$s kutsus sind. Põhjus: %2$s</string>
|
||||||
|
<string name="notice_room_join_with_reason">%1$s liitus jututoaga. Põhjus: %2$s</string>
|
||||||
|
<string name="notice_room_leave_with_reason">%1$s lahkus jututoast. Põhjus: %2$s</string>
|
||||||
|
<string name="notice_room_reject_with_reason">%1$s lükkas kutse tagasi. Põhjus: %2$s</string>
|
||||||
|
<string name="notice_room_kick_with_reason">%1$s müksas välja kasutaja %2$s. Põhjus: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_invite_with_reason">%1$s saatis kasutajale %2$s kutse jututoaga liitumiseks. Põhjus: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_revoked_invite_with_reason">%1$s tühistas kasutajale %2$s saadetud kutse jututoaga liitumiseks. Põhjus: %3$s</string>
|
||||||
|
<string name="notice_room_third_party_registered_invite_with_reason">%1$s võttis vastu kutse %2$s jututoaga liitumiseks. Põhjus: %3$s</string>
|
||||||
|
<string name="notice_room_withdraw_with_reason">%1$s võttis tagasi kasutajale %2$s saadetud kutse. Põhjus: %3$s</string>
|
||||||
|
|
||||||
|
<string name="notice_end_to_end_ok">%1$s lülitas sisse läbiva krüptimise.</string>
|
||||||
|
<string name="notice_end_to_end_unknown_algorithm">%1$s lülitas sisse läbiva krüptimise (tundmatu algoritm %2$s).</string>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -209,4 +209,6 @@
|
||||||
<string name="notice_end_to_end_ok">%1$s laittoi päälle osapuolten välisen salauksen.</string>
|
<string name="notice_end_to_end_ok">%1$s laittoi päälle osapuolten välisen salauksen.</string>
|
||||||
<string name="notice_end_to_end_unknown_algorithm">%1$s laittoi päälle osapuolisten välisen salauksen (tuntematon algoritmi %2$s).</string>
|
<string name="notice_end_to_end_unknown_algorithm">%1$s laittoi päälle osapuolisten välisen salauksen (tuntematon algoritmi %2$s).</string>
|
||||||
|
|
||||||
|
<string name="key_verification_request_fallback_message">%s haluaa varmentaa salausavaimesi, mutta asiakasohjelmasi ei tue keskustelun aikana tapahtuvaa avainten varmennusta. Joudut käyttämään perinteistä varmennustapaa.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<string name="summary_user_sent_image">%1$s dërgoi një figurë.</string>
|
<string name="summary_user_sent_image">%1$s dërgoi një figurë.</string>
|
||||||
<string name="notice_room_invite">%1$s ftoi %2$s</string>
|
<string name="notice_room_invite">%1$s ftoi %2$s</string>
|
||||||
<string name="notice_room_invite_you">%1$s ju ftoi</string>
|
<string name="notice_room_invite_you">%1$s ju ftoi</string>
|
||||||
<string name="notice_room_join">%1$s u bë pjesë</string>
|
<string name="notice_room_join">%1$s hyri në dhomë</string>
|
||||||
<string name="notice_room_leave">%1$s iku</string>
|
<string name="notice_room_leave">%1$s doli nga dhoma</string>
|
||||||
<string name="notice_room_reject">%1$s hodhi tej ftesën</string>
|
<string name="notice_room_reject">%1$s hodhi tej ftesën</string>
|
||||||
<string name="notice_room_kick">%1$s përzuri %2$s</string>
|
<string name="notice_room_kick">%1$s përzuri %2$s</string>
|
||||||
<string name="notice_room_ban">%1$s dëboi %2$s</string>
|
<string name="notice_room_ban">%1$s dëboi %2$s</string>
|
||||||
|
@ -172,8 +172,8 @@
|
||||||
<string name="notice_room_invite_no_invitee_with_reason">Ftesë e %1$s. Arsye: %2$s</string>
|
<string name="notice_room_invite_no_invitee_with_reason">Ftesë e %1$s. Arsye: %2$s</string>
|
||||||
<string name="notice_room_invite_with_reason">%1$s ftoi %2$s. Arsye: %3$s</string>
|
<string name="notice_room_invite_with_reason">%1$s ftoi %2$s. Arsye: %3$s</string>
|
||||||
<string name="notice_room_invite_you_with_reason">%1$s ju ftoi. Arsye: %2$s</string>
|
<string name="notice_room_invite_you_with_reason">%1$s ju ftoi. Arsye: %2$s</string>
|
||||||
<string name="notice_room_join_with_reason">%1$s erdhi. Arsye: %2$s</string>
|
<string name="notice_room_join_with_reason">%1$s erdhi në dhomë. Arsye: %2$s</string>
|
||||||
<string name="notice_room_leave_with_reason">%1$s iku. Arsye: %2$s</string>
|
<string name="notice_room_leave_with_reason">%1$s doli nga dhoma. Arsye: %2$s</string>
|
||||||
<string name="notice_room_reject_with_reason">%1$s hodhi poshtë ftesën. Arsye: %2$s</string>
|
<string name="notice_room_reject_with_reason">%1$s hodhi poshtë ftesën. Arsye: %2$s</string>
|
||||||
<string name="notice_room_kick_with_reason">%1$s përzuri %2$s. Arsye: %3$s</string>
|
<string name="notice_room_kick_with_reason">%1$s përzuri %2$s. Arsye: %3$s</string>
|
||||||
<string name="notice_room_unban_with_reason">%1$s hoqi dëbimin për %2$s. Arsye: %3$s</string>
|
<string name="notice_room_unban_with_reason">%1$s hoqi dëbimin për %2$s. Arsye: %3$s</string>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<string name="notice_room_invite_no_invitee">%s 的邀请</string>
|
<string name="notice_room_invite_no_invitee">%s 的邀请</string>
|
||||||
<string name="notice_room_invite">%1$s 邀请了 %2$s</string>
|
<string name="notice_room_invite">%1$s 邀请了 %2$s</string>
|
||||||
<string name="notice_room_invite_you">%1$s 邀请了您</string>
|
<string name="notice_room_invite_you">%1$s 邀请了您</string>
|
||||||
<string name="notice_room_join">%1$s 加入了</string>
|
<string name="notice_room_join">%1$s 加入了聊天室</string>
|
||||||
<string name="notice_room_leave">%1$s 离开了</string>
|
<string name="notice_room_leave">%1$s 离开了聊天室</string>
|
||||||
<string name="notice_room_reject">%1$s 拒绝了邀请</string>
|
<string name="notice_room_reject">%1$s 拒绝了邀请</string>
|
||||||
<string name="notice_room_kick">%1$s 移除了 %2$s</string>
|
<string name="notice_room_kick">%1$s 移除了 %2$s</string>
|
||||||
<string name="notice_room_unban">%1$s 解封了 %2$s</string>
|
<string name="notice_room_unban">%1$s 解封了 %2$s</string>
|
||||||
|
@ -173,8 +173,8 @@
|
||||||
<string name="notice_room_invite_no_invitee_with_reason">%1$s 的邀请。理由:%2$s</string>
|
<string name="notice_room_invite_no_invitee_with_reason">%1$s 的邀请。理由:%2$s</string>
|
||||||
<string name="notice_room_invite_with_reason">%1$s 邀请了 %2$s。理由:%3$s</string>
|
<string name="notice_room_invite_with_reason">%1$s 邀请了 %2$s。理由:%3$s</string>
|
||||||
<string name="notice_room_invite_you_with_reason">%1$s 邀请了您。理由:%2$s</string>
|
<string name="notice_room_invite_you_with_reason">%1$s 邀请了您。理由:%2$s</string>
|
||||||
<string name="notice_room_join_with_reason">%1$s 已加入。理由:%2$s</string>
|
<string name="notice_room_join_with_reason">%1$s 加入了聊天室。理由:%2$s</string>
|
||||||
<string name="notice_room_leave_with_reason">%1$s 已离开。理由:%2$s</string>
|
<string name="notice_room_leave_with_reason">%1$s 离开了聊天室。理由:%2$s</string>
|
||||||
<string name="notice_room_reject_with_reason">%1$s 已拒绝邀请。理由:%2$s</string>
|
<string name="notice_room_reject_with_reason">%1$s 已拒绝邀请。理由:%2$s</string>
|
||||||
<string name="notice_room_kick_with_reason">%1$s 踢走了 %2$s。理由:%3$s</string>
|
<string name="notice_room_kick_with_reason">%1$s 踢走了 %2$s。理由:%3$s</string>
|
||||||
<string name="notice_room_unban_with_reason">%1$s 取消封锁了 %2$s。理由:%3$s</string>
|
<string name="notice_room_unban_with_reason">%1$s 取消封锁了 %2$s。理由:%3$s</string>
|
||||||
|
|
|
@ -23,7 +23,7 @@ PARAM_KS_PASS=$3
|
||||||
PARAM_KEY_PASS=$4
|
PARAM_KEY_PASS=$4
|
||||||
|
|
||||||
# Other params
|
# Other params
|
||||||
BUILD_TOOLS_VERSION="28.0.3"
|
BUILD_TOOLS_VERSION="29.0.3"
|
||||||
MIN_SDK_VERSION=19
|
MIN_SDK_VERSION=19
|
||||||
|
|
||||||
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."
|
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."
|
||||||
|
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 19
|
ext.versionMinor = 20
|
||||||
ext.versionPatch = 0
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||||
<activity android:name="im.vector.riotx.features.createdirect.CreateDirectRoomActivity" />
|
<activity android:name="im.vector.riotx.features.createdirect.CreateDirectRoomActivity" />
|
||||||
|
<activity android:name="im.vector.riotx.features.invite.InviteUsersToRoomActivity" />
|
||||||
<activity android:name=".features.webview.VectorWebViewActivity" />
|
<activity android:name=".features.webview.VectorWebViewActivity" />
|
||||||
<activity android:name=".features.link.LinkHandlerActivity">
|
<activity android:name=".features.link.LinkHandlerActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -23,8 +23,6 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
|
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
||||||
|
@ -63,6 +61,8 @@ import im.vector.riotx.features.login.LoginSplashFragment
|
||||||
import im.vector.riotx.features.login.LoginWaitForEmailFragment
|
import im.vector.riotx.features.login.LoginWaitForEmailFragment
|
||||||
import im.vector.riotx.features.login.LoginWebFragment
|
import im.vector.riotx.features.login.LoginWebFragment
|
||||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||||
import im.vector.riotx.features.qrcode.QrCodeScannerFragment
|
import im.vector.riotx.features.qrcode.QrCodeScannerFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||||
|
@ -226,13 +226,13 @@ interface FragmentModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(CreateDirectRoomDirectoryUsersFragment::class)
|
@FragmentKey(UserDirectoryFragment::class)
|
||||||
fun bindCreateDirectRoomDirectoryUsersFragment(fragment: CreateDirectRoomDirectoryUsersFragment): Fragment
|
fun bindUserDirectoryFragment(fragment: UserDirectoryFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(CreateDirectRoomKnownUsersFragment::class)
|
@FragmentKey(KnownUsersFragment::class)
|
||||||
fun bindCreateDirectRoomKnownUsersFragment(fragment: CreateDirectRoomKnownUsersFragment): Fragment
|
fun bindKnownUsersFragment(fragment: KnownUsersFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|
|
@ -39,6 +39,7 @@ import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReaction
|
||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
import im.vector.riotx.features.home.room.list.RoomListModule
|
import im.vector.riotx.features.home.room.list.RoomListModule
|
||||||
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.invite.InviteUsersToRoomActivity
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
import im.vector.riotx.features.link.LinkHandlerActivity
|
import im.vector.riotx.features.link.LinkHandlerActivity
|
||||||
import im.vector.riotx.features.login.LoginActivity
|
import im.vector.riotx.features.login.LoginActivity
|
||||||
|
@ -116,6 +117,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: DebugMenuActivity)
|
fun inject(activity: DebugMenuActivity)
|
||||||
fun inject(activity: SharedSecureStorageActivity)
|
fun inject(activity: SharedSecureStorageActivity)
|
||||||
fun inject(activity: BigImageViewerActivity)
|
fun inject(activity: BigImageViewerActivity)
|
||||||
|
fun inject(activity: InviteUsersToRoomActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
|
|
@ -22,7 +22,6 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.core.platform.ConfigurationViewModel
|
import im.vector.riotx.core.platform.ConfigurationViewModel
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomSharedActionViewModel
|
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||||
|
@ -31,10 +30,10 @@ import im.vector.riotx.features.home.HomeSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
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.reactions.EmojiChooserViewModel
|
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
|
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel
|
import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel
|
||||||
import im.vector.riotx.features.workers.signout.SignOutViewModel
|
import im.vector.riotx.features.workers.signout.SignOutViewModel
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -87,8 +86,8 @@ interface ViewModelModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(CreateDirectRoomSharedActionViewModel::class)
|
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
||||||
fun bindCreateDirectRoomSharedActionViewModel(viewModel: CreateDirectRoomSharedActionViewModel): ViewModel
|
fun bindUserDirectorySharedActionViewModel(viewModel: UserDirectorySharedActionViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
@ -110,11 +109,6 @@ interface ViewModelModule {
|
||||||
@ViewModelKey(RoomDirectorySharedActionViewModel::class)
|
@ViewModelKey(RoomDirectorySharedActionViewModel::class)
|
||||||
fun bindRoomDirectorySharedActionViewModel(viewModel: RoomDirectorySharedActionViewModel): ViewModel
|
fun bindRoomDirectorySharedActionViewModel(viewModel: RoomDirectorySharedActionViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@ViewModelKey(LoginSharedActionViewModel::class)
|
|
||||||
fun bindLoginSharedActionViewModel(viewModel: LoginSharedActionViewModel): ViewModel
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(RoomDetailSharedActionViewModel::class)
|
@ViewModelKey(RoomDetailSharedActionViewModel::class)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.Session
|
||||||
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.hideKeyboard
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.activity.*
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -107,4 +108,15 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
|
||||||
}
|
}
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||||
|
viewEvents
|
||||||
|
.observe()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe {
|
||||||
|
hideWaitingView()
|
||||||
|
observer(it)
|
||||||
|
}
|
||||||
|
.disposeOnDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -20,10 +20,5 @@ import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class CreateDirectRoomAction : VectorViewModelAction {
|
sealed class CreateDirectRoomAction : VectorViewModelAction {
|
||||||
object CreateRoomAndInviteSelectedUsers : CreateDirectRoomAction()
|
data class CreateRoomAndInviteSelectedUsers(val selectedUsers: Set<User>) : CreateDirectRoomAction()
|
||||||
data class FilterKnownUsers(val value: String) : CreateDirectRoomAction()
|
|
||||||
data class SearchDirectoryUsers(val value: String) : CreateDirectRoomAction()
|
|
||||||
object ClearFilterKnownUsers : CreateDirectRoomAction()
|
|
||||||
data class SelectUser(val user: User) : CreateDirectRoomAction()
|
|
||||||
data class RemoveSelectedUser(val user: User) : CreateDirectRoomAction()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ import im.vector.riotx.core.extensions.addFragment
|
||||||
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
||||||
import im.vector.riotx.core.platform.WaitingViewData
|
import im.vector.riotx.core.platform.WaitingViewData
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedAction
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryViewModel
|
||||||
import kotlinx.android.synthetic.main.activity.*
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -44,7 +50,8 @@ import javax.inject.Inject
|
||||||
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
||||||
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
@Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
|
||||||
@Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
|
@Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
|
||||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
@ -56,26 +63,40 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
toolbar.visibility = View.GONE
|
toolbar.visibility = View.GONE
|
||||||
sharedActionViewModel = viewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
sharedActionViewModel
|
sharedActionViewModel
|
||||||
.observe()
|
.observe()
|
||||||
.subscribe { sharedAction ->
|
.subscribe { sharedAction ->
|
||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
CreateDirectRoomSharedAction.OpenUsersDirectory ->
|
UserDirectorySharedAction.OpenUsersDirectory ->
|
||||||
addFragmentToBackstack(R.id.container, CreateDirectRoomDirectoryUsersFragment::class.java)
|
addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
|
||||||
CreateDirectRoomSharedAction.Close -> finish()
|
UserDirectorySharedAction.Close -> finish()
|
||||||
CreateDirectRoomSharedAction.GoBack -> onBackPressed()
|
UserDirectorySharedAction.GoBack -> onBackPressed()
|
||||||
|
is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
addFragment(R.id.container, CreateDirectRoomKnownUsersFragment::class.java)
|
addFragment(
|
||||||
|
R.id.container,
|
||||||
|
KnownUsersFragment::class.java,
|
||||||
|
KnownUsersFragmentArgs(
|
||||||
|
title = getString(R.string.fab_menu_create_chat),
|
||||||
|
menuResId = R.menu.vector_create_direct_room
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
|
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
|
||||||
renderCreateAndInviteState(it)
|
renderCreateAndInviteState(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
|
||||||
|
if (action.itemId == R.id.action_create_direct_room) {
|
||||||
|
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.selectedUsers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderCreateAndInviteState(state: Async<String>) {
|
private fun renderCreateAndInviteState(state: Async<String>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is Loading -> renderCreationLoading()
|
is Loading -> renderCreationLoading()
|
||||||
|
|
|
@ -18,7 +18,4 @@ package im.vector.riotx.features.createdirect
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorViewEvents
|
import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
|
|
||||||
/**
|
|
||||||
* Transient events for create direct room screen
|
|
||||||
*/
|
|
||||||
sealed class CreateDirectRoomViewEvents : VectorViewEvents
|
sealed class CreateDirectRoomViewEvents : VectorViewEvents
|
||||||
|
|
|
@ -1,42 +1,31 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * 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.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.createdirect
|
||||||
|
|
||||||
import arrow.core.Option
|
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.core.extensions.toggle
|
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import io.reactivex.Single
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
private typealias KnowUsersFilter = String
|
|
||||||
private typealias DirectoryUsersSearch = String
|
|
||||||
|
|
||||||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: CreateDirectRoomViewState,
|
initialState: CreateDirectRoomViewState,
|
||||||
|
@ -48,9 +37,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
|
fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
|
||||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -60,25 +46,15 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
observeKnownUsers()
|
|
||||||
observeDirectoryUsers()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(action: CreateDirectRoomAction) {
|
override fun handle(action: CreateDirectRoomAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers()
|
is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers(action.selectedUsers)
|
||||||
is CreateDirectRoomAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value))
|
|
||||||
is CreateDirectRoomAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
|
|
||||||
is CreateDirectRoomAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value)
|
|
||||||
is CreateDirectRoomAction.SelectUser -> handleSelectUser(action)
|
|
||||||
is CreateDirectRoomAction.RemoveSelectedUser -> handleRemoveSelectedUser(action)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRoomAndInviteSelectedUsers() = withState { currentState ->
|
private fun createRoomAndInviteSelectedUsers(selectedUsers: Set<User>) {
|
||||||
val roomParams = CreateRoomParams(
|
val roomParams = CreateRoomParams(
|
||||||
invitedUserIds = currentState.selectedUsers.map { it.userId }
|
invitedUserIds = selectedUsers.map { it.userId }
|
||||||
)
|
)
|
||||||
.setDirectMessage()
|
.setDirectMessage()
|
||||||
.enableEncryptionIfInvitedUsersSupportIt()
|
.enableEncryptionIfInvitedUsersSupportIt()
|
||||||
|
@ -89,52 +65,4 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
copy(createAndInviteState = it)
|
copy(createAndInviteState = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSelectedUser(action: CreateDirectRoomAction.RemoveSelectedUser) = withState { state ->
|
|
||||||
val selectedUsers = state.selectedUsers.minus(action.user)
|
|
||||||
setState { copy(selectedUsers = selectedUsers) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleSelectUser(action: CreateDirectRoomAction.SelectUser) = withState { state ->
|
|
||||||
// Reset the filter asap
|
|
||||||
directoryUsersSearch.accept("")
|
|
||||||
val selectedUsers = state.selectedUsers.toggle(action.user)
|
|
||||||
setState { copy(selectedUsers = selectedUsers) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeDirectoryUsers() {
|
|
||||||
directoryUsersSearch
|
|
||||||
.debounce(300, TimeUnit.MILLISECONDS)
|
|
||||||
.switchMapSingle { search ->
|
|
||||||
val stream = if (search.isBlank()) {
|
|
||||||
Single.just(emptyList())
|
|
||||||
} else {
|
|
||||||
session.rx()
|
|
||||||
.searchUsersDirectory(search, 50, emptySet())
|
|
||||||
.map { users ->
|
|
||||||
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream.toAsync {
|
|
||||||
copy(directoryUsers = it, directorySearchTerm = search)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribe()
|
|
||||||
.disposeOnClear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeKnownUsers() {
|
|
||||||
knownUsersFilter
|
|
||||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.switchMap {
|
|
||||||
session.rx().livePagedUsers(it.orNull())
|
|
||||||
}
|
|
||||||
.execute { async ->
|
|
||||||
copy(
|
|
||||||
knownUsers = async,
|
|
||||||
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,25 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * 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.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.createdirect
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
|
||||||
import arrow.core.Option
|
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
|
||||||
|
|
||||||
data class CreateDirectRoomViewState(
|
data class CreateDirectRoomViewState(
|
||||||
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
val createAndInviteState: Async<String> = Uninitialized
|
||||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
) : MvRxState
|
||||||
val selectedUsers: Set<User> = emptySet(),
|
|
||||||
val createAndInviteState: Async<String> = Uninitialized,
|
|
||||||
val directorySearchTerm: String = "",
|
|
||||||
val filterKnownUsersValue: Option<String> = Option.empty()
|
|
||||||
) : MvRxState {
|
|
||||||
|
|
||||||
enum class DisplayMode {
|
|
||||||
KNOWN_USERS,
|
|
||||||
DIRECTORY_USERS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor(
|
||||||
try {
|
try {
|
||||||
sharedViewModel.recoverUsingBackupPass(recoveryKey)
|
sharedViewModel.recoverUsingBackupPass(recoveryKey)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
recoveryCodeErrorText.value = stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt)
|
recoveryCodeErrorText.postValue(stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@
|
||||||
package im.vector.riotx.features.home
|
package im.vector.riotx.features.home
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.AnyThread
|
import androidx.annotation.AnyThread
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.DrawableImageViewTarget
|
import com.bumptech.glide.request.target.DrawableImageViewTarget
|
||||||
|
@ -72,6 +74,28 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
.into(target)
|
.into(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun shortcutDrawable(context: Context, glideRequest: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
|
||||||
|
return glideRequest
|
||||||
|
.asBitmap()
|
||||||
|
.apply {
|
||||||
|
val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
|
||||||
|
if (resolvedUrl != null) {
|
||||||
|
load(resolvedUrl)
|
||||||
|
} else {
|
||||||
|
val avatarColor = avatarColor(matrixItem, context)
|
||||||
|
load(TextDrawable.builder()
|
||||||
|
.beginConfig()
|
||||||
|
.bold()
|
||||||
|
.endConfig()
|
||||||
|
.buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
||||||
|
.toBitmap(width = iconSize, height = iconSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submit(iconSize, iconSize)
|
||||||
|
.get()
|
||||||
|
}
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun getCachedDrawable(glideRequest: GlideRequests, matrixItem: MatrixItem): Drawable {
|
fun getCachedDrawable(glideRequest: GlideRequests, matrixItem: MatrixItem): Drawable {
|
||||||
return buildGlideRequest(glideRequest, matrixItem.avatarUrl)
|
return buildGlideRequest(glideRequest, matrixItem.avatarUrl)
|
||||||
|
@ -82,10 +106,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
||||||
val avatarColor = when (matrixItem) {
|
val avatarColor = avatarColor(matrixItem, context)
|
||||||
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
|
|
||||||
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
|
|
||||||
}
|
|
||||||
return TextDrawable.builder()
|
return TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
.bold()
|
.bold()
|
||||||
|
@ -96,11 +117,21 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
// PRIVATE API *********************************************************************************
|
// PRIVATE API *********************************************************************************
|
||||||
|
|
||||||
private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
||||||
val resolvedUrl = activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
val resolvedUrl = resolvedUrl(avatarUrl)
|
||||||
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
|
||||||
|
|
||||||
return glideRequest
|
return glideRequest
|
||||||
.load(resolvedUrl)
|
.load(resolvedUrl)
|
||||||
.apply(RequestOptions.circleCropTransform())
|
.apply(RequestOptions.circleCropTransform())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolvedUrl(avatarUrl: String?): String? {
|
||||||
|
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
||||||
|
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun avatarColor(matrixItem: MatrixItem, context: Context): Int {
|
||||||
|
return when (matrixItem) {
|
||||||
|
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
|
||||||
|
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||||
|
@Inject lateinit var shortcutsHandler: ShortcutsHandler
|
||||||
|
|
||||||
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||||
override fun onDrawerStateChanged(newState: Int) {
|
override fun onDrawerStateChanged(newState: Int) {
|
||||||
|
@ -144,6 +145,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
&& activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) {
|
&& activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) {
|
||||||
promptCompleteSecurityIfNeeded()
|
promptCompleteSecurityIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shortcutsHandler.observeRoomsAndBuildShortcuts()
|
||||||
|
.disposeOnDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun promptCompleteSecurityIfNeeded() {
|
private fun promptCompleteSecurityIfNeeded() {
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||||
|
private const val adaptiveIconSizeDp = 108
|
||||||
|
private const val adaptiveIconOuterSidesDp = 18
|
||||||
|
|
||||||
|
class ShortcutsHandler @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val homeRoomListStore: HomeRoomListDataSource,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val dimensionConverter: DimensionConverter
|
||||||
|
) {
|
||||||
|
private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
|
||||||
|
private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
|
||||||
|
private val iconSize by lazy {
|
||||||
|
if (useAdaptiveIcon) {
|
||||||
|
adaptiveIconSize - adaptiveIconOuterSides
|
||||||
|
} else {
|
||||||
|
dimensionConverter.dpToPx(72)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeRoomsAndBuildShortcuts(): Disposable {
|
||||||
|
return homeRoomListStore
|
||||||
|
.observe()
|
||||||
|
.distinct()
|
||||||
|
.observeOn(Schedulers.computation())
|
||||||
|
.subscribe { rooms ->
|
||||||
|
val shortcuts = rooms
|
||||||
|
.filter { room -> room.tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE } }
|
||||||
|
.take(n = 4) // Android only allows us to create 4 shortcuts
|
||||||
|
.map { room ->
|
||||||
|
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
|
||||||
|
val bitmap = avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), iconSize)
|
||||||
|
|
||||||
|
ShortcutInfoCompat.Builder(context, room.roomId)
|
||||||
|
.setShortLabel(room.displayName)
|
||||||
|
.setIcon(bitmap.toProfileImageIcon())
|
||||||
|
.setIntent(intent)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||||
|
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE API *********************************************************************************
|
||||||
|
|
||||||
|
private fun Bitmap.toProfileImageIcon(): IconCompat {
|
||||||
|
return if (useAdaptiveIcon) {
|
||||||
|
IconCompat.createWithAdaptiveBitmap(this)
|
||||||
|
} else {
|
||||||
|
IconCompat.createWithBitmap(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,8 +44,14 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
waitingView = waiting_view
|
waitingView = waiting_view
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
|
val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) {
|
||||||
?: return
|
RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!)
|
||||||
|
} else {
|
||||||
|
intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roomDetailArgs == null) return
|
||||||
|
|
||||||
currentRoomId = roomDetailArgs.roomId
|
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)
|
replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java)
|
||||||
|
@ -110,11 +116,20 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
|
const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
|
||||||
|
const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
|
||||||
|
const val ACTION_ROOM_DETAILS_FROM_SHORTCUT = "ROOM_DETAILS_FROM_SHORTCUT"
|
||||||
|
|
||||||
fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent {
|
fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent {
|
||||||
return Intent(context, RoomDetailActivity::class.java).apply {
|
return Intent(context, RoomDetailActivity::class.java).apply {
|
||||||
putExtra(EXTRA_ROOM_DETAIL_ARGS, roomDetailArgs)
|
putExtra(EXTRA_ROOM_DETAIL_ARGS, roomDetailArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun shortcutIntent(context: Context, roomId: String): Intent {
|
||||||
|
return Intent(context, RoomDetailActivity::class.java).apply {
|
||||||
|
action = ACTION_ROOM_DETAILS_FROM_SHORTCUT
|
||||||
|
putExtra(EXTRA_ROOM_ID, roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.invite
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class InviteUsersToRoomAction : VectorViewModelAction {
|
||||||
|
data class InviteSelectedUsers(val selectedUsers: Set<User>) : InviteUsersToRoomAction()
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.invite
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import com.airbnb.mvrx.viewModel
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.extensions.addFragment
|
||||||
|
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
|
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
||||||
|
import im.vector.riotx.core.platform.WaitingViewData
|
||||||
|
import im.vector.riotx.core.utils.toast
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedAction
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryViewModel
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class InviteUsersToRoomArgs(val roomId: String) : Parcelable
|
||||||
|
|
||||||
|
class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
|
private val viewModel: InviteUsersToRoomViewModel by viewModel()
|
||||||
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
@Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
|
||||||
|
@Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory
|
||||||
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
super.injectWith(injector)
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
toolbar.visibility = View.GONE
|
||||||
|
sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
|
sharedActionViewModel
|
||||||
|
.observe()
|
||||||
|
.subscribe { sharedAction ->
|
||||||
|
when (sharedAction) {
|
||||||
|
UserDirectorySharedAction.OpenUsersDirectory ->
|
||||||
|
addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
|
||||||
|
UserDirectorySharedAction.Close -> finish()
|
||||||
|
UserDirectorySharedAction.GoBack -> onBackPressed()
|
||||||
|
is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnDestroy()
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(
|
||||||
|
R.id.container,
|
||||||
|
KnownUsersFragment::class.java,
|
||||||
|
KnownUsersFragmentArgs(
|
||||||
|
title = getString(R.string.invite_users_to_room_title),
|
||||||
|
menuResId = R.menu.vector_invite_users_to_room,
|
||||||
|
excludedUserIds = viewModel.getUserIdsOfRoomMembers()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.observeViewEvents { renderInviteEvents(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
|
||||||
|
if (action.itemId == R.id.action_invite_users_to_room_invite) {
|
||||||
|
viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selectedUsers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderInviteEvents(viewEvent: InviteUsersToRoomViewEvents) {
|
||||||
|
when (viewEvent) {
|
||||||
|
is InviteUsersToRoomViewEvents.Loading -> renderInviteLoading()
|
||||||
|
is InviteUsersToRoomViewEvents.Success -> renderInvitationSuccess(viewEvent.successMessage)
|
||||||
|
is InviteUsersToRoomViewEvents.Failure -> renderInviteFailure(viewEvent.throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderInviteLoading() {
|
||||||
|
updateWaitingView(WaitingViewData(getString(R.string.inviting_users_to_room)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderInviteFailure(error: Throwable) {
|
||||||
|
hideWaitingView()
|
||||||
|
val message = if (error is Failure.ServerError && error.httpCode == HttpURLConnection.HTTP_INTERNAL_ERROR /*500*/) {
|
||||||
|
// This error happen if the invited userId does not exist.
|
||||||
|
getString(R.string.invite_users_to_room_failure)
|
||||||
|
} else {
|
||||||
|
errorFormatter.toHumanReadable(error)
|
||||||
|
}
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderInvitationSuccess(successMessage: String) {
|
||||||
|
toast(successMessage)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun getIntent(context: Context, roomId: String): Intent {
|
||||||
|
return Intent(context, InviteUsersToRoomActivity::class.java).also {
|
||||||
|
it.putExtra(MvRx.KEY_ARG, InviteUsersToRoomArgs(roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.invite
|
||||||
|
|
||||||
|
import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class InviteUsersToRoomViewEvents : VectorViewEvents {
|
||||||
|
object Loading : InviteUsersToRoomViewEvents()
|
||||||
|
data class Failure(val throwable: Throwable) : InviteUsersToRoomViewEvents()
|
||||||
|
data class Success(val successMessage: String) : InviteUsersToRoomViewEvents()
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.invite
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
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.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
|
|
||||||
|
class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
|
initialState: InviteUsersToRoomViewState,
|
||||||
|
session: Session,
|
||||||
|
val stringProvider: StringProvider)
|
||||||
|
: VectorViewModel<InviteUsersToRoomViewState, InviteUsersToRoomAction, InviteUsersToRoomViewEvents>(initialState) {
|
||||||
|
|
||||||
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: InviteUsersToRoomViewState): InviteUsersToRoomViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<InviteUsersToRoomViewModel, InviteUsersToRoomViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: InviteUsersToRoomViewState): InviteUsersToRoomViewModel? {
|
||||||
|
val activity: InviteUsersToRoomActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||||
|
return activity.inviteUsersToRoomViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: InviteUsersToRoomAction) {
|
||||||
|
when (action) {
|
||||||
|
is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.selectedUsers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inviteUsersToRoom(selectedUsers: Set<User>) {
|
||||||
|
_viewEvents.post(InviteUsersToRoomViewEvents.Loading)
|
||||||
|
|
||||||
|
Observable.fromIterable(selectedUsers).flatMapCompletable { user ->
|
||||||
|
room.rx().invite(user.userId, null)
|
||||||
|
}.subscribe(
|
||||||
|
{
|
||||||
|
val successMessage = when (selectedUsers.size) {
|
||||||
|
1 -> stringProvider.getString(R.string.invitation_sent_to_one_user,
|
||||||
|
selectedUsers.first().getBestName())
|
||||||
|
2 -> stringProvider.getString(R.string.invitations_sent_to_two_users,
|
||||||
|
selectedUsers.first().getBestName(),
|
||||||
|
selectedUsers.last().getBestName())
|
||||||
|
else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users,
|
||||||
|
selectedUsers.size - 1,
|
||||||
|
selectedUsers.first().getBestName(),
|
||||||
|
selectedUsers.size - 1)
|
||||||
|
}
|
||||||
|
_viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_viewEvents.post(InviteUsersToRoomViewEvents.Failure(it))
|
||||||
|
})
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserIdsOfRoomMembers(): Set<String> {
|
||||||
|
return room.roomSummary()?.otherMemberIds?.toSet() ?: emptySet()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.invite
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
|
||||||
|
data class InviteUsersToRoomViewState(
|
||||||
|
val roomId: String,
|
||||||
|
val inviteState: Async<Unit> = Uninitialized
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: InviteUsersToRoomArgs) : this(roomId = args.roomId)
|
||||||
|
}
|
|
@ -38,7 +38,6 @@ import javax.net.ssl.HttpsURLConnection
|
||||||
abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||||
|
|
||||||
protected val loginViewModel: LoginViewModel by activityViewModel()
|
protected val loginViewModel: LoginViewModel by activityViewModel()
|
||||||
protected lateinit var loginSharedActionViewModel: LoginSharedActionViewModel
|
|
||||||
|
|
||||||
private var isResetPasswordStarted = false
|
private var isResetPasswordStarted = false
|
||||||
|
|
||||||
|
@ -57,8 +56,6 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java)
|
|
||||||
|
|
||||||
loginViewModel.observeViewEvents {
|
loginViewModel.observeViewEvents {
|
||||||
handleLoginViewEvents(it)
|
handleLoginViewEvents(it)
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,4 +58,6 @@ sealed class LoginAction : VectorViewModelAction {
|
||||||
|
|
||||||
// For the soft logout case
|
// For the soft logout case
|
||||||
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
|
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
|
||||||
|
|
||||||
|
data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE
|
import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE
|
||||||
import im.vector.riotx.core.extensions.addFragment
|
import im.vector.riotx.core.extensions.addFragment
|
||||||
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
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.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
|
@ -54,7 +55,6 @@ import javax.inject.Inject
|
||||||
open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
|
|
||||||
private val loginViewModel: LoginViewModel by viewModel()
|
private val loginViewModel: LoginViewModel by viewModel()
|
||||||
private lateinit var loginSharedActionViewModel: LoginSharedActionViewModel
|
|
||||||
|
|
||||||
@Inject lateinit var loginViewModelFactory: LoginViewModel.Factory
|
@Inject lateinit var loginViewModelFactory: LoginViewModel.Factory
|
||||||
|
|
||||||
|
@ -98,14 +98,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
loginViewModel.handle(LoginAction.InitWith(loginConfig))
|
loginViewModel.handle(LoginAction.InitWith(loginConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
loginSharedActionViewModel = viewModelProvider.get(LoginSharedActionViewModel::class.java)
|
|
||||||
loginSharedActionViewModel
|
|
||||||
.observe()
|
|
||||||
.subscribe {
|
|
||||||
handleLoginNavigation(it)
|
|
||||||
}
|
|
||||||
.disposeOnDestroy()
|
|
||||||
|
|
||||||
loginViewModel
|
loginViewModel
|
||||||
.subscribe(this) {
|
.subscribe(this) {
|
||||||
updateWithState(it)
|
updateWithState(it)
|
||||||
|
@ -124,65 +116,9 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
addFragment(R.id.loginFragmentContainer, LoginSplashFragment::class.java)
|
addFragment(R.id.loginFragmentContainer, LoginSplashFragment::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoginNavigation(loginNavigation: LoginNavigation) {
|
|
||||||
// Assigning to dummy make sure we do not forget a case
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
|
||||||
val dummy = when (loginNavigation) {
|
|
||||||
is LoginNavigation.OpenServerSelection ->
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginServerSelectionFragment::class.java,
|
|
||||||
option = { ft ->
|
|
||||||
findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
|
||||||
// TODO Disabled because it provokes a flickering
|
|
||||||
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
|
||||||
})
|
|
||||||
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
|
|
||||||
is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
|
|
||||||
is LoginNavigation.OnLoginFlowRetrieved ->
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginSignUpSignInSelectionFragment::class.java,
|
|
||||||
option = commonOption)
|
|
||||||
is LoginNavigation.OnWebLoginError -> onWebLoginError(loginNavigation)
|
|
||||||
is LoginNavigation.OnForgetPasswordClicked ->
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginResetPasswordFragment::class.java,
|
|
||||||
option = commonOption)
|
|
||||||
is LoginNavigation.OnResetPasswordSendThreePidDone -> {
|
|
||||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginResetPasswordMailConfirmationFragment::class.java,
|
|
||||||
option = commonOption)
|
|
||||||
}
|
|
||||||
is LoginNavigation.OnResetPasswordMailConfirmationSuccess -> {
|
|
||||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginResetPasswordSuccessFragment::class.java,
|
|
||||||
option = commonOption)
|
|
||||||
}
|
|
||||||
is LoginNavigation.OnResetPasswordMailConfirmationSuccessDone -> {
|
|
||||||
// Go back to the login fragment
|
|
||||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
|
||||||
}
|
|
||||||
is LoginNavigation.OnSendEmailSuccess ->
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginWaitForEmailFragment::class.java,
|
|
||||||
LoginWaitForEmailFragmentArgument(loginNavigation.email),
|
|
||||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
|
||||||
option = commonOption)
|
|
||||||
is LoginNavigation.OnSendMsisdnSuccess ->
|
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
|
||||||
LoginGenericTextInputFormFragment::class.java,
|
|
||||||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn),
|
|
||||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
|
||||||
option = commonOption)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
||||||
when (loginViewEvents) {
|
when (loginViewEvents) {
|
||||||
is LoginViewEvents.RegistrationFlowResult -> {
|
is LoginViewEvents.RegistrationFlowResult -> {
|
||||||
// Check that all flows are supported by the application
|
// Check that all flows are supported by the application
|
||||||
if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) {
|
if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) {
|
||||||
// Display a popup to propose use web fallback
|
// Display a popup to propose use web fallback
|
||||||
|
@ -203,15 +139,64 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LoginViewEvents.OutdatedHomeserver ->
|
is LoginViewEvents.OutdatedHomeserver ->
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.login_error_outdated_homeserver_title)
|
.setTitle(R.string.login_error_outdated_homeserver_title)
|
||||||
.setMessage(R.string.login_error_outdated_homeserver_content)
|
.setMessage(R.string.login_error_outdated_homeserver_content)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
is LoginViewEvents.Failure ->
|
is LoginViewEvents.Failure ->
|
||||||
// This is handled by the Fragments
|
// This is handled by the Fragments
|
||||||
Unit
|
Unit
|
||||||
|
is LoginViewEvents.OpenServerSelection ->
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginServerSelectionFragment::class.java,
|
||||||
|
option = { ft ->
|
||||||
|
findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
|
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
|
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
|
// TODO Disabled because it provokes a flickering
|
||||||
|
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||||
|
})
|
||||||
|
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone()
|
||||||
|
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected()
|
||||||
|
is LoginViewEvents.OnLoginFlowRetrieved ->
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginSignUpSignInSelectionFragment::class.java,
|
||||||
|
option = commonOption)
|
||||||
|
is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents)
|
||||||
|
is LoginViewEvents.OnForgetPasswordClicked ->
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginResetPasswordFragment::class.java,
|
||||||
|
option = commonOption)
|
||||||
|
is LoginViewEvents.OnResetPasswordSendThreePidDone -> {
|
||||||
|
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginResetPasswordMailConfirmationFragment::class.java,
|
||||||
|
option = commonOption)
|
||||||
|
}
|
||||||
|
is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> {
|
||||||
|
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginResetPasswordSuccessFragment::class.java,
|
||||||
|
option = commonOption)
|
||||||
|
}
|
||||||
|
is LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone -> {
|
||||||
|
// Go back to the login fragment
|
||||||
|
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
is LoginViewEvents.OnSendEmailSuccess ->
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginWaitForEmailFragment::class.java,
|
||||||
|
LoginWaitForEmailFragmentArgument(loginViewEvents.email),
|
||||||
|
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||||
|
option = commonOption)
|
||||||
|
is LoginViewEvents.OnSendMsisdnSuccess ->
|
||||||
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginGenericTextInputFormFragment::class.java,
|
||||||
|
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn),
|
||||||
|
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||||
|
option = commonOption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +215,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
loginLoading.isVisible = loginViewState.isLoading()
|
loginLoading.isVisible = loginViewState.isLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onWebLoginError(onWebLoginError: LoginNavigation.OnWebLoginError) {
|
private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) {
|
||||||
// Pop the backstack
|
// Pop the backstack
|
||||||
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||||
|
|
||||||
|
@ -254,11 +239,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
|
|
||||||
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||||
SignMode.SignUp -> {
|
SignMode.SignUp -> {
|
||||||
// This is managed by the LoginViewEvents
|
// This is managed by the LoginViewEvents
|
||||||
}
|
}
|
||||||
SignMode.SignIn -> {
|
SignMode.SignIn -> {
|
||||||
// It depends on the LoginMode
|
// It depends on the LoginMode
|
||||||
when (state.loginMode) {
|
when (state.loginMode) {
|
||||||
LoginMode.Unknown -> error("Developer error")
|
LoginMode.Unknown -> error("Developer error")
|
||||||
|
@ -272,7 +257,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
|
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
SignMode.SignInWithMatrixId -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginFragment::class.java,
|
||||||
|
tag = FRAGMENT_LOGIN_TAG,
|
||||||
|
option = commonOption)
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRegistrationStageNotSupported() {
|
private fun onRegistrationStageNotSupported() {
|
||||||
|
|
|
@ -31,6 +31,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.matrix.android.api.failure.isInvalidPassword
|
import im.vector.matrix.android.api.failure.isInvalidPassword
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.showPassword
|
import im.vector.riotx.core.extensions.showPassword
|
||||||
import im.vector.riotx.core.extensions.toReducedUrl
|
import im.vector.riotx.core.extensions.toReducedUrl
|
||||||
|
@ -73,16 +74,17 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
private fun setupAutoFill(state: LoginViewState) {
|
private fun setupAutoFill(state: LoginViewState) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> {
|
SignMode.SignUp -> {
|
||||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
||||||
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||||
}
|
}
|
||||||
SignMode.SignIn -> {
|
SignMode.SignIn,
|
||||||
|
SignMode.SignInWithMatrixId -> {
|
||||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
|
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
|
||||||
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
|
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
|
||||||
}
|
}
|
||||||
}
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,35 +118,44 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUi(state: LoginViewState) {
|
private fun setupUi(state: LoginViewState) {
|
||||||
val resId = when (state.signMode) {
|
|
||||||
SignMode.Unknown -> error("developer error")
|
|
||||||
SignMode.SignUp -> R.string.login_signup_to
|
|
||||||
SignMode.SignIn -> R.string.login_connect_to
|
|
||||||
}
|
|
||||||
|
|
||||||
loginFieldTil.hint = getString(when (state.signMode) {
|
loginFieldTil.hint = getString(when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> R.string.login_signup_username_hint
|
SignMode.SignUp -> R.string.login_signup_username_hint
|
||||||
SignMode.SignIn -> R.string.login_signin_username_hint
|
SignMode.SignIn -> R.string.login_signin_username_hint
|
||||||
|
SignMode.SignInWithMatrixId -> R.string.login_signin_matrix_id_hint
|
||||||
})
|
})
|
||||||
|
|
||||||
when (state.serverType) {
|
// Handle direct signin first
|
||||||
ServerType.MatrixOrg -> {
|
if (state.signMode == SignMode.SignInWithMatrixId) {
|
||||||
loginServerIcon.isVisible = true
|
loginServerIcon.isVisible = false
|
||||||
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
loginTitle.text = getString(R.string.login_signin_matrix_id_title)
|
||||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
loginNotice.text = getString(R.string.login_signin_matrix_id_notice)
|
||||||
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
} else {
|
||||||
|
val resId = when (state.signMode) {
|
||||||
|
SignMode.Unknown -> error("developer error")
|
||||||
|
SignMode.SignUp -> R.string.login_signup_to
|
||||||
|
SignMode.SignIn -> R.string.login_connect_to
|
||||||
|
SignMode.SignInWithMatrixId -> R.string.login_connect_to
|
||||||
}
|
}
|
||||||
ServerType.Modular -> {
|
|
||||||
loginServerIcon.isVisible = true
|
when (state.serverType) {
|
||||||
loginServerIcon.setImageResource(R.drawable.ic_logo_modular)
|
ServerType.MatrixOrg -> {
|
||||||
loginTitle.text = getString(resId, "Modular")
|
loginServerIcon.isVisible = true
|
||||||
loginNotice.text = getString(R.string.login_server_modular_text)
|
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
||||||
}
|
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||||
ServerType.Other -> {
|
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
||||||
loginServerIcon.isVisible = false
|
}
|
||||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
ServerType.Modular -> {
|
||||||
loginNotice.text = getString(R.string.login_server_other_text)
|
loginServerIcon.isVisible = true
|
||||||
|
loginServerIcon.setImageResource(R.drawable.ic_logo_modular)
|
||||||
|
loginTitle.text = getString(resId, "Modular")
|
||||||
|
loginNotice.text = getString(R.string.login_server_modular_text)
|
||||||
|
}
|
||||||
|
ServerType.Other -> {
|
||||||
|
loginServerIcon.isVisible = false
|
||||||
|
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||||
|
loginNotice.text = getString(R.string.login_server_other_text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,9 +164,10 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn
|
forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn
|
||||||
|
|
||||||
loginSubmit.text = getString(when (state.signMode) {
|
loginSubmit.text = getString(when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> R.string.login_signup_submit
|
SignMode.SignUp -> R.string.login_signup_submit
|
||||||
SignMode.SignIn -> R.string.login_signin
|
SignMode.SignIn,
|
||||||
|
SignMode.SignInWithMatrixId -> R.string.login_signin
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +190,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
|
|
||||||
@OnClick(R.id.forgetPasswordButton)
|
@OnClick(R.id.forgetPasswordButton)
|
||||||
fun forgetPasswordClicked() {
|
fun forgetPasswordClicked() {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnForgetPasswordClicked)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnForgetPasswordClicked))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
private fun setupPasswordReveal() {
|
||||||
|
|
|
@ -217,7 +217,7 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
|
||||||
TextInputFormFragmentMode.SetEmail -> {
|
TextInputFormFragmentMode.SetEmail -> {
|
||||||
if (throwable.is401()) {
|
if (throwable.is401()) {
|
||||||
// This is normal use case, we go to the mail waiting screen
|
// This is normal use case, we go to the mail waiting screen
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnSendEmailSuccess(loginViewModel.currentThreePid ?: ""))
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendEmailSuccess(loginViewModel.currentThreePid ?: "")))
|
||||||
} else {
|
} else {
|
||||||
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
|
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
|
||||||
TextInputFormFragmentMode.SetMsisdn -> {
|
TextInputFormFragmentMode.SetMsisdn -> {
|
||||||
if (throwable.is401()) {
|
if (throwable.is401()) {
|
||||||
// This is normal use case, we go to the enter code screen
|
// This is normal use case, we go to the enter code screen
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: ""))
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: "")))
|
||||||
} else {
|
} else {
|
||||||
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
|
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +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.login
|
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedAction
|
|
||||||
|
|
||||||
// Supported navigation actions for LoginActivity
|
|
||||||
sealed class LoginNavigation : VectorSharedAction {
|
|
||||||
object OpenServerSelection : LoginNavigation()
|
|
||||||
object OnServerSelectionDone : LoginNavigation()
|
|
||||||
object OnLoginFlowRetrieved : LoginNavigation()
|
|
||||||
object OnSignModeSelected : LoginNavigation()
|
|
||||||
object OnForgetPasswordClicked : LoginNavigation()
|
|
||||||
object OnResetPasswordSendThreePidDone : LoginNavigation()
|
|
||||||
object OnResetPasswordMailConfirmationSuccess : LoginNavigation()
|
|
||||||
object OnResetPasswordMailConfirmationSuccessDone : LoginNavigation()
|
|
||||||
|
|
||||||
data class OnSendEmailSuccess(val email: String) : LoginNavigation()
|
|
||||||
data class OnSendMsisdnSuccess(val msisdn: String) : LoginNavigation()
|
|
||||||
|
|
||||||
data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginNavigation()
|
|
||||||
}
|
|
|
@ -149,7 +149,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment()
|
||||||
resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
|
resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
|
||||||
}
|
}
|
||||||
is Success -> {
|
is Success -> {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSendThreePidDone)
|
Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
is Success -> {
|
is Success -> {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccess)
|
Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class LoginResetPasswordSuccessFragment @Inject constructor() : AbstractLoginFra
|
||||||
|
|
||||||
@OnClick(R.id.resetPasswordSuccessSubmit)
|
@OnClick(R.id.resetPasswordSuccessSubmit)
|
||||||
fun submit() {
|
fun submit() {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccessDone)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
|
|
|
@ -95,10 +95,15 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||||
// Request login flow here
|
// Request login flow here
|
||||||
loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url)))
|
loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url)))
|
||||||
} else {
|
} else {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnServerSelectionDone)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnServerSelectionDone))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.loginServerIKnowMyIdSubmit)
|
||||||
|
fun loginWithMatrixId() {
|
||||||
|
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignInWithMatrixId))
|
||||||
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
loginViewModel.handle(LoginAction.ResetHomeServerType)
|
loginViewModel.handle(LoginAction.ResetHomeServerType)
|
||||||
}
|
}
|
||||||
|
@ -108,7 +113,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||||
|
|
||||||
if (state.loginMode != LoginMode.Unknown) {
|
if (state.loginMode != LoginMode.Unknown) {
|
||||||
// LoginFlow for matrix.org has been retrieved
|
// LoginFlow for matrix.org has been retrieved
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||||
|
|
||||||
if (state.loginMode != LoginMode.Unknown) {
|
if (state.loginMode != LoginMode.Unknown) {
|
||||||
// The home server url is valid
|
// The home server url is valid
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
|
||||||
@OnClick(R.id.loginSignupSigninSignIn)
|
@OnClick(R.id.loginSignupSigninSignIn)
|
||||||
fun signIn() {
|
fun signIn() {
|
||||||
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignIn))
|
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignIn))
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
|
|
|
@ -29,7 +29,7 @@ class LoginSplashFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
|
|
||||||
@OnClick(R.id.loginSplashSubmit)
|
@OnClick(R.id.loginSplashSubmit)
|
||||||
fun getStarted() {
|
fun getStarted() {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OpenServerSelection)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OpenServerSelection))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
|
|
|
@ -23,10 +23,26 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
/**
|
/**
|
||||||
* Transient events for Login
|
* Transient events for Login
|
||||||
*/
|
*/
|
||||||
sealed class LoginViewEvents: VectorViewEvents {
|
sealed class LoginViewEvents : VectorViewEvents {
|
||||||
data class Loading(val message: CharSequence? = null) : LoginViewEvents()
|
data class Loading(val message: CharSequence? = null) : LoginViewEvents()
|
||||||
data class Failure(val throwable: Throwable) : LoginViewEvents()
|
data class Failure(val throwable: Throwable) : LoginViewEvents()
|
||||||
|
|
||||||
data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents()
|
data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents()
|
||||||
object OutdatedHomeserver : LoginViewEvents()
|
object OutdatedHomeserver : LoginViewEvents()
|
||||||
|
|
||||||
|
// Navigation event
|
||||||
|
|
||||||
|
object OpenServerSelection : LoginViewEvents()
|
||||||
|
object OnServerSelectionDone : LoginViewEvents()
|
||||||
|
object OnLoginFlowRetrieved : LoginViewEvents()
|
||||||
|
object OnSignModeSelected : LoginViewEvents()
|
||||||
|
object OnForgetPasswordClicked : LoginViewEvents()
|
||||||
|
object OnResetPasswordSendThreePidDone : LoginViewEvents()
|
||||||
|
object OnResetPasswordMailConfirmationSuccess : LoginViewEvents()
|
||||||
|
object OnResetPasswordMailConfirmationSuccessDone : LoginViewEvents()
|
||||||
|
|
||||||
|
data class OnSendEmailSuccess(val email: String) : LoginViewEvents()
|
||||||
|
data class OnSendMsisdnSuccess(val msisdn: String) : LoginViewEvents()
|
||||||
|
|
||||||
|
data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.riotx.features.login
|
package im.vector.riotx.features.login
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
|
@ -29,19 +30,24 @@ import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
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.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
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.FlowResult
|
import im.vector.matrix.android.api.auth.registration.FlowResult
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.Stage
|
import im.vector.matrix.android.api.auth.registration.Stage
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
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.auth.data.LoginFlowTypes
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.extensions.configureAndStart
|
import im.vector.riotx.core.extensions.configureAndStart
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
import im.vector.riotx.features.session.SessionListener
|
import im.vector.riotx.features.session.SessionListener
|
||||||
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
||||||
|
@ -51,14 +57,16 @@ import java.util.concurrent.CancellationException
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
|
class LoginViewModel @AssistedInject constructor(
|
||||||
private val applicationContext: Context,
|
@Assisted initialState: LoginViewState,
|
||||||
private val authenticationService: AuthenticationService,
|
private val applicationContext: Context,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val authenticationService: AuthenticationService,
|
||||||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||||
private val sessionListener: SessionListener,
|
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
||||||
private val reAuthHelper: ReAuthHelper)
|
private val sessionListener: SessionListener,
|
||||||
|
private val reAuthHelper: ReAuthHelper,
|
||||||
|
private val stringProvider: StringProvider)
|
||||||
: VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
|
: VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
|
@ -108,7 +116,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||||
is LoginAction.ResetAction -> handleResetAction(action)
|
is LoginAction.ResetAction -> handleResetAction(action)
|
||||||
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
||||||
}
|
is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
|
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
|
||||||
|
@ -320,11 +329,12 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.signMode == SignMode.SignUp) {
|
when (action.signMode) {
|
||||||
startRegistrationFlow()
|
SignMode.SignUp -> startRegistrationFlow()
|
||||||
} else if (action.signMode == SignMode.SignIn) {
|
SignMode.SignIn -> startAuthenticationFlow()
|
||||||
startAuthenticationFlow()
|
SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected)
|
||||||
}
|
SignMode.Unknown -> Unit
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
|
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
|
||||||
|
@ -365,6 +375,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
resetPasswordEmail = action.email
|
resetPasswordEmail = action.email
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
|
@ -405,6 +417,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
resetPasswordEmail = null
|
resetPasswordEmail = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
|
@ -421,10 +435,78 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
|
|
||||||
private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
|
private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.SignIn -> handleLogin(action)
|
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||||
SignMode.SignUp -> handleRegisterWith(action)
|
SignMode.SignIn -> handleLogin(action)
|
||||||
else -> error("Developer error, invalid sign mode")
|
SignMode.SignUp -> handleRegisterWith(action)
|
||||||
|
SignMode.SignInWithMatrixId -> handleDirectLogin(action)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDirectLogin(action: LoginAction.LoginOrRegister) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Loading()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authenticationService.getWellKnownData(action.username, object : MatrixCallback<WellknownResult> {
|
||||||
|
override fun onSuccess(data: WellknownResult) {
|
||||||
|
when (data) {
|
||||||
|
is WellknownResult.Prompt ->
|
||||||
|
onWellknownSuccess(action, data)
|
||||||
|
is WellknownResult.InvalidMatrixId -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
|
||||||
|
}
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) {
|
||||||
|
val homeServerConnectionConfig = HomeServerConnectionConfig(
|
||||||
|
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||||
|
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
authenticationService.directAuthentication(
|
||||||
|
homeServerConnectionConfig,
|
||||||
|
action.username,
|
||||||
|
action.password,
|
||||||
|
action.initialDeviceName,
|
||||||
|
object : MatrixCallback<Session> {
|
||||||
|
override fun onSuccess(data: Session) {
|
||||||
|
onSessionCreated(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
||||||
|
@ -477,6 +559,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
private fun startAuthenticationFlow() {
|
private fun startAuthenticationFlow() {
|
||||||
// Ensure Wizard is ready
|
// Ensure Wizard is ready
|
||||||
loginWizard
|
loginWizard
|
||||||
|
|
||||||
|
_viewEvents.post(LoginViewEvents.OnSignModeSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFlowResponse(flowResult: FlowResult) {
|
private fun onFlowResponse(flowResult: FlowResult) {
|
||||||
|
|
|
@ -173,7 +173,7 @@ class LoginWebFragment @Inject constructor(
|
||||||
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
||||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||||
|
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnWebLoginError(errorCode, description, failingUrl))
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnWebLoginError(errorCode, description, failingUrl)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||||
|
|
|
@ -21,5 +21,7 @@ enum class SignMode {
|
||||||
// Account creation
|
// Account creation
|
||||||
SignUp,
|
SignUp,
|
||||||
// Login
|
// Login
|
||||||
SignIn
|
SignIn,
|
||||||
|
// Login directly with matrix Id
|
||||||
|
SignInWithMatrixId
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import im.vector.riotx.features.debug.DebugMenuActivity
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
|
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
||||||
import im.vector.riotx.features.media.BigImageViewerActivity
|
import im.vector.riotx.features.media.BigImageViewerActivity
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
|
@ -163,6 +164,11 @@ class DefaultNavigator @Inject constructor(
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openInviteUsersToRoom(context: Context, roomId: String) {
|
||||||
|
val intent = InviteUsersToRoomActivity.getIntent(context, roomId)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun openRoomsFiltering(context: Context) {
|
override fun openRoomsFiltering(context: Context) {
|
||||||
val intent = FilteredRoomsActivity.newIntent(context)
|
val intent = FilteredRoomsActivity.newIntent(context)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
|
|
@ -46,6 +46,8 @@ interface Navigator {
|
||||||
|
|
||||||
fun openCreateDirectRoom(context: Context)
|
fun openCreateDirectRoom(context: Context)
|
||||||
|
|
||||||
|
fun openInviteUsersToRoom(context: Context, roomId: String)
|
||||||
|
|
||||||
fun openRoomDirectory(context: Context, initialFilter: String = "")
|
fun openRoomDirectory(context: Context, initialFilter: String = "")
|
||||||
|
|
||||||
fun openRoomsFiltering(context: Context)
|
fun openRoomsFiltering(context: Context)
|
||||||
|
|
|
@ -348,12 +348,19 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
globalLastMessageTimestamp = lastMessageTimestamp
|
globalLastMessageTimestamp = lastMessageTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val tickerText = if (roomEventGroupInfo.isDirect) {
|
||||||
|
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description)
|
||||||
|
} else {
|
||||||
|
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
|
||||||
|
}
|
||||||
|
|
||||||
val notification = notificationUtils.buildMessagesListNotification(
|
val notification = notificationUtils.buildMessagesListNotification(
|
||||||
style,
|
style,
|
||||||
roomEventGroupInfo,
|
roomEventGroupInfo,
|
||||||
largeBitmap,
|
largeBitmap,
|
||||||
lastMessageTimestamp,
|
lastMessageTimestamp,
|
||||||
myUserDisplayName)
|
myUserDisplayName,
|
||||||
|
tickerText)
|
||||||
|
|
||||||
// is there an id for this room?
|
// is there an id for this room?
|
||||||
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
||||||
|
|
|
@ -381,7 +381,8 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
roomInfo: RoomEventGroupInfo,
|
roomInfo: RoomEventGroupInfo,
|
||||||
largeIcon: Bitmap?,
|
largeIcon: Bitmap?,
|
||||||
lastMessageTimestamp: Long,
|
lastMessageTimestamp: Long,
|
||||||
senderDisplayNameForReplyCompat: String?): Notification {
|
senderDisplayNameForReplyCompat: String?,
|
||||||
|
tickerText: String): Notification {
|
||||||
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
|
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
|
||||||
// Build the pending intent for when the notification is clicked
|
// Build the pending intent for when the notification is clicked
|
||||||
val openRoomIntent = buildOpenRoomIntent(roomInfo.roomId)
|
val openRoomIntent = buildOpenRoomIntent(roomInfo.roomId)
|
||||||
|
@ -478,6 +479,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
setDeleteIntent(pendingIntent)
|
setDeleteIntent(pendingIntent)
|
||||||
}
|
}
|
||||||
|
.setTicker(tickerText)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.riotx.features.roomprofile.members
|
package im.vector.riotx.features.roomprofile.members
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
@ -43,6 +44,18 @@ class RoomMemberListFragment @Inject constructor(
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
|
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.menu_room_member_list
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.menu_room_member_list_add_member -> {
|
||||||
|
navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
roomMemberListController.callback = this
|
roomMemberListController.callback = this
|
||||||
|
|
|
@ -30,7 +30,6 @@ import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||||
import im.vector.matrix.android.api.extensions.tryThis
|
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
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.session.crypto.verification.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||||
|
@ -48,6 +47,7 @@ import io.reactivex.Observable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
data class DevicesViewState(
|
data class DevicesViewState(
|
||||||
|
@ -65,6 +65,7 @@ data class DeviceFullInfo(
|
||||||
val deviceInfo: DeviceInfo,
|
val deviceInfo: DeviceInfo,
|
||||||
val cryptoDeviceInfo: CryptoDeviceInfo?
|
val cryptoDeviceInfo: CryptoDeviceInfo?
|
||||||
)
|
)
|
||||||
|
|
||||||
class DevicesViewModel @AssistedInject constructor(
|
class DevicesViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: DevicesViewState,
|
@Assisted initialState: DevicesViewState,
|
||||||
private val session: Session
|
private val session: Session
|
||||||
|
@ -215,8 +216,13 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
private fun handleVerifyManually(action: DevicesAction.MarkAsManuallyVerified) = withState { state ->
|
private fun handleVerifyManually(action: DevicesAction.MarkAsManuallyVerified) = withState { state ->
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (state.hasAccountCrossSigning) {
|
if (state.hasAccountCrossSigning) {
|
||||||
awaitCallback<Unit> {
|
try {
|
||||||
tryThis { session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId, it) }
|
awaitCallback<Unit> {
|
||||||
|
session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId, it)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e("Failed to manually cross sign device ${action.cryptoDeviceInfo.deviceId} : ${failure.localizedMessage}")
|
||||||
|
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// legacy
|
// legacy
|
||||||
|
|
|
@ -30,7 +30,7 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
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
|
||||||
import im.vector.riotx.features.login.LoginMode
|
import im.vector.riotx.features.login.LoginMode
|
||||||
import im.vector.riotx.features.login.LoginNavigation
|
import im.vector.riotx.features.login.LoginViewEvents
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class SoftLogoutFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun signinFallbackSubmit() {
|
override fun signinFallbackSubmit() {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
|
@ -124,7 +124,7 @@ class SoftLogoutFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun forgetPasswordClicked() {
|
override fun forgetPasswordClicked() {
|
||||||
loginSharedActionViewModel.post(LoginNavigation.OnForgetPasswordClicked)
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnForgetPasswordClicked))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun revealPasswordClicked() {
|
override fun revealPasswordClicked() {
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * 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.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
|
@ -41,7 +39,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||||
|
|
||||||
private var state: CreateDirectRoomViewState? = null
|
private var state: UserDirectoryViewState? = null
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
@ -49,7 +47,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setData(state: CreateDirectRoomViewState) {
|
fun setData(state: UserDirectoryViewState) {
|
||||||
this.state = state
|
this.state = state
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
@ -110,7 +108,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val isSelected = selectedUsers.contains(user.userId)
|
val isSelected = selectedUsers.contains(user.userId)
|
||||||
createDirectRoomUserItem {
|
userDirectoryUserItem {
|
||||||
id(user.userId)
|
id(user.userId)
|
||||||
selected(isSelected)
|
selected(isSelected)
|
||||||
matrixItem(user.toMatrixItem())
|
matrixItem(user.toMatrixItem())
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
@ -49,7 +49,7 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setData(state: CreateDirectRoomViewState) {
|
fun setData(state: UserDirectoryViewState) {
|
||||||
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
|
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
|
||||||
val newSelection = state.selectedUsers.map { it.userId }
|
val newSelection = state.selectedUsers.map { it.userId }
|
||||||
this.users = state.knownUsers
|
this.users = state.knownUsers
|
||||||
|
@ -65,7 +65,7 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
EmptyItem_().id(currentPosition)
|
EmptyItem_().id(currentPosition)
|
||||||
} else {
|
} else {
|
||||||
val isSelected = selectedUsers.contains(item.userId)
|
val isSelected = selectedUsers.contains(item.userId)
|
||||||
CreateDirectRoomUserItem_()
|
UserDirectoryUserItem_()
|
||||||
.id(item.userId)
|
.id(item.userId)
|
||||||
.selected(isSelected)
|
.selected(isSelected)
|
||||||
.matrixItem(item.toMatrixItem())
|
.matrixItem(item.toMatrixItem())
|
||||||
|
@ -84,13 +84,13 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
} else {
|
} else {
|
||||||
var lastFirstLetter: String? = null
|
var lastFirstLetter: String? = null
|
||||||
for (model in models) {
|
for (model in models) {
|
||||||
if (model is CreateDirectRoomUserItem) {
|
if (model is UserDirectoryUserItem) {
|
||||||
if (model.matrixItem.id == session.myUserId) continue
|
if (model.matrixItem.id == session.myUserId) continue
|
||||||
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
|
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
|
||||||
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
||||||
lastFirstLetter = currentFirstLetter
|
lastFirstLetter = currentFirstLetter
|
||||||
|
|
||||||
CreateDirectRoomLetterHeaderItem_()
|
UserDirectoryLetterHeaderItem_()
|
||||||
.id(currentFirstLetter)
|
.id(currentFirstLetter)
|
||||||
.letter(currentFirstLetter)
|
.letter(currentFirstLetter)
|
||||||
.addIf(showLetter, this)
|
.addIf(showLetter, this)
|
|
@ -1,29 +1,29 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * 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.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
|
import androidx.core.view.forEach
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
|
@ -35,30 +35,36 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.setupAsSearch
|
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_known_users.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
class KnownUsersFragment @Inject constructor(
|
||||||
|
val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory,
|
||||||
private val knownUsersController: KnownUsersController,
|
private val knownUsersController: KnownUsersController,
|
||||||
private val dimensionConverter: DimensionConverter
|
private val dimensionConverter: DimensionConverter
|
||||||
) : VectorBaseFragment(), KnownUsersController.Callback {
|
) : VectorBaseFragment(), KnownUsersController.Callback {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_create_direct_room
|
private val args: KnownUsersFragmentArgs by args()
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.vector_create_direct_room
|
override fun getLayoutResId() = R.layout.fragment_known_users
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
override fun getMenuRes() = args.menuResId
|
||||||
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
|
|
||||||
|
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||||
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
vectorBaseActivity.setSupportActionBar(createDirectRoomToolbar)
|
|
||||||
|
knownUsersTitle.text = args.title
|
||||||
|
|
||||||
|
vectorBaseActivity.setSupportActionBar(knownUsersToolbar)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupFilterView()
|
setupFilterView()
|
||||||
setupAddByMatrixIdView()
|
setupAddByMatrixIdView()
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) {
|
viewModel.selectSubscribe(this, UserDirectoryViewState::selectedUsers) {
|
||||||
renderSelectedUsers(it)
|
renderSelectedUsers(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,27 +77,22 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
|
|
||||||
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 showMenuItem = it.selectedUsers.isNotEmpty()
|
val showMenuItem = it.selectedUsers.isNotEmpty()
|
||||||
createMenuItem.setVisible(showMenuItem)
|
menu.forEach { menuItem ->
|
||||||
|
menuItem.isVisible = showMenuItem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.onPrepareOptionsMenu(menu)
|
super.onPrepareOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) {
|
||||||
return when (item.itemId) {
|
sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(item.itemId, it.selectedUsers))
|
||||||
R.id.action_create_direct_room -> {
|
return@withState true
|
||||||
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else ->
|
|
||||||
super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAddByMatrixIdView() {
|
private fun setupAddByMatrixIdView() {
|
||||||
addByMatrixId.setOnClickListener {
|
addByMatrixId.setOnClickListener {
|
||||||
sharedActionViewModel.post(CreateDirectRoomSharedAction.OpenUsersDirectory)
|
sharedActionViewModel.post(UserDirectorySharedAction.OpenUsersDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,26 +103,26 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFilterView() {
|
private fun setupFilterView() {
|
||||||
createDirectRoomFilter
|
knownUsersFilter
|
||||||
.textChanges()
|
.textChanges()
|
||||||
.startWith(createDirectRoomFilter.text)
|
.startWith(knownUsersFilter.text)
|
||||||
.subscribe { text ->
|
.subscribe { text ->
|
||||||
val filterValue = text.trim()
|
val filterValue = text.trim()
|
||||||
val action = if (filterValue.isBlank()) {
|
val action = if (filterValue.isBlank()) {
|
||||||
CreateDirectRoomAction.ClearFilterKnownUsers
|
UserDirectoryAction.ClearFilterKnownUsers
|
||||||
} else {
|
} else {
|
||||||
CreateDirectRoomAction.FilterKnownUsers(filterValue.toString())
|
UserDirectoryAction.FilterKnownUsers(filterValue.toString())
|
||||||
}
|
}
|
||||||
viewModel.handle(action)
|
viewModel.handle(action)
|
||||||
}
|
}
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
createDirectRoomFilter.setupAsSearch()
|
knownUsersFilter.setupAsSearch()
|
||||||
createDirectRoomFilter.requestFocus()
|
knownUsersFilter.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCloseView() {
|
private fun setupCloseView() {
|
||||||
createDirectRoomClose.setOnClickListener {
|
knownUsersClose.setOnClickListener {
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,12 +158,12 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
chip.isCloseIconVisible = true
|
chip.isCloseIconVisible = true
|
||||||
chipGroup.addView(chip)
|
chipGroup.addView(chip)
|
||||||
chip.setOnCloseIconClickListener {
|
chip.setOnCloseIconClickListener {
|
||||||
viewModel.handle(CreateDirectRoomAction.RemoveSelectedUser(user))
|
viewModel.handle(UserDirectoryAction.RemoveSelectedUser(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(user: User) {
|
override fun onItemClick(user: User) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(CreateDirectRoomAction.SelectUser(user))
|
viewModel.handle(UserDirectoryAction.SelectUser(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.userdirectory
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class KnownUsersFragmentArgs(
|
||||||
|
val title: String,
|
||||||
|
val menuResId: Int,
|
||||||
|
val excludedUserIds: Set<String>? = null
|
||||||
|
) : Parcelable
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.userdirectory
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class UserDirectoryAction : VectorViewModelAction {
|
||||||
|
data class FilterKnownUsers(val value: String) : UserDirectoryAction()
|
||||||
|
data class SearchDirectoryUsers(val value: String) : UserDirectoryAction()
|
||||||
|
object ClearFilterKnownUsers : UserDirectoryAction()
|
||||||
|
data class SelectUser(val user: User) : UserDirectoryAction()
|
||||||
|
data class RemoveSelectedUser(val user: User) : UserDirectoryAction()
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -29,22 +29,22 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.setupAsSearch
|
import im.vector.riotx.core.extensions.setupAsSearch
|
||||||
import im.vector.riotx.core.extensions.showKeyboard
|
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.recyclerView
|
||||||
|
import kotlinx.android.synthetic.main.fragment_user_directory.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
class UserDirectoryFragment @Inject constructor(
|
||||||
private val directRoomController: DirectoryUsersController
|
private val directRoomController: DirectoryUsersController
|
||||||
) : VectorBaseFragment(), DirectoryUsersController.Callback {
|
) : VectorBaseFragment(), DirectoryUsersController.Callback {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users
|
override fun getLayoutResId() = R.layout.fragment_user_directory
|
||||||
|
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupSearchByMatrixIdView()
|
setupSearchByMatrixIdView()
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
|
@ -62,19 +62,19 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSearchByMatrixIdView() {
|
private fun setupSearchByMatrixIdView() {
|
||||||
createDirectRoomSearchById.setupAsSearch(searchIconRes = 0)
|
userDirectorySearchById.setupAsSearch(searchIconRes = 0)
|
||||||
createDirectRoomSearchById
|
userDirectorySearchById
|
||||||
.textChanges()
|
.textChanges()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(it.toString()))
|
viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(it.toString()))
|
||||||
}
|
}
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
createDirectRoomSearchById.showKeyboard(andRequestFocus = true)
|
userDirectorySearchById.showKeyboard(andRequestFocus = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCloseView() {
|
private fun setupCloseView() {
|
||||||
createDirectRoomClose.setOnClickListener {
|
userDirectoryClose.setOnClickListener {
|
||||||
sharedActionViewModel.post(CreateDirectRoomSharedAction.GoBack)
|
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +84,12 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onItemClick(user: User) {
|
override fun onItemClick(user: User) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(CreateDirectRoomAction.SelectUser(user))
|
viewModel.handle(UserDirectoryAction.SelectUser(user))
|
||||||
sharedActionViewModel.post(CreateDirectRoomSharedAction.GoBack)
|
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun retryDirectoryUsersRequest() {
|
override fun retryDirectoryUsersRequest() {
|
||||||
val currentSearch = createDirectRoomSearchById.text.toString()
|
val currentSearch = userDirectorySearchById.text.toString()
|
||||||
viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(currentSearch))
|
viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(currentSearch))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
@ -23,8 +23,8 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_create_direct_room_letter_header)
|
@EpoxyModelClass(layout = R.layout.item_user_directory_letter_header)
|
||||||
abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel<CreateDirectRoomLetterHeaderItem.Holder>() {
|
abstract class UserDirectoryLetterHeaderItem : VectorEpoxyModel<UserDirectoryLetterHeaderItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute var letter: String = ""
|
@EpoxyAttribute var letter: String = ""
|
||||||
|
|
||||||
|
@ -33,6 +33,6 @@ abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel<CreateDirectR
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val letterView by bind<TextView>(R.id.createDirectRoomLetterView)
|
val letterView by bind<TextView>(R.id.userDirectoryLetterView)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.userdirectory
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.core.platform.VectorSharedAction
|
||||||
|
|
||||||
|
sealed class UserDirectorySharedAction : VectorSharedAction {
|
||||||
|
object OpenUsersDirectory : UserDirectorySharedAction()
|
||||||
|
object Close : UserDirectorySharedAction()
|
||||||
|
object GoBack : UserDirectorySharedAction()
|
||||||
|
data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set<User>) : UserDirectorySharedAction()
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.login
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LoginSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<LoginNavigation>()
|
class UserDirectorySharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<UserDirectorySharedAction>()
|
|
@ -1,22 +1,20 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * 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.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
@ -31,8 +29,8 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_create_direct_room_user)
|
@EpoxyModelClass(layout = R.layout.item_known_user)
|
||||||
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {
|
abstract class UserDirectoryUserItem : VectorEpoxyModel<UserDirectoryUserItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
|
@ -66,9 +64,9 @@ abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserI
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val userIdView by bind<TextView>(R.id.createDirectRoomUserID)
|
val userIdView by bind<TextView>(R.id.knownUserID)
|
||||||
val nameView by bind<TextView>(R.id.createDirectRoomUserName)
|
val nameView by bind<TextView>(R.id.knownUserName)
|
||||||
val avatarImageView by bind<ImageView>(R.id.createDirectRoomUserAvatar)
|
val avatarImageView by bind<ImageView>(R.id.knownUserAvatar)
|
||||||
val avatarCheckedImageView by bind<ImageView>(R.id.createDirectRoomUserAvatarChecked)
|
val avatarCheckedImageView by bind<ImageView>(R.id.knownUserAvatarChecked)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,9 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class CreateDirectRoomSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<CreateDirectRoomSharedAction>()
|
/**
|
||||||
|
* Transient events for invite users to room screen
|
||||||
|
*/
|
||||||
|
sealed class UserDirectoryViewEvents : VectorViewEvents
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.userdirectory
|
||||||
|
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import arrow.core.Option
|
||||||
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.extensions.toggle
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||||
|
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
private typealias KnowUsersFilter = String
|
||||||
|
private typealias DirectoryUsersSearch = String
|
||||||
|
|
||||||
|
class UserDirectoryViewModel @AssistedInject constructor(@Assisted
|
||||||
|
initialState: UserDirectoryViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<UserDirectoryViewState, UserDirectoryAction, UserDirectoryViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
||||||
|
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<UserDirectoryViewModel, UserDirectoryViewState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: UserDirectoryViewState): UserDirectoryViewModel? {
|
||||||
|
return when (viewModelContext) {
|
||||||
|
is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state)
|
||||||
|
is ActivityViewModelContext -> {
|
||||||
|
when (viewModelContext.activity<FragmentActivity>()) {
|
||||||
|
is CreateDirectRoomActivity -> viewModelContext.activity<CreateDirectRoomActivity>().userDirectoryViewModelFactory.create(state)
|
||||||
|
is InviteUsersToRoomActivity -> viewModelContext.activity<InviteUsersToRoomActivity>().userDirectoryViewModelFactory.create(state)
|
||||||
|
else -> error("Wrong activity or fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> error("Wrong activity or fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeKnownUsers()
|
||||||
|
observeDirectoryUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: UserDirectoryAction) {
|
||||||
|
when (action) {
|
||||||
|
is UserDirectoryAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value))
|
||||||
|
is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
|
||||||
|
is UserDirectoryAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value)
|
||||||
|
is UserDirectoryAction.SelectUser -> handleSelectUser(action)
|
||||||
|
is UserDirectoryAction.RemoveSelectedUser -> handleRemoveSelectedUser(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemoveSelectedUser) = withState { state ->
|
||||||
|
val selectedUsers = state.selectedUsers.minus(action.user)
|
||||||
|
setState { copy(selectedUsers = selectedUsers) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSelectUser(action: UserDirectoryAction.SelectUser) = withState { state ->
|
||||||
|
// Reset the filter asap
|
||||||
|
directoryUsersSearch.accept("")
|
||||||
|
val selectedUsers = state.selectedUsers.toggle(action.user)
|
||||||
|
setState { copy(selectedUsers = selectedUsers) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeDirectoryUsers() = withState { state ->
|
||||||
|
directoryUsersSearch
|
||||||
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.switchMapSingle { search ->
|
||||||
|
val stream = if (search.isBlank()) {
|
||||||
|
Single.just(emptyList())
|
||||||
|
} else {
|
||||||
|
session.rx()
|
||||||
|
.searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet())
|
||||||
|
.map { users ->
|
||||||
|
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.toAsync {
|
||||||
|
copy(directoryUsers = it, directorySearchTerm = search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribe()
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeKnownUsers() = withState { state ->
|
||||||
|
knownUsersFilter
|
||||||
|
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.switchMap {
|
||||||
|
session.rx().livePagedUsers(it.orNull(), state.excludedUserIds)
|
||||||
|
}
|
||||||
|
.execute { async ->
|
||||||
|
copy(
|
||||||
|
knownUsers = async,
|
||||||
|
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue