fetching rooms and displaying when starting a share flow

This commit is contained in:
Adam Brown 2022-05-30 19:16:16 +01:00
parent 2db9ff3baf
commit e6bd0f80dc
17 changed files with 284 additions and 1 deletions

View File

@ -19,6 +19,7 @@ import app.dapk.st.notifications.NotificationsModule
import app.dapk.st.notifications.PushAndroidService
import app.dapk.st.profile.ProfileModule
import app.dapk.st.settings.SettingsModule
import app.dapk.st.share.ShareEntryModule
import app.dapk.st.work.TaskRunnerModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
@ -75,6 +76,7 @@ class SmallTalkApplication : Application(), ModuleProvider {
MessengerModule::class -> featureModules.messengerModule
TaskRunnerModule::class -> appModule.domainModules.taskRunnerModule
CoreAndroidModule::class -> appModule.coreAndroidModule
ShareEntryModule::class -> featureModules.shareEntryModule
else -> throw IllegalArgumentException("Unknown: $klass")
} as T
}

View File

@ -51,6 +51,7 @@ import app.dapk.st.olm.OlmWrapper
import app.dapk.st.profile.ProfileModule
import app.dapk.st.push.PushModule
import app.dapk.st.settings.SettingsModule
import app.dapk.st.share.ShareEntryModule
import app.dapk.st.tracking.TrackingModule
import app.dapk.st.work.TaskRunnerModule
import app.dapk.st.work.WorkModule
@ -178,6 +179,10 @@ internal class FeatureModules internal constructor(
)
}
val shareEntryModule by unsafeLazy {
ShareEntryModule(matrixModules.sync, matrixModules.room)
}
}
internal class MatrixModules(
@ -358,6 +363,7 @@ internal class MatrixModules(
val roomService = services.roomService()
object : RoomMembersService {
override suspend fun find(roomId: RoomId, userIds: List<UserId>) = roomService.findMembers(roomId, userIds)
override suspend fun findSummary(roomId: RoomId) = roomService.findMembersSummary(roomId)
override suspend fun insert(roomId: RoomId, members: List<RoomMember>) = roomService.insertMembers(roomId, members)
}
},

View File

@ -0,0 +1,4 @@
package app.dapk.st.core
@JvmInline
value class AndroidUri(val value: String)

View File

@ -35,4 +35,12 @@ class MemberPersistence(
.map { Json.decodeFromString(RoomMember.serializer(), it) }
}
}
override suspend fun query(roomId: RoomId, limit: Int): List<RoomMember> {
return coroutineDispatchers.withIoContext {
database.roomMemberQueries.selectMembersByRoom(roomId.value, limit.toLong())
.executeAsList()
.map { Json.decodeFromString(RoomMember.serializer(), it) }
}
}
}

View File

@ -10,6 +10,13 @@ SELECT blob
FROM dbRoomMember
WHERE room_id = ? AND user_id IN ?;
selectMembersByRoom:
SELECT blob
FROM dbRoomMember
WHERE room_id = ?
LIMIT ?;
insert:
INSERT OR REPLACE INTO dbRoomMember(user_id, room_id, blob)
VALUES (?, ?, ?);

View File

@ -29,6 +29,10 @@ interface Navigator {
activity.navigateUpTo(intentFactory.home(activity))
}
fun toMessenger(roomId: RoomId) {
intentFactory.messenger(activity, roomId)
}
fun toFilePicker(requestCode: Int) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)

View File

@ -4,6 +4,9 @@ dependencies {
implementation project(":domains:android:core")
implementation project(":domains:android:viewmodel")
implementation project(':domains:store')
implementation project(':matrix:services:sync')
implementation project(':matrix:services:room')
implementation project(':matrix:services:message')
implementation project(":core")
implementation project(":design-library")
}

View File

@ -4,14 +4,30 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
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.design.components.SmallTalkTheme
class ShareEntryActivity : DapkActivity() {
private val viewModel by viewModel { module<ShareEntryModule>().shareEntryViewModel() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val urisToShare = intent.readSendUrisOrNull() ?: throw IllegalArgumentException("")
setContent {
SmallTalkTheme {
Surface(Modifier.fillMaxSize()) {
ShareEntryScreen(viewModel)
}
}
}
viewModel.withUris(urisToShare)
}
}

View File

@ -0,0 +1,15 @@
package app.dapk.st.share
import app.dapk.st.core.ProvidableModule
import app.dapk.st.matrix.room.RoomService
import app.dapk.st.matrix.sync.SyncService
class ShareEntryModule(
private val syncService: SyncService,
private val roomService: RoomService,
) : ProvidableModule {
fun shareEntryViewModel(): ShareEntryViewModel {
return ShareEntryViewModel(FetchRoomsUseCase(syncService, roomService))
}
}

View File

