wip, passing the image urls down to the matrix client layer
This commit is contained in:
parent
92ad630e45
commit
6fed8a35ce
|
@ -0,0 +1,29 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
|
@ -44,6 +44,7 @@ import app.dapk.st.matrix.sync.internal.room.MessageDecrypter
|
|||
import app.dapk.st.messenger.MessengerActivity
|
||||
import app.dapk.st.messenger.MessengerModule
|
||||
import app.dapk.st.navigator.IntentFactory
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import app.dapk.st.notifications.NotificationsModule
|
||||
import app.dapk.st.olm.DeviceKeyFactory
|
||||
import app.dapk.st.olm.OlmPersistenceWrapper
|
||||
|
@ -93,6 +94,11 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
|
|||
override fun home(context: Context) = Intent(context, MainActivity::class.java)
|
||||
override fun messenger(context: Context, roomId: RoomId) = MessengerActivity.newInstance(context, roomId)
|
||||
override fun messengerShortcut(context: Context, roomId: RoomId) = MessengerActivity.newShortcutInstance(context, roomId)
|
||||
override fun messengerAttachments(context: Context, roomId: RoomId, attachments: List<MessageAttachment>) = MessengerActivity.newMessageAttachment(
|
||||
context,
|
||||
roomId,
|
||||
attachments
|
||||
)
|
||||
})
|
||||
|
||||
val featureModules = FeatureModules(
|
||||
|
@ -236,6 +242,7 @@ internal class MatrixModules(
|
|||
val result = serviceProvider.cryptoService().encrypt(
|
||||
roomId = when (message) {
|
||||
is MessageService.Message.TextMessage -> message.roomId
|
||||
is MessageService.Message.ImageMessage -> message.roomId
|
||||
},
|
||||
credentials = credentialsStore.credentials()!!,
|
||||
when (message) {
|
||||
|
@ -250,6 +257,7 @@ internal class MatrixModules(
|
|||
)
|
||||
)
|
||||
)
|
||||
is MessageService.Message.ImageMessage -> TODO()
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package app.dapk.st.core
|
||||
|
||||
sealed interface MimeType {
|
||||
object Image: MimeType
|
||||
}
|
|
@ -33,11 +33,13 @@ class LocalEchoPersistence(
|
|||
inMemoryEchos.value = echos.groupBy {
|
||||
when (val message = it.message) {
|
||||
is MessageService.Message.TextMessage -> message.roomId
|
||||
is MessageService.Message.ImageMessage -> message.roomId
|
||||
}
|
||||
}.mapValues {
|
||||
it.value.associateBy {
|
||||
when (val message = it.message) {
|
||||
is MessageService.Message.TextMessage -> message.localId
|
||||
is MessageService.Message.ImageMessage -> message.localId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ class DirectoryUseCase(
|
|||
lastMessage = LastMessage(
|
||||
content = when (val message = latestEcho.message) {
|
||||
is MessageService.Message.TextMessage -> message.content.body
|
||||
is MessageService.Message.ImageMessage -> "\uD83D\uDCF7"
|
||||
},
|
||||
utcTimestamp = latestEcho.timestampUtc,
|
||||
author = member,
|
||||
|
|
|
@ -19,6 +19,7 @@ internal class LocalEchoMapper(private val metaMapper: MetaMapper) {
|
|||
meta = metaMapper.toMeta(this)
|
||||
)
|
||||
}
|
||||
is MessageService.Message.ImageMessage -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,10 @@ import androidx.activity.compose.setContent
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import app.dapk.st.core.DapkActivity
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.core.viewModel
|
||||
import app.dapk.st.core.*
|
||||
import app.dapk.st.design.components.SmallTalkTheme
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class MessengerActivity : DapkActivity() {
|
||||
|
@ -34,15 +33,22 @@ class MessengerActivity : DapkActivity() {
|
|||
putExtra("shortcut_key", roomId.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun newMessageAttachment(context: Context, roomId: RoomId, attachments: List<MessageAttachment>): Intent {
|
||||
return Intent(context, MessengerActivity::class.java).apply {
|
||||
putExtra("key", MessagerActivityPayload(roomId.value, attachments))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val payload = readPayload<MessagerActivityPayload>()
|
||||
log(AppLogTag.ERROR_NON_FATAL, payload)
|
||||
setContent {
|
||||
SmallTalkTheme {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
MessengerScreen(RoomId(payload.roomId), viewModel, navigator)
|
||||
MessengerScreen(RoomId(payload.roomId), payload.attachments, viewModel, navigator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +57,8 @@ class MessengerActivity : DapkActivity() {
|
|||
|
||||
@Parcelize
|
||||
data class MessagerActivityPayload(
|
||||
val roomId: String
|
||||
val roomId: String,
|
||||
val attachments: List<MessageAttachment>? = null
|
||||
) : Parcelable
|
||||
|
||||
fun <T : Parcelable> Activity.readPayload(): T = intent.getParcelableExtra("key") ?: intent.getStringExtra("shortcut_key")!!.let {
|
||||
|
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.core.net.toUri
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.LifecycleEffect
|
||||
import app.dapk.st.core.StartObserving
|
||||
|
@ -39,18 +40,19 @@ import app.dapk.st.matrix.sync.MessageMeta
|
|||
import app.dapk.st.matrix.sync.RoomEvent
|
||||
import app.dapk.st.matrix.sync.RoomEvent.Message
|
||||
import app.dapk.st.matrix.sync.RoomState
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import app.dapk.st.navigator.Navigator
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun MessengerScreen(roomId: RoomId, viewModel: MessengerViewModel, navigator: Navigator) {
|
||||
internal fun MessengerScreen(roomId: RoomId, attachments: List<MessageAttachment>?, viewModel: MessengerViewModel, navigator: Navigator) {
|
||||
val state = viewModel.state
|
||||
|
||||
viewModel.ObserveEvents()
|
||||
LifecycleEffect(
|
||||
onStart = { viewModel.post(MessengerAction.OnMessengerVisible(roomId)) },
|
||||
onStart = { viewModel.post(MessengerAction.OnMessengerVisible(roomId, attachments)) },
|
||||
onStop = { viewModel.post(MessengerAction.OnMessengerGone) }
|
||||
)
|
||||
|
||||
|
@ -70,12 +72,19 @@ internal fun MessengerScreen(roomId: RoomId, viewModel: MessengerViewModel, navi
|
|||
Room(state.roomState)
|
||||
when (state.composerState) {
|
||||
is ComposerState.Text -> {
|
||||
Composer(
|
||||
state.composerState.value,
|
||||
TextComposer(
|
||||
state.composerState,
|
||||
onTextChange = { viewModel.post(MessengerAction.ComposerTextUpdate(it)) },
|
||||
onSend = { viewModel.post(MessengerAction.ComposerSendText) },
|
||||
)
|
||||
}
|
||||
is ComposerState.Attachments -> {
|
||||
AttachmentComposer(
|
||||
state.composerState,
|
||||
onSend = { viewModel.post(MessengerAction.ComposerSendText) },
|
||||
onCancel = { viewModel.post(MessengerAction.ComposerClear) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -524,7 +533,7 @@ private fun RowScope.SendStatus(message: RoomEvent) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Composer(message: String, onTextChange: (String) -> Unit, onSend: () -> Unit) {
|
||||
private fun TextComposer(state: ComposerState.Text, onTextChange: (String) -> Unit, onSend: () -> Unit) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -541,12 +550,12 @@ private fun Composer(message: String, onTextChange: (String) -> Unit, onSend: ()
|
|||
contentAlignment = Alignment.TopStart,
|
||||
) {
|
||||
Box(Modifier.padding(14.dp)) {
|
||||
if (message.isEmpty()) {
|
||||
if (state.value.isEmpty()) {
|
||||
Text("Message")
|
||||
}
|
||||
BasicTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = message,
|
||||
value = state.value,
|
||||
onValueChange = { onTextChange(it) },
|
||||
cursorBrush = SolidColor(MaterialTheme.colors.primary),
|
||||
textStyle = LocalTextStyle.current.copy(color = LocalContentColor.current.copy(LocalContentAlpha.current)),
|
||||
|
@ -557,10 +566,10 @@ private fun Composer(message: String, onTextChange: (String) -> Unit, onSend: ()
|
|||
Spacer(modifier = Modifier.width(6.dp))
|
||||
var size by remember { mutableStateOf(IntSize(0, 0)) }
|
||||
IconButton(
|
||||
enabled = message.isNotEmpty(),
|
||||
enabled = state.value.isNotEmpty(),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.background(if (message.isEmpty()) Color.DarkGray else MaterialTheme.colors.primary)
|
||||
.background(if (state.value.isEmpty()) Color.DarkGray else MaterialTheme.colors.primary)
|
||||
.run {
|
||||
if (size.height == 0 || size.width == 0) {
|
||||
this
|
||||
|
@ -584,3 +593,65 @@ private fun Composer(message: String, onTextChange: (String) -> Unit, onSend: ()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AttachmentComposer(state: ComposerState.Attachments, onSend: () -> Unit, onCancel: () -> Unit) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min), verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Bottom)
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.background(MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity), RoundedCornerShape(24.dp)),
|
||||
contentAlignment = Alignment.TopStart,
|
||||
) {
|
||||
Box(Modifier.padding(14.dp)) {
|
||||
val context = LocalContext.current
|
||||
Image(
|
||||
modifier = Modifier.size(50.dp, 50.dp),
|
||||
painter = rememberAsyncImagePainter(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(state.values.first().uri.value.toUri())
|
||||
.build()
|
||||
),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
var size by remember { mutableStateOf(IntSize(0, 0)) }
|
||||
IconButton(
|
||||
enabled = true,
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.run {
|
||||
if (size.height == 0 || size.width == 0) {
|
||||
this
|
||||
.onSizeChanged {
|
||||
size = it
|
||||
}
|
||||
.fillMaxHeight()
|
||||
} else {
|
||||
with(LocalDensity.current) {
|
||||
size(size.width.toDp(), size.height.toDp())
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = onSend,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Send,
|
||||
contentDescription = "",
|
||||
tint = MaterialTheme.colors.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package app.dapk.st.messenger
|
|||
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
|
||||
data class MessengerScreenState(
|
||||
val roomId: RoomId?,
|
||||
|
@ -17,4 +18,8 @@ sealed interface ComposerState {
|
|||
val value: String,
|
||||
) : ComposerState
|
||||
|
||||
data class Attachments(
|
||||
val values: List<MessageAttachment>,
|
||||
) : ComposerState
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import app.dapk.st.matrix.message.MessageService
|
|||
import app.dapk.st.matrix.room.RoomService
|
||||
import app.dapk.st.matrix.sync.RoomEvent
|
||||
import app.dapk.st.matrix.sync.RoomStore
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import app.dapk.st.viewmodel.DapkViewModel
|
||||
import app.dapk.st.viewmodel.MutableStateFactory
|
||||
import app.dapk.st.viewmodel.defaultStateFactory
|
||||
|
@ -46,18 +47,19 @@ internal class MessengerViewModel(
|
|||
MessengerAction.OnMessengerGone -> syncJob?.cancel()
|
||||
is MessengerAction.ComposerTextUpdate -> updateState { copy(composerState = ComposerState.Text(action.newValue)) }
|
||||
MessengerAction.ComposerSendText -> sendMessage()
|
||||
MessengerAction.ComposerClear -> updateState { copy(composerState = ComposerState.Text("")) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun start(action: MessengerAction.OnMessengerVisible) {
|
||||
updateState { copy(roomId = action.roomId) }
|
||||
updateState { copy(roomId = action.roomId, composerState = action.attachments?.let { ComposerState.Attachments(it) } ?: composerState) }
|
||||
syncJob = viewModelScope.launch {
|
||||
roomStore.markRead(action.roomId)
|
||||
|
||||
val credentials = credentialsStore.credentials()!!
|
||||
var lastKnownReadEvent: EventId? = null
|
||||
observeTimeline.invoke(action.roomId, credentials.userId).distinctUntilChanged().onEach { state ->
|
||||
state.lastestMessageEventFromOthers(self = credentials.userId)?.let {
|
||||
state.latestMessageEventFromOthers(self = credentials.userId)?.let {
|
||||
if (lastKnownReadEvent != it) {
|
||||
updateRoomReadStateAsync(latestReadEvent = it, state)
|
||||
lastKnownReadEvent = it
|
||||
|
@ -98,12 +100,32 @@ internal class MessengerViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
is ComposerState.Attachments -> {
|
||||
val copy = composerState.copy()
|
||||
updateState { copy(composerState = ComposerState.Text("")) }
|
||||
|
||||
state.roomState.takeIfContent()?.let { content ->
|
||||
val roomState = content.roomState
|
||||
viewModelScope.launch {
|
||||
messageService.scheduleMessage(
|
||||
MessageService.Message.ImageMessage(
|
||||
MessageService.Message.Content.ImageContent(uri = copy.values.first().uri.value),
|
||||
roomId = roomState.roomOverview.roomId,
|
||||
sendEncrypted = roomState.roomOverview.isEncrypted,
|
||||
localId = localIdFactory.create(),
|
||||
timestampUtc = clock.millis(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun MessengerState.lastestMessageEventFromOthers(self: UserId) = this.roomState.events
|
||||
private fun MessengerState.latestMessageEventFromOthers(self: UserId) = this.roomState.events
|
||||
.filterIsInstance<RoomEvent.Message>()
|
||||
.filterNot { it.author.id == self }
|
||||
.firstOrNull()
|
||||
|
@ -112,6 +134,7 @@ private fun MessengerState.lastestMessageEventFromOthers(self: UserId) = this.ro
|
|||
sealed interface MessengerAction {
|
||||
data class ComposerTextUpdate(val newValue: String) : MessengerAction
|
||||
object ComposerSendText : MessengerAction
|
||||
data class OnMessengerVisible(val roomId: RoomId) : MessengerAction
|
||||
object ComposerClear : MessengerAction
|
||||
data class OnMessengerVisible(val roomId: RoomId, val attachments: List<MessageAttachment>?) : MessengerAction
|
||||
object OnMessengerGone : MessengerAction
|
||||
}
|
|
@ -36,7 +36,7 @@ class RoomSettingsActivity : DapkActivity() {
|
|||
setContent {
|
||||
SmallTalkTheme {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
MessengerScreen(RoomId(payload.roomId), viewModel, navigator)
|
||||
// MessengerScreen(RoomId(payload.roomId), payload.attachments, viewModel, navigator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
applyAndroidLibraryModule(project)
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
dependencies {
|
||||
implementation project(":core")
|
||||
|
|
|
@ -3,7 +3,13 @@ package app.dapk.st.navigator
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import app.dapk.st.core.AndroidUri
|
||||
import app.dapk.st.core.MimeType
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import kotlinx.parcelize.Parceler
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
@ -29,8 +35,9 @@ interface Navigator {
|
|||
activity.navigateUpTo(intentFactory.home(activity))
|
||||
}
|
||||
|
||||
fun toMessenger(roomId: RoomId) {
|
||||
intentFactory.messenger(activity, roomId)
|
||||
fun toMessenger(roomId: RoomId, attachments: List<MessageAttachment>) {
|
||||
val intent = intentFactory.messengerAttachments(activity, roomId, attachments)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
|
||||
fun toFilePicker(requestCode: Int) {
|
||||
|
@ -47,6 +54,7 @@ interface IntentFactory {
|
|||
fun home(context: Context): Intent
|
||||
fun messenger(context: Context, roomId: RoomId): Intent
|
||||
fun messengerShortcut(context: Context, roomId: RoomId): Intent
|
||||
fun messengerAttachments(context: Context, roomId: RoomId, attachments: List<MessageAttachment>): Intent
|
||||
|
||||
}
|
||||
|
||||
|
@ -65,3 +73,24 @@ private class DefaultNavigator(activity: Activity, intentFactory: IntentFactory)
|
|||
override val navigate: Navigator.Dsl = Navigator.Dsl(activity, intentFactory)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class MessageAttachment(val uri: AndroidUri, val type: MimeType) : Parcelable {
|
||||
private companion object : Parceler<MessageAttachment> {
|
||||
override fun create(parcel: Parcel): MessageAttachment {
|
||||
val uri = AndroidUri(parcel.readString()!!)
|
||||
val type = when(parcel.readString()!!) {
|
||||
"mimetype-image" -> MimeType.Image
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
return MessageAttachment(uri, type)
|
||||
}
|
||||
|
||||
override fun MessageAttachment.write(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(uri.value)
|
||||
when (type) {
|
||||
MimeType.Image -> parcel.writeString("mimetype-image")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,4 +9,5 @@ dependencies {
|
|||
implementation project(':matrix:services:message')
|
||||
implementation project(":core")
|
||||
implementation project(":design-library")
|
||||
implementation project(":features:navigator")
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package app.dapk.st.share
|
||||
|
||||
import app.dapk.st.matrix.room.RoomService
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
class FetchRoomsUseCase(
|
||||
private val syncSyncService: SyncService,
|
||||
private val roomService: RoomService,
|
||||
) {
|
||||
|
||||
suspend fun bar(): List<Item> {
|
||||
return syncSyncService.overview().first().map {
|
||||
Item(
|
||||
it.roomId,
|
||||
it.roomAvatarUrl,
|
||||
it.roomName ?: "",
|
||||
roomService.findMembersSummary(it.roomId).map { it.displayName ?: it.id.value }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,11 +19,11 @@ class ShareEntryActivity : DapkActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val urisToShare = intent.readSendUrisOrNull() ?: throw IllegalArgumentException("")
|
||||
val urisToShare = intent.readSendUrisOrNull() ?: throw IllegalArgumentException("Expected deeplink uris but they were missing")
|
||||
setContent {
|
||||
SmallTalkTheme {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
ShareEntryScreen(viewModel)
|
||||
ShareEntryScreen(navigator, viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,37 +11,36 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.dapk.st.core.LifecycleEffect
|
||||
import app.dapk.st.core.MimeType
|
||||
import app.dapk.st.core.StartObserving
|
||||
import app.dapk.st.core.components.CenteredLoading
|
||||
import app.dapk.st.design.components.CircleishAvatar
|
||||
import app.dapk.st.design.components.GenericEmpty
|
||||
import app.dapk.st.design.components.GenericError
|
||||
import app.dapk.st.design.components.Toolbar
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import app.dapk.st.navigator.Navigator
|
||||
import app.dapk.st.share.DirectoryScreenState.*
|
||||
|
||||
@Composable
|
||||
fun ShareEntryScreen(viewModel: ShareEntryViewModel) {
|
||||
fun ShareEntryScreen(navigator: Navigator, viewModel: ShareEntryViewModel) {
|
||||
val state = viewModel.state
|
||||
|
||||
val listState: LazyListState = rememberLazyListState(
|
||||
initialFirstVisibleItemIndex = 0,
|
||||
)
|
||||
|
||||
viewModel.ObserveEvents(listState)
|
||||
viewModel.ObserveEvents(navigator)
|
||||
|
||||
LifecycleEffect(
|
||||
onStart = { viewModel.start() },
|
||||
onStop = { viewModel.stop() }
|
||||
)
|
||||
|
||||
val listState: LazyListState = rememberLazyListState(
|
||||
initialFirstVisibleItemIndex = 0,
|
||||
)
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Toolbar(title = "Send to...")
|
||||
when (state) {
|
||||
|
@ -50,18 +49,21 @@ fun ShareEntryScreen(viewModel: ShareEntryViewModel) {
|
|||
is Error -> GenericError {
|
||||
// TODO
|
||||
}
|
||||
is Content -> Content(listState, state)
|
||||
is Content -> Content(listState, state) {
|
||||
viewModel.onRoomSelected(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShareEntryViewModel.ObserveEvents(listState: LazyListState) {
|
||||
val context = LocalContext.current
|
||||
private fun ShareEntryViewModel.ObserveEvents(navigator: Navigator) {
|
||||
StartObserving {
|
||||
this@ObserveEvents.events.launch {
|
||||
when (it) {
|
||||
is DirectoryEvent.SelectRoom -> TODO()
|
||||
is DirectoryEvent.SelectRoom -> {
|
||||
navigator.navigate.toMessenger(it.item.id, it.uris.map { MessageAttachment(it, MimeType.Image) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,33 +71,27 @@ private fun ShareEntryViewModel.ObserveEvents(listState: LazyListState) {
|
|||
|
||||
|
||||
@Composable
|
||||
private fun Content(listState: LazyListState, state: Content) {
|
||||
val context = LocalContext.current
|
||||
val navigateToRoom = { roomId: RoomId ->
|
||||
// todo
|
||||
// context.startActivity(MessengerActivity.newInstance(context, roomId))
|
||||
}
|
||||
private fun Content(listState: LazyListState, state: Content, onClick: (Item) -> Unit) {
|
||||
LazyColumn(Modifier.fillMaxSize(), state = listState, contentPadding = PaddingValues(top = 72.dp)) {
|
||||
items(
|
||||
items = state.items,
|
||||
key = { it.id.value },
|
||||
) {
|
||||
DirectoryItem(it, onClick = navigateToRoom)
|
||||
DirectoryItem(it, onClick = onClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DirectoryItem(item: Item, onClick: (RoomId) -> Unit) {
|
||||
private fun DirectoryItem(item: Item, onClick: (Item) -> Unit) {
|
||||
val roomName = item.roomName
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.height(IntrinsicSize.Min)
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onClick(item.id)
|
||||
}) {
|
||||
.clickable { onClick(item) }
|
||||
) {
|
||||
Row(Modifier.padding(20.dp)) {
|
||||
val secondaryText = MaterialTheme.colors.onBackground.copy(alpha = 0.5f)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package app.dapk.st.share
|
||||
|
||||
import app.dapk.st.core.AndroidUri
|
||||
import app.dapk.st.matrix.common.AvatarUrl
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
|
||||
|
@ -13,7 +14,7 @@ sealed interface DirectoryScreenState {
|
|||
}
|
||||
|
||||
sealed interface DirectoryEvent {
|
||||
data class SelectRoom(val item: Item) : DirectoryEvent
|
||||
data class SelectRoom(val item: Item, val uris: List<AndroidUri>) : DirectoryEvent
|
||||
}
|
||||
|
||||
data class Item(val id: RoomId, val roomAvatarUrl: AvatarUrl?, val roomName: String, val members: List<String>)
|
||||
|
|
|
@ -3,15 +3,10 @@ package app.dapk.st.share
|
|||
import android.net.Uri
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.dapk.st.core.AndroidUri
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.message.MessageService
|
||||
import app.dapk.st.matrix.room.RoomService
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import app.dapk.st.viewmodel.DapkViewModel
|
||||
import app.dapk.st.viewmodel.MutableStateFactory
|
||||
import app.dapk.st.viewmodel.defaultStateFactory
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ShareEntryViewModel(
|
||||
|
@ -22,6 +17,7 @@ class ShareEntryViewModel(
|
|||
factory,
|
||||
) {
|
||||
|
||||
private var urisToShare: List<AndroidUri>? = null
|
||||
private var syncJob: Job? = null
|
||||
|
||||
fun start() {
|
||||
|
@ -34,31 +30,14 @@ class ShareEntryViewModel(
|
|||
syncJob?.cancel()
|
||||
}
|
||||
|
||||
|
||||
fun sendAttachment() {
|
||||
|
||||
}
|
||||
|
||||
fun withUris(urisToShare: List<Uri>) {
|
||||
// TODO("Not yet implemented")
|
||||
this.urisToShare = urisToShare.map { AndroidUri(it.toString()) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FetchRoomsUseCase(
|
||||
private val syncSyncService: SyncService,
|
||||
private val roomService: RoomService,
|
||||
) {
|
||||
|
||||
suspend fun bar(): List<Item> {
|
||||
return syncSyncService.overview().first().map {
|
||||
Item(
|
||||
it.roomId,
|
||||
it.roomAvatarUrl,
|
||||
it.roomName ?: "",
|
||||
roomService.findMembersSummary(it.roomId).map { it.displayName ?: it.id.value }
|
||||
)
|
||||
fun onRoomSelected(item: Item) {
|
||||
viewModelScope.launch {
|
||||
_events.emit(DirectoryEvent.SelectRoom(item, uris = urisToShare ?: throw IllegalArgumentException("Not uris set")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,5 +14,6 @@ enum class EventType(val value: String) {
|
|||
}
|
||||
|
||||
enum class MessageType(val value: String) {
|
||||
TEXT("m.text")
|
||||
TEXT("m.text"),
|
||||
IMAGE("m.image"),
|
||||
}
|
|
@ -46,6 +46,16 @@ interface MessageService : MatrixService {
|
|||
@SerialName("timestamp") val timestampUtc: Long,
|
||||
) : Message()
|
||||
|
||||
@Serializable
|
||||
@SerialName("image_message")
|
||||
data class ImageMessage(
|
||||
@SerialName("content") val content: Content.ImageContent,
|
||||
@SerialName("send_encrypted") val sendEncrypted: Boolean,
|
||||
@SerialName("room_id") val roomId: RoomId,
|
||||
@SerialName("local_id") val localId: String,
|
||||
@SerialName("timestamp") val timestampUtc: Long,
|
||||
) : Message()
|
||||
|
||||
@Serializable
|
||||
sealed class Content {
|
||||
@Serializable
|
||||
|
@ -53,6 +63,12 @@ interface MessageService : MatrixService {
|
|||
@SerialName("body") val body: String,
|
||||
@SerialName("msgtype") val type: String = MessageType.TEXT.value,
|
||||
) : Content()
|
||||
|
||||
@Serializable
|
||||
data class ImageContent(
|
||||
@SerialName("uri") val uri: String,
|
||||
@SerialName("msgtype") val type: String = MessageType.IMAGE.value,
|
||||
) : Content()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,16 +82,19 @@ interface MessageService : MatrixService {
|
|||
@Transient
|
||||
val timestampUtc = when (message) {
|
||||
is Message.TextMessage -> message.timestampUtc
|
||||
is Message.ImageMessage -> message.timestampUtc
|
||||
}
|
||||
|
||||
@Transient
|
||||
val roomId = when (message) {
|
||||
is Message.TextMessage -> message.roomId
|
||||
is Message.ImageMessage -> message.roomId
|
||||
}
|
||||
|
||||
@Transient
|
||||
val localId = when (message) {
|
||||
is Message.TextMessage -> message.localId
|
||||
is Message.ImageMessage -> message.localId
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.net.SocketException
|
|||
import java.net.UnknownHostException
|
||||
|
||||
private const val MATRIX_MESSAGE_TASK_TYPE = "matrix-text-message"
|
||||
private const val MATRIX_IMAGE_MESSAGE_TASK_TYPE = "matrix-image-message"
|
||||
|
||||
internal class DefaultMessageService(
|
||||
httpClient: MatrixHttpClient,
|
||||
|
@ -50,6 +51,7 @@ internal class DefaultMessageService(
|
|||
localEchoStore.markSending(message)
|
||||
val localId = when (message) {
|
||||
is MessageService.Message.TextMessage -> message.localId
|
||||
is MessageService.Message.ImageMessage -> message.localId
|
||||
}
|
||||
backgroundScheduler.schedule(key = localId, message.toTask())
|
||||
}
|
||||
|
@ -68,6 +70,10 @@ internal class DefaultMessageService(
|
|||
Json.encodeToString(MessageService.Message.TextMessage.serializer(), this)
|
||||
)
|
||||
}
|
||||
is MessageService.Message.ImageMessage -> BackgroundScheduler.Task(
|
||||
type = MATRIX_IMAGE_MESSAGE_TASK_TYPE,
|
||||
Json.encodeToString(MessageService.Message.ImageMessage.serializer(), this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,36 @@ internal class SendMessageUseCase(
|
|||
}
|
||||
httpClient.execute(request).eventId
|
||||
}
|
||||
is MessageService.Message.ImageMessage -> {
|
||||
// upload image, then send message
|
||||
// POST /_matrix/media/v3/upload
|
||||
// message.content.uri
|
||||
|
||||
/**
|
||||
* {
|
||||
"content": {
|
||||
"body": "filename.jpg",
|
||||
"info": {
|
||||
"h": 398,
|
||||
"mimetype": "image/jpeg",
|
||||
"size": 31037,
|
||||
"w": 394
|
||||
},
|
||||
"msgtype": "m.image",
|
||||
"url": "mxc://example.org/JWEIFJgwEIhweiWJE"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1234
|
||||
}
|
||||
}
|
||||
*/
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, con
|
|||
method = MatrixHttpClient.Method.PUT,
|
||||
body = when (content) {
|
||||
is Message.Content.TextContent -> jsonBody(Message.Content.TextContent.serializer(), content, MatrixHttpClient.jsonWithDefaults)
|
||||
is Message.Content.ImageContent -> jsonBody(Message.Content.ImageContent.serializer(), content, MatrixHttpClient.jsonWithDefaults)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ class TestMatrix(
|
|||
val result = serviceProvider.cryptoService().encrypt(
|
||||
roomId = when (message) {
|
||||
is MessageService.Message.TextMessage -> message.roomId
|
||||
is MessageService.Message.ImageMessage -> message.roomId
|
||||
},
|
||||
credentials = storeModule.credentialsStore().credentials()!!,
|
||||
when (message) {
|
||||
|
@ -128,6 +129,7 @@ class TestMatrix(
|
|||
)
|
||||
)
|
||||
)
|
||||
is MessageService.Message.ImageMessage -> TODO()
|
||||
}
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue