lifts room muting to the chat engine
This commit is contained in:
parent
7e184cf200
commit
dc5a46e0cd
|
@ -18,7 +18,6 @@ import app.dapk.st.core.extensions.ErrorTracker
|
|||
import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.directory.DirectoryModule
|
||||
import app.dapk.st.domain.StoreModule
|
||||
import app.dapk.st.domain.room.MutedRoomsStorePersistence
|
||||
import app.dapk.st.engine.MatrixEngine
|
||||
import app.dapk.st.firebase.messaging.MessagingModule
|
||||
import app.dapk.st.home.BetaVersionUpgradeUseCase
|
||||
|
@ -164,7 +163,6 @@ internal class FeatureModules internal constructor(
|
|||
context,
|
||||
storeModule.value.messageStore(),
|
||||
deviceMeta,
|
||||
MutedRoomsStorePersistence(storeModule.value.cachingPreferences)
|
||||
)
|
||||
}
|
||||
val homeModule by unsafeLazy {
|
||||
|
|
|
@ -36,6 +36,8 @@ interface ChatEngine : TaskRunner {
|
|||
|
||||
fun pushHandler(): PushHandler
|
||||
|
||||
suspend fun muteRoom(roomId: RoomId)
|
||||
suspend fun unmuteRoom(roomId: RoomId)
|
||||
}
|
||||
|
||||
interface TaskRunner {
|
||||
|
|
|
@ -87,7 +87,8 @@ sealed interface ImportResult {
|
|||
data class MessengerPageState(
|
||||
val self: UserId,
|
||||
val roomState: RoomState,
|
||||
val typing: Typing?
|
||||
val typing: Typing?,
|
||||
val isMuted: Boolean,
|
||||
)
|
||||
|
||||
data class RoomState(
|
||||
|
|
|
@ -13,6 +13,7 @@ import app.dapk.st.domain.preference.CachingPreferences
|
|||
import app.dapk.st.domain.preference.PropertyCache
|
||||
import app.dapk.st.domain.profile.ProfilePersistence
|
||||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.domain.room.MutedStorePersistence
|
||||
import app.dapk.st.domain.sync.OverviewPersistence
|
||||
import app.dapk.st.domain.sync.RoomPersistence
|
||||
import app.dapk.st.matrix.common.CredentialsStore
|
||||
|
@ -34,7 +35,13 @@ class StoreModule(
|
|||
) {
|
||||
|
||||
fun overviewStore(): OverviewStore = OverviewPersistence(database, coroutineDispatchers)
|
||||
fun roomStore(): RoomStore = RoomPersistence(database, OverviewPersistence(database, coroutineDispatchers), coroutineDispatchers)
|
||||
fun roomStore(): RoomStore = RoomPersistence(
|
||||
database = database,
|
||||
overviewPersistence = OverviewPersistence(database, coroutineDispatchers),
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
muteableStore = MutedStorePersistence(preferences),
|
||||
)
|
||||
|
||||
fun credentialsStore(): CredentialsStore = CredentialsPreferences(credentialPreferences)
|
||||
fun syncStore(): SyncStore = SyncTokenPreferences(preferences)
|
||||
fun filterStore(): FilterStore = FilterPreferences(preferences)
|
||||
|
|
|
@ -4,19 +4,13 @@ import app.dapk.st.core.Preferences
|
|||
import app.dapk.st.core.append
|
||||
import app.dapk.st.core.removeFromSet
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.sync.MuteableStore
|
||||
|
||||
private const val KEY_MUTE = "mute"
|
||||
|
||||
interface MutedRoomsStore {
|
||||
suspend fun mute(roomId: RoomId)
|
||||
suspend fun unmute(roomId: RoomId)
|
||||
suspend fun isMuted(roomId: RoomId): Boolean
|
||||
suspend fun allMuted(): Set<RoomId>
|
||||
}
|
||||
|
||||
class MutedRoomsStorePersistence(
|
||||
internal class MutedStorePersistence(
|
||||
private val preferences: Preferences
|
||||
) : MutedRoomsStore {
|
||||
) : MuteableStore {
|
||||
|
||||
override suspend fun mute(roomId: RoomId) {
|
||||
preferences.append(KEY_MUTE, roomId.value)
|
||||
|
@ -28,8 +22,6 @@ class MutedRoomsStorePersistence(
|
|||
|
||||
override suspend fun isMuted(roomId: RoomId): Boolean {
|
||||
val allMuted = allMuted()
|
||||
println("??? isMuted - $roomId")
|
||||
println("??? all - $allMuted")
|
||||
return allMuted.contains(roomId)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,10 @@ import app.dapk.db.DapkDb
|
|||
import app.dapk.db.model.RoomEventQueries
|
||||
import app.dapk.st.core.CoroutineDispatchers
|
||||
import app.dapk.st.core.withIoContext
|
||||
import app.dapk.st.domain.room.MutedStorePersistence
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.sync.RoomEvent
|
||||
import app.dapk.st.matrix.sync.RoomOverview
|
||||
import app.dapk.st.matrix.sync.RoomState
|
||||
import app.dapk.st.matrix.sync.RoomStore
|
||||
import app.dapk.st.matrix.sync.*
|
||||
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
||||
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
||||
import com.squareup.sqldelight.runtime.coroutines.mapToOneNotNull
|
||||
|
@ -25,7 +23,8 @@ internal class RoomPersistence(
|
|||
private val database: DapkDb,
|
||||
private val overviewPersistence: OverviewPersistence,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : RoomStore {
|
||||
private val muteableStore: MutedStorePersistence,
|
||||
) : RoomStore, MuteableStore by muteableStore {
|
||||
|
||||
override suspend fun persist(roomId: RoomId, events: List<RoomEvent>) {
|
||||
coroutineDispatchers.withIoContext {
|
||||
|
|
|
@ -10,7 +10,6 @@ import app.dapk.st.domain.application.message.MessageOptionsStore
|
|||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.messenger.state.MessengerState
|
||||
import app.dapk.st.domain.room.MutedRoomsStore
|
||||
import app.dapk.st.messenger.state.messengerReducer
|
||||
|
||||
class MessengerModule(
|
||||
|
@ -18,7 +17,6 @@ class MessengerModule(
|
|||
private val context: Context,
|
||||
private val messageOptionsStore: MessageOptionsStore,
|
||||
private val deviceMeta: DeviceMeta,
|
||||
private val mutedRoomsStore: MutedRoomsStore,
|
||||
) : ProvidableModule {
|
||||
|
||||
internal fun messengerState(launchPayload: MessagerActivityPayload): MessengerState {
|
||||
|
@ -29,7 +27,6 @@ class MessengerModule(
|
|||
CopyToClipboard(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager),
|
||||
deviceMeta,
|
||||
messageOptionsStore,
|
||||
mutedRoomsStore,
|
||||
RoomId(launchPayload.roomId),
|
||||
launchPayload.attachments,
|
||||
it
|
||||
|
|
|
@ -87,8 +87,9 @@ internal fun MessengerScreen(
|
|||
|
||||
Column {
|
||||
Toolbar(onNavigate = { navigator.navigate.upToHome() }, roomTitle, actions = {
|
||||
state.roomState.takeIfContent()?.let {
|
||||
OverflowMenu {
|
||||
when (state.isMuted) {
|
||||
when (it.isMuted) {
|
||||
true -> DropdownMenuItem(text = { Text("Unmute notifications", color = MaterialTheme.colorScheme.onSecondaryContainer) }, onClick = {
|
||||
viewModel.dispatch(ScreenAction.Notifications.Unmute)
|
||||
})
|
||||
|
@ -98,6 +99,8 @@ internal fun MessengerScreen(
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
when (state.composerState) {
|
||||
|
|
|
@ -8,7 +8,6 @@ import app.dapk.st.core.asString
|
|||
import app.dapk.st.core.extensions.takeIfContent
|
||||
import app.dapk.st.design.components.BubbleModel
|
||||
import app.dapk.st.domain.application.message.MessageOptionsStore
|
||||
import app.dapk.st.domain.room.MutedRoomsStore
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.engine.MessengerPageState
|
||||
import app.dapk.st.engine.RoomEvent
|
||||
|
@ -27,7 +26,6 @@ internal fun messengerReducer(
|
|||
copyToClipboard: CopyToClipboard,
|
||||
deviceMeta: DeviceMeta,
|
||||
messageOptionsStore: MessageOptionsStore,
|
||||
mutedRoomsStore: MutedRoomsStore,
|
||||
roomId: RoomId,
|
||||
initialAttachments: List<MessageAttachment>?,
|
||||
eventEmitter: suspend (MessengerEvent) -> Unit,
|
||||
|
@ -38,7 +36,6 @@ internal fun messengerReducer(
|
|||
roomState = Lce.Loading(),
|
||||
composerState = initialComposerState(initialAttachments),
|
||||
viewerState = null,
|
||||
isMuted = false,
|
||||
),
|
||||
|
||||
async(ComponentLifecycle::class) { action ->
|
||||
|
@ -50,7 +47,6 @@ internal fun messengerReducer(
|
|||
.onEach { dispatch(MessagesStateChange.Content(it)) }
|
||||
.launchIn(coroutineScope)
|
||||
)
|
||||
dispatch(MessagesStateChange.MuteContent(mutedRoomsStore.isMuted(roomId)))
|
||||
}
|
||||
|
||||
ComponentLifecycle.Gone -> jobBag.cancel("messages")
|
||||
|
@ -141,15 +137,17 @@ internal fun messengerReducer(
|
|||
},
|
||||
|
||||
change(MessagesStateChange.MuteContent::class) { action, state ->
|
||||
state.copy(isMuted = action.isMuted).also {
|
||||
println("??? action - $action previous state: ${state.isMuted} next: ${it.isMuted}")
|
||||
when (val roomState = state.roomState) {
|
||||
is Lce.Content -> state.copy(roomState = roomState.copy(value = roomState.value.copy(isMuted = action.isMuted)))
|
||||
is Lce.Error -> state
|
||||
is Lce.Loading -> state
|
||||
}
|
||||
},
|
||||
|
||||
async(ScreenAction.Notifications::class) { action ->
|
||||
when (action) {
|
||||
ScreenAction.Notifications.Mute -> mutedRoomsStore.mute(roomId)
|
||||
ScreenAction.Notifications.Unmute -> mutedRoomsStore.unmute(roomId)
|
||||
ScreenAction.Notifications.Mute -> chatEngine.muteRoom(roomId)
|
||||
ScreenAction.Notifications.Unmute -> chatEngine.unmuteRoom(roomId)
|
||||
}
|
||||
|
||||
dispatch(
|
||||
|
|
|
@ -15,7 +15,6 @@ data class MessengerScreenState(
|
|||
val roomState: Lce<MessengerPageState>,
|
||||
val composerState: ComposerState,
|
||||
val viewerState: ViewerState?,
|
||||
val isMuted: Boolean,
|
||||
)
|
||||
|
||||
data class ViewerState(
|
||||
|
|
|
@ -3,30 +3,28 @@ package app.dapk.st.engine
|
|||
import app.dapk.st.core.Base64
|
||||
import app.dapk.st.core.BuildMeta
|
||||
import app.dapk.st.core.CoroutineDispatchers
|
||||
import app.dapk.st.core.SingletonFlows
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.matrix.MatrixClient
|
||||
import app.dapk.st.matrix.MatrixTaskRunner
|
||||
import app.dapk.st.matrix.auth.DeviceDisplayNameGenerator
|
||||
import app.dapk.st.matrix.auth.authService
|
||||
import app.dapk.st.matrix.auth.installAuthService
|
||||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.crypto.*
|
||||
import app.dapk.st.matrix.common.CredentialsStore
|
||||
import app.dapk.st.matrix.common.MatrixLogger
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.crypto.MatrixMediaDecrypter
|
||||
import app.dapk.st.matrix.crypto.cryptoService
|
||||
import app.dapk.st.matrix.device.KnownDeviceStore
|
||||
import app.dapk.st.matrix.device.deviceService
|
||||
import app.dapk.st.matrix.device.installEncryptionService
|
||||
import app.dapk.st.matrix.http.ktor.KtorMatrixHttpClientFactory
|
||||
import app.dapk.st.matrix.message.*
|
||||
import app.dapk.st.matrix.message.BackgroundScheduler
|
||||
import app.dapk.st.matrix.message.LocalEchoStore
|
||||
import app.dapk.st.matrix.message.internal.ImageContentReader
|
||||
import app.dapk.st.matrix.push.installPushService
|
||||
import app.dapk.st.matrix.message.messageService
|
||||
import app.dapk.st.matrix.push.pushService
|
||||
import app.dapk.st.matrix.room.*
|
||||
import app.dapk.st.matrix.room.MemberStore
|
||||
import app.dapk.st.matrix.room.ProfileStore
|
||||
import app.dapk.st.matrix.room.profileService
|
||||
import app.dapk.st.matrix.room.roomService
|
||||
import app.dapk.st.matrix.sync.*
|
||||
import app.dapk.st.matrix.sync.internal.request.ApiToDeviceEvent
|
||||
import app.dapk.st.matrix.sync.internal.room.MessageDecrypter
|
||||
import app.dapk.st.olm.DeviceKeyFactory
|
||||
import app.dapk.st.olm.OlmStore
|
||||
import app.dapk.st.olm.OlmWrapper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -114,6 +112,10 @@ class MatrixEngine internal constructor(
|
|||
|
||||
override fun pushHandler() = matrixPushHandler.value
|
||||
|
||||
override suspend fun muteRoom(roomId: RoomId) = matrix.value.roomService().muteRoom(roomId)
|
||||
|
||||
override suspend fun unmuteRoom(roomId: RoomId) = matrix.value.roomService().unmuteRoom(roomId)
|
||||
|
||||
override suspend fun runTask(task: ChatEngineTask): TaskRunner.TaskResult {
|
||||
return when (val result = matrix.value.run(MatrixTaskRunner.MatrixTask(task.type, task.jsonPayload))) {
|
||||
is MatrixTaskRunner.TaskResult.Failure -> TaskRunner.TaskResult.Failure(result.canRetry)
|
||||
|
@ -209,222 +211,4 @@ class MatrixEngine internal constructor(
|
|||
|
||||
}
|
||||
|
||||
|
||||
object MatrixFactory {
|
||||
|
||||
fun createMatrix(
|
||||
base64: Base64,
|
||||
buildMeta: BuildMeta,
|
||||
logger: MatrixLogger,
|
||||
nameGenerator: DeviceDisplayNameGenerator,
|
||||
coroutineDispatchers: CoroutineDispatchers,
|
||||
errorTracker: ErrorTracker,
|
||||
imageContentReader: ImageContentReader,
|
||||
backgroundScheduler: BackgroundScheduler,
|
||||
memberStore: MemberStore,
|
||||
roomStore: RoomStore,
|
||||
profileStore: ProfileStore,
|
||||
syncStore: SyncStore,
|
||||
overviewStore: OverviewStore,
|
||||
filterStore: FilterStore,
|
||||
localEchoStore: LocalEchoStore,
|
||||
credentialsStore: CredentialsStore,
|
||||
knownDeviceStore: KnownDeviceStore,
|
||||
olmStore: OlmStore,
|
||||
) = MatrixClient(
|
||||
KtorMatrixHttpClientFactory(
|
||||
credentialsStore,
|
||||
includeLogging = buildMeta.isDebug,
|
||||
),
|
||||
logger
|
||||
).also {
|
||||
it.install {
|
||||
installAuthService(credentialsStore, nameGenerator)
|
||||
installEncryptionService(knownDeviceStore)
|
||||
|
||||
val singletonFlows = SingletonFlows(coroutineDispatchers)
|
||||
val olm = OlmWrapper(
|
||||
olmStore = olmStore,
|
||||
singletonFlows = singletonFlows,
|
||||
jsonCanonicalizer = JsonCanonicalizer(),
|
||||
deviceKeyFactory = DeviceKeyFactory(JsonCanonicalizer()),
|
||||
errorTracker = errorTracker,
|
||||
logger = logger,
|
||||
clock = Clock.systemUTC(),
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
installCryptoService(
|
||||
credentialsStore,
|
||||
olm,
|
||||
roomMembersProvider = { services ->
|
||||
RoomMembersProvider {
|
||||
services.roomService().joinedMembers(it).map { it.userId }
|
||||
}
|
||||
},
|
||||
base64 = base64,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
installMessageService(
|
||||
localEchoStore,
|
||||
backgroundScheduler,
|
||||
imageContentReader,
|
||||
messageEncrypter = {
|
||||
val cryptoService = it.cryptoService()
|
||||
MessageEncrypter { message ->
|
||||
val result = cryptoService.encrypt(
|
||||
roomId = message.roomId,
|
||||
credentials = credentialsStore.credentials()!!,
|
||||
messageJson = message.contents,
|
||||
)
|
||||
|
||||
MessageEncrypter.EncryptedMessagePayload(
|
||||
result.algorithmName,
|
||||
result.senderKey,
|
||||
result.cipherText,
|
||||
result.sessionId,
|
||||
result.deviceId,
|
||||
)
|
||||
}
|
||||
},
|
||||
mediaEncrypter = {
|
||||
val cryptoService = it.cryptoService()
|
||||
MediaEncrypter { input ->
|
||||
val result = cryptoService.encrypt(input)
|
||||
MediaEncrypter.Result(
|
||||
uri = result.uri,
|
||||
contentLength = result.contentLength,
|
||||
algorithm = result.algorithm,
|
||||
ext = result.ext,
|
||||
keyOperations = result.keyOperations,
|
||||
kty = result.kty,
|
||||
k = result.k,
|
||||
iv = result.iv,
|
||||
hashes = result.hashes,
|
||||
v = result.v,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
installRoomService(
|
||||
memberStore,
|
||||
roomMessenger = {
|
||||
val messageService = it.messageService()
|
||||
object : RoomMessenger {
|
||||
override suspend fun enableEncryption(roomId: RoomId) {
|
||||
messageService.sendEventMessage(
|
||||
roomId, MessageService.EventMessage.Encryption(
|
||||
algorithm = AlgorithmName("m.megolm.v1.aes-sha2")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
roomInviteRemover = {
|
||||
overviewStore.removeInvites(listOf(it))
|
||||
}
|
||||
)
|
||||
|
||||
installProfileService(profileStore, singletonFlows, credentialsStore)
|
||||
|
||||
installSyncService(
|
||||
credentialsStore,
|
||||
overviewStore,
|
||||
roomStore,
|
||||
syncStore,
|
||||
filterStore,
|
||||
deviceNotifier = { services ->
|
||||
val encryption = services.deviceService()
|
||||
val crypto = services.cryptoService()
|
||||
DeviceNotifier { userIds, syncToken ->
|
||||
encryption.updateStaleDevices(userIds)
|
||||
crypto.updateOlmSession(userIds, syncToken)
|
||||
}
|
||||
},
|
||||
messageDecrypter = { serviceProvider ->
|
||||
val cryptoService = serviceProvider.cryptoService()
|
||||
MessageDecrypter {
|
||||
cryptoService.decrypt(it)
|
||||
}
|
||||
},
|
||||
keySharer = { serviceProvider ->
|
||||
val cryptoService = serviceProvider.cryptoService()
|
||||
KeySharer { sharedRoomKeys ->
|
||||
cryptoService.importRoomKeys(sharedRoomKeys)
|
||||
}
|
||||
},
|
||||
verificationHandler = { services ->
|
||||
val cryptoService = services.cryptoService()
|
||||
VerificationHandler { apiEvent ->
|
||||
logger.matrixLog(MatrixLogTag.VERIFICATION, "got a verification request $it")
|
||||
cryptoService.onVerificationEvent(
|
||||
when (apiEvent) {
|
||||
is ApiToDeviceEvent.VerificationRequest -> Verification.Event.Requested(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.fromDevice,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.methods,
|
||||
apiEvent.content.timestampPosix,
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationReady -> Verification.Event.Ready(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.fromDevice,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.methods,
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationStart -> Verification.Event.Started(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.fromDevice,
|
||||
apiEvent.content.method,
|
||||
apiEvent.content.protocols,
|
||||
apiEvent.content.hashes,
|
||||
apiEvent.content.codes,
|
||||
apiEvent.content.short,
|
||||
apiEvent.content.transactionId,
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationCancel -> TODO()
|
||||
is ApiToDeviceEvent.VerificationAccept -> TODO()
|
||||
is ApiToDeviceEvent.VerificationKey -> Verification.Event.Key(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.key
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationMac -> Verification.Event.Mac(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.keys,
|
||||
apiEvent.content.mac,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
oneTimeKeyProducer = { services ->
|
||||
val cryptoService = services.cryptoService()
|
||||
MaybeCreateMoreKeys {
|
||||
cryptoService.maybeCreateMoreKeys(it)
|
||||
}
|
||||
},
|
||||
roomMembersService = { services ->
|
||||
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)
|
||||
}
|
||||
},
|
||||
errorTracker = errorTracker,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
|
||||
installPushService(credentialsStore)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun <T> unsafeLazy(initializer: () -> T): Lazy<T> = lazy(mode = LazyThreadSafetyMode.NONE, initializer = initializer)
|
||||
private fun <T> unsafeLazy(initializer: () -> T): Lazy<T> = lazy(mode = LazyThreadSafetyMode.NONE, initializer = initializer)
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
package app.dapk.st.engine
|
||||
|
||||
import app.dapk.st.core.Base64
|
||||
import app.dapk.st.core.BuildMeta
|
||||
import app.dapk.st.core.CoroutineDispatchers
|
||||
import app.dapk.st.core.SingletonFlows
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.matrix.MatrixClient
|
||||
import app.dapk.st.matrix.auth.DeviceDisplayNameGenerator
|
||||
import app.dapk.st.matrix.auth.installAuthService
|
||||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.crypto.RoomMembersProvider
|
||||
import app.dapk.st.matrix.crypto.Verification
|
||||
import app.dapk.st.matrix.crypto.cryptoService
|
||||
import app.dapk.st.matrix.crypto.installCryptoService
|
||||
import app.dapk.st.matrix.device.KnownDeviceStore
|
||||
import app.dapk.st.matrix.device.deviceService
|
||||
import app.dapk.st.matrix.device.installEncryptionService
|
||||
import app.dapk.st.matrix.http.ktor.KtorMatrixHttpClientFactory
|
||||
import app.dapk.st.matrix.message.*
|
||||
import app.dapk.st.matrix.message.internal.ImageContentReader
|
||||
import app.dapk.st.matrix.push.installPushService
|
||||
import app.dapk.st.matrix.room.*
|
||||
import app.dapk.st.matrix.room.internal.SingleRoomStore
|
||||
import app.dapk.st.matrix.sync.*
|
||||
import app.dapk.st.matrix.sync.internal.request.ApiToDeviceEvent
|
||||
import app.dapk.st.matrix.sync.internal.room.MessageDecrypter
|
||||
import app.dapk.st.olm.DeviceKeyFactory
|
||||
import app.dapk.st.olm.OlmStore
|
||||
import app.dapk.st.olm.OlmWrapper
|
||||
import java.time.Clock
|
||||
|
||||
internal object MatrixFactory {
|
||||
|
||||
fun createMatrix(
|
||||
base64: Base64,
|
||||
buildMeta: BuildMeta,
|
||||
logger: MatrixLogger,
|
||||
nameGenerator: DeviceDisplayNameGenerator,
|
||||
coroutineDispatchers: CoroutineDispatchers,
|
||||
errorTracker: ErrorTracker,
|
||||
imageContentReader: ImageContentReader,
|
||||
backgroundScheduler: BackgroundScheduler,
|
||||
memberStore: MemberStore,
|
||||
roomStore: RoomStore,
|
||||
profileStore: ProfileStore,
|
||||
syncStore: SyncStore,
|
||||
overviewStore: OverviewStore,
|
||||
filterStore: FilterStore,
|
||||
localEchoStore: LocalEchoStore,
|
||||
credentialsStore: CredentialsStore,
|
||||
knownDeviceStore: KnownDeviceStore,
|
||||
olmStore: OlmStore,
|
||||
) = MatrixClient(
|
||||
KtorMatrixHttpClientFactory(
|
||||
credentialsStore,
|
||||
includeLogging = buildMeta.isDebug,
|
||||
),
|
||||
logger
|
||||
).also {
|
||||
it.install {
|
||||
installAuthService(credentialsStore, nameGenerator)
|
||||
installEncryptionService(knownDeviceStore)
|
||||
|
||||
val singletonFlows = SingletonFlows(coroutineDispatchers)
|
||||
val olm = OlmWrapper(
|
||||
olmStore = olmStore,
|
||||
singletonFlows = singletonFlows,
|
||||
jsonCanonicalizer = JsonCanonicalizer(),
|
||||
deviceKeyFactory = DeviceKeyFactory(JsonCanonicalizer()),
|
||||
errorTracker = errorTracker,
|
||||
logger = logger,
|
||||
clock = Clock.systemUTC(),
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
installCryptoService(
|
||||
credentialsStore,
|
||||
olm,
|
||||
roomMembersProvider = { services ->
|
||||
RoomMembersProvider {
|
||||
services.roomService().joinedMembers(it).map { it.userId }
|
||||
}
|
||||
},
|
||||
base64 = base64,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
installMessageService(
|
||||
localEchoStore,
|
||||
backgroundScheduler,
|
||||
imageContentReader,
|
||||
messageEncrypter = {
|
||||
val cryptoService = it.cryptoService()
|
||||
MessageEncrypter { message ->
|
||||
val result = cryptoService.encrypt(
|
||||
roomId = message.roomId,
|
||||
credentials = credentialsStore.credentials()!!,
|
||||
messageJson = message.contents,
|
||||
)
|
||||
|
||||
MessageEncrypter.EncryptedMessagePayload(
|
||||
result.algorithmName,
|
||||
result.senderKey,
|
||||
result.cipherText,
|
||||
result.sessionId,
|
||||
result.deviceId,
|
||||
)
|
||||
}
|
||||
},
|
||||
mediaEncrypter = {
|
||||
val cryptoService = it.cryptoService()
|
||||
MediaEncrypter { input ->
|
||||
val result = cryptoService.encrypt(input)
|
||||
MediaEncrypter.Result(
|
||||
uri = result.uri,
|
||||
contentLength = result.contentLength,
|
||||
algorithm = result.algorithm,
|
||||
ext = result.ext,
|
||||
keyOperations = result.keyOperations,
|
||||
kty = result.kty,
|
||||
k = result.k,
|
||||
iv = result.iv,
|
||||
hashes = result.hashes,
|
||||
v = result.v,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
installRoomService(
|
||||
memberStore,
|
||||
roomMessenger = {
|
||||
val messageService = it.messageService()
|
||||
object : RoomMessenger {
|
||||
override suspend fun enableEncryption(roomId: RoomId) {
|
||||
messageService.sendEventMessage(
|
||||
roomId, MessageService.EventMessage.Encryption(
|
||||
algorithm = AlgorithmName("m.megolm.v1.aes-sha2")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
roomInviteRemover = {
|
||||
overviewStore.removeInvites(listOf(it))
|
||||
},
|
||||
singleRoomStore = object : SingleRoomStore {
|
||||
override suspend fun mute(roomId: RoomId) = roomStore.mute(roomId)
|
||||
override suspend fun unmute(roomId: RoomId) = roomStore.unmute(roomId)
|
||||
override suspend fun isMuted(roomId: RoomId) = roomStore.isMuted(roomId)
|
||||
}
|
||||
)
|
||||
|
||||
installProfileService(profileStore, singletonFlows, credentialsStore)
|
||||
|
||||
installSyncService(
|
||||
credentialsStore,
|
||||
overviewStore,
|
||||
roomStore,
|
||||
syncStore,
|
||||
filterStore,
|
||||
deviceNotifier = { services ->
|
||||
val encryption = services.deviceService()
|
||||
val crypto = services.cryptoService()
|
||||
DeviceNotifier { userIds, syncToken ->
|
||||
encryption.updateStaleDevices(userIds)
|
||||
crypto.updateOlmSession(userIds, syncToken)
|
||||
}
|
||||
},
|
||||
messageDecrypter = { serviceProvider ->
|
||||
val cryptoService = serviceProvider.cryptoService()
|
||||
MessageDecrypter {
|
||||
cryptoService.decrypt(it)
|
||||
}
|
||||
},
|
||||
keySharer = { serviceProvider ->
|
||||
val cryptoService = serviceProvider.cryptoService()
|
||||
KeySharer { sharedRoomKeys ->
|
||||
cryptoService.importRoomKeys(sharedRoomKeys)
|
||||
}
|
||||
},
|
||||
verificationHandler = { services ->
|
||||
val cryptoService = services.cryptoService()
|
||||
VerificationHandler { apiEvent ->
|
||||
logger.matrixLog(MatrixLogTag.VERIFICATION, "got a verification request $it")
|
||||
cryptoService.onVerificationEvent(
|
||||
when (apiEvent) {
|
||||
is ApiToDeviceEvent.VerificationRequest -> Verification.Event.Requested(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.fromDevice,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.methods,
|
||||
apiEvent.content.timestampPosix,
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationReady -> Verification.Event.Ready(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.fromDevice,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.methods,
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationStart -> Verification.Event.Started(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.fromDevice,
|
||||
apiEvent.content.method,
|
||||
apiEvent.content.protocols,
|
||||
apiEvent.content.hashes,
|
||||
apiEvent.content.codes,
|
||||
apiEvent.content.short,
|
||||
apiEvent.content.transactionId,
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationCancel -> TODO()
|
||||
is ApiToDeviceEvent.VerificationAccept -> TODO()
|
||||
is ApiToDeviceEvent.VerificationKey -> Verification.Event.Key(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.key
|
||||
)
|
||||
|
||||
is ApiToDeviceEvent.VerificationMac -> Verification.Event.Mac(
|
||||
apiEvent.sender,
|
||||
apiEvent.content.transactionId,
|
||||
apiEvent.content.keys,
|
||||
apiEvent.content.mac,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
oneTimeKeyProducer = { services ->
|
||||
val cryptoService = services.cryptoService()
|
||||
MaybeCreateMoreKeys {
|
||||
cryptoService.maybeCreateMoreKeys(it)
|
||||
}
|
||||
},
|
||||
roomMembersService = { services ->
|
||||
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)
|
||||
}
|
||||
},
|
||||
errorTracker = errorTracker,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
|
||||
installPushService(credentialsStore)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -38,6 +38,7 @@ internal class TimelineUseCaseImpl(
|
|||
},
|
||||
typing = events.filterIsInstance<SyncService.SyncEvent.Typing>().firstOrNull { it.roomId == roomId }?.engine(),
|
||||
self = userId,
|
||||
isMuted = roomService.isMuted(roomId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,7 @@ import app.dapk.st.matrix.common.EventId
|
|||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.common.RoomMember
|
||||
import app.dapk.st.matrix.common.UserId
|
||||
import app.dapk.st.matrix.room.internal.DefaultRoomService
|
||||
import app.dapk.st.matrix.room.internal.RoomInviteRemover
|
||||
import app.dapk.st.matrix.room.internal.RoomMembers
|
||||
import app.dapk.st.matrix.room.internal.RoomMembersCache
|
||||
import app.dapk.st.matrix.room.internal.*
|
||||
|
||||
private val SERVICE_KEY = RoomService::class
|
||||
|
||||
|
@ -27,6 +24,10 @@ interface RoomService : MatrixService {
|
|||
suspend fun joinRoom(roomId: RoomId)
|
||||
suspend fun rejectJoinRoom(roomId: RoomId)
|
||||
|
||||
suspend fun muteRoom(roomId: RoomId)
|
||||
suspend fun unmuteRoom(roomId: RoomId)
|
||||
suspend fun isMuted(roomId: RoomId): Boolean
|
||||
|
||||
data class JoinedMember(
|
||||
val userId: UserId,
|
||||
val displayName: String?,
|
||||
|
@ -39,6 +40,7 @@ fun MatrixServiceInstaller.installRoomService(
|
|||
memberStore: MemberStore,
|
||||
roomMessenger: ServiceDepFactory<RoomMessenger>,
|
||||
roomInviteRemover: RoomInviteRemover,
|
||||
singleRoomStore: SingleRoomStore,
|
||||
): InstallExtender<RoomService> {
|
||||
return this.install { (httpClient, _, services, logger) ->
|
||||
SERVICE_KEY to DefaultRoomService(
|
||||
|
@ -46,7 +48,8 @@ fun MatrixServiceInstaller.installRoomService(
|
|||
logger,
|
||||
RoomMembers(memberStore, RoomMembersCache()),
|
||||
roomMessenger.create(services),
|
||||
roomInviteRemover
|
||||
roomInviteRemover,
|
||||
singleRoomStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class DefaultRoomService(
|
|||
private val roomMembers: RoomMembers,
|
||||
private val roomMessenger: RoomMessenger,
|
||||
private val roomInviteRemover: RoomInviteRemover,
|
||||
private val singleRoomStore: SingleRoomStore,
|
||||
) : RoomService {
|
||||
|
||||
override suspend fun joinedMembers(roomId: RoomId): List<RoomService.JoinedMember> {
|
||||
|
@ -82,6 +83,7 @@ class DefaultRoomService(
|
|||
} else {
|
||||
throw it
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else -> throw it
|
||||
|
@ -90,6 +92,22 @@ class DefaultRoomService(
|
|||
)
|
||||
roomInviteRemover.remove(roomId)
|
||||
}
|
||||
|
||||
override suspend fun muteRoom(roomId: RoomId) {
|
||||
singleRoomStore.mute(roomId)
|
||||
}
|
||||
|
||||
override suspend fun unmuteRoom(roomId: RoomId) {
|
||||
singleRoomStore.unmute(roomId)
|
||||
}
|
||||
|
||||
override suspend fun isMuted(roomId: RoomId) = singleRoomStore.isMuted(roomId)
|
||||
}
|
||||
|
||||
interface SingleRoomStore {
|
||||
suspend fun mute(roomId: RoomId)
|
||||
suspend fun unmute(roomId: RoomId)
|
||||
suspend fun isMuted(roomId: RoomId): Boolean
|
||||
}
|
||||
|
||||
internal fun joinedMembersRequest(roomId: RoomId) = httpRequest<JoinedMembersResponse>(
|
||||
|
|
|
@ -5,7 +5,7 @@ import app.dapk.st.matrix.common.RoomId
|
|||
import app.dapk.st.matrix.common.SyncToken
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface RoomStore {
|
||||
interface RoomStore : MuteableStore {
|
||||
|
||||
suspend fun persist(roomId: RoomId, events: List<RoomEvent>)
|
||||
suspend fun remove(rooms: List<RoomId>)
|
||||
|
@ -21,6 +21,14 @@ interface RoomStore {
|
|||
|
||||
}
|
||||
|
||||
interface MuteableStore {
|
||||
suspend fun mute(roomId: RoomId)
|
||||
suspend fun unmute(roomId: RoomId)
|
||||
suspend fun isMuted(roomId: RoomId): Boolean
|
||||
suspend fun allMuted(): Set<RoomId>
|
||||
}
|
||||
|
||||
|
||||
interface FilterStore {
|
||||
|
||||
suspend fun store(key: String, filterId: String)
|
||||
|
|
Loading…
Reference in New Issue