mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-25 09:14:01 +03:00
Merge branch 'release/0.13.0'
This commit is contained in:
commit
f471d9cff8
206 changed files with 4929 additions and 844 deletions
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -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)
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,7 +3,6 @@
|
|||
/local.properties
|
||||
# idea files: exclude everything except dictionnaries
|
||||
.idea/caches
|
||||
.idea/codeStyles
|
||||
.idea/libraries
|
||||
.idea/*.xml
|
||||
.DS_Store
|
||||
|
|
158
.idea/codeStyles/Project.xml
generated
Normal file
158
.idea/codeStyles/Project.xml
generated
Normal file
|
@ -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>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
18
CHANGES.md
18
CHANGES.md
|
@ -1,3 +1,21 @@
|
|||
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)
|
||||
|
||||
Changes in RiotX 0.12.0 (2020-01-09)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -91,7 +91,8 @@ object EventType {
|
|||
STATE_ROOM_CANONICAL_ALIAS,
|
||||
STATE_ROOM_HISTORY_VISIBILITY,
|
||||
STATE_ROOM_RELATED_GROUPS,
|
||||
STATE_ROOM_PINNED_EVENT
|
||||
STATE_ROOM_PINNED_EVENT,
|
||||
STATE_ROOM_ENCRYPTION
|
||||
)
|
||||
|
||||
fun isStateEvent(type: String): Boolean {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -22,12 +22,13 @@ import im.vector.matrix.android.api.session.room.members.MembershipService
|
|||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
|
||||
import im.vector.matrix.android.api.session.room.reporting.ReportingService
|
||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||
import im.vector.matrix.android.api.session.room.reporting.ReportingService
|
||||
import im.vector.matrix.android.api.session.room.send.DraftService
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
||||
/**
|
||||
|
@ -38,6 +39,7 @@ interface Room :
|
|||
SendService,
|
||||
DraftService,
|
||||
ReadService,
|
||||
TypingService,
|
||||
MembershipService,
|
||||
StateService,
|
||||
ReportingService,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -28,17 +28,23 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {})
|
|||
*/
|
||||
data class RoomMemberQueryParams(
|
||||
val displayName: QueryStringValue,
|
||||
val memberships: List<Membership>
|
||||
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
|
||||
|
||||
fun build() = RoomMemberQueryParams(
|
||||
displayName = displayName,
|
||||
memberships = memberships
|
||||
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()
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Class representing the EventType.STATE_ROOM_GUEST_ACCESS state event content
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#m-room-guest-access
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RoomGuestAccessContent(
|
||||
// Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
|
||||
@Json(name = "guest_access") val guestAccess: GuestAccess? = null
|
||||
)
|
||||
|
||||
enum class GuestAccess(val value: String) {
|
||||
@Json(name = "can_join")
|
||||
CanJoin("can_join"),
|
||||
@Json(name = "forbidden")
|
||||
Forbidden("forbidden")
|
||||
}
|
|
@ -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,
|
||||
|
@ -42,7 +44,8 @@ data class RoomSummary(
|
|||
val versioningState: VersioningState = VersioningState.NONE,
|
||||
val readMarkerId: String? = null,
|
||||
val userDrafts: List<UserDraft> = emptyList(),
|
||||
var isEncrypted: Boolean
|
||||
var isEncrypted: Boolean,
|
||||
val typingRoomMemberIds: List<String> = emptyList()
|
||||
) {
|
||||
|
||||
val isVersioned: Boolean
|
||||
|
|
|
@ -24,10 +24,12 @@ 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
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Parameter to create a room, with facilities functions to configure it
|
||||
|
@ -88,7 +90,7 @@ class CreateRoomParams {
|
|||
* A list of state events to set in the new room.
|
||||
* This allows the user to override the default state events set in the new room.
|
||||
* The expected format of the state events are an object with type, state_key and content keys set.
|
||||
* Takes precedence over events set by presets, but gets overriden by name and topic keys.
|
||||
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
|
||||
*/
|
||||
@Json(name = "initial_state")
|
||||
var initialStates: MutableList<Event>? = null
|
||||
|
@ -113,21 +115,21 @@ 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.
|
||||
*
|
||||
* @param algorithm the algorithm
|
||||
*/
|
||||
fun addCryptoAlgorithm(algorithm: String) {
|
||||
if (algorithm.isNotBlank()) {
|
||||
val contentMap = HashMap<String, String>()
|
||||
contentMap["algorithm"] = algorithm
|
||||
fun enableEncryptionWithAlgorithm(algorithm: String) {
|
||||
if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
val contentMap = mapOf("algorithm" to algorithm)
|
||||
|
||||
val algoEvent = Event(type = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
content = contentMap.toContent()
|
||||
val algoEvent = Event(
|
||||
type = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
content = contentMap.toContent()
|
||||
)
|
||||
|
||||
if (null == initialStates) {
|
||||
|
@ -135,6 +137,8 @@ class CreateRoomParams {
|
|||
} else {
|
||||
initialStates!!.add(algoEvent)
|
||||
}
|
||||
} else {
|
||||
Timber.e("Unsupported algorithm: $algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,15 +149,15 @@ class CreateRoomParams {
|
|||
*/
|
||||
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
||||
// Remove the existing value if any.
|
||||
initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY }
|
||||
initialStates?.removeAll { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }
|
||||
|
||||
if (historyVisibility != null) {
|
||||
val contentMap = HashMap<String, RoomHistoryVisibility>()
|
||||
contentMap["history_visibility"] = historyVisibility
|
||||
val contentMap = mapOf("history_visibility" to historyVisibility)
|
||||
|
||||
val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
content = contentMap.toContent())
|
||||
val historyVisibilityEvent = Event(
|
||||
type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
content = contentMap.toContent())
|
||||
|
||||
if (null == initialStates) {
|
||||
initialStates = mutableListOf(historyVisibilityEvent)
|
||||
|
@ -192,8 +196,8 @@ class CreateRoomParams {
|
|||
*/
|
||||
fun isDirect(): Boolean {
|
||||
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
|
||||
&& isDirect == true
|
||||
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
|
||||
&& isDirect == true
|
||||
&& (1 == getInviteCount() || 1 == getInvite3PidCount())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,8 +222,8 @@ class CreateRoomParams {
|
|||
invite3pids = ArrayList()
|
||||
}
|
||||
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
|
||||
medium = ThreePidMedium.EMAIL,
|
||||
address = id)
|
||||
medium = ThreePidMedium.EMAIL,
|
||||
address = id)
|
||||
|
||||
invite3pids!!.add(pid)
|
||||
} else if (isUserId(id)) {
|
||||
|
|
|
@ -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>>
|
||||
}
|
||||
|
|
|
@ -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.matrix.android.api.session.room.typing
|
||||
|
||||
/**
|
||||
* This interface defines methods to handle typing data. It's implemented at the room level.
|
||||
*/
|
||||
interface TypingService {
|
||||
|
||||
/**
|
||||
* To call when user is typing a message in the room
|
||||
* The SDK will handle the requests scheduling to the homeserver:
|
||||
* - No more than one typing request per 10s
|
||||
* - If not called after 10s, the SDK will notify the homeserver that the user is not typing anymore
|
||||
*/
|
||||
fun userIsTyping()
|
||||
|
||||
/**
|
||||
* To call when user stops typing in the room
|
||||
* Notify immediately the homeserver that the user is not typing anymore in the room, for
|
||||
* instance when user has emptied the composer, or when the user quits the timeline screen.
|
||||
*/
|
||||
fun userStopsTyping()
|
||||
}
|
|
@ -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
|
||||
|
@ -697,9 +697,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
|
||||
|
@ -722,7 +722,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)
|
||||
while (read != -1) {
|
||||
encodedBytes = encryptCipher.update(data, 0, read)
|
||||
messageDigest.update(encodedBytes, 0, encodedBytes.size)
|
||||
outStream.write(encodedBytes)
|
||||
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)
|
||||
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,59 +141,50 @@ 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)
|
||||
|
||||
try {
|
||||
val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
|
||||
val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT)
|
||||
val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
|
||||
val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
var read: Int
|
||||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var decodedBytes: ByteArray
|
||||
|
||||
var read: Int
|
||||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var decodedBytes: ByteArray
|
||||
attachmentStream.use { inputStream ->
|
||||
read = inputStream.read(data)
|
||||
while (read != -1) {
|
||||
messageDigest.update(data, 0, read)
|
||||
decodedBytes = decryptCipher.update(data, 0, read)
|
||||
outputStream.write(decodedBytes)
|
||||
read = inputStream.read(data)
|
||||
}
|
||||
}
|
||||
|
||||
read = attachmentStream.read(data)
|
||||
while (read != -1) {
|
||||
messageDigest.update(data, 0, read)
|
||||
decodedBytes = decryptCipher.update(data, 0, read)
|
||||
outStream.write(decodedBytes)
|
||||
read = attachmentStream.read(data)
|
||||
// decrypt the last chunk
|
||||
decodedBytes = decryptCipher.doFinal()
|
||||
outputStream.write(decodedBytes)
|
||||
|
||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||
|
||||
if (elementToDecrypt.sha256 != currentDigestValue) {
|
||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||
return null
|
||||
}
|
||||
|
||||
return ByteArrayInputStream(outputStream.toByteArray())
|
||||
.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## decryptAttachment() failed: OOM")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptAttachment() failed")
|
||||
}
|
||||
|
||||
// decrypt the last chunk
|
||||
decodedBytes = decryptCipher.doFinal()
|
||||
outStream.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
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptAttachment() : failed ${e.message}")
|
||||
}
|
||||
|
||||
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,11 +36,12 @@ 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,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
context: Context) {
|
||||
internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
private val realmKeysUtils: RealmKeysUtils,
|
||||
@SessionFilesDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
context: Context) {
|
||||
|
||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryMapper @Inject constructor(
|
||||
|
@ -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,
|
||||
|
@ -71,7 +73,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@ import io.realm.RealmObject
|
|||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "",
|
||||
@Index var userId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
var displayName: String = "",
|
||||
var avatarUrl: String = "",
|
||||
var reason: String? = null,
|
||||
var isDirect: Boolean = false
|
||||
internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "",
|
||||
@Index var userId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
var displayName: String? = null,
|
||||
var avatarUrl: String? = null,
|
||||
var reason: String? = null,
|
||||
var isDirect: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
|
@ -44,7 +44,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||
var aliases: RealmList<String> = RealmList(),
|
||||
// this is required for querying
|
||||
var flatAliases: String = "",
|
||||
var isEncrypted: Boolean = false
|
||||
var isEncrypted: Boolean = false,
|
||||
var typingUserIds: RealmList<String> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
|
|
@ -50,6 +50,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,12 +41,13 @@ 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,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
||||
|
||||
val okHttpClient = OkHttpClient()
|
||||
internal class DefaultFileService @Inject constructor(
|
||||
@SessionCacheDirectory
|
||||
private val cacheDirectory: File,
|
||||
private val contentUrlResolver: ContentUrlResolver,
|
||||
@Unauthenticated
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
||||
|
||||
/**
|
||||
* Download file in the cache folder, and eventually decrypt it
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -31,6 +31,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
|
||||
|
@ -64,6 +65,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||
CryptoModule::class,
|
||||
PushersModule::class,
|
||||
AccountDataModule::class,
|
||||
ProfileModule::class,
|
||||
SessionAssistedInjectModule::class
|
||||
]
|
||||
)
|
||||
|
|
|
@ -93,7 +93,7 @@ internal abstract class SessionModule {
|
|||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@UserCacheDirectory
|
||||
@SessionFilesDirectory
|
||||
fun providesFilesDir(@UserMd5 userMd5: String,
|
||||
@SessionId sessionId: String,
|
||||
context: Context): File {
|
||||
|
@ -106,6 +106,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
|
||||
}
|
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.send.DraftService
|
|||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||
|
@ -49,6 +50,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
private val stateService: StateService,
|
||||
private val reportingService: ReportingService,
|
||||
private val readService: ReadService,
|
||||
private val typingService: TypingService,
|
||||
private val cryptoService: CryptoService,
|
||||
private val relationService: RelationService,
|
||||
private val roomMembersService: MembershipService,
|
||||
|
@ -60,6 +62,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||
StateService by stateService,
|
||||
ReportingService by reportingService,
|
||||
ReadService by readService,
|
||||
TypingService by typingService,
|
||||
RelationService by relationService,
|
||||
MembershipService by roomMembersService,
|
||||
RoomPushRuleService by roomPushRuleService {
|
||||
|
|
|
@ -18,13 +18,13 @@ package im.vector.matrix.android.internal.session.room
|
|||
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
|
||||
import im.vector.matrix.android.internal.session.room.relation.RelationsResponse
|
||||
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.reporting.ReportContentBod
|
|||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||
import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
|
||||
import im.vector.matrix.android.internal.session.room.typing.TypingBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.*
|
||||
|
||||
|
@ -268,4 +269,12 @@ internal interface RoomAPI {
|
|||
*/
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
||||
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
|
||||
|
||||
/**
|
||||
* Inform that the user is starting to type or has stopped typing
|
||||
*/
|
||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}")
|
||||
fun sendTypingState(@Path("roomId") roomId: String,
|
||||
@Path("userId") userId: String,
|
||||
@Body body: TypingBody): Call<Unit>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReporting
|
|||
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
||||
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
||||
import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface RoomFactory {
|
||||
|
@ -46,6 +47,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
|
|||
private val stateServiceFactory: DefaultStateService.Factory,
|
||||
private val reportingServiceFactory: DefaultReportingService.Factory,
|
||||
private val readServiceFactory: DefaultReadService.Factory,
|
||||
private val typingServiceFactory: DefaultTypingService.Factory,
|
||||
private val relationServiceFactory: DefaultRelationService.Factory,
|
||||
private val membershipServiceFactory: DefaultMembershipService.Factory,
|
||||
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory) :
|
||||
|
@ -62,6 +64,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
|
|||
stateServiceFactory.create(roomId),
|
||||
reportingServiceFactory.create(roomId),
|
||||
readServiceFactory.create(roomId),
|
||||
typingServiceFactory.create(roomId),
|
||||
cryptoService,
|
||||
relationServiceFactory.create(roomId),
|
||||
membershipServiceFactory.create(roomId),
|
||||
|
|
|
@ -52,6 +52,8 @@ import im.vector.matrix.android.internal.session.room.reporting.ReportContentTas
|
|||
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.*
|
||||
import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask
|
||||
import im.vector.matrix.android.internal.session.room.typing.SendTypingTask
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
@ -68,74 +70,77 @@ internal abstract class RoomModule {
|
|||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomFactory(roomFactory: DefaultRoomFactory): RoomFactory
|
||||
abstract fun bindRoomFactory(factory: DefaultRoomFactory): RoomFactory
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomService(roomService: DefaultRoomService): RoomService
|
||||
abstract fun bindRoomService(service: DefaultRoomService): RoomService
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomDirectoryService(roomDirectoryService: DefaultRoomDirectoryService): RoomDirectoryService
|
||||
abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
|
||||
|
||||
@Binds
|
||||
abstract fun bindEventRelationsAggregationTask(eventRelationsAggregationTask: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask
|
||||
abstract fun bindFileService(service: DefaultFileService): FileService
|
||||
|
||||
@Binds
|
||||
abstract fun bindCreateRoomTask(createRoomTask: DefaultCreateRoomTask): CreateRoomTask
|
||||
abstract fun bindEventRelationsAggregationTask(task: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetPublicRoomTask(getPublicRoomTask: DefaultGetPublicRoomTask): GetPublicRoomTask
|
||||
abstract fun bindCreateRoomTask(task: DefaultCreateRoomTask): CreateRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetThirdPartyProtocolsTask(getThirdPartyProtocolsTask: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
|
||||
abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindInviteTask(inviteTask: DefaultInviteTask): InviteTask
|
||||
abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindJoinRoomTask(joinRoomTask: DefaultJoinRoomTask): JoinRoomTask
|
||||
abstract fun bindInviteTask(task: DefaultInviteTask): InviteTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask
|
||||
abstract fun bindJoinRoomTask(task: DefaultJoinRoomTask): JoinRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask
|
||||
abstract fun bindLeaveRoomTask(task: DefaultLeaveRoomTask): LeaveRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask
|
||||
abstract fun bindLoadRoomMembersTask(task: DefaultLoadRoomMembersTask): LoadRoomMembersTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask
|
||||
abstract fun bindPruneEventTask(task: DefaultPruneEventTask): PruneEventTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindMarkAllRoomsReadTask(markAllRoomsReadTask: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask
|
||||
abstract fun bindSetReadMarkersTask(task: DefaultSetReadMarkersTask): SetReadMarkersTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
|
||||
abstract fun bindMarkAllRoomsReadTask(task: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindUpdateQuickReactionTask(updateQuickReactionTask: DefaultUpdateQuickReactionTask): UpdateQuickReactionTask
|
||||
abstract fun bindFindReactionEventForUndoTask(task: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask
|
||||
abstract fun bindUpdateQuickReactionTask(task: DefaultUpdateQuickReactionTask): UpdateQuickReactionTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindReportContentTask(reportContentTask: DefaultReportContentTask): ReportContentTask
|
||||
abstract fun bindSendStateTask(task: DefaultSendStateTask): SendStateTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
|
||||
abstract fun bindReportContentTask(task: DefaultReportContentTask): ReportContentTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindClearUnlinkedEventsTask(clearUnlinkedEventsTask: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask
|
||||
abstract fun bindGetContextOfEventTask(task: DefaultGetContextOfEventTask): GetContextOfEventTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
|
||||
abstract fun bindClearUnlinkedEventsTask(task: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
||||
abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFetchEditHistoryTask(fetchEditHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
||||
abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetRoomIdByAliasTask(getRoomIdByAliasTask: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
|
||||
abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ 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
|
||||
import io.realm.Realm
|
||||
|
@ -65,7 +66,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
membership: Membership? = null,
|
||||
roomSummary: RoomSyncSummary? = null,
|
||||
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
||||
updateMembers: Boolean = false) {
|
||||
updateMembers: Boolean = false,
|
||||
ephemeralResult: RoomSyncHandler.EphemeralResult? = null) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
if (roomSummary != null) {
|
||||
if (roomSummary.heroes.isNotEmpty()) {
|
||||
|
@ -93,8 +95,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev()
|
||||
|
||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||
// avoid this call if we are sure there are unread events
|
||||
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
|
||||
// avoid this call if we are sure there are unread events
|
||||
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
|
||||
|
||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
||||
|
@ -104,16 +106,18 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
?.canonicalAlias
|
||||
|
||||
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
|
||||
?: emptyList()
|
||||
?: emptyList()
|
||||
roomSummaryEntity.aliases.clear()
|
||||
roomSummaryEntity.aliases.addAll(roomAliases)
|
||||
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
||||
roomSummaryEntity.isEncrypted = encryptionEvent != null
|
||||
roomSummaryEntity.typingUserIds.clear()
|
||||
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,12 @@ 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
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||
|
@ -39,13 +40,16 @@ import im.vector.matrix.android.internal.util.fetchCopied
|
|||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
|
||||
internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val inviteTask: InviteTask,
|
||||
private val joinTask: JoinRoomTask,
|
||||
private val leaveRoomTask: LeaveRoomTask
|
||||
internal class DefaultMembershipService @AssistedInject constructor(
|
||||
@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val inviteTask: InviteTask,
|
||||
private val joinTask: JoinRoomTask,
|
||||
private val leaveRoomTask: LeaveRoomTask,
|
||||
@UserId
|
||||
private val userId: String
|
||||
) : MembershipService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
|
@ -62,14 +66,14 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
|||
.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)
|
||||
|
@ -80,7 +84,7 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
|||
)
|
||||
}
|
||||
|
||||
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>> {
|
||||
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{
|
||||
roomMembersQuery(it, queryParams)
|
||||
|
@ -91,15 +95,21 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
|||
)
|
||||
}
|
||||
|
||||
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(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,8 +32,8 @@ import io.realm.Sort
|
|||
* It allows to get the live membership of a user.
|
||||
*/
|
||||
|
||||
internal class RoomMembers(private val realm: Realm,
|
||||
private val roomId: String
|
||||
internal class RoomMemberHelper(private val realm: Realm,
|
||||
private val roomId: String
|
||||
) {
|
||||
|
||||
private val roomSummary: RoomSummaryEntity? by lazy {
|
||||
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.room.typing
|
||||
|
||||
import android.os.SystemClock
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Rules:
|
||||
* - user is typing: notify the homeserver (true), at least once every 10s
|
||||
* - user stop typing: after 10s delay: notify the homeserver (false)
|
||||
* - user empty the text composer or quit the timeline screen: notify the homeserver (false)
|
||||
*/
|
||||
internal class DefaultTypingService @AssistedInject constructor(
|
||||
@Assisted private val roomId: String,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendTypingTask: SendTypingTask
|
||||
) : TypingService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): TypingService
|
||||
}
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
private var currentAutoStopTask: Cancelable? = null
|
||||
|
||||
// What the homeserver knows
|
||||
private var userIsTyping = false
|
||||
// Last time the user is typing event has been sent
|
||||
private var lastRequestTimestamp: Long = 0
|
||||
|
||||
override fun userIsTyping() {
|
||||
scheduleAutoStop()
|
||||
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
|
||||
if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) {
|
||||
Timber.d("Typing: Skip start request")
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("Typing: Send start request")
|
||||
userIsTyping = true
|
||||
lastRequestTimestamp = now
|
||||
|
||||
currentTask?.cancel()
|
||||
|
||||
val params = SendTypingTask.Params(roomId, true)
|
||||
currentTask = sendTypingTask
|
||||
.configureWith(params)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun userStopsTyping() {
|
||||
if (!userIsTyping) {
|
||||
Timber.d("Typing: Skip stop request")
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("Typing: Send stop request")
|
||||
userIsTyping = false
|
||||
lastRequestTimestamp = 0
|
||||
|
||||
currentAutoStopTask?.cancel()
|
||||
currentTask?.cancel()
|
||||
|
||||
val params = SendTypingTask.Params(roomId, false)
|
||||
currentTask = sendTypingTask
|
||||
.configureWith(params)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
private fun scheduleAutoStop() {
|
||||
Timber.d("Typing: Schedule auto stop")
|
||||
currentAutoStopTask?.cancel()
|
||||
|
||||
val params = SendTypingTask.Params(
|
||||
roomId,
|
||||
false,
|
||||
delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS)
|
||||
currentAutoStopTask = sendTypingTask
|
||||
.configureWith(params) {
|
||||
callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
userIsTyping = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS = 10_000L
|
||||
private const val MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS = 10_000L
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.room.typing
|
||||
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import kotlinx.coroutines.delay
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
|
||||
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val isTyping: Boolean,
|
||||
val typingTimeoutMillis: Int? = 30_000,
|
||||
// Optional delay before sending the request to the homeserver
|
||||
val delay: Long? = null
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultSendTypingTask @Inject constructor(
|
||||
private val roomAPI: RoomAPI,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus
|
||||
) : SendTypingTask {
|
||||
|
||||
override suspend fun execute(params: SendTypingTask.Params) {
|
||||
delay(params.delay ?: -1)
|
||||
|
||||
executeRequest<Unit>(eventBus) {
|
||||
apiCall = roomAPI.sendTypingState(
|
||||
params.roomId,
|
||||
userId,
|
||||
TypingBody(params.isTyping, params.typingTimeoutMillis?.takeIf { params.isTyping })
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.room.typing
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TypingBody(
|
||||
// Required. Whether the user is typing or not. If false, the timeout key can be omitted.
|
||||
@Json(name = "typing")
|
||||
val typing: Boolean,
|
||||
// The length of time in milliseconds to mark this user as typing.
|
||||
@Json(name = "timeout")
|
||||
val timeout: Int?
|
||||
)
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.room.typing
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TypingEventContent(
|
||||
@Json(name = "user_ids")
|
||||
val typingUserIds: List<String> = emptyList()
|
||||
)
|
|
@ -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))
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
|||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberEventHandler
|
||||
import im.vector.matrix.android.internal.session.room.read.FullyReadContent
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.internal.session.room.typing.TypingEventContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.*
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
|
@ -97,11 +98,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
isInitialSync: Boolean): RoomEntity {
|
||||
Timber.v("Handle join sync for room $roomId")
|
||||
|
||||
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
|
||||
handleEphemeral(realm, roomId, roomSync.ephemeral, isInitialSync)
|
||||
var ephemeralResult: EphemeralResult? = null
|
||||
if (roomSync.ephemeral?.events?.isNotEmpty() == true) {
|
||||
ephemeralResult = handleEphemeral(realm, roomId, roomSync.ephemeral, isInitialSync)
|
||||
}
|
||||
|
||||
if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) {
|
||||
if (roomSync.accountData?.events?.isNotEmpty() == true) {
|
||||
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
|
||||
}
|
||||
|
||||
|
@ -114,7 +116,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
|
||||
// State event
|
||||
|
||||
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
||||
if (roomSync.state?.events?.isNotEmpty() == true) {
|
||||
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
|
||||
?: Int.MIN_VALUE
|
||||
val untimelinedStateIndex = minStateIndex + 1
|
||||
|
@ -125,7 +127,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
roomMemberEventHandler.handle(realm, roomId, event)
|
||||
}
|
||||
}
|
||||
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
|
||||
if (roomSync.timeline?.events?.isNotEmpty() == true) {
|
||||
val chunkEntity = handleTimelineEvents(
|
||||
realm,
|
||||
roomEntity,
|
||||
|
@ -141,7 +143,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
it.type == EventType.STATE_ROOM_MEMBER
|
||||
} != null
|
||||
|
||||
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, updateMembers = hasRoomMember)
|
||||
roomSummaryUpdater.update(
|
||||
realm,
|
||||
roomId,
|
||||
Membership.JOIN,
|
||||
roomSync.summary,
|
||||
roomSync.unreadNotifications,
|
||||
updateMembers = hasRoomMember,
|
||||
ephemeralResult = ephemeralResult)
|
||||
return roomEntity
|
||||
}
|
||||
|
||||
|
@ -215,16 +224,33 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
return chunkEntity
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
data class EphemeralResult(
|
||||
val typingUserIds: List<String> = emptyList()
|
||||
)
|
||||
|
||||
private fun handleEphemeral(realm: Realm,
|
||||
roomId: String,
|
||||
ephemeral: RoomSyncEphemeral,
|
||||
isInitialSync: Boolean) {
|
||||
isInitialSync: Boolean): EphemeralResult {
|
||||
var result = EphemeralResult()
|
||||
for (event in ephemeral.events) {
|
||||
if (event.type != EventType.RECEIPT) continue
|
||||
val readReceiptContent = event.content as? ReadReceiptContent ?: continue
|
||||
readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync)
|
||||
when (event.type) {
|
||||
EventType.RECEIPT -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(event.content as? ReadReceiptContent)?.let { readReceiptContent ->
|
||||
readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync)
|
||||
}
|
||||
}
|
||||
EventType.TYPING -> {
|
||||
event.content.toModel<TypingEventContent>()?.let { typingEventContent ->
|
||||
result = result.copy(typingUserIds = typingEventContent.typingUserIds)
|
||||
}
|
||||
}
|
||||
else -> Timber.w("Ephemeral event type '${event.type}' not yet supported")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
onStart(isInitialSync)
|
||||
// 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)
|
||||
|
|
|
@ -272,4 +272,7 @@
|
|||
<string name="notice_room_canonical_alias_set">"%1$s set the main address for this room to %2$s."</string>
|
||||
<string name="notice_room_canonical_alias_unset">"%1$s removed the main address for this room."</string>
|
||||
|
||||
<string name="notice_room_guest_access_can_join">"%1$s has allowed guests to join the room."</string>
|
||||
<string name="notice_room_guest_access_forbidden">"%1$s has prevented guests from joining the room."</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
|||
}
|
||||
|
||||
ext.versionMajor = 0
|
||||
ext.versionMinor = 12
|
||||
ext.versionMinor = 13
|
||||
ext.versionPatch = 0
|
||||
|
||||
static def getGitTimestamp() {
|
||||
|
@ -24,12 +24,16 @@ static def getGitTimestamp() {
|
|||
}
|
||||
|
||||
static def generateVersionCodeFromTimestamp() {
|
||||
// It's unix timestamp divided by 10: It's incremented by one every 10 seconds.
|
||||
return (getGitTimestamp() / 10).toInteger()
|
||||
// It's unix timestamp, minus timestamp of October 3rd 2018 (first commit date) divided by 100: It's incremented by one every 100 seconds.
|
||||
// plus 20_000_000 for compatibility reason with the previous way the Version Code was computed
|
||||
// Note that the result will be multiplied by 10 when adding the digit for the arch
|
||||
return ((getGitTimestamp() - 1_538_524_800 ) / 100).toInteger() + 20_000_000
|
||||
}
|
||||
|
||||
def generateVersionCodeFromVersionName() {
|
||||
return versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch
|
||||
// plus 4_000_000 for compatibility reason with the previous way the Version Code was computed
|
||||
// Note that the result will be multiplied by 10 when adding the digit for the arch
|
||||
return (versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch) + 4_000_000
|
||||
}
|
||||
|
||||
def getVersionCode() {
|
||||
|
@ -77,8 +81,8 @@ project.android.buildTypes.all { buildType ->
|
|||
]
|
||||
}
|
||||
|
||||
// map for the version codes
|
||||
// x86 must have greater values than arm, see https://software.intel.com/en-us/android/articles/google-play-supports-cpu-architecture-filtering-for-multiple-apk
|
||||
// map for the version codes last digit
|
||||
// x86 must have greater values than arm
|
||||
// 64 bits have greater value than 32 bits
|
||||
ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].withDefault { 0 }
|
||||
|
||||
|
@ -144,7 +148,7 @@ android {
|
|||
variant.outputs.each { output ->
|
||||
def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI))
|
||||
// Known limitation: it does not modify the value in the BuildConfig.java generated file
|
||||
output.versionCodeOverride = baseAbiVersionCode * 10_000_000 + variant.versionCode
|
||||
output.versionCodeOverride = variant.versionCode * 10 + baseAbiVersionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -30,20 +30,23 @@ import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragm
|
|||
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
|
||||
|
@ -259,6 +262,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)
|
||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.riotx.features.MainActivity
|
|||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
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()
|
||||
}
|
28
vector/src/main/java/im/vector/riotx/core/epoxy/Listener.kt
Normal file
28
vector/src/main/java/im/vector/riotx/core/epoxy/Listener.kt
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -16,14 +16,13 @@
|
|||
|
||||
package im.vector.riotx.core.error
|
||||
|
||||
import im.vector.riotx.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* throw in debug, only log in production. As this method does not always throw, next statement should be a return
|
||||
*/
|
||||
fun fatalError(message: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
fun fatalError(message: String, failFast: Boolean) {
|
||||
if (failFast) {
|
||||
error(message)
|
||||
} else {
|
||||
Timber.e(message)
|
||||
|
|
|
@ -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
|
||||
?: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,11 +224,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
mainActivityStarted = true
|
||||
|
||||
MainActivity.restartApp(this,
|
||||
MainActivityArgs(
|
||||
clearCredentials = !globalError.softLogout,
|
||||
isUserLoggedOut = true,
|
||||
isSoftLogout = globalError.softLogout
|
||||
)
|
||||
MainActivityArgs(
|
||||
clearCredentials = !globalError.softLogout,
|
||||
isUserLoggedOut = true,
|
||||
isSoftLogout = globalError.softLogout
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
supportActionBar?.let {
|
||||
it.setDisplayShowHomeEnabled(displayBack)
|
||||
it.setDisplayHomeAsUpEnabled(displayBack)
|
||||
it.title = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue