Merge branch 'main' into feature/share-images-via-small-talk
This commit is contained in:
commit
92ad630e45
|
@ -1,6 +1,8 @@
|
|||
package test
|
||||
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
inline fun <T : Any, reified R> T.expect(crossinline block: suspend MockKMatcherScope.(T) -> R) {
|
||||
coEvery { block(this@expect) } returns mockk(relaxed = true)
|
||||
|
@ -16,11 +18,22 @@ fun <T, B> MockKStubScope<T, B>.delegateReturn() = object : Returns<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fun <T, B> MockKStubScope<Flow<T>, B>.delegateEmit() = object : Emits<T> {
|
||||
override fun emits(vararg values: T) {
|
||||
answers(ConstantAnswer(flowOf(*values)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun <T> returns(block: (T) -> Unit) = object : Returns<T> {
|
||||
override fun returns(value: T) = block(value)
|
||||
override fun throws(value: Throwable) = throw value
|
||||
}
|
||||
|
||||
interface Emits<T> {
|
||||
fun emits(vararg values: T)
|
||||
}
|
||||
|
||||
interface Returns<T> {
|
||||
fun returns(value: T)
|
||||
fun throws(value: Throwable)
|
||||
|
|
|
@ -20,7 +20,7 @@ internal class MergeWithLocalEchosUseCaseImpl(
|
|||
val existingWithEcho = updateExistingEventsWithLocalEchoMeta(roomState, echosByEventId)
|
||||
|
||||
val sortedEvents = (existingWithEcho + uniqueEchos)
|
||||
.sortedByDescending { if (it is RoomEvent.Message) it.utcTimestamp else null }
|
||||
.sortedByDescending { it.utcTimestamp }
|
||||
.distinctBy { it.eventId }
|
||||
return roomState.copy(events = sortedEvents)
|
||||
}
|
||||
|
|
|
@ -218,6 +218,9 @@ private fun <T : RoomEvent> LazyItemScope.AlignedBubble(
|
|||
|
||||
@Composable
|
||||
private fun MessageImage(content: BubbleContent<RoomEvent.Image>) {
|
||||
val context = LocalContext.current
|
||||
val fetcherFactory = remember { DecryptingFetcherFactory(context) }
|
||||
|
||||
Box(modifier = Modifier.padding(start = 6.dp)) {
|
||||
Box(
|
||||
Modifier
|
||||
|
@ -245,8 +248,8 @@ private fun MessageImage(content: BubbleContent<RoomEvent.Image>) {
|
|||
Image(
|
||||
modifier = Modifier.size(content.message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||
painter = rememberAsyncImagePainter(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.fetcherFactory(DecryptingFetcherFactory(LocalContext.current))
|
||||
model = ImageRequest.Builder(context)
|
||||
.fetcherFactory(fetcherFactory)
|
||||
.data(content.message)
|
||||
.build()
|
||||
),
|
||||
|
@ -387,6 +390,8 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
|||
.width(IntrinsicSize.Max)
|
||||
.defaultMinSize(minWidth = 50.dp)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val fetcherFactory = remember { DecryptingFetcherFactory(context) }
|
||||
Column(
|
||||
Modifier
|
||||
.background(if (content.isNotSelf) SmallTalkTheme.extendedColors.otherBubbleReplyBackground else SmallTalkTheme.extendedColors.selfBubbleReplyBackground)
|
||||
|
@ -415,8 +420,8 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
|||
Image(
|
||||
modifier = Modifier.size(replyingTo.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||
painter = rememberAsyncImagePainter(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.fetcherFactory(DecryptingFetcherFactory(LocalContext.current))
|
||||
model = ImageRequest.Builder(context)
|
||||
.fetcherFactory(fetcherFactory)
|
||||
.data(replyingTo)
|
||||
.build()
|
||||
),
|
||||
|
@ -452,9 +457,9 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
|||
Image(
|
||||
modifier = Modifier.size(message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||
painter = rememberAsyncImagePainter(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(content.message)
|
||||
.fetcherFactory(DecryptingFetcherFactory(LocalContext.current))
|
||||
.fetcherFactory(fetcherFactory)
|
||||
.build()
|
||||
),
|
||||
contentDescription = null,
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.amshove.kluent.shouldBeEqualTo
|
|||
import org.junit.Test
|
||||
|
||||
private val A_ROOM_MESSAGE_EVENT = aRoomMessageEvent(eventId = anEventId("1"))
|
||||
private val A_ROOM_IMAGE_MESSAGE_EVENT = aRoomMessageEvent(eventId = anEventId("2"))
|
||||
private val A_LOCAL_ECHO_EVENT_ID = anEventId("2")
|
||||
private const val A_LOCAL_ECHO_BODY = "body"
|
||||
private val A_ROOM_MEMBER = aRoomMember()
|
||||
|
@ -21,7 +22,7 @@ class MergeWithLocalEchosUseCaseTest {
|
|||
private val mergeWithLocalEchosUseCase = MergeWithLocalEchosUseCaseImpl(fakeLocalEchoMapper.instance)
|
||||
|
||||
@Test
|
||||
fun `given no local echos, when merging, then returns original state`() {
|
||||
fun `given no local echos, when merging text message, then returns original state`() {
|
||||
val roomState = aRoomState(events = listOf(A_ROOM_MESSAGE_EVENT))
|
||||
|
||||
val result = mergeWithLocalEchosUseCase.invoke(roomState, A_ROOM_MEMBER, emptyList())
|
||||
|
@ -29,6 +30,15 @@ class MergeWithLocalEchosUseCaseTest {
|
|||
result shouldBeEqualTo roomState
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no local echos, when merging events, then returns original ordered by timestamp descending`() {
|
||||
val roomState = aRoomState(events = listOf(A_ROOM_IMAGE_MESSAGE_EVENT.copy(utcTimestamp = 1500), A_ROOM_MESSAGE_EVENT.copy(utcTimestamp = 1000)))
|
||||
|
||||
val result = mergeWithLocalEchosUseCase.invoke(roomState, A_ROOM_MEMBER, emptyList())
|
||||
|
||||
result shouldBeEqualTo roomState.copy(events = roomState.events.sortedByDescending { it.utcTimestamp })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given local echo with sending state, when merging then maps to room event with local echo state`() {
|
||||
val second = createLocalEcho(A_LOCAL_ECHO_EVENT_ID, A_LOCAL_ECHO_BODY, state = MessageService.LocalEcho.State.Sending)
|
||||
|
|
|
@ -31,7 +31,7 @@ class NotificationsModule(
|
|||
fun credentialProvider() = credentialsStore
|
||||
fun firebasePushTokenUseCase() = firebasePushTokenUseCase
|
||||
fun roomStore() = roomStore
|
||||
fun notificationsUseCase() = NotificationsUseCase(
|
||||
fun notificationsUseCase() = RenderNotificationsUseCase(
|
||||
NotificationRenderer(notificationManager(), NotificationFactory(iconLoader, context, intentFactory), dispatchers),
|
||||
ObserveUnreadNotificationsUseCaseImpl(roomStore),
|
||||
NotificationChannels(notificationManager()),
|
||||
|
|
|
@ -7,7 +7,7 @@ import app.dapk.st.matrix.sync.RoomOverview
|
|||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class NotificationsUseCase(
|
||||
class RenderNotificationsUseCase(
|
||||
private val notificationRenderer: NotificationRenderer,
|
||||
private val observeRenderableUnreadEventsUseCase: ObserveUnreadNotificationsUseCase,
|
||||
notificationChannels: NotificationChannels,
|
|
@ -1,6 +1,6 @@
|
|||
package app.dapk.st.notifications
|
||||
|
||||
import app.dapk.st.notifications.NotificationFixtures.aNotifications
|
||||
import fixture.NotificationFixtures.aNotifications
|
||||
import fake.FakeNotificationFactory
|
||||
import fake.FakeNotificationManager
|
||||
import fake.aFakeNotification
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package app.dapk.st.notifications
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.sync.RoomEvent
|
||||
import app.dapk.st.matrix.sync.RoomOverview
|
||||
import fake.FakeRoomStore
|
||||
import fixture.aRoomId
|
||||
import fixture.aRoomMessageEvent
|
||||
import fixture.aRoomOverview
|
||||
import fixture.anEventId
|
||||
import fixture.*
|
||||
import fixture.NotificationDiffFixtures.aNotificationDiff
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -21,7 +17,7 @@ val A_MESSAGE_2 = aRoomMessageEvent(eventId = anEventId("2"), content = "world")
|
|||
val A_ROOM_OVERVIEW = aRoomOverview(roomId = aRoomId("1"))
|
||||
val A_ROOM_OVERVIEW_2 = aRoomOverview(roomId = aRoomId("2"))
|
||||
|
||||
class ObserveUnreadNotificationsUseCaseTest {
|
||||
class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||
|
||||
private val fakeRoomStore = FakeRoomStore()
|
||||
|
||||
|
@ -94,12 +90,5 @@ class ObserveUnreadNotificationsUseCaseTest {
|
|||
private fun givenNoInitialUnreads(vararg unreads: Map<RoomOverview, List<RoomEvent>>) = fakeRoomStore.givenUnreadEvents(flowOf(NO_UNREADS, *unreads))
|
||||
}
|
||||
|
||||
private fun aNotificationDiff(
|
||||
unchanged: Map<RoomId, List<EventId>> = emptyMap(),
|
||||
changedOrNew: Map<RoomId, List<EventId>> = emptyMap(),
|
||||
removed: Map<RoomId, List<EventId>> = emptyMap(),
|
||||
newRooms: Set<RoomId> = emptySet(),
|
||||
) = NotificationDiff(unchanged, changedOrNew, removed, newRooms)
|
||||
|
||||
private fun RoomOverview.withUnreads(vararg events: RoomEvent) = mapOf(this to events.toList())
|
||||
private fun RoomOverview.toDiff(vararg events: RoomEvent) = mapOf(this.roomId to events.map { it.eventId })
|
|
@ -0,0 +1,41 @@
|
|||
package app.dapk.st.notifications
|
||||
|
||||
import fake.FakeNotificationChannels
|
||||
import fake.FakeNotificationRenderer
|
||||
import fake.FakeObserveUnreadNotificationsUseCase
|
||||
import fixture.NotificationDiffFixtures.aNotificationDiff
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import test.expect
|
||||
|
||||
private val AN_UNREAD_NOTIFICATIONS = UnreadNotifications(emptyMap(), aNotificationDiff())
|
||||
|
||||
class RenderNotificationsUseCaseTest {
|
||||
|
||||
private val fakeNotificationRenderer = FakeNotificationRenderer()
|
||||
private val fakeObserveUnreadNotificationsUseCase = FakeObserveUnreadNotificationsUseCase()
|
||||
private val fakeNotificationChannels = FakeNotificationChannels().also {
|
||||
it.instance.expect { it.initChannels() }
|
||||
}
|
||||
|
||||
private val renderNotificationsUseCase = RenderNotificationsUseCase(
|
||||
fakeNotificationRenderer.instance,
|
||||
fakeObserveUnreadNotificationsUseCase,
|
||||
fakeNotificationChannels.instance,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `when creating use case instance, then initiates channels`() {
|
||||
fakeNotificationChannels.verifyInitiated()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given renderable unread events, when listening for changes, then renders change`() = runTest {
|
||||
fakeNotificationRenderer.instance.expect { it.render(any(), any(), any(), any()) }
|
||||
fakeObserveUnreadNotificationsUseCase.given().emits(AN_UNREAD_NOTIFICATIONS)
|
||||
|
||||
renderNotificationsUseCase.listenForNotificationChanges()
|
||||
|
||||
fakeNotificationRenderer.verifyRenders(AN_UNREAD_NOTIFICATIONS)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package fake
|
||||
|
||||
import app.dapk.st.notifications.NotificationChannels
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
||||
class FakeNotificationChannels {
|
||||
val instance = mockk<NotificationChannels>()
|
||||
|
||||
fun verifyInitiated() {
|
||||
verify { instance.initChannels() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package fake
|
||||
|
||||
import app.dapk.st.notifications.NotificationRenderer
|
||||
import app.dapk.st.notifications.UnreadNotifications
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeNotificationRenderer {
|
||||
val instance = mockk<NotificationRenderer>()
|
||||
|
||||
fun verifyRenders(vararg unreadNotifications: UnreadNotifications) {
|
||||
unreadNotifications.forEach { unread ->
|
||||
coVerify {
|
||||
instance.render(
|
||||
allUnread = unread.first,
|
||||
removedRooms = unread.second.removed.keys,
|
||||
roomsWithNewEvents = unread.second.changedOrNew.keys,
|
||||
newRooms = unread.second.newRooms,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package fake
|
||||
|
||||
import app.dapk.st.notifications.ObserveUnreadNotificationsUseCase
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import test.delegateEmit
|
||||
|
||||
class FakeObserveUnreadNotificationsUseCase : ObserveUnreadNotificationsUseCase by mockk() {
|
||||
fun given() = coEvery { this@FakeObserveUnreadNotificationsUseCase.invoke() }.delegateEmit()
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package fixture
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.notifications.NotificationDiff
|
||||
|
||||
object NotificationDiffFixtures {
|
||||
|
||||
fun aNotificationDiff(
|
||||
unchanged: Map<RoomId, List<EventId>> = emptyMap(),
|
||||
changedOrNew: Map<RoomId, List<EventId>> = emptyMap(),
|
||||
removed: Map<RoomId, List<EventId>> = emptyMap(),
|
||||
newRooms: Set<RoomId> = emptySet(),
|
||||
) = NotificationDiff(unchanged, changedOrNew, removed, newRooms)
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package app.dapk.st.notifications
|
||||
package fixture
|
||||
|
||||
import android.app.Notification
|
||||
import app.dapk.st.notifications.NotificationDelegate
|
||||
import app.dapk.st.notifications.Notifications
|
||||
|
||||
object NotificationFixtures {
|
||||
|
||||
|
|
|
@ -14,6 +14,16 @@ fun aRoomMessageEvent(
|
|||
edited: Boolean = false,
|
||||
) = RoomEvent.Message(eventId, utcTimestamp, content, author, meta, encryptedContent, edited)
|
||||
|
||||
fun aRoomImageMessageEvent(
|
||||
eventId: EventId = anEventId(),
|
||||
utcTimestamp: Long = 0L,
|
||||
content: RoomEvent.Image.ImageMeta = anImageMeta(),
|
||||
author: RoomMember = aRoomMember(),
|
||||
meta: MessageMeta = MessageMeta.FromServer,
|
||||
encryptedContent: RoomEvent.Message.MegOlmV1? = null,
|
||||
edited: Boolean = false,
|
||||
) = RoomEvent.Image(eventId, utcTimestamp, content, author, meta, encryptedContent, edited)
|
||||
|
||||
fun aRoomReplyMessageEvent(
|
||||
message: RoomEvent = aRoomMessageEvent(),
|
||||
replyingTo: RoomEvent = aRoomMessageEvent(eventId = anEventId("in-reply-to-id")),
|
||||
|
@ -34,4 +44,11 @@ fun aMegolmV1(
|
|||
deviceId: DeviceId = aDeviceId(),
|
||||
senderKey: String = "a-sender-key",
|
||||
sessionId: SessionId = aSessionId(),
|
||||
) = RoomEvent.Message.MegOlmV1(cipherText, deviceId, senderKey, sessionId)
|
||||
) = RoomEvent.Message.MegOlmV1(cipherText, deviceId, senderKey, sessionId)
|
||||
|
||||
fun anImageMeta(
|
||||
width: Int? = 100,
|
||||
height: Int? = 100,
|
||||
url: String = "https://a-url.com",
|
||||
keys: RoomEvent.Image.ImageMeta.Keys? = null
|
||||
) = RoomEvent.Image.ImageMeta(width, height, url, keys)
|
Loading…
Reference in New Issue