Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 634540a703 | |||
| d2afecafcf | |||
| 50e9971694 |
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
@@ -1,6 +1,5 @@
|
|||||||
package net.buzzert.kordophonedroid.ui.attachments
|
package net.buzzert.kordophonedroid.ui.attachments
|
||||||
|
|
||||||
import androidx.compose.foundation.Indication
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -8,16 +7,15 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import coil.compose.SubcomposeAsyncImage
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import net.buzzert.kordophonedroid.ui.LocalNavController
|
import net.buzzert.kordophonedroid.ui.LocalNavController
|
||||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
||||||
@@ -41,12 +39,19 @@ fun AttachmentViewer(attachmentGuid: String) {
|
|||||||
Column(modifier = Modifier.padding(padding)) {
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
AsyncImage(
|
SubcomposeAsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data(data)
|
.data(data)
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.build(),
|
.build(),
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
|
loading = {
|
||||||
|
Box {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.zoomable(zoomState)
|
.zoomable(zoomState)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -1,170 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:fillColor="@color/black"
|
||||||
android:viewportHeight="108">
|
>
|
||||||
<path
|
<gradient
|
||||||
android:fillColor="#3DDC84"
|
android:startColor="#000"
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
android:endColor="#333"
|
||||||
<path
|
android:angle="1.0"
|
||||||
android:fillColor="#00000000"
|
/>
|
||||||
android:pathData="M9,0L9,108"
|
</shape>
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/kordophone_ic.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
app/src/main/res/drawable/kordophone_ic_small.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 840 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 6.8 KiB |
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#3D3D3D</color>
|
||||||
|
</resources>
|
||||||
@@ -97,6 +97,7 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
date = conversation.date
|
date = conversation.date
|
||||||
unreadCount = conversation.unreadCount
|
unreadCount = conversation.unreadCount
|
||||||
lastMessagePreview = conversation.lastMessagePreview
|
lastMessagePreview = conversation.lastMessagePreview
|
||||||
|
lastMessageGUID = conversation.lastMessageGUID
|
||||||
}
|
}
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
// Conversation does not exist. Copy it to the realm.
|
// Conversation does not exist. Copy it to the realm.
|
||||||
@@ -121,6 +122,10 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun writeMessages(messages: List<ModelMessage>, conversation: ModelConversation, outgoing: Boolean = false) {
|
fun writeMessages(messages: List<ModelMessage>, conversation: ModelConversation, outgoing: Boolean = false) {
|
||||||
|
if (messages.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val dbConversation = getManagedConversationByGuid(conversation.guid)
|
val dbConversation = getManagedConversationByGuid(conversation.guid)
|
||||||
realm.writeBlocking {
|
realm.writeBlocking {
|
||||||
messages
|
messages
|
||||||
@@ -128,8 +133,17 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
.map { copyToRealm(it, updatePolicy = UpdatePolicy.ALL) }
|
.map { copyToRealm(it, updatePolicy = UpdatePolicy.ALL) }
|
||||||
|
|
||||||
findLatest(dbConversation)?.let {
|
findLatest(dbConversation)?.let {
|
||||||
it.lastMessagePreview = messages.last().displayText
|
val lastMessage = messages.maxByOrNull { it.date }!!
|
||||||
it.date = messages.last().date.toInstant().toRealmInstant()
|
|
||||||
|
val lastMessageDate = lastMessage.date.toInstant().toRealmInstant()
|
||||||
|
if (lastMessageDate > it.date) {
|
||||||
|
it.lastMessageGUID = lastMessage.guid
|
||||||
|
it.lastMessagePreview = lastMessage.displayText
|
||||||
|
|
||||||
|
// This will cause sort order to change. I think this ends
|
||||||
|
// up getting updated whenever we get conversation changes anyway.
|
||||||
|
// it.date = lastMessageDate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package net.buzzert.kordophone.backend.db.model
|
package net.buzzert.kordophone.backend.db.model
|
||||||
|
|
||||||
import io.realm.kotlin.Realm
|
|
||||||
import io.realm.kotlin.ext.realmListOf
|
import io.realm.kotlin.ext.realmListOf
|
||||||
import io.realm.kotlin.ext.realmSetOf
|
|
||||||
import io.realm.kotlin.ext.toRealmList
|
import io.realm.kotlin.ext.toRealmList
|
||||||
import io.realm.kotlin.types.RealmInstant
|
import io.realm.kotlin.types.RealmInstant
|
||||||
import io.realm.kotlin.types.RealmList
|
import io.realm.kotlin.types.RealmList
|
||||||
import io.realm.kotlin.types.RealmObject
|
import io.realm.kotlin.types.RealmObject
|
||||||
import io.realm.kotlin.types.RealmSet
|
|
||||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
import org.mongodb.kbson.ObjectId
|
import org.mongodb.kbson.ObjectId
|
||||||
import java.time.Instant
|
|
||||||
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
|
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@@ -24,6 +20,7 @@ open class Conversation(
|
|||||||
var date: RealmInstant,
|
var date: RealmInstant,
|
||||||
var unreadCount: Int,
|
var unreadCount: Int,
|
||||||
|
|
||||||
|
var lastMessageGUID: String?,
|
||||||
var lastMessagePreview: String?,
|
var lastMessagePreview: String?,
|
||||||
): RealmObject
|
): RealmObject
|
||||||
{
|
{
|
||||||
@@ -35,10 +32,11 @@ open class Conversation(
|
|||||||
date = RealmInstant.now(),
|
date = RealmInstant.now(),
|
||||||
unreadCount = 0,
|
unreadCount = 0,
|
||||||
lastMessagePreview = null,
|
lastMessagePreview = null,
|
||||||
|
lastMessageGUID = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toConversation(): ModelConversation {
|
fun toConversation(): ModelConversation {
|
||||||
val conversation = ModelConversation(
|
return ModelConversation(
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
participants = participants.toList(),
|
participants = participants.toList(),
|
||||||
date = Date.from(date.toInstant()),
|
date = Date.from(date.toInstant()),
|
||||||
@@ -46,9 +44,8 @@ open class Conversation(
|
|||||||
guid = guid,
|
guid = guid,
|
||||||
lastMessagePreview = lastMessagePreview,
|
lastMessagePreview = lastMessagePreview,
|
||||||
lastMessage = null,
|
lastMessage = null,
|
||||||
|
lastFetchedMessageGUID = lastMessageGUID
|
||||||
)
|
)
|
||||||
|
|
||||||
return conversation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -57,6 +54,10 @@ open class Conversation(
|
|||||||
val o = other as Conversation
|
val o = other as Conversation
|
||||||
return guid == o.guid
|
return guid == o.guid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return guid.hashCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ModelConversation.toDatabaseConversation(): Conversation {
|
fun ModelConversation.toDatabaseConversation(): Conversation {
|
||||||
@@ -67,6 +68,7 @@ fun ModelConversation.toDatabaseConversation(): Conversation {
|
|||||||
date = from.date.toInstant().toRealmInstant()
|
date = from.date.toInstant().toRealmInstant()
|
||||||
unreadCount = from.unreadCount
|
unreadCount = from.unreadCount
|
||||||
lastMessagePreview = from.lastMessagePreview
|
lastMessagePreview = from.lastMessagePreview
|
||||||
|
lastMessageGUID = from.lastFetchedMessageGUID
|
||||||
guid = from.guid
|
guid = from.guid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ data class Conversation(
|
|||||||
|
|
||||||
@SerializedName("lastMessage")
|
@SerializedName("lastMessage")
|
||||||
var lastMessage: Message?,
|
var lastMessage: Message?,
|
||||||
|
|
||||||
|
var lastFetchedMessageGUID: String?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun generate(): Conversation {
|
fun generate(): Conversation {
|
||||||
@@ -38,6 +40,7 @@ data class Conversation(
|
|||||||
unreadCount = 0,
|
unreadCount = 0,
|
||||||
lastMessagePreview = null,
|
lastMessagePreview = null,
|
||||||
lastMessage = null,
|
lastMessage = null,
|
||||||
|
lastFetchedMessageGUID = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +62,7 @@ data class Conversation(
|
|||||||
participants == o.participants &&
|
participants == o.participants &&
|
||||||
displayName == o.displayName &&
|
displayName == o.displayName &&
|
||||||
unreadCount == o.unreadCount &&
|
unreadCount == o.unreadCount &&
|
||||||
lastMessagePreview == o.lastMessagePreview
|
lastFetchedMessageGUID == o.lastFetchedMessageGUID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +72,8 @@ data class Conversation(
|
|||||||
result = 31 * result + participants.hashCode()
|
result = 31 * result + participants.hashCode()
|
||||||
result = 31 * result + (displayName?.hashCode() ?: 0)
|
result = 31 * result + (displayName?.hashCode() ?: 0)
|
||||||
result = 31 * result + unreadCount
|
result = 31 * result + unreadCount
|
||||||
result = 31 * result + (lastMessagePreview?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + (lastMessage?.hashCode() ?: 0)
|
result = 31 * result + (lastMessage?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (lastFetchedMessageGUID?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,9 +182,7 @@ class ChatRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun synchronizeConversation(conversation: Conversation, limit: Int = 15) = withErrorChannelHandling {
|
suspend fun synchronizeConversation(conversation: Conversation, limit: Int = 15) = withErrorChannelHandling {
|
||||||
// TODO: Should only fetch messages after the last GUID we know about.
|
val messages = fetchMessages(conversation, limit = limit, afterGUID = conversation.lastFetchedMessageGUID)
|
||||||
// But keep in mind that outgoing message GUIDs are fake...
|
|
||||||
val messages = fetchMessages(conversation, limit = limit)
|
|
||||||
database.writeMessages(messages, conversation)
|
database.writeMessages(messages, conversation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,10 +231,10 @@ class ChatRepository(
|
|||||||
private suspend fun fetchMessages(
|
private suspend fun fetchMessages(
|
||||||
conversation: Conversation,
|
conversation: Conversation,
|
||||||
limit: Int? = null,
|
limit: Int? = null,
|
||||||
before: Message? = null,
|
beforeGUID: String? = null,
|
||||||
after: Message? = null,
|
afterGUID: String? = null,
|
||||||
): List<Message> {
|
): List<Message> {
|
||||||
return apiInterface.getMessages(conversation.guid, limit, before?.guid, after?.guid)
|
return apiInterface.getMessages(conversation.guid, limit, beforeGUID, afterGUID)
|
||||||
.bodyOnSuccessOrThrow()
|
.bodyOnSuccessOrThrow()
|
||||||
.onEach { it.conversation = conversation }
|
.onEach { it.conversation = conversation }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.buzzert.kordophone.backend.db.CachedChatDatabase
|
import net.buzzert.kordophone.backend.db.CachedChatDatabase
|
||||||
import net.buzzert.kordophone.backend.model.Message
|
import net.buzzert.kordophone.backend.model.Message
|
||||||
|
import net.buzzert.kordophone.backend.model.OutgoingMessage
|
||||||
import net.buzzert.kordophone.backend.server.APIClient
|
import net.buzzert.kordophone.backend.server.APIClient
|
||||||
import net.buzzert.kordophone.backend.server.APIInterface
|
import net.buzzert.kordophone.backend.server.APIInterface
|
||||||
import net.buzzert.kordophone.backend.server.Authentication
|
import net.buzzert.kordophone.backend.server.Authentication
|
||||||
@@ -85,13 +86,18 @@ class BackendTests {
|
|||||||
val (repository, mockServer) = mockRepository()
|
val (repository, mockServer) = mockRepository()
|
||||||
|
|
||||||
val conversation = mockServer.addTestConversations(1).first()
|
val conversation = mockServer.addTestConversations(1).first()
|
||||||
val outgoingMessage = MockServer.generateMessage(conversation)
|
val generatedMessage = MockServer.generateMessage(conversation)
|
||||||
|
val outgoingMessage = OutgoingMessage(
|
||||||
|
body = generatedMessage.text,
|
||||||
|
conversation = conversation,
|
||||||
|
attachmentUris = setOf(),
|
||||||
|
attachmentDataSource = { null },
|
||||||
|
)
|
||||||
|
|
||||||
val guid = repository.enqueueOutgoingMessage(outgoingMessage, conversation)
|
repository.enqueueOutgoingMessage(outgoingMessage)
|
||||||
|
|
||||||
val event = repository.messageDeliveredChannel.first()
|
val event = repository.messageDeliveredChannel.first()
|
||||||
assertEquals(event.requestGuid, guid)
|
assertEquals(event.message.text, outgoingMessage.body)
|
||||||
assertEquals(event.message.text, outgoingMessage.text)
|
|
||||||
|
|
||||||
repository.close()
|
repository.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,18 +18,19 @@ import net.buzzert.kordophone.backend.server.AuthenticationRequest
|
|||||||
import net.buzzert.kordophone.backend.server.AuthenticationResponse
|
import net.buzzert.kordophone.backend.server.AuthenticationResponse
|
||||||
import net.buzzert.kordophone.backend.server.SendMessageRequest
|
import net.buzzert.kordophone.backend.server.SendMessageRequest
|
||||||
import net.buzzert.kordophone.backend.server.SendMessageResponse
|
import net.buzzert.kordophone.backend.server.SendMessageResponse
|
||||||
|
import net.buzzert.kordophone.backend.server.UploadAttachmentResponse
|
||||||
import net.buzzert.kordophone.backend.server.authenticatedWebSocketURL
|
import net.buzzert.kordophone.backend.server.authenticatedWebSocketURL
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import okhttp3.WebSocket
|
import okhttp3.WebSocket
|
||||||
import okhttp3.WebSocketListener
|
import okhttp3.WebSocketListener
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -66,7 +67,8 @@ class MockServer {
|
|||||||
unreadCount = 0,
|
unreadCount = 0,
|
||||||
lastMessagePreview = null,
|
lastMessagePreview = null,
|
||||||
lastMessage = null,
|
lastMessage = null,
|
||||||
guid = UUID.randomUUID().toString()
|
guid = UUID.randomUUID().toString(),
|
||||||
|
lastFetchedMessageGUID = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +168,8 @@ class MockServerClient(private val server: MockServer): APIClient, WebSocketList
|
|||||||
private var updateWatchJob: Job? = null
|
private var updateWatchJob: Job? = null
|
||||||
private val gson: Gson = Gson()
|
private val gson: Gson = Gson()
|
||||||
|
|
||||||
|
override val isConfigured: Boolean = true
|
||||||
|
|
||||||
override fun getAPIInterface(): APIInterface {
|
override fun getAPIInterface(): APIInterface {
|
||||||
return MockServerInterface(server)
|
return MockServerInterface(server)
|
||||||
}
|
}
|
||||||
@@ -268,6 +272,13 @@ class MockServerInterface(private val server: MockServer): APIInterface {
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun uploadAttachment(
|
||||||
|
filename: String,
|
||||||
|
body: RequestBody
|
||||||
|
): Response<UploadAttachmentResponse> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun authenticate(request: AuthenticationRequest): Response<AuthenticationResponse> {
|
override suspend fun authenticate(request: AuthenticationRequest): Response<AuthenticationResponse> {
|
||||||
// Anything goes!
|
// Anything goes!
|
||||||
val response = AuthenticationResponse(
|
val response = AuthenticationResponse(
|
||||||
|
|||||||