@ -0,0 +1,125 @@
package app.dapk.st.share
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.MaterialTheme
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.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.share.DirectoryScreenState.*
@Composable
fun ShareEntryScreen(viewModel: ShareEntryViewModel) {
val state = viewModel.state
val listState: LazyListState = rememberLazyListState(
initialFirstVisibleItemIndex = 0,
)
viewModel.ObserveEvents(listState)
LifecycleEffect(
onStart = { viewModel.start() },
onStop = { viewModel.stop() }
)
Box(modifier = Modifier.fillMaxSize()) {
Toolbar(title = "Send to...")
when (state) {
EmptyLoading -> CenteredLoading()
Empty -> GenericEmpty()
is Error -> GenericError {
// TODO
}
is Content -> Content(listState, state)
}
}
}
@Composable
private fun ShareEntryViewModel.ObserveEvents(listState: LazyListState) {
val context = LocalContext.current
StartObserving {
this@ObserveEvents.events.launch {
when (it) {
is DirectoryEvent.SelectRoom -> TODO()
}
}
}
}
@Composable
private fun Content(listState: LazyListState, state: Content) {
val context = LocalContext.current
val navigateToRoom = { roomId: RoomId ->
// todo
// context.startActivity(MessengerActivity.newInstance(context, roomId))
}
LazyColumn(Modifier.fillMaxSize(), state = listState, contentPadding = PaddingValues(top = 72.dp)) {
items(
items = state.items,
key = { it.id.value },
) {
DirectoryItem(it, onClick = navigateToRoom)
}
}
}
@Composable
private fun DirectoryItem(item: Item, onClick: (RoomId) -> Unit) {
val roomName = item.roomName
Box(
Modifier
.height(IntrinsicSize.Min)
.fillMaxWidth()
.clickable {
onClick(item.id)
}) {
Row(Modifier.padding(20.dp)) {
val secondaryText = MaterialTheme.colors.onBackground.copy(alpha = 0.5f)
Box(Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) {
CircleishAvatar(item.roomAvatarUrl?.value, roomName, size = 50.dp)
}
Spacer(Modifier.width(20.dp))
Column {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Text(
modifier = Modifier.weight(1f),
maxLines = 1,
fontSize = 18.sp,
text = roomName,
overflow = TextOverflow.Ellipsis,
fontFamily = FontFamily.SansSerif,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colors.onBackground
)
Spacer(modifier = Modifier.width(6.dp))
}
Text(text = item.members.joinToString(), color = secondaryText, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
}
}

View File

@ -0,0 +1,19 @@
package app.dapk.st.share
import app.dapk.st.matrix.common.AvatarUrl
import app.dapk.st.matrix.common.RoomId
sealed interface DirectoryScreenState {
object EmptyLoading : DirectoryScreenState
object Empty : DirectoryScreenState
data class Content(
val items: List<Item>,
) : DirectoryScreenState
}
sealed interface DirectoryEvent {
data class SelectRoom(val item: Item) : DirectoryEvent
}
data class Item(val id: RoomId, val roomAvatarUrl: AvatarUrl?, val roomName: String, val members: List<String>)

View File

@ -0,0 +1,64 @@
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(
private val fetchRoomsUseCase: FetchRoomsUseCase,
factory: MutableStateFactory<DirectoryScreenState> = defaultStateFactory(),
) : DapkViewModel<DirectoryScreenState, DirectoryEvent>(
initialState = DirectoryScreenState.EmptyLoading,
factory,
) {
private var syncJob: Job? = null
fun start() {
syncJob = viewModelScope.launch {
state = DirectoryScreenState.Content(fetchRoomsUseCase.bar())
}
}
fun stop() {
syncJob?.cancel()
}
fun sendAttachment() {
}
fun withUris(urisToShare: List<Uri>) {
// TODO("Not yet implemented")
}
}
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 }
)
}
}
}

View File

@ -21,6 +21,7 @@ interface RoomService : MatrixService {
suspend fun findMember(roomId: RoomId, userId: UserId): RoomMember?
suspend fun findMembers(roomId: RoomId, userIds: List<UserId>): List<RoomMember>
suspend fun findMembersSummary(roomId: RoomId): List<RoomMember>
suspend fun insertMembers(roomId: RoomId, members: List<RoomMember>)
suspend fun createDm(userId: UserId, encrypted: Boolean): RoomId
@ -50,6 +51,7 @@ fun MatrixServiceProvider.roomService(): RoomService = this.getService(key = SER
interface MemberStore {
suspend fun insert(roomId: RoomId, members: List<RoomMember>)
suspend fun query(roomId: RoomId, userIds: List<UserId>): List<RoomMember>
suspend fun query(roomId: RoomId, limit: Int): List<RoomMember>
}
interface RoomMessenger {

View File

@ -39,6 +39,10 @@ class DefaultRoomService(
return roomMembers.findMembers(roomId, userIds)
}
override suspend fun findMembersSummary(roomId: RoomId): List<RoomMember> {
return roomMembers.findMembersSummary(roomId)
}
override suspend fun insertMembers(roomId: RoomId, members: List<RoomMember>) {
roomMembers.insert(roomId, members)
}

View File

@ -36,6 +36,8 @@ class RoomMembers(private val memberStore: MemberStore, private val membersCache
}
}
suspend fun findMembersSummary(roomId: RoomId) = memberStore.query(roomId, limit = 8)
suspend fun insert(roomId: RoomId, members: List<RoomMember>) {
membersCache.insert(roomId, members)
memberStore.insert(roomId, members)

View File

@ -128,6 +128,7 @@ internal object NoOpKeySharer : KeySharer {
interface RoomMembersService {
suspend fun find(roomId: RoomId, userIds: List<UserId>): List<RoomMember>
suspend fun findSummary(roomId: RoomId): List<RoomMember>
suspend fun insert(roomId: RoomId, members: List<RoomMember>)
}

View File

@ -246,6 +246,7 @@ class TestMatrix(
val roomService = services.roomService()
object : RoomMembersService {
override suspend fun find(roomId: RoomId, userIds: List<UserId>) = roomService.findMembers(roomId, userIds)
override suspend fun findSummary(roomId: RoomId) = roomService.findMembersSummary(roomId)
override suspend fun insert(roomId: RoomId, members: List<RoomMember>) = roomService.insertMembers(roomId, members)
}
},