Merge branch 'develop' into cross_signing
This commit is contained in:
commit
8400ab6efe
|
@ -7,4 +7,4 @@
|
|||
- [ ] Pull request is based on the develop branch
|
||||
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md)
|
||||
- [ ] Pull request includes screenshots or videos if containing UI changes
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/local.properties
|
||||
# idea files: exclude everything except dictionnaries
|
||||
.idea/caches
|
||||
.idea/codeStyles
|
||||
.idea/libraries
|
||||
.idea/*.xml
|
||||
.DS_Store
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
|
||||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="true" />
|
||||
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" />
|
||||
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="0" />
|
||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="0" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="0" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="0" />
|
||||
<option name="ASSIGNMENT_WRAP" value="0" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
24
CHANGES.md
24
CHANGES.md
|
@ -1,11 +1,11 @@
|
|||
Changes in RiotX 0.13.0 (2020-XX-XX)
|
||||
Changes in RiotX 0.14.0 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Send and render typing events (#564)
|
||||
-
|
||||
|
||||
Improvements 🙌:
|
||||
- Render events m.room.encryption and m.room.guest_access in the timeline
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
@ -16,6 +16,24 @@ Bugfix 🐛:
|
|||
Translations 🗣:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
Changes in RiotX 0.13.0 (2020-01-17)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Send and render typing events (#564)
|
||||
- Create Room Profile screen (#54)
|
||||
- Create Room Member Profile screen (#59)
|
||||
|
||||
Improvements 🙌:
|
||||
- Render events m.room.encryption and m.room.guest_access in the timeline
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix broken background sync in F-Droid version
|
||||
- Fix issue with downloaded file on encrypted rooms. The file was not properly decrypted
|
||||
|
||||
Build 🧱:
|
||||
- Change the way versionCode is computed (#827)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Contributing code to Matrix
|
||||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
|
||||
|
||||
Android support can be found in this [![Riot Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riot-android:matrix.org.svg?label=%23riot-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||
|
||||
|
@ -11,6 +11,7 @@ Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org]
|
|||
## Android Studio settings
|
||||
|
||||
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||
Please ensure that your using the project formatting rules (which are in the project at .idea/codeStyles/), and format the file before committing them.
|
||||
|
||||
## Compilation
|
||||
|
||||
|
|
|
@ -22,3 +22,9 @@ import io.reactivex.Observable
|
|||
fun <T : Any> Observable<Optional<T>>.unwrap(): Observable<T> {
|
||||
return filter { it.hasValue() }.map { it.get() }
|
||||
}
|
||||
|
||||
fun <T : Any, U : Any> Observable<Optional<T>>.mapOptional(fn: (T) -> U?): Observable<Optional<U>> {
|
||||
return map {
|
||||
it.map(fn)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||
|
@ -37,7 +38,7 @@ class RxRoom(private val room: Room) {
|
|||
.startWith(room.roomSummary().toOptional())
|
||||
}
|
||||
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMember>> {
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||
return room.getRoomMembersLive(queryParams).asObservable()
|
||||
.startWith(room.getRoomMembers(queryParams))
|
||||
}
|
||||
|
@ -52,6 +53,11 @@ class RxRoom(private val room: Room) {
|
|||
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
||||
}
|
||||
|
||||
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType).asObservable()
|
||||
.startWith(room.getStateEvent(eventType).toOptional())
|
||||
}
|
||||
|
||||
fun liveReadMarker(): Observable<Optional<String>> {
|
||||
return room.getReadMarkerLive().asObservable()
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
|
@ -56,7 +58,8 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
|
||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||
return session.getUserLive(userId).asObservable().distinctUntilChanged()
|
||||
return session.getUserLive(userId).asObservable()
|
||||
.startWith(session.getUser(userId).toOptional())
|
||||
}
|
||||
|
||||
fun liveUsers(): Observable<List<User>> {
|
||||
|
@ -91,6 +94,10 @@ class RxSession(private val session: Session) {
|
|||
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
|
||||
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
||||
}
|
||||
|
||||
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
|
||||
session.getProfile(userId, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
|
|
|
@ -55,8 +55,6 @@ class AttachmentEncryptionTest {
|
|||
|
||||
assertNotNull(decryptedStream)
|
||||
|
||||
inputStream.close()
|
||||
|
||||
val buffer = ByteArray(100)
|
||||
|
||||
val len = decryptedStream!!.read(buffer)
|
||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import kotlin.random.Random
|
||||
|
@ -63,7 +63,7 @@ object RoomDataHelper {
|
|||
}
|
||||
|
||||
fun createFakeRoomMemberEvent(): Event {
|
||||
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,13 @@ import com.zhuinden.monarchy.Monarchy
|
|||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -96,5 +99,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
|
||||
fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? {
|
||||
return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevels
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||
|
||||
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
|
||||
|
||||
|
@ -28,7 +29,8 @@ class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.se
|
|||
return "User power level <$key>"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean {
|
||||
return event.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key)
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevel(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
|||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
|
@ -51,6 +52,7 @@ interface Session :
|
|||
SignOutService,
|
||||
FilterService,
|
||||
FileService,
|
||||
ProfileService,
|
||||
PushRuleService,
|
||||
PushersService,
|
||||
InitialSyncProgressService,
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 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.session.profile
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
||||
/**
|
||||
* This interface defines methods to handling profile information. It's implemented at the session level.
|
||||
*/
|
||||
interface ProfileService {
|
||||
|
||||
companion object Constants {
|
||||
const val DISPLAY_NAME_KEY = "displayname"
|
||||
const val AVATAR_URL_KEY = "avatar_url"
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current dispayname for this user
|
||||
* @param userId the userId param to look for
|
||||
*
|
||||
*/
|
||||
fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
|
||||
|
||||
/**
|
||||
* Return the current avatarUrl for this user.
|
||||
* @param userId the userId param to look for
|
||||
*
|
||||
*/
|
||||
fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
|
||||
|
||||
/**
|
||||
* Get the combined profile information for this user.
|
||||
* This may return keys which are not limited to displayname or avatar_url.
|
||||
* @param userId the userId param to look for
|
||||
*
|
||||
*/
|
||||
fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
|
||||
}
|
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.session.room.members
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
|
@ -38,21 +38,21 @@ interface MembershipService {
|
|||
*
|
||||
* @return the roomMember with userId or null
|
||||
*/
|
||||
fun getRoomMember(userId: String): RoomMember?
|
||||
fun getRoomMember(userId: String): RoomMemberSummary?
|
||||
|
||||
/**
|
||||
* Return all the roomMembers of the room with params
|
||||
* @param queryParams the params to query for
|
||||
* @return a roomMember list.
|
||||
*/
|
||||
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember>
|
||||
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMemberSummary>
|
||||
|
||||
/**
|
||||
* Return all the roomMembers of the room filtered by memberships
|
||||
* @param queryParams the params to query for
|
||||
* @return a [LiveData] of roomMember list.
|
||||
*/
|
||||
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>>
|
||||
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>>
|
||||
|
||||
fun getNumberOfJoinedMembers(): Int
|
||||
|
||||
|
|
|
@ -29,11 +29,13 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {})
|
|||
data class RoomMemberQueryParams(
|
||||
val displayName: QueryStringValue,
|
||||
val memberships: List<Membership>,
|
||||
val userId: QueryStringValue,
|
||||
val excludeSelf: Boolean
|
||||
) {
|
||||
|
||||
class Builder {
|
||||
|
||||
var userId: QueryStringValue = QueryStringValue.NoCondition
|
||||
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||
var memberships: List<Membership> = Membership.all()
|
||||
var excludeSelf: Boolean = false
|
||||
|
@ -41,6 +43,7 @@ data class RoomMemberQueryParams(
|
|||
fun build() = RoomMemberQueryParams(
|
||||
displayName = displayName,
|
||||
memberships = memberships,
|
||||
userId = userId,
|
||||
excludeSelf = excludeSelf
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,121 +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.matrix.android.api.session.room.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
||||
/**
|
||||
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PowerLevels(
|
||||
@Json(name = "ban") val ban: Int = 50,
|
||||
@Json(name = "kick") val kick: Int = 50,
|
||||
@Json(name = "invite") val invite: Int = 50,
|
||||
@Json(name = "redact") val redact: Int = 50,
|
||||
@Json(name = "events_default") val eventsDefault: Int = 0,
|
||||
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
|
||||
@Json(name = "users_default") val usersDefault: Int = 0,
|
||||
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
|
||||
@Json(name = "state_default") val stateDefault: Int = 50,
|
||||
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
|
||||
) {
|
||||
|
||||
/**
|
||||
* Returns the user power level of a dedicated user Id
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return the power level
|
||||
*/
|
||||
fun getUserPowerLevel(userId: String): Int {
|
||||
return users.getOrElse(userId) { usersDefault }
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user power levels of a dedicated user id
|
||||
*
|
||||
* @param userId the user
|
||||
* @param powerLevel the new power level
|
||||
*/
|
||||
fun setUserPowerLevel(userId: String, powerLevel: Int) {
|
||||
users[userId] = powerLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if an user can send an event of type 'eventTypeString'.
|
||||
*
|
||||
* @param eventTypeString the event type (in Event.EVENT_TYPE_XXX values)
|
||||
* @param userId the user id
|
||||
* @return true if the user can send the event
|
||||
*/
|
||||
fun maySendEventOfType(eventTypeString: String, userId: String): Boolean {
|
||||
return if (eventTypeString.isNotEmpty() && userId.isNotEmpty()) {
|
||||
getUserPowerLevel(userId) >= minimumPowerLevelForSendingEventAsMessage(eventTypeString)
|
||||
} else false
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if an user can send a room message.
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return true if the user can send a room message
|
||||
*/
|
||||
fun maySendMessage(userId: String): Boolean {
|
||||
return maySendEventOfType(EventType.MESSAGE, userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the minimum power level the user must have to send an event of the given type
|
||||
* as a message.
|
||||
*
|
||||
* @param eventTypeString the type of event (in Event.EVENT_TYPE_XXX values)
|
||||
* @return the required minimum power level.
|
||||
*/
|
||||
fun minimumPowerLevelForSendingEventAsMessage(eventTypeString: String?): Int {
|
||||
return events[eventTypeString] ?: eventsDefault
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the minimum power level the user must have to send an event of the given type
|
||||
* as a state event.
|
||||
*
|
||||
* @param eventTypeString the type of event (in Event.EVENT_TYPE_STATE_ values).
|
||||
* @return the required minimum power level.
|
||||
*/
|
||||
fun minimumPowerLevelForSendingEventAsStateEvent(eventTypeString: String?): Int {
|
||||
return events[eventTypeString] ?: stateDefault
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification level for a dedicated key.
|
||||
*
|
||||
* @param key the notification key
|
||||
* @return the level
|
||||
*/
|
||||
fun notificationLevel(key: String): Int {
|
||||
val valAsVoid = notifications[key] ?: return 50
|
||||
|
||||
// the first implementation was a string value
|
||||
return if (valAsVoid is String) {
|
||||
valAsVoid.toInt()
|
||||
} else {
|
||||
valAsVoid as Int
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
|
||||
|
||||
/**
|
||||
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PowerLevelsContent(
|
||||
@Json(name = "ban") val ban: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "kick") val kick: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "invite") val invite: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "redact") val redact: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "events_default") val eventsDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
|
||||
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
|
||||
@Json(name = "users_default") val usersDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
|
||||
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
|
||||
@Json(name = "state_default") val stateDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
|
||||
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
|
||||
)
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room.model
|
|||
/**
|
||||
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
||||
*/
|
||||
data class RoomMember(
|
||||
data class RoomMemberSummary(
|
||||
val membership: Membership,
|
||||
val userId: String,
|
||||
val displayName: String? = null,
|
|
@ -32,6 +32,8 @@ data class RoomSummary(
|
|||
val canonicalAlias: String? = null,
|
||||
val aliases: List<String> = emptyList(),
|
||||
val isDirect: Boolean = false,
|
||||
val joinedMembersCount: Int? = 0,
|
||||
val invitedMembersCount: Int? = 0,
|
||||
val latestPreviewableEvent: TimelineEvent? = null,
|
||||
val otherMemberIds: List<String> = emptyList(),
|
||||
val notificationCount: Int = 0,
|
||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevels
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||
|
@ -115,7 +115,7 @@ class CreateRoomParams {
|
|||
* The power level content to override in the default power level event
|
||||
*/
|
||||
@Json(name = "power_level_content_override")
|
||||
var powerLevelContentOverride: PowerLevels? = null
|
||||
var powerLevelContentOverride: PowerLevelsContent? = null
|
||||
|
||||
/**
|
||||
* Add the crypto algorithm to the room creation parameters.
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 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.session.room.powerlevels
|
||||
|
||||
object PowerLevelsConstants {
|
||||
|
||||
const val DEFAULT_ROOM_ADMIN_LEVEL = 100
|
||||
const val DEFAULT_ROOM_MODERATOR_LEVEL = 50
|
||||
const val DEFAULT_ROOM_USER_LEVEL = 0
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 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.session.room.powerlevels
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
|
||||
/**
|
||||
* This class is an helper around PowerLevelsContent.
|
||||
*/
|
||||
class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||
|
||||
/**
|
||||
* Returns the user power level of a dedicated user Id
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return the power level
|
||||
*/
|
||||
fun getUserPowerLevel(userId: String): Int {
|
||||
return powerLevelsContent.users.getOrElse(userId) {
|
||||
powerLevelsContent.usersDefault
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if an user can send an event of a certain type
|
||||
*
|
||||
* @param eventType the event type to check for
|
||||
* @param userId the user id
|
||||
* @return true if the user can send this type of event
|
||||
*/
|
||||
fun isAllowedToSend(eventType: String, userId: String): Boolean {
|
||||
return if (eventType.isNotEmpty() && userId.isNotEmpty()) {
|
||||
val powerLevel = getUserPowerLevel(userId)
|
||||
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
||||
?: if (EventType.isStateEvent(eventType)) {
|
||||
powerLevelsContent.stateDefault
|
||||
} else {
|
||||
powerLevelsContent.eventsDefault
|
||||
}
|
||||
powerLevel >= minimumPowerLevel
|
||||
} else false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification level for a dedicated key.
|
||||
*
|
||||
* @param key the notification key
|
||||
* @return the level
|
||||
*/
|
||||
fun notificationLevel(key: String): Int {
|
||||
return when (val value = powerLevelsContent.notifications[key]) {
|
||||
// the first implementation was a string value
|
||||
is String -> value.toInt()
|
||||
is Int -> value
|
||||
else -> PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.room.state
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
||||
interface StateService {
|
||||
|
||||
|
@ -32,4 +34,6 @@ interface StateService {
|
|||
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun getStateEvent(eventType: String): Event?
|
||||
|
||||
fun getStateEventLive(eventType: String): LiveData<Optional<Event>>
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.util
|
|||
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
|
@ -147,4 +147,4 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
|
|||
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
|
||||
fun RoomMember.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||
|
|
|
@ -27,6 +27,14 @@ data class Optional<T : Any> constructor(private val value: T?) {
|
|||
return value
|
||||
}
|
||||
|
||||
fun <U : Any> map(fn: (T) -> U?): Optional<U> {
|
||||
return if (value == null) {
|
||||
from(null)
|
||||
} else {
|
||||
from(fn(value))
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrElse(fn: () -> T): T {
|
||||
return value ?: fn()
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
|||
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||
import im.vector.matrix.android.internal.di.UserCacheDirectory
|
||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||
|
@ -53,7 +53,7 @@ internal abstract class CryptoModule {
|
|||
@Provides
|
||||
@CryptoDatabase
|
||||
@SessionScope
|
||||
fun providesRealmConfiguration(@UserCacheDirectory directory: File,
|
||||
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
|
||||
@UserMd5 userMd5: String,
|
||||
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
|
|
|
@ -38,7 +38,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
|
||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
|
||||
|
@ -64,7 +64,7 @@ import im.vector.matrix.android.internal.di.MoshiProvider
|
|||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
|
@ -701,9 +701,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
&& shouldEncryptForInvitedMembers(roomId)
|
||||
|
||||
userIds = if (encryptForInvitedMembers) {
|
||||
RoomMembers(realm, roomId).getActiveRoomMemberIds()
|
||||
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
||||
} else {
|
||||
RoomMembers(realm, roomId).getJoinedRoomMemberIds()
|
||||
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
||||
}
|
||||
}
|
||||
return userIds
|
||||
|
@ -726,7 +726,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return
|
||||
}
|
||||
event.stateKey?.let { userId ->
|
||||
val roomMember: RoomMember? = event.content.toModel()
|
||||
val roomMember: RoomMemberSummary? = event.content.toModel()
|
||||
val membership = roomMember?.membership
|
||||
if (membership == Membership.JOIN) {
|
||||
// make sure we are tracking the deviceList for this user.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 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.crypto.attachments
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||
|
||||
/**
|
||||
* Define the result of an encryption file
|
||||
*/
|
||||
internal data class EncryptionResult(
|
||||
var encryptedFileInfo: EncryptedFileInfo,
|
||||
var encryptedByteArray: ByteArray
|
||||
)
|
|
@ -29,23 +29,15 @@ import javax.crypto.Cipher
|
|||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object MXEncryptedAttachments {
|
||||
internal object MXEncryptedAttachments {
|
||||
private const val CRYPTO_BUFFER_SIZE = 32 * 1024
|
||||
private const val CIPHER_ALGORITHM = "AES/CTR/NoPadding"
|
||||
private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
|
||||
private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
|
||||
|
||||
/**
|
||||
* Define the result of an encryption file
|
||||
*/
|
||||
data class EncryptionResult(
|
||||
var encryptedFileInfo: EncryptedFileInfo,
|
||||
var encryptedByteArray: ByteArray
|
||||
)
|
||||
|
||||
/***
|
||||
* Encrypt an attachment stream.
|
||||
* @param attachmentStream the attachment stream
|
||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||
* @param mimetype the mime type
|
||||
* @return the encryption file info
|
||||
*/
|
||||
|
@ -67,9 +59,7 @@ object MXEncryptedAttachments {
|
|||
val key = ByteArray(32)
|
||||
secureRandom.nextBytes(key)
|
||||
|
||||
val outStream = ByteArrayOutputStream()
|
||||
|
||||
outStream.use {
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
val encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
|
@ -81,20 +71,22 @@ object MXEncryptedAttachments {
|
|||
var read: Int
|
||||
var encodedBytes: ByteArray
|
||||
|
||||
read = attachmentStream.read(data)
|
||||
attachmentStream.use { inputStream ->
|
||||
read = inputStream.read(data)
|
||||
while (read != -1) {
|
||||
encodedBytes = encryptCipher.update(data, 0, read)
|
||||
messageDigest.update(encodedBytes, 0, encodedBytes.size)
|
||||
outStream.write(encodedBytes)
|
||||
read = attachmentStream.read(data)
|
||||
outputStream.write(encodedBytes)
|
||||
read = inputStream.read(data)
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt the latest chunk
|
||||
encodedBytes = encryptCipher.doFinal()
|
||||
messageDigest.update(encodedBytes, 0, encodedBytes.size)
|
||||
outStream.write(encodedBytes)
|
||||
outputStream.write(encodedBytes)
|
||||
|
||||
val result = EncryptionResult(
|
||||
return EncryptionResult(
|
||||
encryptedFileInfo = EncryptedFileInfo(
|
||||
url = null,
|
||||
mimetype = mimetype,
|
||||
|
@ -109,18 +101,16 @@ object MXEncryptedAttachments {
|
|||
hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
|
||||
v = "v2"
|
||||
),
|
||||
encryptedByteArray = outStream.toByteArray()
|
||||
encryptedByteArray = outputStream.toByteArray()
|
||||
)
|
||||
|
||||
Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms")
|
||||
return result
|
||||
.also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an attachment
|
||||
*
|
||||
* @param attachmentStream the attachment stream
|
||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||
* @param encryptedFileInfo the encryption file info
|
||||
* @return the decrypted attachment stream
|
||||
*/
|
||||
|
@ -138,7 +128,7 @@ object MXEncryptedAttachments {
|
|||
/**
|
||||
* Decrypt an attachment
|
||||
*
|
||||
* @param attachmentStream the attachment stream
|
||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||
* @param elementToDecrypt the elementToDecrypt info
|
||||
* @return the decrypted attachment stream
|
||||
*/
|
||||
|
@ -151,8 +141,7 @@ object MXEncryptedAttachments {
|
|||
|
||||
val t0 = System.currentTimeMillis()
|
||||
|
||||
val outStream = ByteArrayOutputStream()
|
||||
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
try {
|
||||
val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
|
||||
val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT)
|
||||
|
@ -168,42 +157,34 @@ object MXEncryptedAttachments {
|
|||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var decodedBytes: ByteArray
|
||||
|
||||
read = attachmentStream.read(data)
|
||||
attachmentStream.use { inputStream ->
|
||||
read = inputStream.read(data)
|
||||
while (read != -1) {
|
||||
messageDigest.update(data, 0, read)
|
||||
decodedBytes = decryptCipher.update(data, 0, read)
|
||||
outStream.write(decodedBytes)
|
||||
read = attachmentStream.read(data)
|
||||
outputStream.write(decodedBytes)
|
||||
read = inputStream.read(data)
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt the last chunk
|
||||
decodedBytes = decryptCipher.doFinal()
|
||||
outStream.write(decodedBytes)
|
||||
outputStream.write(decodedBytes)
|
||||
|
||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||
|
||||
if (elementToDecrypt.sha256 != currentDigestValue) {
|
||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||
outStream.close()
|
||||
return null
|
||||
}
|
||||
|
||||
val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
|
||||
outStream.close()
|
||||
|
||||
Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms")
|
||||
|
||||
return decryptedStream
|
||||
return ByteArrayInputStream(outputStream.toByteArray())
|
||||
.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
|
||||
Timber.e(oom, "## decryptAttachment() failed: OOM")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptAttachment() : failed ${e.message}")
|
||||
Timber.e(e, "## decryptAttachment() failed")
|
||||
}
|
||||
|
||||
try {
|
||||
outStream.close()
|
||||
} catch (closeException: Exception) {
|
||||
Timber.e(closeException, "## decryptAttachment() : fail to close the file")
|
||||
}
|
||||
|
||||
return null
|
||||
|
|
|
@ -18,8 +18,8 @@ package im.vector.matrix.android.internal.database
|
|||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.UserCacheDirectory
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.session.SessionModule
|
||||
import io.realm.Realm
|
||||
|
@ -36,8 +36,9 @@ private const val REALM_NAME = "disk_store.realm"
|
|||
* It will handle corrupted realm by clearing the db file. It allows to just clear cache without losing your crypto keys.
|
||||
* It's clearly not perfect but there is no way to catch the native crash.
|
||||
*/
|
||||
internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils,
|
||||
@UserCacheDirectory val directory: File,
|
||||
internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
private val realmKeysUtils: RealmKeysUtils,
|
||||
@SessionFilesDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
context: Context) {
|
||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
|
|||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.fastContains
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
|
||||
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
||||
chunks.remove(chunkEntity)
|
||||
|
@ -59,7 +59,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
|||
val eventEntity = event.toEntity(roomId).apply {
|
||||
this.sendState = SendState.UNSENT
|
||||
}
|
||||
val roomMembers = RoomMembers(realm, roomId)
|
||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||
val myUser = roomMembers.getLastRoomMember(senderId)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val timelineEventEntity = TimelineEventEntity(localId).also {
|
||||
|
|
|
@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.query.prev
|
|||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
@ -128,7 +128,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
|
|||
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
|
||||
result.senderAvatar = it.avatarUrl
|
||||
result.senderName = it.displayName
|
||||
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
// We try to fallback on prev content if we got a room member state events with null fields
|
||||
if (root?.type == EventType.STATE_ROOM_MEMBER) {
|
||||
|
@ -138,7 +138,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
|
|||
}
|
||||
if (result.senderName == null && it.displayName != null) {
|
||||
result.senderName = it.displayName
|
||||
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,21 +16,21 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
|
||||
internal object RoomMemberMapper {
|
||||
internal object RoomMemberSummaryMapper {
|
||||
|
||||
fun map(roomMemberEntity: RoomMemberEntity): RoomMember {
|
||||
return RoomMember(
|
||||
userId = roomMemberEntity.userId,
|
||||
avatarUrl = roomMemberEntity.avatarUrl,
|
||||
displayName = roomMemberEntity.displayName,
|
||||
membership = roomMemberEntity.membership
|
||||
fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
|
||||
return RoomMemberSummary(
|
||||
userId = roomMemberSummaryEntity.userId,
|
||||
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
||||
displayName = roomMemberSummaryEntity.displayName,
|
||||
membership = roomMemberSummaryEntity.membership
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RoomMemberEntity.asDomain(): RoomMember {
|
||||
return RoomMemberMapper.map(this)
|
||||
internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
|
||||
return RoomMemberSummaryMapper.map(this)
|
||||
}
|
|
@ -60,6 +60,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||
isDirect = roomSummaryEntity.isDirect,
|
||||
latestPreviewableEvent = latestEvent,
|
||||
joinedMembersCount = roomSummaryEntity.joinedMembersCount,
|
||||
invitedMembersCount = roomSummaryEntity.invitedMembersCount,
|
||||
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
|
||||
highlightCount = roomSummaryEntity.highlightCount,
|
||||
notificationCount = roomSummaryEntity.notificationCount,
|
||||
|
|
|
@ -21,11 +21,11 @@ import io.realm.RealmObject
|
|||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "",
|
||||
internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "",
|
||||
@Index var userId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
var displayName: String = "",
|
||||
var avatarUrl: String = "",
|
||||
var displayName: String? = null,
|
||||
var avatarUrl: String? = null,
|
||||
var reason: String? = null,
|
||||
var isDirect: Boolean = false
|
||||
) : RealmObject() {
|
|
@ -51,6 +51,6 @@ import io.realm.annotations.RealmModule
|
|||
UserDraftsEntity::class,
|
||||
DraftEntity::class,
|
||||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberEntity::class
|
||||
RoomMemberSummaryEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -60,20 +60,7 @@ internal fun EventEntity.Companion.types(realm: Realm,
|
|||
return query
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
|
||||
if (from != null) {
|
||||
if (strict) {
|
||||
this.greaterThan(EventEntityFields.STATE_INDEX, from)
|
||||
} else {
|
||||
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
|
||||
}
|
||||
}
|
||||
return this
|
||||
.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
|
||||
internal fun RealmQuery<EventEntity>.descending(since: Int? = null, strict: Boolean = false): RealmQuery<EventEntity> {
|
||||
if (since != null) {
|
||||
if (strict) {
|
||||
this.lessThan(EventEntityFields.STATE_INDEX, since)
|
||||
|
@ -81,9 +68,26 @@ internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean =
|
|||
this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, since)
|
||||
}
|
||||
}
|
||||
return this
|
||||
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||
.findFirst()
|
||||
return this.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.ascending(from: Int? = null, strict: Boolean = true): RealmQuery<EventEntity> {
|
||||
if (from != null) {
|
||||
if (strict) {
|
||||
this.greaterThan(EventEntityFields.STATE_INDEX, from)
|
||||
} else {
|
||||
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
|
||||
}
|
||||
}
|
||||
return this.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
|
||||
return this.ascending(from, strict).findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
|
||||
return descending(since, strict).findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmList<EventEntity>.find(eventId: String): EventEntity? {
|
||||
|
|
|
@ -16,19 +16,19 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun RoomMemberEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberEntity> {
|
||||
internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberSummaryEntity> {
|
||||
val query = realm
|
||||
.where<RoomMemberEntity>()
|
||||
.equalTo(RoomMemberEntityFields.ROOM_ID, roomId)
|
||||
.where<RoomMemberSummaryEntity>()
|
||||
.equalTo(RoomMemberSummaryEntityFields.ROOM_ID, roomId)
|
||||
|
||||
if (userId != null) {
|
||||
query.equalTo(RoomMemberEntityFields.USER_ID, userId)
|
||||
query.equalTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -14,16 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Unfortunatly "ktlint-disable filename" this does not work so this file is renamed to UserCacheDirectory.kt
|
||||
* If a new qualifier is added, please rename this file ti FileQualifiers.kt...
|
||||
*/
|
||||
/* ktlint-disable filename */
|
||||
|
||||
package im.vector.matrix.android.internal.di
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class UserCacheDirectory
|
||||
annotation class SessionFilesDirectory
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SessionCacheDirectory
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
|
@ -25,7 +24,8 @@ import im.vector.matrix.android.api.session.file.FileService
|
|||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.SessionCacheDirectory
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.md5
|
||||
|
@ -41,13 +41,14 @@ import java.io.File
|
|||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultFileService @Inject constructor(private val context: Context,
|
||||
@SessionId private val sessionId: String,
|
||||
internal class DefaultFileService @Inject constructor(
|
||||
@SessionCacheDirectory
|
||||
private val cacheDirectory: File,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
@Unauthenticated
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
||||
|
||||
val okHttpClient = OkHttpClient()
|
||||
|
||||
/**
|
||||
* Download file in the cache folder, and eventually decrypt it
|
||||
* TODO implement clear file, to delete "MF"
|
||||
|
@ -74,7 +75,7 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
|
|||
.build()
|
||||
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
val inputStream = response.body?.byteStream()
|
||||
var inputStream = response.body?.byteStream()
|
||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||
if (!response.isSuccessful
|
||||
|| inputStream == null) {
|
||||
|
@ -83,7 +84,7 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
|
|||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## decrypt file")
|
||||
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
inputStream = MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
?: throw IllegalStateException("Decryption error")
|
||||
}
|
||||
|
||||
|
@ -103,10 +104,9 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
|
|||
return when (downloadMode) {
|
||||
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
||||
// Create dir tree (MF stands for Matrix File):
|
||||
// <cache>/MF/<sessionId>/<md5(id)>/
|
||||
val tmpFolderRoot = File(context.cacheDir, "MF")
|
||||
val tmpFolderUser = File(tmpFolderRoot, sessionId)
|
||||
File(tmpFolderUser, id.md5())
|
||||
// <cache>/<sessionId>/MF/<md5(id)>/
|
||||
val tmpFolderSession = File(cacheDirectory, "MF")
|
||||
File(tmpFolderSession, id.md5())
|
||||
}
|
||||
FileService.DownloadMode.TO_EXPORT -> {
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
|||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
|
@ -80,6 +81,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||
private val fileService: Lazy<FileService>,
|
||||
private val secureStorageService: Lazy<SecureStorageService>,
|
||||
private val profileService: Lazy<ProfileService>,
|
||||
private val syncThreadProvider: Provider<SyncThread>,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
private val syncTokenStore: SyncTokenStore,
|
||||
|
@ -101,7 +103,8 @@ internal class DefaultSession @Inject constructor(
|
|||
FileService by fileService.get(),
|
||||
InitialSyncProgressService by initialSyncProgressService.get(),
|
||||
SecureStorageService by secureStorageService.get(),
|
||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get() {
|
||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||
ProfileService by profileService.get() {
|
||||
|
||||
private var isOpen = false
|
||||
|
||||
|
|
|
@ -32,6 +32,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.GroupModule
|
||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
||||
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.PushersModule
|
||||
import im.vector.matrix.android.internal.session.room.RoomModule
|
||||
|
@ -65,6 +66,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||
CryptoModule::class,
|
||||
PushersModule::class,
|
||||
AccountDataModule::class,
|
||||
ProfileModule::class,
|
||||
SessionAssistedInjectModule::class
|
||||
]
|
||||
)
|
||||
|
|
|
@ -101,7 +101,7 @@ internal abstract class SessionModule {
|
|||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@UserCacheDirectory
|
||||
@SessionFilesDirectory
|
||||
fun providesFilesDir(@UserMd5 userMd5: String,
|
||||
@SessionId sessionId: String,
|
||||
context: Context): File {
|
||||
|
@ -114,6 +114,14 @@ internal abstract class SessionModule {
|
|||
return File(context.filesDir, sessionId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionCacheDirectory
|
||||
fun providesCacheDir(@SessionId sessionId: String,
|
||||
context: Context): File {
|
||||
return File(context.cacheDir, sessionId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionDatabase
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 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.profile
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
|
||||
private val getProfileInfoTask: GetProfileInfoTask) : ProfileService {
|
||||
|
||||
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||
val params = GetProfileInfoTask.Params(userId)
|
||||
return getProfileInfoTask
|
||||
.configureWith(params) {
|
||||
this.callback = object : MatrixCallback<JsonDict> {
|
||||
override fun onSuccess(data: JsonDict) {
|
||||
val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String
|
||||
matrixCallback.onSuccess(Optional.from(displayName))
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
matrixCallback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||
val params = GetProfileInfoTask.Params(userId)
|
||||
return getProfileInfoTask
|
||||
.configureWith(params) {
|
||||
this.callback = object : MatrixCallback<JsonDict> {
|
||||
override fun onSuccess(data: JsonDict) {
|
||||
val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String
|
||||
matrixCallback.onSuccess(Optional.from(avatarUrl))
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
matrixCallback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable {
|
||||
val params = GetProfileInfoTask.Params(userId)
|
||||
return getProfileInfoTask
|
||||
.configureWith(params) {
|
||||
this.callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 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.profile
|
||||
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
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 abstract class GetProfileInfoTask : Task<GetProfileInfoTask.Params, JsonDict> {
|
||||
data class Params(
|
||||
val userId: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultGetProfileInfoTask @Inject constructor(private val profileAPI: ProfileAPI,
|
||||
private val eventBus: EventBus) : GetProfileInfoTask() {
|
||||
|
||||
override suspend fun execute(params: Params): JsonDict {
|
||||
return executeRequest(eventBus) {
|
||||
apiCall = profileAPI.getProfile(params.userId)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 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.profile
|
||||
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface ProfileAPI {
|
||||
|
||||
/**
|
||||
* Get the combined profile information for this user.
|
||||
* This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
|
||||
* This API may return keys which are not limited to displayname or avatar_url.
|
||||
* @param userId the user id to fetch profile info
|
||||
*/
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}")
|
||||
fun getProfile(@Path("userId") userId: String): Call<JsonDict>
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 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.profile
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
internal abstract class ProfileModule {
|
||||
|
||||
@Module
|
||||
companion object {
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@SessionScope
|
||||
fun providesProfileAPI(retrofit: Retrofit): ProfileAPI {
|
||||
return retrofit.create(ProfileAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindProfileService(service: DefaultProfileService): ProfileService
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetProfileTask(task: DefaultGetProfileInfoTask): GetProfileInfoTask
|
||||
}
|
|
@ -22,11 +22,11 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.prev
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomAvatarResolver @Inject constructor(private val monarchy: Monarchy,
|
||||
|
@ -45,13 +45,13 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
|
|||
if (!res.isNullOrEmpty()) {
|
||||
return@doWithRealm
|
||||
}
|
||||
val roomMembers = RoomMembers(realm, roomId)
|
||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
|
||||
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
|
||||
if (members.size == 1) {
|
||||
res = members.firstOrNull()?.avatarUrl
|
||||
} else if (members.size == 2) {
|
||||
val firstOtherMember = members.where().notEqualTo(RoomMemberEntityFields.USER_ID, userId).findFirst()
|
||||
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
|
||||
res = firstOtherMember?.avatarUrl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
|||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.*
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.sync.RoomSyncHandler
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
|
@ -115,9 +115,9 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
roomSummaryEntity.typingUserIds.addAll(ephemeralResult?.typingUserIds.orEmpty())
|
||||
|
||||
if (updateMembers) {
|
||||
val otherRoomMembers = RoomMembers(realm, roomId)
|
||||
val otherRoomMembers = RoomMemberHelper(realm, roomId)
|
||||
.queryRoomMembersEvent()
|
||||
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
|
||||
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||
.findAll()
|
||||
.asSequence()
|
||||
.map { it.userId }
|
||||
|
|
|
@ -24,11 +24,11 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.query.process
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
||||
|
@ -66,14 +66,14 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getRoomMember(userId: String): RoomMember? {
|
||||
override fun getRoomMember(userId: String): RoomMemberSummary? {
|
||||
val roomMemberEntity = monarchy.fetchCopied {
|
||||
RoomMembers(it, roomId).getLastRoomMember(userId)
|
||||
RoomMemberHelper(it, roomId).getLastRoomMember(userId)
|
||||
}
|
||||
return roomMemberEntity?.asDomain()
|
||||
}
|
||||
|
||||
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember> {
|
||||
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMemberSummary> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{
|
||||
roomMembersQuery(it, queryParams)
|
||||
|
@ -84,7 +84,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>> {
|
||||
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{
|
||||
roomMembersQuery(it, queryParams)
|
||||
|
@ -95,20 +95,21 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberEntity> {
|
||||
return RoomMembers(realm, roomId).queryRoomMembersEvent()
|
||||
.process(RoomMemberEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
.process(RoomMemberEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
|
||||
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
|
||||
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
|
||||
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||
.apply {
|
||||
if (queryParams.excludeSelf) {
|
||||
notEqualTo(RoomMemberEntityFields.USER_ID, userId)
|
||||
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNumberOfJoinedMembers(): Int {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
RoomMembers(it, roomId).getNumberOfJoinedMembers()
|
||||
RoomMemberHelper(it, roomId).getNumberOfJoinedMembers()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
|||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.query.prev
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
|
@ -75,7 +75,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||
return@doWithRealm
|
||||
}
|
||||
|
||||
val roomMembers = RoomMembers(realm, roomId)
|
||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||
val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll()
|
||||
|
||||
if (roomEntity?.membership == Membership.INVITE) {
|
||||
|
@ -83,7 +83,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||
val inviterId = inviteMeEvent?.sender
|
||||
name = if (inviterId != null) {
|
||||
activeMembers.where()
|
||||
.equalTo(RoomMemberEntityFields.USER_ID, inviterId)
|
||||
.equalTo(RoomMemberSummaryEntityFields.USER_ID, inviterId)
|
||||
.findFirst()
|
||||
?.displayName
|
||||
} else {
|
||||
|
@ -91,7 +91,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||
}
|
||||
} else if (roomEntity?.membership == Membership.JOIN) {
|
||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
val otherMembersSubset: List<RoomMemberEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
||||
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
||||
roomSummary.heroes.mapNotNull { userId ->
|
||||
roomMembers.getLastRoomMember(userId)?.takeIf {
|
||||
it.membership == Membership.INVITE || it.membership == Membership.JOIN
|
||||
|
@ -99,7 +99,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||
}
|
||||
} else {
|
||||
activeMembers.where()
|
||||
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
|
||||
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||
.limit(3)
|
||||
.findAll()
|
||||
.createSnapshot()
|
||||
|
@ -123,14 +123,14 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||
return name ?: roomId
|
||||
}
|
||||
|
||||
private fun resolveRoomMemberName(roomMember: RoomMemberEntity?,
|
||||
roomMembers: RoomMembers): String? {
|
||||
if (roomMember == null) return null
|
||||
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
|
||||
private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?,
|
||||
roomMemberHelper: RoomMemberHelper): String? {
|
||||
if (roomMemberSummary == null) return null
|
||||
val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName)
|
||||
return if (isUnique) {
|
||||
roomMember.displayName
|
||||
roomMemberSummary.displayName
|
||||
} else {
|
||||
"${roomMember.displayName} (${roomMember.userId})"
|
||||
"${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,18 @@
|
|||
package im.vector.matrix.android.internal.session.room.membership
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
|
||||
internal object RoomMemberEntityFactory {
|
||||
|
||||
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberEntity {
|
||||
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity {
|
||||
val primaryKey = "${roomId}_$userId"
|
||||
return RoomMemberEntity(
|
||||
return RoomMemberSummaryEntity(
|
||||
primaryKey = primaryKey,
|
||||
userId = userId,
|
||||
roomId = roomId,
|
||||
displayName = roomMember.displayName ?: "",
|
||||
avatarUrl = roomMember.avatarUrl ?: ""
|
||||
displayName = roomMember.displayName,
|
||||
avatarUrl = roomMember.avatarUrl
|
||||
).apply {
|
||||
membership = roomMember.membership
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import io.realm.Realm
|
||||
|
@ -32,7 +32,7 @@ import io.realm.Sort
|
|||
* It allows to get the live membership of a user.
|
||||
*/
|
||||
|
||||
internal class RoomMembers(private val realm: Realm,
|
||||
internal class RoomMemberHelper(private val realm: Realm,
|
||||
private val roomId: String
|
||||
) {
|
||||
|
||||
|
@ -48,8 +48,8 @@ internal class RoomMembers(private val realm: Realm,
|
|||
.findFirst()
|
||||
}
|
||||
|
||||
fun getLastRoomMember(userId: String): RoomMemberEntity? {
|
||||
return RoomMemberEntity
|
||||
fun getLastRoomMember(userId: String): RoomMemberSummaryEntity? {
|
||||
return RoomMemberSummaryEntity
|
||||
.where(realm, roomId, userId)
|
||||
.findFirst()
|
||||
}
|
||||
|
@ -66,26 +66,26 @@ internal class RoomMembers(private val realm: Realm,
|
|||
.size == 1
|
||||
}
|
||||
|
||||
fun queryRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||
return RoomMemberEntity.where(realm, roomId)
|
||||
fun queryRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
|
||||
return RoomMemberSummaryEntity.where(realm, roomId)
|
||||
}
|
||||
|
||||
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
|
||||
return queryRoomMembersEvent()
|
||||
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
}
|
||||
|
||||
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
|
||||
return queryRoomMembersEvent()
|
||||
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
|
||||
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
|
||||
}
|
||||
|
||||
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
|
||||
return queryRoomMembersEvent()
|
||||
.beginGroup()
|
||||
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
|
||||
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
|
||||
.or()
|
||||
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
.endGroup()
|
||||
}
|
||||
|
|
@ -16,27 +16,30 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.room.state
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.query.descending
|
||||
import im.vector.matrix.android.internal.database.query.prev
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import java.security.InvalidParameterException
|
||||
|
||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
@SessionDatabase
|
||||
private val realmConfiguration: RealmConfiguration,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendStateTask: SendStateTask
|
||||
) : StateService {
|
||||
|
@ -47,11 +50,21 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
|||
}
|
||||
|
||||
override fun getStateEvent(eventType: String): Event? {
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
EventEntity.where(realm, roomId, eventType).prev()?.asDomain()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStateEventLive(eventType: String): LiveData<Optional<Event>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm -> EventEntity.where(realm, roomId, eventType).descending() },
|
||||
{ it.asDomain() }
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
|
|
|
@ -52,7 +52,8 @@ internal class DefaultSignOutTask @Inject constructor(
|
|||
private val sessionParamsStore: SessionParamsStore,
|
||||
@SessionDatabase private val clearSessionDataTask: ClearCacheTask,
|
||||
@CryptoDatabase private val clearCryptoDataTask: ClearCacheTask,
|
||||
@UserCacheDirectory private val userFile: File,
|
||||
@SessionFilesDirectory private val sessionFiles: File,
|
||||
@SessionCacheDirectory private val sessionCache: File,
|
||||
private val realmKeysUtils: RealmKeysUtils,
|
||||
@SessionDatabase private val realmSessionConfiguration: RealmConfiguration,
|
||||
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
|
||||
|
@ -98,7 +99,8 @@ internal class DefaultSignOutTask @Inject constructor(
|
|||
clearCryptoDataTask.execute(Unit)
|
||||
|
||||
Timber.d("SignOut: clear file system")
|
||||
userFile.deleteRecursively()
|
||||
sessionFiles.deleteRecursively()
|
||||
sessionCache.deleteRecursively()
|
||||
|
||||
Timber.d("SignOut: clear the database keys")
|
||||
realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
|
||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.matrix.android.internal.database.query.getDirectRooms
|
|||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
||||
|
@ -70,7 +70,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
var hasUpdate = false
|
||||
monarchy.doWithRealm { realm ->
|
||||
invites.forEach { (roomId, _) ->
|
||||
val myUserStateEvent = RoomMembers(realm, roomId).getLastStateEvent(userId)
|
||||
val myUserStateEvent = RoomMemberHelper(realm, roomId).getLastStateEvent(userId)
|
||||
val inviterId = myUserStateEvent?.sender
|
||||
val myUserRoomMember: RoomMemberContent? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
|
||||
val isDirect = myUserRoomMember?.isDirect
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.sync.job
|
|||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.failure.isTokenError
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
|
@ -56,29 +57,15 @@ abstract class SyncService : Service() {
|
|||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.i("onStartCommand $intent")
|
||||
intent?.let {
|
||||
val matrix = Matrix.getInstance(applicationContext)
|
||||
val safeSessionId = it.getStringExtra(EXTRA_SESSION_ID) ?: return@let
|
||||
val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId)
|
||||
?: return@let
|
||||
session = sessionComponent.session()
|
||||
sessionId = safeSessionId
|
||||
syncTask = sessionComponent.syncTask()
|
||||
isInitialSync = !session.hasAlreadySynced()
|
||||
networkConnectivityChecker = sessionComponent.networkConnectivityChecker()
|
||||
taskExecutor = sessionComponent.taskExecutor()
|
||||
coroutineDispatchers = sessionComponent.coroutineDispatchers()
|
||||
backgroundDetectionObserver = matrix.backgroundDetectionObserver
|
||||
if (isRunning.get()) {
|
||||
Timber.i("Received a start while was already syncing... ignore")
|
||||
} else {
|
||||
isRunning.set(true)
|
||||
serviceScope.launch(coroutineDispatchers.io) {
|
||||
doSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
val isInit = initialize(intent)
|
||||
if (isInit) {
|
||||
onStart(isInitialSync)
|
||||
doSyncIfNotAlreadyRunning()
|
||||
} else {
|
||||
// We should start and stop as we have to ensure to call Service.startForeground()
|
||||
onStart(isInitialSync)
|
||||
stopMe()
|
||||
}
|
||||
// No intent just start the service, an alarm will should call with intent
|
||||
return START_STICKY
|
||||
}
|
||||
|
@ -98,6 +85,17 @@ abstract class SyncService : Service() {
|
|||
stopSelf()
|
||||
}
|
||||
|
||||
private fun doSyncIfNotAlreadyRunning() {
|
||||
if (isRunning.get()) {
|
||||
Timber.i("Received a start while was already syncing... ignore")
|
||||
} else {
|
||||
isRunning.set(true)
|
||||
serviceScope.launch(coroutineDispatchers.io) {
|
||||
doSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doSync() {
|
||||
if (!networkConnectivityChecker.hasInternetAccess()) {
|
||||
Timber.v("No network reschedule to avoid wasting resources")
|
||||
|
@ -129,6 +127,33 @@ abstract class SyncService : Service() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun initialize(intent: Intent?): Boolean {
|
||||
if (intent == null) {
|
||||
return false
|
||||
}
|
||||
val matrix = Matrix.getInstance(applicationContext)
|
||||
val safeSessionId = intent.getStringExtra(EXTRA_SESSION_ID) ?: return false
|
||||
try {
|
||||
val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId)
|
||||
?: throw IllegalStateException("You should have a session to make it work")
|
||||
session = sessionComponent.session()
|
||||
sessionId = safeSessionId
|
||||
syncTask = sessionComponent.syncTask()
|
||||
isInitialSync = !session.hasAlreadySynced()
|
||||
networkConnectivityChecker = sessionComponent.networkConnectivityChecker()
|
||||
taskExecutor = sessionComponent.taskExecutor()
|
||||
coroutineDispatchers = sessionComponent.coroutineDispatchers()
|
||||
backgroundDetectionObserver = matrix.backgroundDetectionObserver
|
||||
return true
|
||||
} catch (exception: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw exception
|
||||
}
|
||||
Timber.e(exception, "An exception occurred during initialisation")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onStart(isInitialSync: Boolean)
|
||||
|
||||
abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long)
|
||||
|
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
|||
}
|
||||
|
||||
ext.versionMajor = 0
|
||||
ext.versionMinor = 13
|
||||
ext.versionMinor = 14
|
||||
ext.versionPatch = 0
|
||||
|
||||
static def getGitTimestamp() {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"message": "William Shakespeare (bapt. 26 April 1564 – 23 April 1616) was an English poet, playwright and actor, widely regarded as the greatest writer in the English language and the world's greatest dramatist. He is often called England's national poet and the \"Bard of Avon\". His extant works, including collaborations, consist of approximately 39 plays, 154 sonnets, two long narrative poems, and a few other verses, some of uncertain authorship. His plays have been translated into every major living language and are performed more often than those of any other playwright.\n\nShakespeare was born and raised in Stratford-upon-Avon, Warwickshire. At the age of 18, he married Anne Hathaway, with whom he had three children: Susanna and twins Hamnet and Judith. Sometime between 1585 and 1592, he began a successful career in London as an actor, writer, and part-owner of a playing company called the Lord Chamberlain's Men, later known as the King's Men. At age 49 (around 1613), he appears to have retired to Stratford, where he died three years later. Few records of Shakespeare's private life survive; this has stimulated considerable speculation about such matters as his physical appearance, his sexuality, his religious beliefs, and whether the works attributed to him were written by others. Such theories are often criticised for failing to adequately note that few records survive of most commoners of the period.\n\nShakespeare produced most of his known works between 1589 and 1613. His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. Until about 1608, he wrote mainly tragedies, among them Hamlet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language. In the last phase of his life, he wrote tragicomedies (also known as romances) and collaborated with other playwrights.\n\nMany of Shakespeare's plays were published in editions of varying quality and accuracy in his lifetime. However, in 1623, two fellow actors and friends of Shakespeare's, John Heminges and Henry Condell, published a more definitive text known as the First Folio, a posthumous collected edition of Shakespeare's dramatic works that included all but two of his plays. The volume was prefaced with a poem by Ben Jonson, in which Jonson presciently hails Shakespeare in a now-famous quote as \"not of an age, but for all time\".\n\nThroughout the 20th and 21st centuries, Shakespeare's works have been continually adapted and rediscovered by new movements in scholarship and performance. His plays remain popular and are studied, performed, and reinterpreted through various cultural and political contexts around the world.",
|
||||
"roomName": "Matrix HQ",
|
||||
"roomAlias": "#matrix:matrix.org",
|
||||
"roomTopic": "Welcome to Matrix HQ! Here is the rest of the room topic…"
|
||||
"roomTopic": "Welcome to Matrix HQ! Here is the rest of the room topic, with a https://www.example.org url and a phone number: 0102030405 which should not be clickable."
|
||||
},
|
||||
{
|
||||
"displayName": "benoit",
|
||||
|
|
|
@ -31,7 +31,7 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
|||
if (appContext is HasVectorInjector) {
|
||||
val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession()
|
||||
if (activeSession != null) {
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.myUserId, 10)
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ object FcmHelper {
|
|||
// We need to use alarm in this mode
|
||||
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||
val currentSession = activeSessionHolder.getActiveSession()
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L)
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.sessionId, 4_000L)
|
||||
Timber.i("Alarm scheduled to restart service")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
android:value=".features.home.HomeActivity" />
|
||||
</activity>
|
||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||
<activity android:name=".features.home.createdirect.CreateDirectRoomActivity" />
|
||||
<activity android:name="im.vector.riotx.features.createdirect.CreateDirectRoomActivity" />
|
||||
<activity android:name=".features.webview.VectorWebViewActivity" />
|
||||
<activity android:name=".features.link.LinkHandlerActivity">
|
||||
<intent-filter>
|
||||
|
@ -106,6 +106,9 @@
|
|||
<category android:name="android.intent.category.OPENABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.roomprofile.RoomProfileActivity" />
|
||||
|
||||
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
||||
<activity
|
||||
android:name=".features.signout.soft.SoftLogoutActivity"
|
||||
|
@ -122,6 +125,14 @@
|
|||
<data android:host="matrix.to" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
||||
android:parentActivityName=".features.home.HomeActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".features.home.HomeActivity" />
|
||||
</activity>
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
<service
|
||||
|
|
|
@ -25,8 +25,8 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.features.home.HomeRoomListDataSource
|
||||
import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
|
||||
import im.vector.riotx.features.home.group.SelectedGroupDataSource
|
||||
import im.vector.riotx.features.grouplist.ALL_COMMUNITIES_GROUP_ID
|
||||
import im.vector.riotx.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.riotx.features.home.room.list.ChronologicalRoomComparator
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 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.core.animations
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
|
||||
import kotlin.math.abs
|
||||
|
||||
abstract class AppBarStateChangeListener : OnOffsetChangedListener {
|
||||
|
||||
enum class State {
|
||||
EXPANDED, COLLAPSED, IDLE
|
||||
}
|
||||
|
||||
private var currentState = State.IDLE
|
||||
|
||||
override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
|
||||
currentState = if (i == 0) {
|
||||
if (currentState != State.EXPANDED) {
|
||||
onStateChanged(appBarLayout, State.EXPANDED)
|
||||
}
|
||||
State.EXPANDED
|
||||
} else if (abs(i) >= appBarLayout.totalScrollRange) {
|
||||
if (currentState != State.COLLAPSED) {
|
||||
onStateChanged(appBarLayout, State.COLLAPSED)
|
||||
}
|
||||
State.COLLAPSED
|
||||
} else {
|
||||
if (currentState != State.IDLE) {
|
||||
onStateChanged(appBarLayout, State.IDLE)
|
||||
}
|
||||
State.IDLE
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onStateChanged(appBarLayout: AppBarLayout, state: State)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 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.core.animations
|
||||
|
||||
import android.view.View
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
|
||||
class MatrixItemAppBarStateChangeListener(private val headerView: View, private val toolbarViews: List<View>) : AppBarStateChangeListener() {
|
||||
|
||||
override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {
|
||||
if (state == State.COLLAPSED) {
|
||||
headerView.visibility = View.INVISIBLE
|
||||
toolbarViews.forEach {
|
||||
it.animate().alpha(1f).duration = 150
|
||||
}
|
||||
} else {
|
||||
headerView.visibility = View.VISIBLE
|
||||
toolbarViews.forEach {
|
||||
it.animate().alpha(0f).duration = 150
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.animations.behavior
|
||||
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
|
||||
import im.vector.riotx.R
|
||||
import kotlin.math.abs
|
||||
|
||||
private const val UNSPECIFIED_INT = Integer.MAX_VALUE
|
||||
private val UNSPECIFIED_FLOAT = Float.MAX_VALUE
|
||||
private const val DEPEND_TYPE_HEIGHT = 0
|
||||
private const val DEPEND_TYPE_WIDTH = 1
|
||||
private const val DEPEND_TYPE_X = 2
|
||||
private const val DEPEND_TYPE_Y = 3
|
||||
|
||||
class PercentViewBehavior<V : View>(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<V>(context, attrs) {
|
||||
|
||||
private var dependType: Int = 0
|
||||
private var dependViewId: Int = 0
|
||||
private var dependTarget: Int = 0
|
||||
private var dependStartX: Int = 0
|
||||
private var dependStartY: Int = 0
|
||||
private var dependStartWidth: Int = 0
|
||||
private var dependStartHeight: Int = 0
|
||||
|
||||
private var startX: Int = 0
|
||||
private var startY: Int = 0
|
||||
private var startWidth: Int = 0
|
||||
private var startHeight: Int = 0
|
||||
private var startBackgroundColor: Int = 0
|
||||
private var startAlpha: Float = 0f
|
||||
private var startRotateX: Float = 0f
|
||||
private var startRotateY: Float = 0f
|
||||
|
||||
private var targetX: Int = 0
|
||||
private var targetY: Int = 0
|
||||
private var targetWidth: Int = 0
|
||||
private var targetHeight: Int = 0
|
||||
private var targetBackgroundColor: Int = 0
|
||||
private var targetAlpha: Float = 0f
|
||||
private var targetRotateX: Float = 0f
|
||||
private var targetRotateY: Float = 0f
|
||||
|
||||
/**
|
||||
* Is the values prepared to be use
|
||||
*/
|
||||
private var isPrepared: Boolean = false
|
||||
|
||||
init {
|
||||
val a = context.obtainStyledAttributes(attrs, R.styleable.PercentViewBehavior)
|
||||
dependViewId = a.getResourceId(R.styleable.PercentViewBehavior_behavior_dependsOn, 0)
|
||||
dependType = a.getInt(R.styleable.PercentViewBehavior_behavior_dependType, DEPEND_TYPE_WIDTH)
|
||||
dependTarget = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_dependTarget, UNSPECIFIED_INT)
|
||||
targetX = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetX, UNSPECIFIED_INT)
|
||||
targetY = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetY, UNSPECIFIED_INT)
|
||||
targetWidth = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetWidth, UNSPECIFIED_INT)
|
||||
targetHeight = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetHeight, UNSPECIFIED_INT)
|
||||
targetBackgroundColor = a.getColor(R.styleable.PercentViewBehavior_behavior_targetBackgroundColor, UNSPECIFIED_INT)
|
||||
targetAlpha = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetAlpha, UNSPECIFIED_FLOAT)
|
||||
targetRotateX = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetRotateX, UNSPECIFIED_FLOAT)
|
||||
targetRotateY = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetRotateY, UNSPECIFIED_FLOAT)
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
private fun prepare(parent: CoordinatorLayout, child: View, dependency: View) {
|
||||
dependStartX = dependency.x.toInt()
|
||||
dependStartY = dependency.y.toInt()
|
||||
dependStartWidth = dependency.width
|
||||
dependStartHeight = dependency.height
|
||||
startX = child.x.toInt()
|
||||
startY = child.y.toInt()
|
||||
startWidth = child.width
|
||||
startHeight = child.height
|
||||
startAlpha = child.alpha
|
||||
startRotateX = child.rotationX
|
||||
startRotateY = child.rotationY
|
||||
|
||||
// only set the start background color when the background is color drawable
|
||||
val background = child.background
|
||||
if (background is ColorDrawable) {
|
||||
startBackgroundColor = background.color
|
||||
}
|
||||
|
||||
// if parent fitsSystemWindows is true, add status bar height to target y if specified
|
||||
if (parent.fitsSystemWindows && targetY != UNSPECIFIED_INT) {
|
||||
var result = 0
|
||||
val resources = parent.context.resources
|
||||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
if (resourceId > 0) {
|
||||
result = resources.getDimensionPixelSize(resourceId)
|
||||
}
|
||||
targetY += result
|
||||
}
|
||||
isPrepared = true
|
||||
}
|
||||
|
||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
|
||||
return dependency.id == dependViewId
|
||||
}
|
||||
|
||||
override fun onDependentViewChanged(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
|
||||
// first time, prepare values before continue
|
||||
if (!isPrepared) {
|
||||
prepare(parent, child, dependency)
|
||||
}
|
||||
updateView(child, dependency)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
|
||||
val bool = super.onLayoutChild(parent, child, layoutDirection)
|
||||
if (isPrepared) {
|
||||
updateView(child, parent.getDependencies(child)[0])
|
||||
}
|
||||
return bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the child view from the dependency states
|
||||
*
|
||||
* @param child child view
|
||||
* @param dependency dependency view
|
||||
*/
|
||||
private fun updateView(child: V, dependency: View) {
|
||||
var percent = 0f
|
||||
var start = 0f
|
||||
var current = 0f
|
||||
var end = UNSPECIFIED_INT.toFloat()
|
||||
when (dependType) {
|
||||
DEPEND_TYPE_WIDTH -> {
|
||||
start = dependStartWidth.toFloat()
|
||||
current = dependency.width.toFloat()
|
||||
end = dependTarget.toFloat()
|
||||
}
|
||||
DEPEND_TYPE_HEIGHT -> {
|
||||
start = dependStartHeight.toFloat()
|
||||
current = dependency.height.toFloat()
|
||||
end = dependTarget.toFloat()
|
||||
}
|
||||
DEPEND_TYPE_X -> {
|
||||
start = dependStartX.toFloat()
|
||||
current = dependency.x
|
||||
end = dependTarget.toFloat()
|
||||
}
|
||||
DEPEND_TYPE_Y -> {
|
||||
start = dependStartY.toFloat()
|
||||
current = dependency.y
|
||||
end = dependTarget.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
// need to define target value according to the depend type, if not then skip
|
||||
if (end != UNSPECIFIED_INT.toFloat()) {
|
||||
percent = abs(current - start) / abs(end - start)
|
||||
}
|
||||
updateViewWithPercent(child, if (percent > 1f) 1f else percent)
|
||||
}
|
||||
|
||||
private fun updateViewWithPercent(child: View, percent: Float) {
|
||||
var newX = if (targetX == UNSPECIFIED_INT) 0f else (targetX - startX) * percent
|
||||
var newY = if (targetY == UNSPECIFIED_INT) 0f else (targetY - startY) * percent
|
||||
|
||||
// set scale
|
||||
if (targetWidth != UNSPECIFIED_INT) {
|
||||
val newWidth = startWidth + (targetWidth - startWidth) * percent
|
||||
child.scaleX = newWidth / startWidth
|
||||
newX -= (startWidth - newWidth) / 2
|
||||
}
|
||||
if (targetHeight != UNSPECIFIED_INT) {
|
||||
val newHeight = startHeight + (targetHeight - startHeight) * percent
|
||||
child.scaleY = newHeight / startHeight
|
||||
newY -= (startHeight - newHeight) / 2
|
||||
}
|
||||
|
||||
// set new position
|
||||
child.translationX = newX
|
||||
child.translationY = newY
|
||||
|
||||
// set alpha
|
||||
if (targetAlpha != UNSPECIFIED_FLOAT) {
|
||||
child.alpha = startAlpha + (targetAlpha - startAlpha) * percent
|
||||
}
|
||||
|
||||
// set background color
|
||||
if (targetBackgroundColor != UNSPECIFIED_INT && startBackgroundColor != 0) {
|
||||
val evaluator = ArgbEvaluator()
|
||||
val color = evaluator.evaluate(percent, startBackgroundColor, targetBackgroundColor) as Int
|
||||
child.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
// set rotation
|
||||
if (targetRotateX != UNSPECIFIED_FLOAT) {
|
||||
child.rotationX = startRotateX + (targetRotateX - startRotateX) * percent
|
||||
}
|
||||
if (targetRotateY != UNSPECIFIED_FLOAT) {
|
||||
child.rotationY = startRotateY + (targetRotateY - startRotateY) * percent
|
||||
}
|
||||
|
||||
child.requestLayout()
|
||||
}
|
||||
}
|
|
@ -27,20 +27,23 @@ import im.vector.riotx.features.crypto.verification.*
|
|||
import im.vector.riotx.features.home.HomeDetailFragment
|
||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||
import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
||||
import im.vector.riotx.features.home.group.GroupListFragment
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
||||
import im.vector.riotx.features.grouplist.GroupListFragment
|
||||
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.login.*
|
||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
||||
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||
import im.vector.riotx.features.settings.*
|
||||
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||
|
@ -261,6 +264,21 @@ interface FragmentModule {
|
|||
@FragmentKey(PublicRoomsFragment::class)
|
||||
fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomProfileFragment::class)
|
||||
fun bindRoomProfileFragment(fragment: RoomProfileFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomMemberListFragment::class)
|
||||
fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomMemberProfileFragment::class)
|
||||
fun bindRoomMemberProfileFragment(fragment: RoomMemberProfileFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(BreadcrumbsFragment::class)
|
||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActiv
|
|||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import im.vector.riotx.features.home.HomeModule
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||
|
|
|
@ -36,7 +36,7 @@ import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
|
|||
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.HomeRoomListDataSource
|
||||
import im.vector.riotx.features.home.group.SelectedGroupDataSource
|
||||
import im.vector.riotx.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||
import im.vector.riotx.features.navigation.Navigator
|
||||
|
|
|
@ -22,19 +22,20 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
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.KeysBackupRestoreFromPassphraseViewModel
|
||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.riotx.features.login.LoginSharedActionViewModel
|
||||
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
||||
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||
import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel
|
||||
import im.vector.riotx.features.workers.signout.SignOutViewModel
|
||||
|
||||
@Module
|
||||
|
@ -124,4 +125,9 @@ interface ViewModelModule {
|
|||
@IntoMap
|
||||
@ViewModelKey(RoomDetailSharedActionViewModel::class)
|
||||
fun bindRoomDetailSharedActionViewModel(viewModel: RoomDetailSharedActionViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(RoomProfileSharedActionViewModel::class)
|
||||
fun bindRoomProfileSharedActionViewModel(viewModel: RoomProfileSharedActionViewModel): ViewModel
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.core.epoxy
|
||||
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
|
||||
/**
|
||||
* Default background color is for the bottom sheets (R.attr.vctr_list_bottom_sheet_divider_color).
|
||||
* To use in fragment, set color using R.attr.vctr_list_divider_color
|
||||
*/
|
||||
@EpoxyModelClass(layout = R.layout.item_divider)
|
||||
abstract class DividerItem : VectorEpoxyModel<DividerItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute var color: Int = -1
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
if (color != -1) {
|
||||
holder.view.setBackgroundColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder()
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 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.core.epoxy
|
||||
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
* Generally we do not care about the View parameter in [View.OnClickListener.onClick()], so create facility to remove it.
|
||||
*/
|
||||
typealias ClickListener = () -> Unit
|
||||
|
||||
fun View.onClick(listener: ClickListener?) {
|
||||
setOnClickListener { listener?.invoke() }
|
||||
}
|
|
@ -45,9 +45,12 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
|||
var time: CharSequence? = null
|
||||
@EpoxyAttribute
|
||||
var movementMethod: MovementMethod? = null
|
||||
@EpoxyAttribute
|
||||
var userClicked: (() -> Unit)? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
avatarRenderer.render(matrixItem, holder.avatar)
|
||||
holder.avatar.setOnClickListener { userClicked?.invoke() }
|
||||
holder.sender.setTextOrHide(matrixItem.displayName)
|
||||
holder.body.movementMethod = movementMethod
|
||||
holder.body.text = body
|
||||
|
|
|
@ -23,8 +23,10 @@ import com.airbnb.epoxy.EpoxyAttribute
|
|||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.ClickListener
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.epoxy.onClick
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
|
||||
|
@ -38,12 +40,13 @@ abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel<BottomSheetRoomPrev
|
|||
lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute
|
||||
lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var settingsClickListener: View.OnClickListener? = null
|
||||
@EpoxyAttribute var settingsClickListener: ClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
avatarRenderer.render(matrixItem, holder.avatar)
|
||||
holder.avatar.onClick(settingsClickListener)
|
||||
holder.roomName.setTextOrHide(matrixItem.displayName)
|
||||
holder.roomSettings.setOnClickListener(settingsClickListener)
|
||||
holder.roomSettings.onClick(settingsClickListener)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.epoxy.profiles
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_profile_action)
|
||||
abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var title: String
|
||||
@EpoxyAttribute
|
||||
var subtitle: String? = null
|
||||
@EpoxyAttribute
|
||||
var iconRes: Int = 0
|
||||
@EpoxyAttribute
|
||||
var editable: Boolean = true
|
||||
@EpoxyAttribute
|
||||
var destructive: Boolean = false
|
||||
@EpoxyAttribute
|
||||
lateinit var listener: View.OnClickListener
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.view.setOnClickListener(listener)
|
||||
holder.editable.isVisible = editable
|
||||
holder.title.text = title
|
||||
val tintColor = if (destructive) {
|
||||
ContextCompat.getColor(holder.view.context, R.color.riotx_notice)
|
||||
} else {
|
||||
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_primary)
|
||||
}
|
||||
holder.title.setTextColor(tintColor)
|
||||
holder.subtitle.setTextOrHide(subtitle)
|
||||
if (iconRes != 0) {
|
||||
holder.icon.setImageResource(iconRes)
|
||||
holder.icon.isVisible = true
|
||||
} else {
|
||||
holder.icon.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val icon by bind<ImageView>(R.id.actionIcon)
|
||||
val title by bind<TextView>(R.id.actionTitle)
|
||||
val subtitle by bind<TextView>(R.id.actionSubtitle)
|
||||
val editable by bind<ImageView>(R.id.actionEditable)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 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.core.epoxy.profiles
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.riotx.core.epoxy.dividerItem
|
||||
|
||||
fun EpoxyController.buildProfileSection(title: String) {
|
||||
profileSectionItem {
|
||||
id("section_$title")
|
||||
title(title)
|
||||
}
|
||||
}
|
||||
|
||||
fun EpoxyController.buildProfileAction(
|
||||
id: String,
|
||||
title: String,
|
||||
dividerColor: Int,
|
||||
subtitle: String? = null,
|
||||
editable: Boolean = true,
|
||||
@DrawableRes icon: Int = 0,
|
||||
destructive: Boolean = false,
|
||||
divider: Boolean = true,
|
||||
action: () -> Unit
|
||||
) {
|
||||
profileActionItem {
|
||||
iconRes(icon)
|
||||
id("action_$id")
|
||||
subtitle(subtitle)
|
||||
editable(editable)
|
||||
destructive(destructive)
|
||||
title(title)
|
||||
listener { _ ->
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
if (divider) {
|
||||
dividerItem {
|
||||
id("divider_$title")
|
||||
color(dividerColor)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 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.core.epoxy.profiles
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
|
||||
abstract class ProfileMatrixItem : VectorEpoxyModel<ProfileMatrixItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
val bestName = matrixItem.getBestName()
|
||||
val matrixId = matrixItem.id.takeIf { it != bestName }
|
||||
holder.view.setOnClickListener(clickListener)
|
||||
holder.titleView.text = bestName
|
||||
holder.subtitleView.setTextOrHide(matrixId)
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val titleView by bind<TextView>(R.id.matrixItemTitle)
|
||||
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
|
||||
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.epoxy.profiles
|
||||
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_profile_section)
|
||||
abstract class ProfileSectionItem: VectorEpoxyModel<ProfileSectionItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var title: String
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.sectionView.text = title
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val sectionView by bind<TextView>(R.id.itemProfileSectionView)
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ package im.vector.riotx.core.extensions
|
|||
/**
|
||||
* Returns the last element yielding the smallest value of the given function or `null` if there are no elements.
|
||||
*/
|
||||
public inline fun <T, R : Comparable<R>> Iterable<T>.lastMinBy(selector: (T) -> R): T? {
|
||||
inline fun <T, R : Comparable<R>> Iterable<T>.lastMinBy(selector: (T) -> R): T? {
|
||||
val iterator = iterator()
|
||||
if (!iterator.hasNext()) return null
|
||||
var minElem = iterator.next()
|
||||
|
@ -34,3 +34,17 @@ public inline fun <T, R : Comparable<R>> Iterable<T>.lastMinBy(selector: (T) ->
|
|||
}
|
||||
return minElem
|
||||
}
|
||||
|
||||
/**
|
||||
* Call each for each item, and between between each items
|
||||
*/
|
||||
inline fun <T> Collection<T>.join(each: (T) -> Unit, between: (T) -> Unit) {
|
||||
val lastIndex = size - 1
|
||||
forEachIndexed { idx, t ->
|
||||
each(t)
|
||||
|
||||
if (idx != lastIndex) {
|
||||
between(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import com.bumptech.glide.load.model.ModelLoader
|
|||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.features.media.ImageContentRenderer
|
||||
import okhttp3.OkHttpClient
|
||||
|
@ -116,7 +116,7 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde
|
|||
return
|
||||
}
|
||||
stream = if (data.elementToDecrypt != null && data.elementToDecrypt.k.isNotBlank()) {
|
||||
MXEncryptedAttachments.decryptAttachment(inputStream, data.elementToDecrypt)
|
||||
Matrix.decryptStream(inputStream, data.elementToDecrypt)
|
||||
} else {
|
||||
inputStream
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
|
|||
data class Error(val message: CharSequence? = null) : State()
|
||||
}
|
||||
|
||||
private var eventCallback: EventCallback? = null
|
||||
var eventCallback: EventCallback? = null
|
||||
|
||||
var contentView: View? = null
|
||||
|
||||
|
|
|
@ -23,11 +23,18 @@ import android.os.Parcelable
|
|||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.annotation.*
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentFactory
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
@ -41,7 +48,12 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import im.vector.matrix.android.api.failure.GlobalError
|
||||
import im.vector.riotx.BuildConfig
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.*
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.di.HasVectorInjector
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.di.VectorComponent
|
||||
import im.vector.riotx.core.dialogs.DialogLocker
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.utils.toast
|
||||
|
@ -92,6 +104,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
lateinit var rageShake: RageShake
|
||||
private set
|
||||
protected lateinit var navigator: Navigator
|
||||
private lateinit var fragmentFactory: FragmentFactory
|
||||
private lateinit var activeSessionHolder: ActiveSessionHolder
|
||||
private lateinit var vectorPreferences: VectorPreferences
|
||||
|
||||
|
@ -145,7 +158,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
}
|
||||
Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms")
|
||||
ThemeUtils.setActivityTheme(this, getOtherThemes())
|
||||
supportFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
||||
fragmentFactory = screenComponent.fragmentFactory()
|
||||
supportFragmentManager.fragmentFactory = fragmentFactory
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModelFactory = screenComponent.viewModelFactory()
|
||||
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
|
||||
|
@ -196,7 +210,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
handleInvalidToken(globalError)
|
||||
is GlobalError.ConsentNotGivenError ->
|
||||
consentNotGivenHelper.displayDialog(globalError.consentUri,
|
||||
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "")
|
||||
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host
|
||||
?: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,6 +291,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
|
||||
protected open fun injectWith(injector: ScreenComponent) = Unit
|
||||
|
||||
protected fun createFragment(fragmentClass: Class<out Fragment>, args: Bundle?): Fragment {
|
||||
return fragmentFactory.instantiate(classLoader, fragmentClass.name).apply {
|
||||
arguments = args
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* PRIVATE METHODS
|
||||
* ========================================================================================== */
|
||||
|
@ -372,12 +393,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
*/
|
||||
protected fun configureToolbar(toolbar: Toolbar, displayBack: Boolean = true) {
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
if (displayBack) {
|
||||
supportActionBar?.let {
|
||||
it.setDisplayShowHomeEnabled(true)
|
||||
it.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
it.setDisplayShowHomeEnabled(displayBack)
|
||||
it.setDisplayHomeAsUpEnabled(displayBack)
|
||||
it.title = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package im.vector.riotx.core.platform
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
|
@ -59,6 +62,8 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
protected lateinit var navigator: Navigator
|
||||
protected lateinit var errorFormatter: ErrorFormatter
|
||||
|
||||
private var progress: ProgressDialog? = null
|
||||
|
||||
/* ==========================================================================================
|
||||
* View model
|
||||
* ========================================================================================== */
|
||||
|
@ -96,6 +101,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
}
|
||||
|
||||
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
Timber.i("onCreateView Fragment ${this.javaClass.simpleName}")
|
||||
return inflater.inflate(getLayoutResId(), container, false)
|
||||
}
|
||||
|
||||
|
@ -117,6 +123,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
@CallSuper
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
Timber.i("onDestroyView Fragment ${this.javaClass.simpleName}")
|
||||
mUnBinder?.unbind()
|
||||
mUnBinder = null
|
||||
uiDisposables.clear()
|
||||
|
@ -175,6 +182,19 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
}
|
||||
}
|
||||
|
||||
protected fun showLoadingDialog(message: CharSequence, cancelable: Boolean = false) {
|
||||
progress = ProgressDialog(requireContext()).apply {
|
||||
setCancelable(cancelable)
|
||||
setMessage(message)
|
||||
setProgressStyle(ProgressDialog.STYLE_SPINNER)
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun dismissLoadingDialog() {
|
||||
progress?.dismiss()
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Toolbar
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.riotx.core.utils
|
|||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import com.jakewharton.rxrelay2.PublishRelay
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
||||
interface DataSource<T> {
|
||||
fun observe(): Observable<T>
|
||||
|
@ -37,7 +37,7 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
|
|||
private val behaviorRelay = createRelay()
|
||||
|
||||
override fun observe(): Observable<T> {
|
||||
return behaviorRelay.hide().observeOn(Schedulers.computation())
|
||||
return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
override fun post(value: T) {
|
||||
|
@ -61,7 +61,7 @@ open class PublishDataSource<T> : MutableDataSource<T> {
|
|||
private val publishRelay = PublishRelay.create<T>()
|
||||
|
||||
override fun observe(): Observable<T> {
|
||||
return publishRelay.hide()
|
||||
return publishRelay.hide().observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
override fun post(value: T) {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home
|
||||
package im.vector.riotx.core.utils
|
||||
|
||||
import androidx.annotation.ColorRes
|
||||
import im.vector.riotx.R
|
|
@ -17,20 +17,20 @@
|
|||
package im.vector.riotx.features.autocomplete.member
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMember>>() {
|
||||
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMemberSummary>>() {
|
||||
|
||||
var listener: AutocompleteClickListener<RoomMember>? = null
|
||||
var listener: AutocompleteClickListener<RoomMemberSummary>? = null
|
||||
|
||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
override fun buildModels(data: List<RoomMember>?) {
|
||||
override fun buildModels(data: List<RoomMemberSummary>?) {
|
||||
if (data.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -25,14 +25,14 @@ import im.vector.matrix.android.api.query.QueryStringValue
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||
|
||||
class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
|
||||
@Assisted val roomId: String,
|
||||
private val session: Session,
|
||||
private val controller: AutocompleteMemberController
|
||||
) : RecyclerViewPresenter<RoomMember>(context), AutocompleteClickListener<RoomMember> {
|
||||
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
|
||||
|
||||
private val room = session.getRoom(roomId)!!
|
||||
|
||||
|
@ -51,7 +51,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
|
|||
return controller.adapter
|
||||
}
|
||||
|
||||
override fun onItemClick(t: RoomMember) {
|
||||
override fun onItemClick(t: RoomMemberSummary) {
|
||||
dispatchClick(t)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import im.vector.riotx.core.platform.VectorSharedAction
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
||||
import javax.inject.Inject
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import arrow.core.Option
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue