mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge develop into feature/fre/apply_push_rules_after_decryption
This commit is contained in:
commit
4146b5511a
@ -26,10 +26,10 @@ buildscript {
|
||||
classpath libs.gradle.hiltPlugin
|
||||
classpath 'com.google.firebase:firebase-appdistribution-gradle:3.2.0'
|
||||
classpath 'com.google.gms:google-services:4.3.15'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
|
||||
classpath "com.likethesalad.android:stem-plugin:2.3.0"
|
||||
classpath 'org.owasp:dependency-check-gradle:8.1.0'
|
||||
classpath 'org.owasp:dependency-check-gradle:8.1.2'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
|
||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
|
||||
|
1
changelog.d/8157.feature
Normal file
1
changelog.d/8157.feature
Normal file
@ -0,0 +1 @@
|
||||
Add aggregated unread indicator for spaces in the new layout
|
1
changelog.d/8168.bugfix
Normal file
1
changelog.d/8168.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix timeline loading a wrong room on permalink if a matching event id is found in a different room
|
1
changelog.d/8171.bugfix
Normal file
1
changelog.d/8171.bugfix
Normal file
@ -0,0 +1 @@
|
||||
[Rich text editor] Fix code appearance
|
1
changelog.d/8190.bugfix
Normal file
1
changelog.d/8190.bugfix
Normal file
@ -0,0 +1 @@
|
||||
[Poll history] Fixing small issue about font style
|
@ -11,17 +11,17 @@ def gradle = "7.4.1"
|
||||
def kotlin = "1.8.10"
|
||||
def kotlinCoroutines = "1.6.4"
|
||||
def dagger = "2.45"
|
||||
def firebaseBom = "31.2.1"
|
||||
def firebaseBom = "31.2.2"
|
||||
def appDistribution = "16.0.0-beta05"
|
||||
def retrofit = "2.9.0"
|
||||
def markwon = "4.6.2"
|
||||
def moshi = "1.14.0"
|
||||
def lifecycle = "2.5.1"
|
||||
def flowBinding = "1.2.0"
|
||||
def flipper = "0.182.0"
|
||||
def flipper = "0.183.0"
|
||||
def epoxy = "5.0.0"
|
||||
def mavericks = "3.0.1"
|
||||
def glide = "4.14.2"
|
||||
def glide = "4.15.0"
|
||||
def bigImageViewer = "1.8.1"
|
||||
def jjwt = "0.11.5"
|
||||
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
||||
@ -59,7 +59,7 @@ ext.libs = [
|
||||
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
|
||||
'fragmentTestingManifest' : "androidx.fragment:fragment-testing-manifest:$fragment",
|
||||
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4",
|
||||
'work' : "androidx.work:work-runtime-ktx:2.7.1",
|
||||
'work' : "androidx.work:work-runtime-ktx:2.8.0",
|
||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
|
||||
'junit' : "androidx.test.ext:junit:1.1.5",
|
||||
@ -70,7 +70,7 @@ ext.libs = [
|
||||
'datastore' : "androidx.datastore:datastore:1.0.0",
|
||||
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
|
||||
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
|
||||
'coreTesting' : "androidx.arch.core:core-testing:2.1.0",
|
||||
'coreTesting' : "androidx.arch.core:core-testing:2.2.0",
|
||||
'testCore' : "androidx.test:core:$androidxTest",
|
||||
'orchestrator' : "androidx.test:orchestrator:$androidxOrchestrator",
|
||||
'testRunner' : "androidx.test:runner:$androidxTest",
|
||||
@ -79,7 +79,7 @@ ext.libs = [
|
||||
'espressoContrib' : "androidx.test.espresso:espresso-contrib:$espresso",
|
||||
'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso",
|
||||
'viewpager2' : "androidx.viewpager2:viewpager2:1.0.0",
|
||||
'transition' : "androidx.transition:transition:1.2.0",
|
||||
'transition' : "androidx.transition:transition:1.4.1",
|
||||
],
|
||||
google : [
|
||||
'material' : "com.google.android.material:material:1.8.0",
|
||||
@ -88,7 +88,7 @@ ext.libs = [
|
||||
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
|
||||
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.6"
|
||||
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.7"
|
||||
],
|
||||
dagger : [
|
||||
'dagger' : "com.google.dagger:dagger:$dagger",
|
||||
@ -103,7 +103,7 @@ ext.libs = [
|
||||
],
|
||||
element : [
|
||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||
'wysiwyg' : "io.element.android:wysiwyg:1.0.0"
|
||||
'wysiwyg' : "io.element.android:wysiwyg:1.1.1"
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||
|
2
library/external/jsonviewer/build.gradle
vendored
2
library/external/jsonviewer/build.gradle
vendored
@ -65,7 +65,7 @@ dependencies {
|
||||
implementation libs.jetbrains.coroutinesCore
|
||||
implementation libs.jetbrains.coroutinesAndroid
|
||||
|
||||
testImplementation 'org.json:json:20220924'
|
||||
testImplementation 'org.json:json:20230227'
|
||||
testImplementation libs.tests.junit
|
||||
androidTestImplementation libs.androidx.junit
|
||||
androidTestImplementation libs.androidx.espressoCore
|
||||
|
@ -297,7 +297,7 @@ internal fun updateThreadNotifications(roomId: String, realm: Realm, currentUser
|
||||
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId, threadId = rootThreadEventId) ?: return
|
||||
|
||||
val readReceiptChunk = ChunkEntity
|
||||
.findIncludingEvent(realm, readReceipt) ?: return
|
||||
.findIncludingEvent(realm, roomId, readReceipt) ?: return
|
||||
|
||||
val readReceiptChunkThreadEvents = readReceiptChunk
|
||||
.timelineEvents
|
||||
|
@ -72,15 +72,16 @@ internal fun ChunkEntity.Companion.findEventInThreadChunk(realm: Realm, roomId:
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
|
||||
internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, roomId: String, eventIds: List<String>): RealmResults<ChunkEntity> {
|
||||
return realm.where<ChunkEntity>()
|
||||
.equalTo(ChunkEntityFields.ROOM.ROOM_ID, roomId)
|
||||
.`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray())
|
||||
.isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID)
|
||||
.findAll()
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: String): ChunkEntity? {
|
||||
return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull()
|
||||
internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, roomId: String, eventId: String): ChunkEntity? {
|
||||
return findAllIncludingEvents(realm, roomId, listOf(eventId)).firstOrNull()
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.Companion.create(
|
||||
|
@ -76,11 +76,11 @@ private fun hasReadMissingEvent(realm: Realm,
|
||||
userId: String,
|
||||
eventId: String,
|
||||
threadId: String? = ReadService.THREAD_ID_MAIN): Boolean {
|
||||
return realm.doesEventExistInChunkHistory(eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId, threadId)
|
||||
return realm.doesEventExistInChunkHistory(roomId, eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId, threadId)
|
||||
}
|
||||
|
||||
private fun Realm.doesEventExistInChunkHistory(eventId: String): Boolean {
|
||||
return ChunkEntity.findIncludingEvent(this, eventId) != null
|
||||
private fun Realm.doesEventExistInChunkHistory(roomId: String, eventId: String): Boolean {
|
||||
return ChunkEntity.findIncludingEvent(this, roomId, eventId) != null
|
||||
}
|
||||
|
||||
private fun Realm.hasReadReceiptInLatestChunk(latestChunkEntity: ChunkEntity, roomId: String, userId: String, threadId: String?): Boolean {
|
||||
|
@ -59,7 +59,7 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor(
|
||||
?: throw IllegalStateException("No token found")
|
||||
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val chunkToUpdate = ChunkEntity.findIncludingEvent(realm, params.lastKnownEventId)
|
||||
val chunkToUpdate = ChunkEntity.findIncludingEvent(realm, params.roomId, params.lastKnownEventId)
|
||||
if (params.direction == PaginationDirection.FORWARDS) {
|
||||
chunkToUpdate?.nextToken = fromToken
|
||||
} else {
|
||||
|
@ -278,7 +278,7 @@ internal class LoadTimelineStrategy constructor(
|
||||
.findAll()
|
||||
}
|
||||
is Mode.Permalink -> {
|
||||
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
|
||||
ChunkEntity.findAllIncludingEvents(realm, roomId, listOf(mode.originEventId))
|
||||
}
|
||||
is Mode.Thread -> {
|
||||
recreateThreadChunkEntity(realm, mode.rootThreadEventId)
|
||||
|
@ -199,7 +199,7 @@ dependencies {
|
||||
implementation 'com.github.hyuwah:DraggableView:1.0.0'
|
||||
|
||||
// Custom Tab
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'androidx.browser:browser:1.5.0'
|
||||
|
||||
// Passphrase strength helper
|
||||
implementation 'com.nulab-inc:zxcvbn:1.7.0'
|
||||
|
@ -40,6 +40,7 @@ import im.vector.app.features.discovery.DiscoverySettingsViewModel
|
||||
import im.vector.app.features.discovery.change.SetIdentityServerViewModel
|
||||
import im.vector.app.features.home.HomeActivityViewModel
|
||||
import im.vector.app.features.home.HomeDetailViewModel
|
||||
import im.vector.app.features.home.NewHomeDetailViewModel
|
||||
import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel
|
||||
import im.vector.app.features.home.UnreadMessagesSharedViewModel
|
||||
import im.vector.app.features.home.UserColorAccountDataViewModel
|
||||
@ -717,4 +718,9 @@ interface MavericksViewModelModule {
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(RoomPollDetailViewModel::class)
|
||||
fun roomPollDetailViewModelFactory(factory: RoomPollDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(NewHomeDetailViewModel::class)
|
||||
fun newHomeDetailViewModelFactory(factory: NewHomeDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.home
|
||||
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import im.vector.app.features.spaces.GetSpacesUseCase
|
||||
import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetSpacesNotificationBadgeStateUseCase @Inject constructor(
|
||||
private val getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase,
|
||||
private val getSpacesUseCase: GetSpacesUseCase,
|
||||
) {
|
||||
|
||||
fun execute(): Flow<UnreadCounterBadgeView.State> {
|
||||
val params = spaceSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
displayName = QueryStringValue.IsNotEmpty
|
||||
}
|
||||
return combine(
|
||||
getNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter),
|
||||
getSpacesUseCase.execute(params),
|
||||
) { spacesNotificationCount, spaceInvites ->
|
||||
computeSpacesNotificationCounterBadgeState(spacesNotificationCount, spaceInvites)
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeSpacesNotificationCounterBadgeState(
|
||||
spacesNotificationCount: RoomAggregateNotificationCount,
|
||||
spaceInvites: List<RoomSummary>,
|
||||
): UnreadCounterBadgeView.State {
|
||||
val hasPendingSpaceInvites = spaceInvites.isNotEmpty()
|
||||
return if (hasPendingSpaceInvites && spacesNotificationCount.notificationCount == 0) {
|
||||
UnreadCounterBadgeView.State.Text(
|
||||
text = "!",
|
||||
highlighted = true,
|
||||
)
|
||||
} else {
|
||||
UnreadCounterBadgeView.State.Count(
|
||||
count = spacesNotificationCount.notificationCount,
|
||||
highlighted = spacesNotificationCount.isHighlight || hasPendingSpaceInvites,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.dialpad.PstnDialActivity
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
|
||||
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
|
||||
@ -82,6 +83,7 @@ class NewHomeDetailFragment :
|
||||
@Inject lateinit var buildMeta: BuildMeta
|
||||
|
||||
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
||||
private val newHomeDetailViewModel: NewHomeDetailViewModel by fragmentViewModel()
|
||||
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
|
||||
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel()
|
||||
|
||||
@ -180,6 +182,10 @@ class NewHomeDetailFragment :
|
||||
currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls())
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
newHomeDetailViewModel.onEach { viewState ->
|
||||
refreshUnreadCounterBadge(viewState.spacesNotificationCounterBadgeState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
@ -379,6 +385,10 @@ class NewHomeDetailFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshUnreadCounterBadge(badgeState: UnreadCounterBadgeView.State) {
|
||||
views.spacesUnreadCounterBadge.render(badgeState)
|
||||
}
|
||||
|
||||
override fun onTapToReturnToCall() {
|
||||
callManager.getCurrentCall()?.let { call ->
|
||||
VectorCallActivity.newIntent(
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.home
|
||||
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.EmptyAction
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class NewHomeDetailViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: NewHomeDetailViewState,
|
||||
private val getSpacesNotificationBadgeStateUseCase: GetSpacesNotificationBadgeStateUseCase,
|
||||
) : VectorViewModel<NewHomeDetailViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> {
|
||||
override fun create(initialState: NewHomeDetailViewState): NewHomeDetailViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
init {
|
||||
observeSpacesNotificationBadgeState()
|
||||
}
|
||||
|
||||
private fun observeSpacesNotificationBadgeState() {
|
||||
getSpacesNotificationBadgeStateUseCase.execute()
|
||||
.onEach { badgeState -> setState { copy(spacesNotificationCounterBadgeState = badgeState) } }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.home
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
|
||||
data class NewHomeDetailViewState(
|
||||
val spacesNotificationCounterBadgeState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State.Count(count = 0, highlighted = false),
|
||||
) : MavericksState
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.spaces
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetSpacesUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
) {
|
||||
|
||||
fun execute(params: SpaceSummaryQueryParams): Flow<List<RoomSummary>> {
|
||||
val session = activeSessionHolder.getSafeActiveSession()
|
||||
|
||||
return session?.flow()?.liveSpaceSummaries(params) ?: emptyFlow()
|
||||
}
|
||||
}
|
@ -29,16 +29,13 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
@ -48,25 +45,22 @@ import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.session.space.SpaceOrderUtils
|
||||
import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent
|
||||
import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
|
||||
class SpaceListViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: SpaceListViewState,
|
||||
private val spaceStateHandler: SpaceStateHandler,
|
||||
private val session: Session,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val autoAcceptInvites: AutoAcceptInvites,
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase,
|
||||
private val getSpacesUseCase: GetSpacesUseCase,
|
||||
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
@ -92,39 +86,14 @@ class SpaceListViewModel @AssistedInject constructor(
|
||||
copy(selectedSpace = selectedSpaceOption.orNull())
|
||||
}
|
||||
|
||||
// XXX there should be a way to refactor this and share it
|
||||
session.roomService().getPagedRoomSummariesLive(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
this.spaceFilter = roomsInSpaceFilter()
|
||||
}, sortOrder = RoomSortOrder.NONE
|
||||
).asFlow()
|
||||
.sample(300)
|
||||
.onEach {
|
||||
val inviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.roomService().getRoomSummaries(
|
||||
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
|
||||
).size
|
||||
}
|
||||
val totalCount = session.roomService().getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
this.spaceFilter = roomsInSpaceFilter()
|
||||
}
|
||||
)
|
||||
val counts = RoomAggregateNotificationCount(
|
||||
totalCount.notificationCount + inviteCount,
|
||||
totalCount.highlightCount + inviteCount
|
||||
)
|
||||
getNotificationCountForSpacesUseCase.execute(roomsInSpaceFilter())
|
||||
.onEach { counts ->
|
||||
setState {
|
||||
copy(
|
||||
homeAggregateCount = counts
|
||||
)
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
@ -267,7 +236,7 @@ class SpaceListViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
combine(
|
||||
session.flow().liveSpaceSummaries(params),
|
||||
getSpacesUseCase.execute(params),
|
||||
session.accountDataService()
|
||||
.getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER))
|
||||
.asFlow()
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.spaces.notification
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetNotificationCountForSpacesUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val autoAcceptInvites: AutoAcceptInvites,
|
||||
) {
|
||||
|
||||
fun execute(spaceFilter: SpaceFilter): Flow<RoomAggregateNotificationCount> {
|
||||
val session = activeSessionHolder.getSafeActiveSession()
|
||||
|
||||
val spaceQueryParams = roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
this.spaceFilter = spaceFilter
|
||||
}
|
||||
return session
|
||||
?.roomService()
|
||||
?.getPagedRoomSummariesLive(queryParams = spaceQueryParams, sortOrder = RoomSortOrder.NONE)
|
||||
?.asFlow()
|
||||
?.sample(300)
|
||||
?.mapLatest {
|
||||
val inviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.roomService().getRoomSummaries(
|
||||
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
|
||||
).size
|
||||
}
|
||||
val totalCount = session.roomService().getNotificationCountForRooms(spaceQueryParams)
|
||||
RoomAggregateNotificationCount(
|
||||
notificationCount = totalCount.notificationCount + inviteCount,
|
||||
highlightCount = totalCount.highlightCount + inviteCount,
|
||||
)
|
||||
}
|
||||
?.flowOn(session.coroutineDispatchers.main)
|
||||
?: emptyFlow()
|
||||
}
|
||||
}
|
22
vector/src/main/res/drawable/bg_code_block.xml
Normal file
22
vector/src/main/res/drawable/bg_code_block.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 The Android Open Source Project
|
||||
~ Modifications Copyright 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?vctr_system"/>
|
||||
<stroke android:width="@dimen/code_block_border_width" android:color="?vctr_content_quinary"/>
|
||||
<corners android:radius="@dimen/code_block_border_radius"/>
|
||||
</shape>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 The Android Open Source Project
|
||||
~ Modifications Copyright 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?vctr_system"/>
|
||||
<stroke android:width="@dimen/inline_code_border_width" android:color="?vctr_content_quinary"/>
|
||||
<corners android:topLeftRadius="@dimen/inline_code_border_radius"
|
||||
android:bottomLeftRadius="@dimen/inline_code_border_radius"/>
|
||||
</shape>
|
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 The Android Open Source Project
|
||||
~ Modifications Copyright 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?vctr_system"/>
|
||||
<stroke android:width="@dimen/inline_code_border_width" android:color="?vctr_content_quinary"/>
|
||||
</shape>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 The Android Open Source Project
|
||||
~ Modifications Copyright 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?vctr_system"/>
|
||||
<stroke android:width="@dimen/inline_code_border_width" android:color="?vctr_content_quinary"/>
|
||||
<corners android:topRightRadius="@dimen/inline_code_border_radius"
|
||||
android:bottomRightRadius="@dimen/inline_code_border_radius"/>
|
||||
</shape>
|
22
vector/src/main/res/drawable/bg_inline_code_single_line.xml
Normal file
22
vector/src/main/res/drawable/bg_inline_code_single_line.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2018 The Android Open Source Project
|
||||
~ Modifications Copyright 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?vctr_system"/>
|
||||
<stroke android:width="@dimen/inline_code_border_width" android:color="?vctr_content_quinary"/>
|
||||
<corners android:radius="@dimen/inline_code_border_radius"/>
|
||||
</shape>
|
@ -126,6 +126,11 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/composerModeBarrier"
|
||||
app:bulletRadius="4sp"
|
||||
app:bulletGap="8sp"
|
||||
app:codeBlockBackgroundDrawable="@drawable/bg_code_block"
|
||||
app:inlineCodeSingleLineBg="@drawable/bg_inline_code_single_line"
|
||||
app:inlineCodeMultiLineBgLeft="@drawable/bg_inline_code_multi_line_left"
|
||||
app:inlineCodeMultiLineBgMid="@drawable/bg_inline_code_multi_line_mid"
|
||||
app:inlineCodeMultiLineBgRight="@drawable/bg_inline_code_multi_line_right"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
|
@ -120,6 +120,28 @@
|
||||
tools:targetApi="lollipop_mr1"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
android:id="@+id/spacesUnreadCounterBadge"
|
||||
style="@style/Widget.Vector.TextView.Micro"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:elevation="15dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="16dp"
|
||||
android:minHeight="16dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textColor="?colorOnError"
|
||||
app:layout_constraintCircle="@id/newLayoutOpenSpacesButton"
|
||||
app:layout_constraintCircleAngle="45"
|
||||
app:layout_constraintCircleRadius="24dp"
|
||||
tools:background="@drawable/bg_unread_highlight"
|
||||
tools:ignore="MissingConstraints"
|
||||
tools:text="147"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/newLayoutCreateChatButton"
|
||||
style="@style/Widget.Vector.FloatingActionButton"
|
||||
|
@ -54,7 +54,8 @@
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Body"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
android:textSize="17sp"
|
||||
android:textSize="15sp"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -8,4 +8,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:codeBlockBackgroundDrawable="@drawable/bg_code_block"
|
||||
app:inlineCodeSingleLineBg="@drawable/bg_inline_code_single_line"
|
||||
app:inlineCodeMultiLineBgLeft="@drawable/bg_inline_code_multi_line_left"
|
||||
app:inlineCodeMultiLineBgMid="@drawable/bg_inline_code_multi_line_mid"
|
||||
app:inlineCodeMultiLineBgRight="@drawable/bg_inline_code_multi_line_right"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:text="@sample/messages.json/data/message" />
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.home
|
||||
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import im.vector.app.features.spaces.GetSpacesUseCase
|
||||
import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase
|
||||
import im.vector.app.test.test
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
|
||||
internal class GetSpacesNotificationBadgeStateUseCaseTest {
|
||||
|
||||
private val fakeGetNotificationCountForSpacesUseCase = mockk<GetNotificationCountForSpacesUseCase>()
|
||||
private val fakeGetSpacesUseCase = mockk<GetSpacesUseCase>()
|
||||
|
||||
private val getSpacesNotificationBadgeStateUseCase = GetSpacesNotificationBadgeStateUseCase(
|
||||
getNotificationCountForSpacesUseCase = fakeGetNotificationCountForSpacesUseCase,
|
||||
getSpacesUseCase = fakeGetSpacesUseCase,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given flow of spaces invite and notification count then flow of state is correct`() = runTest {
|
||||
// Given
|
||||
val noSpacesInvite = emptyList<RoomSummary>()
|
||||
val existingSpaceInvite = listOf<RoomSummary>(mockk())
|
||||
val noNotification = RoomAggregateNotificationCount(
|
||||
notificationCount = 0,
|
||||
highlightCount = 0,
|
||||
)
|
||||
val existingNotificationNotHighlighted = RoomAggregateNotificationCount(
|
||||
notificationCount = 1,
|
||||
highlightCount = 0,
|
||||
)
|
||||
val existingNotificationHighlighted = RoomAggregateNotificationCount(
|
||||
notificationCount = 1,
|
||||
highlightCount = 1,
|
||||
)
|
||||
every { fakeGetSpacesUseCase.execute(any()) } returns
|
||||
flowOf(noSpacesInvite, existingSpaceInvite, existingSpaceInvite, noSpacesInvite, noSpacesInvite)
|
||||
every { fakeGetNotificationCountForSpacesUseCase.execute(any()) } returns
|
||||
flowOf(noNotification, noNotification, existingNotificationNotHighlighted, existingNotificationNotHighlighted, existingNotificationHighlighted)
|
||||
|
||||
// When
|
||||
val testObserver = getSpacesNotificationBadgeStateUseCase.execute().test(this)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
val expectedState1 = UnreadCounterBadgeView.State.Count(count = 0, highlighted = false)
|
||||
val expectedState2 = UnreadCounterBadgeView.State.Text(text = "!", highlighted = true)
|
||||
val expectedState3 = UnreadCounterBadgeView.State.Count(count = 1, highlighted = true)
|
||||
val expectedState4 = UnreadCounterBadgeView.State.Count(count = 1, highlighted = false)
|
||||
val expectedState5 = UnreadCounterBadgeView.State.Count(count = 1, highlighted = true)
|
||||
testObserver
|
||||
.assertValues(expectedState1, expectedState2, expectedState3, expectedState4, expectedState5)
|
||||
.finish()
|
||||
verify {
|
||||
fakeGetSpacesUseCase.execute(match {
|
||||
it.memberships == listOf(Membership.INVITE) && it.displayName == QueryStringValue.IsNotEmpty
|
||||
})
|
||||
}
|
||||
verify { fakeGetNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter) }
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.home
|
||||
|
||||
import com.airbnb.mvrx.test.MavericksTestRule
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import im.vector.app.test.test
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
internal class NewHomeDetailViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher())
|
||||
|
||||
private val initialState = NewHomeDetailViewState()
|
||||
private val fakeGetSpacesNotificationBadgeStateUseCase = mockk<GetSpacesNotificationBadgeStateUseCase>()
|
||||
|
||||
private fun createViewModel(): NewHomeDetailViewModel {
|
||||
return NewHomeDetailViewModel(
|
||||
initialState = initialState,
|
||||
getSpacesNotificationBadgeStateUseCase = fakeGetSpacesNotificationBadgeStateUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the viewModel is created then viewState is updated with space notifications badge state`() {
|
||||
// Given
|
||||
val aBadgeState = UnreadCounterBadgeView.State.Count(count = 1, highlighted = false)
|
||||
every { fakeGetSpacesNotificationBadgeStateUseCase.execute() } returns flowOf(aBadgeState)
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
|
||||
// Then
|
||||
val expectedViewState = initialState.copy(
|
||||
spacesNotificationCounterBadgeState = aBadgeState,
|
||||
)
|
||||
viewModelTest
|
||||
.assertLatestState(expectedViewState)
|
||||
.finish()
|
||||
verify {
|
||||
fakeGetSpacesNotificationBadgeStateUseCase.execute()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.spaces
|
||||
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||
import im.vector.app.test.fakes.givenAsFlow
|
||||
import im.vector.app.test.test
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||
|
||||
internal class GetSpacesUseCaseTest {
|
||||
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||
private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
|
||||
|
||||
private val getSpacesUseCase = GetSpacesUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
fakeFlowLiveDataConversions.setup()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given params when execute then the list of summaries is returned`() = runTest {
|
||||
// Given
|
||||
val queryParams = givenSpaceQueryParams()
|
||||
val firstSummaries = listOf<RoomSummary>(mockk())
|
||||
val nextSummaries = listOf<RoomSummary>(mockk())
|
||||
fakeActiveSessionHolder.fakeSession
|
||||
.fakeSpaceService
|
||||
.givenGetSpaceSummariesReturns(firstSummaries)
|
||||
fakeActiveSessionHolder.fakeSession
|
||||
.fakeSpaceService
|
||||
.givenGetSpaceSummariesLiveReturns(nextSummaries)
|
||||
.givenAsFlow()
|
||||
|
||||
// When
|
||||
val testObserver = getSpacesUseCase.execute(queryParams).test(this)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
testObserver
|
||||
.assertValues(firstSummaries, nextSummaries)
|
||||
.finish()
|
||||
verify {
|
||||
fakeActiveSessionHolder.fakeSession.fakeSpaceService.getSpaceSummaries(queryParams)
|
||||
fakeActiveSessionHolder.fakeSession.fakeSpaceService.getSpaceSummariesLive(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no active session when execute then empty flow is returned`() = runTest {
|
||||
// Given
|
||||
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||
val queryParams = givenSpaceQueryParams()
|
||||
|
||||
// When
|
||||
val testObserver = getSpacesUseCase.execute(queryParams).test(this)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
testObserver
|
||||
.assertNoValues()
|
||||
.finish()
|
||||
verify(inverse = true) {
|
||||
fakeActiveSessionHolder.fakeSession.fakeSpaceService.getSpaceSummaries(queryParams)
|
||||
fakeActiveSessionHolder.fakeSession.fakeSpaceService.getSpaceSummariesLive(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
private fun givenSpaceQueryParams(): SpaceSummaryQueryParams {
|
||||
return mockk()
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.features.spaces.notification
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.fakes.FakeAutoAcceptInvites
|
||||
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||
import im.vector.app.test.fakes.givenAsFlow
|
||||
import im.vector.app.test.test
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkAll
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
|
||||
internal class GetNotificationCountForSpacesUseCaseTest {
|
||||
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||
private val fakeAutoAcceptInvites = FakeAutoAcceptInvites()
|
||||
private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
|
||||
|
||||
private val getNotificationCountForSpacesUseCase = GetNotificationCountForSpacesUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
autoAcceptInvites = fakeAutoAcceptInvites,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
fakeFlowLiveDataConversions.setup()
|
||||
mockkStatic("kotlinx.coroutines.flow.FlowKt")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given space filter and auto accept invites when execute then correct notification count is returned`() = runTest {
|
||||
// given
|
||||
val spaceFilter = SpaceFilter.NoFilter
|
||||
val pagedList = mockk<PagedList<RoomSummary>>()
|
||||
val pagedListFlow = fakeActiveSessionHolder.fakeSession
|
||||
.fakeRoomService
|
||||
.givenGetPagedRoomSummariesLiveReturns(pagedList)
|
||||
.givenAsFlow()
|
||||
every { pagedListFlow.sample(any<Long>()) } returns pagedListFlow
|
||||
val expectedNotificationCount = RoomAggregateNotificationCount(
|
||||
notificationCount = 1,
|
||||
highlightCount = 0,
|
||||
)
|
||||
fakeActiveSessionHolder.fakeSession
|
||||
.fakeRoomService
|
||||
.givenGetNotificationCountForRoomsReturns(expectedNotificationCount)
|
||||
fakeAutoAcceptInvites._isEnabled = true
|
||||
|
||||
// When
|
||||
val testObserver = getNotificationCountForSpacesUseCase.execute(spaceFilter).test(this)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
testObserver
|
||||
.assertValues(expectedNotificationCount)
|
||||
.finish()
|
||||
verify {
|
||||
fakeActiveSessionHolder.fakeSession.fakeRoomService.getNotificationCountForRooms(
|
||||
queryParams = match { it.memberships == listOf(Membership.JOIN) && it.spaceFilter == spaceFilter }
|
||||
)
|
||||
fakeActiveSessionHolder.fakeSession.fakeRoomService.getPagedRoomSummariesLive(
|
||||
queryParams = match { it.memberships == listOf(Membership.JOIN) && it.spaceFilter == spaceFilter },
|
||||
pagedListConfig = any(),
|
||||
sortOrder = RoomSortOrder.NONE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given space filter and show invites when execute then correct notification count is returned`() = runTest {
|
||||
// given
|
||||
val spaceFilter = SpaceFilter.NoFilter
|
||||
val pagedList = mockk<PagedList<RoomSummary>>()
|
||||
val pagedListFlow = fakeActiveSessionHolder.fakeSession
|
||||
.fakeRoomService
|
||||
.givenGetPagedRoomSummariesLiveReturns(pagedList)
|
||||
.givenAsFlow()
|
||||
every { pagedListFlow.sample(any<Long>()) } returns pagedListFlow
|
||||
val notificationCount = RoomAggregateNotificationCount(
|
||||
notificationCount = 1,
|
||||
highlightCount = 0,
|
||||
)
|
||||
fakeActiveSessionHolder.fakeSession
|
||||
.fakeRoomService
|
||||
.givenGetNotificationCountForRoomsReturns(notificationCount)
|
||||
val invitedRooms = listOf<RoomSummary>(mockk())
|
||||
fakeActiveSessionHolder.fakeSession
|
||||
.fakeRoomService
|
||||
.givenGetRoomSummaries(invitedRooms)
|
||||
fakeAutoAcceptInvites._isEnabled = false
|
||||
val expectedNotificationCount = RoomAggregateNotificationCount(
|
||||
notificationCount = notificationCount.notificationCount + invitedRooms.size,
|
||||
highlightCount = notificationCount.highlightCount + invitedRooms.size,
|
||||
)
|
||||
|
||||
// When
|
||||
val testObserver = getNotificationCountForSpacesUseCase.execute(spaceFilter).test(this)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
testObserver
|
||||
.assertValues(expectedNotificationCount)
|
||||
.finish()
|
||||
verify {
|
||||
fakeActiveSessionHolder.fakeSession.fakeRoomService.getRoomSummaries(
|
||||
queryParams = match { it.memberships == listOf(Membership.INVITE) }
|
||||
)
|
||||
fakeActiveSessionHolder.fakeSession.fakeRoomService.getNotificationCountForRooms(
|
||||
queryParams = match { it.memberships == listOf(Membership.JOIN) && it.spaceFilter == spaceFilter }
|
||||
)
|
||||
fakeActiveSessionHolder.fakeSession.fakeRoomService.getPagedRoomSummariesLive(
|
||||
queryParams = match { it.memberships == listOf(Membership.JOIN) && it.spaceFilter == spaceFilter },
|
||||
pagedListConfig = any(),
|
||||
sortOrder = RoomSortOrder.NONE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no active session when execute then empty flow is returned`() = runTest {
|
||||
// given
|
||||
val spaceFilter = SpaceFilter.NoFilter
|
||||
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||
|
||||
// When
|
||||
val testObserver = getNotificationCountForSpacesUseCase.execute(spaceFilter).test(this)
|
||||
|
||||
// Then
|
||||
testObserver
|
||||
.assertNoValues()
|
||||
.finish()
|
||||
verify(inverse = true) {
|
||||
fakeActiveSessionHolder.fakeSession.fakeRoomService.getPagedRoomSummariesLive(
|
||||
queryParams = any(),
|
||||
pagedListConfig = any(),
|
||||
sortOrder = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class FakeFlowLiveDataConversions {
|
||||
@ -28,6 +29,8 @@ class FakeFlowLiveDataConversions {
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.givenAsFlow() {
|
||||
every { asFlow() } returns flowOf(value!!)
|
||||
fun <T> LiveData<T>.givenAsFlow(): Flow<T> {
|
||||
return flowOf(value!!).also {
|
||||
every { asFlow() } returns it
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,14 @@
|
||||
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.PagedList
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.session.room.RoomService
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
|
||||
class FakeRoomService(
|
||||
private val fakeRoom: FakeRoom = FakeRoom()
|
||||
@ -34,4 +38,18 @@ class FakeRoomService(
|
||||
fun set(roomSummary: RoomSummary?) {
|
||||
every { getRoomSummary(any()) } returns roomSummary
|
||||
}
|
||||
|
||||
fun givenGetPagedRoomSummariesLiveReturns(pagedList: PagedList<RoomSummary>): LiveData<PagedList<RoomSummary>> {
|
||||
return MutableLiveData(pagedList).also {
|
||||
every { getPagedRoomSummariesLive(queryParams = any(), pagedListConfig = any(), sortOrder = any()) } returns it
|
||||
}
|
||||
}
|
||||
|
||||
fun givenGetNotificationCountForRoomsReturns(roomAggregateNotificationCount: RoomAggregateNotificationCount) {
|
||||
every { getNotificationCountForRooms(queryParams = any()) } returns roomAggregateNotificationCount
|
||||
}
|
||||
|
||||
fun givenGetRoomSummaries(roomSummaries: List<RoomSummary>) {
|
||||
every { getRoomSummaries(any()) } returns roomSummaries
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ class FakeSession(
|
||||
val fakeUserService: FakeUserService = FakeUserService(),
|
||||
private val fakeEventService: FakeEventService = FakeEventService(),
|
||||
val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService(),
|
||||
val fakeSpaceService: FakeSpaceService = FakeSpaceService(),
|
||||
) : Session by mockk(relaxed = true) {
|
||||
|
||||
init {
|
||||
@ -66,6 +67,7 @@ class FakeSession(
|
||||
override fun pushersService() = fakePushersService
|
||||
override fun accountDataService() = fakeSessionAccountDataService
|
||||
override fun userService() = fakeUserService
|
||||
override fun spaceService() = fakeSpaceService
|
||||
|
||||
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
|
||||
coEvery {
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.app.test.fakes
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
|
||||
class FakeSpaceService : SpaceService by mockk() {
|
||||
|
||||
fun givenGetSpaceSummariesLiveReturns(roomSummaries: List<RoomSummary>): LiveData<List<RoomSummary>> {
|
||||
return MutableLiveData(roomSummaries).also {
|
||||
every { getSpaceSummariesLive(any()) } returns it
|
||||
}
|
||||
}
|
||||
|
||||
fun givenGetSpaceSummariesReturns(roomSummaries: List<RoomSummary>) {
|
||||
every { getSpaceSummaries(any()) } returns roomSummaries
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user