diff --git a/briar b/briar index 20b52804bf4a4306739eeafa4de9f312ffdea9b9..65be2d2b2655062c539d6b4ba931bfe730ece2e2 160000 --- a/briar +++ b/briar @@ -1 +1 @@ -Subproject commit 20b52804bf4a4306739eeafa4de9f312ffdea9b9 +Subproject commit 65be2d2b2655062c539d6b4ba931bfe730ece2e2 diff --git a/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt index c5756288c17207ed5da5754fe5426d11b7cdbec7..e5727398c46921001bd19449aac5c2f9ad5b4c56 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt @@ -30,12 +30,16 @@ import org.briarproject.bramble.system.DesktopSecureRandomModule import org.briarproject.bramble.system.JavaSystemModule import org.briarproject.bramble.util.OsUtils.isLinux import org.briarproject.bramble.util.OsUtils.isMac +import org.briarproject.briar.attachment.AttachmentModule +import org.briarproject.briar.desktop.attachment.media.ImageCompressor +import org.briarproject.briar.desktop.attachment.media.ImageCompressorImpl import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.BriarExecutorsImpl import org.briarproject.briar.desktop.threading.UiExecutor import org.briarproject.briar.desktop.ui.BriarUi import org.briarproject.briar.desktop.ui.BriarUiImpl import org.briarproject.briar.desktop.viewmodel.ViewModelModule +import org.briarproject.briar.identity.IdentityModule import java.io.File import java.nio.file.Path import java.util.Collections.emptyList @@ -45,6 +49,7 @@ import javax.inject.Singleton @Module( includes = [ AccountModule::class, + IdentityModule::class, CircumventionModule::class, ClockModule::class, DefaultBatteryManagerModule::class, @@ -55,6 +60,7 @@ import javax.inject.Singleton JavaSystemModule::class, SocksModule::class, ViewModelModule::class, + AttachmentModule::class, ] ) internal class DesktopModule( @@ -123,4 +129,10 @@ internal class DesktopModule( override fun shouldEnableProfilePictures() = false override fun shouldEnableDisappearingMessages() = false } + + @Provides + @Singleton + internal fun provideImageCompressor(imageCompressor: ImageCompressorImpl): ImageCompressor { + return imageCompressor + } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressor.kt b/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressor.kt new file mode 100644 index 0000000000000000000000000000000000000000..a9115a53da5d8b6ea5d44af0f15135d065d26d44 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressor.kt @@ -0,0 +1,19 @@ +package org.briarproject.briar.desktop.attachment.media + +import java.awt.image.BufferedImage +import java.io.IOException +import java.io.InputStream + +interface ImageCompressor { + + /** + * Compress an image and return an InputStream from which the resulting + * image can be read. The image will be compressed as a JPEG image such that + * it fits into a message. + * + * @param image the source image + * @return a stream from which the resulting image can be read + */ + @Throws(IOException::class) + fun compressImage(image: BufferedImage): InputStream +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt b/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..c3b515652d6d1cf27d3b402bea65efc0748551be --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/attachment/media/ImageCompressorImpl.kt @@ -0,0 +1,43 @@ +package org.briarproject.briar.desktop.attachment.media + +import mu.KotlinLogging +import org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE +import java.awt.image.BufferedImage +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import javax.imageio.IIOImage +import javax.imageio.ImageIO +import javax.imageio.ImageWriteParam +import javax.inject.Inject + +class ImageCompressorImpl @Inject internal constructor() : ImageCompressor { + + companion object { + val LOG = KotlinLogging.logger {} + } + + override fun compressImage(image: BufferedImage): InputStream { + val out = ByteArrayOutputStream() + for (quality in 100 downTo 1 step 10) { + val jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next() + jpgWriter.output = ImageIO.createImageOutputStream(out) + + val jpgWriteParam = jpgWriter.defaultWriteParam + jpgWriteParam.compressionMode = ImageWriteParam.MODE_EXPLICIT + jpgWriteParam.compressionQuality = quality / 100f + + val outputImage = IIOImage(image, null, null) + jpgWriter.write(null, outputImage, jpgWriteParam) + + jpgWriter.dispose() + if (out.size() <= MAX_IMAGE_SIZE) { + LOG.info { "Compressed image to ${out.size()} bytes, quality $quality" } + return ByteArrayInputStream(out.toByteArray()) + } + out.reset() + } + throw IOException() + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt index 6147e41fe80af7fe7b06a1ad560d51e3c470be75..8b6744ddd4ff5e5e21e83852f9e5d1edcbba9d39 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt @@ -56,7 +56,8 @@ fun main() = preview( isConnected = getBooleanParameter("isConnected"), isEmpty = getBooleanParameter("isEmpty"), unread = getIntParameter("unread"), - timestamp = getLongParameter("timestamp") + timestamp = getLongParameter("timestamp"), + avatar = null, ), {}, getBooleanParameter("selected") ) @@ -84,8 +85,7 @@ fun ContactCard( when (contactItem) { is ContactItem -> { Box(modifier = Modifier.align(Alignment.CenterVertically)) { - // TODO Pull profile pictures - ProfileCircle(36.dp, contactItem.authorId.bytes) + ProfileCircle(36.dp, contactItem) MessageCounter( unread = contactItem.unread, modifier = Modifier.align(Alignment.TopEnd) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt index f9a914ec779f990689372fdb824a63ddd4936b98..b222a55776cf0b4e77f79d823dd9a233b1c7328b 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt @@ -1,5 +1,6 @@ package org.briarproject.briar.desktop.contact +import androidx.compose.ui.graphics.ImageBitmap import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.identity.AuthorId import org.briarproject.briar.api.client.MessageTracker @@ -14,12 +15,18 @@ data class ContactItem( val isConnected: Boolean, val isEmpty: Boolean, val unread: Int, - override val timestamp: Long + override val timestamp: Long, + val avatar: ImageBitmap?, ) : BaseContactItem { override val displayName = getContactDisplayName(name, alias) - constructor(contact: Contact, isConnected: Boolean, groupCount: MessageTracker.GroupCount) : this( + constructor( + contact: Contact, + isConnected: Boolean, + groupCount: MessageTracker.GroupCount, + avatar: ImageBitmap? + ) : this( idWrapper = RealContactIdWrapper(contact.id), authorId = contact.author.id, name = contact.author.name, @@ -27,7 +34,8 @@ data class ContactItem( isConnected = isConnected, isEmpty = groupCount.msgCount == 0, unread = groupCount.unreadCount, - timestamp = groupCount.latestMsgTime + timestamp = groupCount.latestMsgTime, + avatar = avatar, ) fun updateTimestampAndUnread(timestamp: Long, read: Boolean): ContactItem = diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt index c2b3577ae2ce563baeb206a7429cb1950130c6b4..745a2addf65f7d0b124e73ed658bbd2ab93e5ac9 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt @@ -10,8 +10,11 @@ import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.lifecycle.LifecycleManager +import org.briarproject.briar.api.attachment.AttachmentReader +import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.event.ConversationMessageTrackedEvent +import org.briarproject.briar.api.identity.AuthorManager import org.briarproject.briar.desktop.conversation.ConversationMessagesReadEvent import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.viewmodel.asState @@ -21,14 +24,24 @@ class ContactListViewModel @Inject constructor( contactManager: ContactManager, + authorManager: AuthorManager, conversationManager: ConversationManager, connectionRegistry: ConnectionRegistry, + attachmentReader: AttachmentReader, briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, eventBus: EventBus, ) : ContactsViewModel( - contactManager, conversationManager, connectionRegistry, briarExecutors, lifecycleManager, db, eventBus + contactManager, + authorManager, + conversationManager, + connectionRegistry, + attachmentReader, + briarExecutors, + lifecycleManager, + db, + eventBus, ) { companion object { @@ -75,7 +88,6 @@ constructor( LOG.info { "Conversation message tracked, updating item" } updateItem(e.contactId) { it.updateTimestampAndUnread(e.timestamp, e.read) } } - // is AvatarUpdatedEvent -> {} is ContactAliasChangedEvent -> { updateItem(e.contactId) { it.updateAlias(e.alias) } } @@ -83,6 +95,10 @@ constructor( LOG.info("${e.count} conversation messages read, updating item") updateItem(e.contactId) { it.updateFromMessagesRead(e.count) } } + is AvatarUpdatedEvent -> { + LOG.info("received avatar update: ${e.attachmentHeader}") + // TODO: update avatar + } } } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt index 0fcf16cd13ae79d49ccc032728c128604c0e39ee..848ab77a18f5549f5d0da2c80c3325ac0770febf 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt @@ -15,8 +15,11 @@ import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent +import org.briarproject.briar.api.attachment.AttachmentReader import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.identity.AuthorManager import org.briarproject.briar.desktop.threading.BriarExecutors +import org.briarproject.briar.desktop.utils.ImageUtils.loadAvatar import org.briarproject.briar.desktop.utils.clearAndAddAll import org.briarproject.briar.desktop.utils.removeFirst import org.briarproject.briar.desktop.utils.replaceFirst @@ -24,8 +27,10 @@ import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel abstract class ContactsViewModel( protected val contactManager: ContactManager, + private val authorManager: AuthorManager, private val conversationManager: ConversationManager, private val connectionRegistry: ConnectionRegistry, + private val attachmentReader: AttachmentReader, briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, @@ -58,6 +63,7 @@ abstract class ContactsViewModel( contact, connectionRegistry.isConnected(contact.id), conversationManager.getGroupCount(txn, contact.id), + loadAvatar(authorManager, attachmentReader, txn, contact), ) } ) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ProfileCircle.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ProfileCircle.kt index 34e723dafe870b63a8f552016aedfd6ec8052723..3c53a2ce1aa54f98d482c99fc19065bc3fe25a9d 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ProfileCircle.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ProfileCircle.kt @@ -2,6 +2,7 @@ package org.briarproject.briar.desktop.contact import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape @@ -9,6 +10,8 @@ import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import org.briarproject.briar.desktop.theme.outline @@ -24,8 +27,25 @@ fun PreviewProfileCircle() { ProfileCircle(90.dp, bytes) } +/** + * Display the avatar for a [ContactItem]. If it has an avatar image, display that, otherwise + * display an [Identicon] based on the user's author id. Either way the profile image is displayed + * within a circle. + * + * @param size the size of the circle. In order to avoid aliasing effects for Identicon-based profile images, + * pass a multiple of 9 here. That helps as the image is based on a 9x9 square grid. + */ +@Composable +fun ProfileCircle(size: Dp, contactItem: ContactItem) { + if (contactItem.avatar == null) + ProfileCircle(size, contactItem.authorId.bytes) + else + ProfileCircle(size, contactItem.avatar) +} + /** * Display an [Identicon] as a profile image within a circle based on a user's author id. + * * @param size the size of the circle. In order to avoid aliasing effects, pass a multiple * of 9 here. That helps as the image is based on a 9x9 square grid. */ @@ -37,7 +57,24 @@ fun ProfileCircle(size: Dp, input: ByteArray) { } /** - * Used for pending contacts. + * Display an avatar bitmap as a profile image within a circle. + * + * @param size the size of the circle. + */ +@Composable +fun ProfileCircle(size: Dp, avatar: ImageBitmap) { + Image( + bitmap = avatar, + contentDescription = null, + contentScale = ContentScale.FillBounds, + modifier = Modifier.size(size).clip(CircleShape).border(2.dp, MaterialTheme.colors.outline, CircleShape), + ) +} + +/** + * Display a placeholder avatar for pending contacts. + * + * @param size the size of the circle. */ @Composable fun ProfileCircle(size: Dp) { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt index deeca393355e666042958afdb4135950c970ba6a..a6566260d67adf60248d6230b267d161bcf486fc 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt @@ -42,7 +42,7 @@ fun ConversationHeader( Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { Row(modifier = Modifier.align(Alignment.Center)) { - ProfileCircle(36.dp, contactItem.authorId.bytes) + ProfileCircle(36.dp, contactItem) Canvas( modifier = Modifier.align(Alignment.CenterVertically), onDraw = { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt index adbbb13391584c567733d90c3d8096b7087ccd3f..bef737f175fa25ec663d0e77151b710072d8848d 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt @@ -24,10 +24,12 @@ import org.briarproject.bramble.api.sync.event.MessagesAckedEvent import org.briarproject.bramble.api.sync.event.MessagesSentEvent import org.briarproject.bramble.api.versioning.event.ClientVersionUpdatedEvent import org.briarproject.bramble.util.LogUtils +import org.briarproject.briar.api.attachment.AttachmentReader import org.briarproject.briar.api.autodelete.UnexpectedTimerException import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent +import org.briarproject.briar.api.identity.AuthorManager import org.briarproject.briar.api.introduction.IntroductionManager import org.briarproject.briar.api.messaging.MessagingManager import org.briarproject.briar.api.messaging.PrivateMessage @@ -36,6 +38,7 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader import org.briarproject.briar.desktop.contact.ContactItem import org.briarproject.briar.desktop.conversation.ConversationRequestItem.RequestType.INTRODUCTION import org.briarproject.briar.desktop.threading.BriarExecutors +import org.briarproject.briar.desktop.utils.ImageUtils.loadAvatar import org.briarproject.briar.desktop.utils.KLoggerUtils.logDuration import org.briarproject.briar.desktop.utils.clearAndAddAll import org.briarproject.briar.desktop.utils.replaceIf @@ -50,6 +53,7 @@ class ConversationViewModel constructor( private val connectionRegistry: ConnectionRegistry, private val contactManager: ContactManager, + private val authorManager: AuthorManager, private val conversationManager: ConversationManager, private val introductionManager: IntroductionManager, private val messagingManager: MessagingManager, @@ -57,6 +61,7 @@ constructor( briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, + private val attachmentReader: AttachmentReader, private val eventBus: EventBus, ) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) { @@ -167,10 +172,14 @@ constructor( private fun loadContact(txn: Transaction, id: ContactId): ContactItem { try { val start = LogUtils.now() + + val contact = contactManager.getContact(txn, id) + val contactItem = ContactItem( - contactManager.getContact(txn, id), + contact, connectionRegistry.isConnected(id), conversationManager.getGroupCount(txn, id), + loadAvatar(authorManager, attachmentReader, txn, contact), ) LOG.logDuration("Loading contact", start) txn.attach { _contactItem.value = contactItem } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt index 7ee9d42f34a535911e2b8c9112e6d1933f2d0d1b..c49ea03c788370cbf07346ed1a7c532ef6db3421 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt @@ -6,7 +6,9 @@ import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.lifecycle.LifecycleManager +import org.briarproject.briar.api.attachment.AttachmentReader import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.identity.AuthorManager import org.briarproject.briar.api.introduction.IntroductionManager import org.briarproject.briar.desktop.contact.BaseContactItem import org.briarproject.briar.desktop.contact.ContactItem @@ -20,14 +22,24 @@ class IntroductionViewModel constructor( private val introductionManager: IntroductionManager, contactManager: ContactManager, + authorManager: AuthorManager, conversationManager: ConversationManager, connectionRegistry: ConnectionRegistry, + attachmentReader: AttachmentReader, briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, eventBus: EventBus, ) : ContactsViewModel( - contactManager, conversationManager, connectionRegistry, briarExecutors, lifecycleManager, db, eventBus + contactManager, + authorManager, + conversationManager, + connectionRegistry, + attachmentReader, + briarExecutors, + lifecycleManager, + db, + eventBus, ) { private val _firstContact = mutableStateOf<ContactItem?>(null) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/utils/ImageUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/utils/ImageUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..a8a346ddaed28001629c5588ee76217df13fd5a1 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/utils/ImageUtils.kt @@ -0,0 +1,27 @@ +package org.briarproject.briar.desktop.utils + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.res.loadImageBitmap +import org.briarproject.bramble.api.contact.Contact +import org.briarproject.bramble.api.db.Transaction +import org.briarproject.briar.api.attachment.AttachmentReader +import org.briarproject.briar.api.identity.AuthorManager + +object ImageUtils { + + fun loadAvatar( + authorManager: AuthorManager, + attachmentReader: AttachmentReader, + txn: Transaction, + contact: Contact + ): ImageBitmap? { + val authorInfo = authorManager.getAuthorInfo(txn, contact) + if (authorInfo.avatarHeader == null) { + return null + } + val attachment = attachmentReader.getAttachment(txn, authorInfo.avatarHeader) + attachment.stream.use { + return loadImageBitmap(it) + } + } +} diff --git a/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt b/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt index 101340c5276c0b27a7d32e3296b8cb8a88238209..7bf547826efa6f7d2245ca5cddd34557370cd223 100644 --- a/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt +++ b/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt @@ -31,14 +31,19 @@ import org.briarproject.bramble.system.JavaSystemModule import org.briarproject.bramble.util.OsUtils.isLinux import org.briarproject.bramble.util.OsUtils.isMac import org.briarproject.briar.api.test.TestAvatarCreator +import org.briarproject.briar.attachment.AttachmentModule +import org.briarproject.briar.desktop.attachment.media.ImageCompressor +import org.briarproject.briar.desktop.attachment.media.ImageCompressorImpl import org.briarproject.briar.desktop.testdata.DeterministicTestDataCreator import org.briarproject.briar.desktop.testdata.DeterministicTestDataCreatorImpl +import org.briarproject.briar.desktop.testdata.TestAvatarCreatorImpl import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.BriarExecutorsImpl import org.briarproject.briar.desktop.threading.UiExecutor import org.briarproject.briar.desktop.ui.BriarUi import org.briarproject.briar.desktop.ui.BriarUiImpl import org.briarproject.briar.desktop.viewmodel.ViewModelModule +import org.briarproject.briar.identity.IdentityModule import org.briarproject.briar.test.TestModule import java.io.File import java.nio.file.Path @@ -49,6 +54,7 @@ import javax.inject.Singleton @Module( includes = [ AccountModule::class, + IdentityModule::class, CircumventionModule::class, ClockModule::class, DefaultBatteryManagerModule::class, @@ -59,7 +65,8 @@ import javax.inject.Singleton JavaSystemModule::class, SocksModule::class, TestModule::class, - ViewModelModule::class + ViewModelModule::class, + AttachmentModule::class, ] ) internal class DesktopTestModule( @@ -130,7 +137,16 @@ internal class DesktopTestModule( } @Provides - internal fun provideTestAvatarCreator() = TestAvatarCreator { null } + @Singleton + internal fun provideImageCompressor(imageCompressor: ImageCompressorImpl): ImageCompressor { + return imageCompressor + } + + @Provides + @Singleton + internal fun provideTestAvatarCreator(testAvatarCreator: TestAvatarCreatorImpl): TestAvatarCreator { + return testAvatarCreator + } @Provides @Singleton diff --git a/src/test/kotlin/org/briarproject/briar/desktop/contact/ContactItemTest.kt b/src/test/kotlin/org/briarproject/briar/desktop/contact/ContactItemTest.kt index 7f1ac6910312b573acc30766b36db15804053dc9..7376e74ba74abd1fa4c755ccab598c80d0571333 100644 --- a/src/test/kotlin/org/briarproject/briar/desktop/contact/ContactItemTest.kt +++ b/src/test/kotlin/org/briarproject/briar/desktop/contact/ContactItemTest.kt @@ -1,8 +1,13 @@ package org.briarproject.briar.desktop.contact import org.briarproject.bramble.api.UniqueId +import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.contact.ContactId +import org.briarproject.bramble.api.crypto.CryptoConstants +import org.briarproject.bramble.api.crypto.SignaturePublicKey +import org.briarproject.bramble.api.identity.Author import org.briarproject.bramble.api.identity.AuthorId +import org.briarproject.briar.api.client.MessageTracker import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -12,15 +17,27 @@ class ContactItemTest { @Test fun test() { val random = Random(1) + + val localAuthorId = AuthorId(random.nextBytes(UniqueId.LENGTH)) + + val id = AuthorId(random.nextBytes(UniqueId.LENGTH)) + val name = "Alice" + val publicKey = SignaturePublicKey(random.nextBytes(CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES)) + val author = Author(id, Author.FORMAT_VERSION, name, publicKey) + + val contact = Contact( + ContactId(random.nextInt()), + author, + localAuthorId, + null, + null, + false, + ) val item = ContactItem( - idWrapper = RealContactIdWrapper(ContactId(random.nextInt())), - authorId = AuthorId(random.nextBytes(UniqueId.LENGTH)), - name = "Alice", - alias = null, + contact = contact, isConnected = false, - isEmpty = false, - unread = 10, - timestamp = random.nextLong() + groupCount = MessageTracker.GroupCount(0, 0, System.currentTimeMillis()), + avatar = null ) assertEquals("Alice", item.displayName) diff --git a/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt b/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt index 9c336eee4d4378a5807c5969539eff434dd8926d..47a754fbf597dc7abd3ff80d43a24ff88dcc0746 100644 --- a/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt +++ b/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt @@ -66,7 +66,9 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor( private val avatarMessageEncoder: AvatarMessageEncoder, private val clientHelper: ClientHelper, private val eventBus: EventBus, - @field:IoExecutor @param:IoExecutor private val ioExecutor: Executor + @field:IoExecutor + @param:IoExecutor + private val ioExecutor: Executor, ) : DeterministicTestDataCreator { companion object { @@ -75,6 +77,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor( private val random = Random() private val localAuthors: MutableMap<ContactId, LocalAuthor> = HashMap() + override fun createTestData( numContacts: Int, numPrivateMsgs: Int, @@ -204,6 +207,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor( props[TorConstants.ID] = tor return props } + private val randomBluetoothAddress: String get() { val mac = ByteArray(6) @@ -215,6 +219,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor( } return sb.toString() } + private val randomUUID: String get() { val uuid = ByteArray(BluetoothConstants.UUID_BYTES) @@ -240,6 +245,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor( sb.append(':').append(randomPortNumber) return sb.toString() } + private val randomPortNumber: Int get() = 32768 + random.nextInt(32768) diff --git a/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestAvatarCreatorImpl.kt b/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestAvatarCreatorImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..2bf319fba25a24a89a35b1632474f6290e5b5279 --- /dev/null +++ b/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestAvatarCreatorImpl.kt @@ -0,0 +1,74 @@ +package org.briarproject.briar.desktop.testdata + +import org.briarproject.briar.api.test.TestAvatarCreator +import org.briarproject.briar.desktop.attachment.media.ImageCompressor +import java.awt.Color +import java.awt.Graphics2D +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.io.InputStream +import java.lang.Integer.max +import javax.inject.Inject +import kotlin.random.Random + +class TestAvatarCreatorImpl @Inject internal constructor(private val imageCompressor: ImageCompressor) : + TestAvatarCreator { + + private val WIDTH = 800 + private val HEIGHT = 640 + + private val random = Random(0) + + override fun getAvatarInputStream(): InputStream { + val image = BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB) + + if (random.nextBoolean()) { + generateColoredPixels(image) + } else { + generateColoredCircles(image) + } + + return imageCompressor.compressImage(image) + } + + private fun generateColoredPixels(image: BufferedImage) { + val g: Graphics2D = image.createGraphics() + val pixelMultiplier: Int = random.nextInt(500) + 1 + + for (x in 0..WIDTH step pixelMultiplier) { + for (y in 0..HEIGHT step pixelMultiplier) { + g.color = Color(getRandomColor()) + g.fillRect(x, y, pixelMultiplier, pixelMultiplier) + } + } + } + + private fun generateColoredCircles(image: BufferedImage) { + val g: Graphics2D = image.createGraphics() + + g.color = Color.WHITE + g.fillRect(0, 0, image.width, image.height) + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + + val biggestSide = max(WIDTH, HEIGHT) + val selectedCount = random.nextInt(10) + 2 + val radiusFrom = biggestSide / 12f + val radiusTo = biggestSide / 4f + for (i in 0..selectedCount) { + val cx = random.nextInt(WIDTH) + val cy = random.nextInt(HEIGHT) + val radius = (random.nextInt((radiusTo - radiusFrom).toInt()) + radiusFrom).toInt() + val diameter = radius * 2 + g.color = Color(getRandomColor()) + g.fillOval(cx - radius, cy - radius, diameter, diameter) + } + } + + private fun getRandomColor(): Int { + val hue = random.nextFloat() + val saturation = random.nextFloat() + val brightness = 1f + return Color.HSBtoRGB(hue, saturation, brightness) + } +}