Merge branch 'develop' into feature/db_clean_up

This commit is contained in:
ganfra 2020-07-03 10:21:48 +02:00
commit 3648d6292a
29 changed files with 367 additions and 65 deletions

View File

@ -1,6 +1,37 @@
Changes in RiotX 0.23.0 (2020-XX-XX)
Changes in Riot.imX 0.91.4 (2020-XX-XX)
===================================================
Features ✨:
-
Improvements 🙌:
-
Bugfix 🐛:
- Fix crash when coming from a notification (#1601)
- Fix Exception when importing keys (#1576)
Translations 🗣:
-
SDK API changes ⚠️:
-
Build 🧱:
-
Other changes:
-
Changes in Riot.imX 0.91.3 (2020-07-01)
===================================================
Notes:
- This version is the third beta version of RiotX codebase published as Riot-Android on the PlayStore.
- Changelog below includes changes of v0.91.0, v0.91.1, and v0.91.2, because the first beta versions have been tagged and
published from the branch feature/migration_from_legacy.
- This version uses temporary name `Riot.imX`, to distinguish the app with RiotX app.
Features ✨:
- Call with WebRTC support (##611)
- Add capability to change the display name (#1529)
@ -13,6 +44,8 @@ Improvements 🙌:
- Update user avatar (#1054)
- Allow self-signed certificate (#1564)
- Improve file download and open in timeline
- Catchup tab is removed temporarily (#1565)
- Render room avatar change (#1319)
Bugfix 🐛:
- Fix dark theme issue on login screen (#1097)
@ -21,12 +54,7 @@ Bugfix 🐛:
- Use vendor prefix for non merged MSC (#1537)
- Compress images before sending (#1333)
- Searching by displayname is case sensitive (#1468)
Translations 🗣:
-
SDK API changes ⚠️:
-
- Fix layout overlap issue (#1407)
Build 🧱:
- Enable code optimization (Proguard)

View File

@ -64,3 +64,19 @@
### Webrtc
-keep class org.webrtc.** { *; }
### Serializable persisted classes
# https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}

View File

@ -127,6 +127,12 @@ interface Session :
*/
fun getSyncStateLive(): LiveData<SyncState>
/**
* This method returns the current sync state.
* @return the current [SyncState].
*/
fun getSyncState(): SyncState
/**
* This methods return true if an initial sync has been processed
*/

View File

@ -176,13 +176,14 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
}
}
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper2
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
realm.schema.get("OlmInboundGroupSessionEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
val newOlmInboundGroupSessionWrapper2 = OlmInboundGroupSessionWrapper2()
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
.apply {
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
roomId = mxOlmInboundGroupSession2.mRoomId
@ -191,7 +192,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
}
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper2))
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
}
} catch (e: Exception) {
Timber.e(e, "Error")

View File

@ -175,7 +175,7 @@ internal object CertUtil {
}
}
val trustPinned = arrayOf<TrustManager>(PinnedTrustManager(hsConfig.allowedFingerprints, defaultTrustManager))
val trustPinned = arrayOf<TrustManager>(PinnedTrustManagerProvider.provide(hsConfig.allowedFingerprints, defaultTrustManager))
val sslSocketFactory: SSLSocketFactory
@ -239,12 +239,12 @@ internal object CertUtil {
fun newConnectionSpecs(hsConfig: HomeServerConnectionConfig): List<ConnectionSpec> {
val builder = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
val tlsVersions = hsConfig.tlsVersions
if (null != tlsVersions) {
if (null != tlsVersions && tlsVersions.isNotEmpty()) {
builder.tlsVersions(*tlsVersions.toTypedArray())
}
val tlsCipherSuites = hsConfig.tlsCipherSuites
if (null != tlsCipherSuites) {
if (null != tlsCipherSuites && tlsCipherSuites.isNotEmpty()) {
builder.cipherSuites(*tlsCipherSuites.toTypedArray())
}
@ -252,7 +252,8 @@ internal object CertUtil {
builder.supportsTlsExtensions(hsConfig.shouldAcceptTlsExtensions)
val list = ArrayList<ConnectionSpec>()
list.add(builder.build())
if (hsConfig.allowHttpExtension) {
// TODO: we should display a warning if user enter an http url
if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) {
list.add(ConnectionSpec.CLEARTEXT)
}
return list

View File

@ -27,26 +27,23 @@ import javax.net.ssl.X509TrustManager
*/
/**
* @param fingerprints An array of SHA256 cert fingerprints
* @param fingerprints Not empty array of SHA256 cert fingerprints
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
* any of the fingerprints. Can be null.
*/
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>,
private val defaultTrustManager: X509TrustManager?) : X509TrustManager {
// Set to false to perform some test
private val USE_DEFAULT_TRUST_MANAGER = true
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, s: String) {
try {
if (defaultTrustManager != null && USE_DEFAULT_TRUST_MANAGER) {
if (defaultTrustManager != null) {
defaultTrustManager.checkClientTrusted(chain, s)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isNullOrEmpty()) {
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
}
}
@ -57,13 +54,13 @@ internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
try {
if (defaultTrustManager != null && USE_DEFAULT_TRUST_MANAGER) {
if (defaultTrustManager != null) {
defaultTrustManager.checkServerTrusted(chain, s)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints == null || fingerprints.isEmpty()) {
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
}
}
@ -75,22 +72,12 @@ internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
private fun checkTrusted(chain: Array<X509Certificate>) {
val cert = chain[0]
var found = false
if (fingerprints != null) {
for (allowedFingerprint in fingerprints) {
if (allowedFingerprint.matchesCert(cert)) {
found = true
break
}
}
}
if (!found) {
if (!fingerprints.any { it.matchesCert(cert) }) {
throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null)
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return emptyArray()
return defaultTrustManager?.acceptedIssuers ?: emptyArray()
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.network.ssl
import android.os.Build
import androidx.annotation.RequiresApi
import java.net.Socket
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLEngine
import javax.net.ssl.X509ExtendedTrustManager
/**
* Implements a TrustManager that checks Certificates against an explicit list of known
* fingerprints.
*/
/**
* @param fingerprints An array of SHA256 cert fingerprints
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
* any of the fingerprints. Can be null.
*/
@RequiresApi(Build.VERSION_CODES.N)
internal class PinnedTrustManagerApi24(private val fingerprints: List<Fingerprint>,
private val defaultTrustManager: X509ExtendedTrustManager?) : X509ExtendedTrustManager() {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, engine: SSLEngine?) {
try {
if (defaultTrustManager != null) {
defaultTrustManager.checkClientTrusted(chain, authType, engine)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
}
}
checkTrusted(chain)
}
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket?) {
try {
if (defaultTrustManager != null) {
defaultTrustManager.checkClientTrusted(chain, authType, socket)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
}
}
checkTrusted(chain)
}
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
try {
if (defaultTrustManager != null) {
defaultTrustManager.checkClientTrusted(chain, authType)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
}
}
checkTrusted(chain)
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket?) {
try {
if (defaultTrustManager != null) {
defaultTrustManager.checkServerTrusted(chain, authType, socket)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
}
}
checkTrusted(chain)
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, engine: SSLEngine?) {
try {
if (defaultTrustManager != null) {
defaultTrustManager.checkServerTrusted(chain, authType, engine)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
}
}
checkTrusted(chain)
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
try {
if (defaultTrustManager != null) {
defaultTrustManager.checkServerTrusted(chain, s)
return
}
} catch (e: CertificateException) {
// If there is an exception we fall back to checking fingerprints
if (fingerprints.isEmpty()) {
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
}
}
checkTrusted(chain)
}
@Throws(CertificateException::class)
private fun checkTrusted(chain: Array<X509Certificate>) {
val cert = chain[0]
if (!fingerprints.any { it.matchesCert(cert) }) {
throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null)
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return defaultTrustManager?.acceptedIssuers ?: emptyArray()
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.network.ssl
import android.os.Build
import javax.net.ssl.X509ExtendedTrustManager
import javax.net.ssl.X509TrustManager
internal object PinnedTrustManagerProvider {
// Set to false to perform some tests
private const val USE_DEFAULT_TRUST_MANAGER = true
fun provide(fingerprints: List<Fingerprint>?,
defaultTrustManager: X509TrustManager?): X509TrustManager {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && defaultTrustManager is X509ExtendedTrustManager) {
PinnedTrustManagerApi24(
fingerprints.orEmpty(),
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER }
)
} else {
PinnedTrustManager(
fingerprints.orEmpty(),
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER }
)
}
}
}

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.session
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
@ -45,7 +44,6 @@ import im.vector.matrix.android.api.session.securestorage.SecureStorageService
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.session.typing.TypingUsersTracker
import im.vector.matrix.android.api.session.user.UserService
@ -254,9 +252,9 @@ internal class DefaultSession @Inject constructor(
eventBus.unregister(this)
}
override fun getSyncStateLive(): LiveData<SyncState> {
return getSyncThread().liveState()
}
override fun getSyncStateLive() = getSyncThread().liveState()
override fun getSyncState() = getSyncThread().currentState()
override fun hasAlreadySynced(): Boolean {
return syncTokenStore.getLastToken() != null

View File

@ -65,6 +65,7 @@ internal class RoomSummaryUpdater @Inject constructor(
EventType.MESSAGE,
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
EventType.CALL_INVITE,

View File

@ -104,7 +104,7 @@ abstract class SyncService : Service() {
try {
syncTask.execute(params)
// Start sync if we were doing an initial sync and the syncThread is not launched yet
if (isInitialSync && session.getSyncStateLive().value == SyncState.Idle) {
if (isInitialSync && session.getSyncState() == SyncState.Idle) {
val isForeground = !backgroundDetectionObserver.isInBackground
session.startSync(isForeground)
}

View File

@ -51,7 +51,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
private var state: SyncState = SyncState.Idle
private var liveState = MutableLiveData<SyncState>()
private var liveState = MutableLiveData<SyncState>(state)
private val lock = Object()
private val syncScope = CoroutineScope(SupervisorJob())
private val debouncer = Debouncer(createUIHandler())
@ -98,6 +98,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
lock.notify()
}
fun currentState() = state
fun liveState(): LiveData<SyncState> {
return liveState
}

View File

@ -37,6 +37,8 @@
<string name="notice_display_name_removed_by_you">You removed your display name (it was %1$s)</string>
<string name="notice_room_topic_changed">%1$s changed the topic to: %2$s</string>
<string name="notice_room_topic_changed_by_you">You changed the topic to: %1$s</string>
<string name="notice_room_avatar_changed">%1$s changed the room avatar</string>
<string name="notice_room_avatar_changed_by_you">You changed the room avatar</string>
<string name="notice_room_name_changed">%1$s changed the room name to: %2$s</string>
<string name="notice_room_name_changed_by_you">You changed the room name to: %1$s</string>
<string name="notice_placed_video_call">%s placed a video call.</string>
@ -71,6 +73,8 @@
<string name="notice_room_name_removed_by_you">You removed the room name</string>
<string name="notice_room_topic_removed">%1$s removed the room topic</string>
<string name="notice_room_topic_removed_by_you">You removed the room topic</string>
<string name="notice_room_avatar_removed">%1$s removed the room avatar</string>
<string name="notice_room_avatar_removed_by_you">You removed the room avatar</string>
<string name="notice_event_redacted">Message removed</string>
<string name="notice_event_redacted_by">Message removed by %1$s</string>
<string name="notice_event_redacted_with_reason">Message removed [reason: %1$s]</string>

View File

@ -17,7 +17,7 @@ androidExtensions {
// Note: 2 digits max for each value
ext.versionMajor = 0
ext.versionMinor = 91
ext.versionPatch = 2
ext.versionPatch = 4
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'

View File

@ -20,4 +20,7 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep class im.vector.riotx.features.** { *; }
-keep class im.vector.riotx.features.** { *; }
## print all the rules in a file
# -printconfiguration ../proguard_files/full-r8-config.txt

View File

@ -32,6 +32,7 @@ import im.vector.riotx.core.extensions.startSyncing
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.utils.deleteAllFiles
import im.vector.riotx.features.home.HomeActivity
import im.vector.riotx.features.home.ShortcutsHandler
import im.vector.riotx.features.login.LoginActivity
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.settings.VectorPreferences
@ -82,6 +83,7 @@ class MainActivity : VectorBaseActivity() {
@Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var uiStateRepository: UiStateRepository
@Inject lateinit var shortcutsHandler: ShortcutsHandler
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
@ -105,6 +107,9 @@ class MainActivity : VectorBaseActivity() {
// Dismiss all notifications
notificationDrawerManager.clearAllEvents()
notificationDrawerManager.persistInfo()
// Also clear the dynamic shortcuts
shortcutsHandler.clearShortcuts()
}
private fun parseArgs(): MainActivityArgs {

View File

@ -117,8 +117,12 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
.observeOn(Schedulers.computation())
.map { it.asSequence() }
.subscribe { summaries ->
val invites = summaries
.filter { it.membership == Membership.INVITE }
val invitesDm = summaries
.filter { it.membership == Membership.INVITE && it.isDirect }
.count()
val invitesRoom = summaries
.filter { it.membership == Membership.INVITE && it.isDirect.not() }
.count()
val peopleNotifications = summaries
@ -139,12 +143,12 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
setState {
copy(
notificationCountCatchup = peopleNotifications + roomsNotifications + invites,
notificationCountCatchup = peopleNotifications + roomsNotifications + invitesDm + invitesRoom,
notificationHighlightCatchup = peopleHasHighlight || roomsHasHighlight,
notificationCountPeople = peopleNotifications,
notificationHighlightPeople = peopleHasHighlight,
notificationCountRooms = roomsNotifications,
notificationHighlightRooms = roomsHasHighlight
notificationCountPeople = peopleNotifications + invitesDm,
notificationHighlightPeople = peopleHasHighlight || invitesDm > 0,
notificationCountRooms = roomsNotifications + invitesRoom,
notificationHighlightRooms = roomsHasHighlight || invitesRoom > 0
)
}
}

View File

@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.sync.SyncState
data class HomeDetailViewState(
val groupSummary: Option<GroupSummary> = Option.empty(),
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val displayMode: RoomListDisplayMode = RoomListDisplayMode.HOME,
val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE,
val notificationCountCatchup: Int = 0,
val notificationHighlightCatchup: Boolean = false,
val notificationCountPeople: Int = 0,

View File

@ -17,6 +17,7 @@
package im.vector.riotx.features.home
import android.content.Context
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.os.Build
import androidx.core.content.pm.ShortcutInfoCompat
@ -26,6 +27,7 @@ import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
@ -51,6 +53,11 @@ class ShortcutsHandler @Inject constructor(
}
fun observeRoomsAndBuildShortcuts(): Disposable {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
// No op
return Observable.empty<Unit>().subscribe()
}
return homeRoomListStore
.observe()
.distinct()
@ -78,6 +85,25 @@ class ShortcutsHandler @Inject constructor(
}
}
fun clearShortcuts() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
// No op
return
}
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
// We can only disabled pinned shortcuts with the API, but at least it will prevent the crash
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
context.getSystemService(ShortcutManager::class.java)
?.let {
it.disableShortcuts(it.pinnedShortcuts.map { pinnedShortcut -> pinnedShortcut.id })
}
}
}
}
// PRIVATE API *********************************************************************************
private fun Bitmap.toProfileImageIcon(): IconCompat {

View File

@ -179,6 +179,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_ALIASES,
EventType.STATE_ROOM_CANONICAL_ALIAS,

View File

@ -48,6 +48,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_ROOM_TOMBSTONE,
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_ALIASES,
EventType.STATE_ROOM_CANONICAL_ALIAS,

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.GuestAccess
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
@ -57,6 +58,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root)
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
@ -149,6 +151,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName)
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName)
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName)
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName)
EventType.CALL_INVITE,
@ -220,6 +223,23 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
}
}
private fun formatRoomAvatarEvent(event: Event, senderName: String?): CharSequence? {
val content = event.getClearContent().toModel<RoomAvatarContent>() ?: return null
return if (content.avatarUrl.isNullOrEmpty()) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_avatar_removed_by_you)
} else {
sp.getString(R.string.notice_room_avatar_removed, senderName)
}
} else {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_avatar_changed_by_you)
} else {
sp.getString(R.string.notice_room_avatar_changed, senderName)
}
}
}
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null

View File

@ -28,6 +28,7 @@ object TimelineDisplayableEvents {
EventType.STATE_ROOM_WIDGET,
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_ALIASES,
EventType.STATE_ROOM_CANONICAL_ALIAS,

View File

@ -30,8 +30,8 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) :
return when (displayMode) {
RoomListDisplayMode.HOME ->
roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty()
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive()
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive()
RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
}
}

View File

@ -205,7 +205,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
@SuppressLint("NewApi")
fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
// build the pending intent go to the home screen if this is clicked.
val i = Intent(context, HomeActivity::class.java)
val i = HomeActivity.newIntent(context)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
val pi = PendingIntent.getActivity(context, 0, i, 0)
@ -307,7 +307,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
val answerCallPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(VectorCallActivity.newIntent(
context = context,
callId = callId,
@ -459,7 +459,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
)
val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
// TODO other userId
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
@ -651,7 +651,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
stringProvider.getString(R.string.join),
joinIntentPendingIntent)
val contentIntent = Intent(context, HomeActivity::class.java)
val contentIntent = HomeActivity.newIntent(context)
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
contentIntent.data = Uri.parse("foobar://" + inviteNotifiableEvent.eventId)
@ -689,7 +689,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
.setColor(accentColor)
.setAutoCancel(true)
.apply {
val contentIntent = Intent(context, HomeActivity::class.java)
val contentIntent = HomeActivity.newIntent(context)
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
contentIntent.data = Uri.parse("foobar://" + simpleNotifiableEvent.eventId)
@ -718,7 +718,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
// Recreate the back stack
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(roomIntentTap)
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
}

View File

@ -171,6 +171,7 @@ object ThemeUtils {
* @param resourceId the resource id in the light theme
* @return the resource Id for the current theme
*/
// TODO Now that we are API 21, this is not necessary anymore
fun getResourceId(c: Context, resourceId: Int): Int {
val theme = getApplicationTheme(c)

View File

@ -36,7 +36,7 @@ class SharedPreferencesUiStateRepository @Inject constructor(private val sharedP
return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) {
VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
else -> RoomListDisplayMode.HOME
else -> RoomListDisplayMode.PEOPLE // RoomListDisplayMode.HOME
}
}

View File

@ -30,13 +30,13 @@
<TextView
android:id="@+id/formSwitchSummary"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textColor="?riotx_text_secondary"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@+id/formSwitchDivider"
app:layout_constraintEnd_toStartOf="@+id/formSwitchSwitch"
app:layout_constraintStart_toStartOf="@+id/formSwitchTitle"
app:layout_constraintTop_toBottomOf="@+id/formSwitchTitle"
tools:text="@string/create_room_public_description" />
@ -46,7 +46,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="19dp"
android:layout_marginRight="19dp"
app:layout_constraintBottom_toTopOf="@+id/formSwitchDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -5,7 +5,8 @@
android:id="@+id/bottom_action_home"
android:enabled="true"
android:icon="@drawable/ic_home_bottom_catchup"
android:title="@string/bottom_action_home" />
android:title="@string/bottom_action_home"
android:visible="false" />
<item
android:id="@+id/bottom_action_people"