moving login and home modules to the chat engine

This commit is contained in:
Adam Brown 2022-10-09 17:24:05 +01:00
parent 128c2db432
commit 492d7df9ac
13 changed files with 174 additions and 104 deletions

View File

@ -25,7 +25,6 @@ import app.dapk.st.imageloader.ImageLoaderModule
import app.dapk.st.login.LoginModule import app.dapk.st.login.LoginModule
import app.dapk.st.matrix.MatrixClient import app.dapk.st.matrix.MatrixClient
import app.dapk.st.matrix.auth.DeviceDisplayNameGenerator 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.auth.installAuthService
import app.dapk.st.matrix.common.* import app.dapk.st.matrix.common.*
import app.dapk.st.matrix.crypto.RoomMembersProvider import app.dapk.st.matrix.crypto.RoomMembersProvider
@ -171,9 +170,8 @@ internal class FeatureModules internal constructor(
} }
val loginModule by unsafeLazy { val loginModule by unsafeLazy {
LoginModule( LoginModule(
matrixModules.auth, matrixModules.engine,
domainModules.pushModule, domainModules.pushModule,
matrixModules.profile,
trackingModule.errorTracker trackingModule.errorTracker
) )
} }
@ -191,7 +189,7 @@ internal class FeatureModules internal constructor(
storeModule.value.messageStore(), storeModule.value.messageStore(),
) )
} }
val homeModule by unsafeLazy { HomeModule(storeModule.value, matrixModules.profile, matrixModules.sync, buildMeta) } val homeModule by unsafeLazy { HomeModule(matrixModules.engine, storeModule.value, buildMeta) }
val settingsModule by unsafeLazy { val settingsModule by unsafeLazy {
SettingsModule( SettingsModule(
storeModule.value, storeModule.value,
@ -473,7 +471,6 @@ internal class MatrixModules(
} }
} }
val auth by unsafeLazy { matrix.authService() }
val push by unsafeLazy { matrix.pushService() } val push by unsafeLazy { matrix.pushService() }
val sync by unsafeLazy { matrix.syncService() } val sync by unsafeLazy { matrix.syncService() }
val message by unsafeLazy { matrix.messageService() } val message by unsafeLazy { matrix.messageService() }

View File

@ -5,8 +5,4 @@ plugins {
dependencies { dependencies {
api Dependencies.mavenCentral.kotlinCoroutinesCore api Dependencies.mavenCentral.kotlinCoroutinesCore
api project(":matrix:common") api project(":matrix:common")
implementation project(":matrix:services:sync")
implementation project(":matrix:services:message")
implementation project(":matrix:services:room")
} }

View File

@ -1,46 +1,14 @@
package app.dapk.st.engine package app.dapk.st.engine
import app.dapk.st.matrix.common.AvatarUrl
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.RoomMember
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ChatEngine { interface ChatEngine {
fun directory(): Flow<DirectoryState> fun directory(): Flow<DirectoryState>
fun invites(): Flow<InviteState>
suspend fun login(request: LoginRequest): LoginResult
suspend fun me(forceRefresh: Boolean): Me
} }
typealias DirectoryState = List<DirectoryItem>
typealias OverviewState = List<RoomOverview>
data class DirectoryItem(
val overview: RoomOverview,
val unreadCount: UnreadCount,
val typing: Typing?
)
data class RoomOverview(
val roomId: RoomId,
val roomCreationUtc: Long,
val roomName: String?,
val roomAvatarUrl: AvatarUrl?,
val lastMessage: LastMessage?,
val isGroup: Boolean,
val readMarker: EventId?,
val isEncrypted: Boolean,
) {
data class LastMessage(
val content: String,
val utcTimestamp: Long,
val author: RoomMember,
)
}
@JvmInline
value class UnreadCount(val value: Int)
data class Typing(val roomId: RoomId, val members: List<RoomMember>)

View File

@ -0,0 +1,64 @@
package app.dapk.st.engine
import app.dapk.st.matrix.common.*
typealias DirectoryState = List<DirectoryItem>
typealias OverviewState = List<RoomOverview>
typealias InviteState = List<RoomInvite>
data class DirectoryItem(
val overview: RoomOverview,
val unreadCount: UnreadCount,
val typing: Typing?
)
data class RoomOverview(
val roomId: RoomId,
val roomCreationUtc: Long,
val roomName: String?,
val roomAvatarUrl: AvatarUrl?,
val lastMessage: LastMessage?,
val isGroup: Boolean,
val readMarker: EventId?,
val isEncrypted: Boolean,
) {
data class LastMessage(
val content: String,
val utcTimestamp: Long,
val author: RoomMember,
)
}
data class RoomInvite(
val from: RoomMember,
val roomId: RoomId,
val inviteMeta: InviteMeta,
) {
sealed class InviteMeta {
object DirectMessage : InviteMeta()
data class Room(val roomName: String? = null) : InviteMeta()
}
}
@JvmInline
value class UnreadCount(val value: Int)
data class Typing(val roomId: RoomId, val members: List<RoomMember>)
data class LoginRequest(val userName: String, val password: String, val serverUrl: String?)
sealed interface LoginResult {
data class Success(val userCredentials: UserCredentials) : LoginResult
object MissingWellKnown : LoginResult
data class Error(val cause: Throwable) : LoginResult
}
data class Me(
val userId: UserId,
val displayName: String?,
val avatarUrl: AvatarUrl?,
val homeServerUrl: HomeServerUrl,
)

View File

@ -1,9 +1,7 @@
applyAndroidComposeLibraryModule(project) applyAndroidComposeLibraryModule(project)
dependencies { dependencies {
implementation project(":matrix:services:profile") implementation project(":chat-engine")
implementation project(":matrix:services:crypto")
implementation project(":matrix:services:sync")
implementation project(":features:directory") implementation project(":features:directory")
implementation project(":features:login") implementation project(":features:login")
implementation project(":features:settings") implementation project(":features:settings")

View File

@ -4,31 +4,28 @@ import app.dapk.st.core.BuildMeta
import app.dapk.st.core.ProvidableModule import app.dapk.st.core.ProvidableModule
import app.dapk.st.directory.DirectoryViewModel import app.dapk.st.directory.DirectoryViewModel
import app.dapk.st.domain.StoreModule import app.dapk.st.domain.StoreModule
import app.dapk.st.engine.ChatEngine
import app.dapk.st.login.LoginViewModel import app.dapk.st.login.LoginViewModel
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.matrix.sync.SyncService
import app.dapk.st.profile.ProfileViewModel import app.dapk.st.profile.ProfileViewModel
class HomeModule( class HomeModule(
private val chatEngine: ChatEngine,
private val storeModule: StoreModule, private val storeModule: StoreModule,
private val profileService: ProfileService,
private val syncService: SyncService,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
) : ProvidableModule { ) : ProvidableModule {
fun homeViewModel(directory: DirectoryViewModel, login: LoginViewModel, profileViewModel: ProfileViewModel): HomeViewModel { fun homeViewModel(directory: DirectoryViewModel, login: LoginViewModel, profileViewModel: ProfileViewModel): HomeViewModel {
return HomeViewModel( return HomeViewModel(
chatEngine,
storeModule.credentialsStore(), storeModule.credentialsStore(),
directory, directory,
login, login,
profileViewModel, profileViewModel,
profileService,
storeModule.cacheCleaner(), storeModule.cacheCleaner(),
BetaVersionUpgradeUseCase( BetaVersionUpgradeUseCase(
storeModule.applicationStore(), storeModule.applicationStore(),
buildMeta, buildMeta,
), ),
syncService,
) )
} }

View File

@ -4,13 +4,13 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import app.dapk.st.matrix.room.ProfileService import app.dapk.st.engine.Me
sealed interface HomeScreenState { sealed interface HomeScreenState {
object Loading : HomeScreenState object Loading : HomeScreenState
object SignedOut : HomeScreenState object SignedOut : HomeScreenState
data class SignedIn(val page: Page, val me: ProfileService.Me, val invites: Int) : HomeScreenState data class SignedIn(val page: Page, val me: Me, val invites: Int) : HomeScreenState
enum class Page(val icon: ImageVector) { enum class Page(val icon: ImageVector) {
Directory(Icons.Filled.Menu), Directory(Icons.Filled.Menu),

View File

@ -3,12 +3,11 @@ package app.dapk.st.home
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.dapk.st.directory.DirectoryViewModel import app.dapk.st.directory.DirectoryViewModel
import app.dapk.st.domain.StoreCleaner import app.dapk.st.domain.StoreCleaner
import app.dapk.st.engine.ChatEngine
import app.dapk.st.home.HomeScreenState.* import app.dapk.st.home.HomeScreenState.*
import app.dapk.st.login.LoginViewModel import app.dapk.st.login.LoginViewModel
import app.dapk.st.matrix.common.CredentialsStore import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.isSignedIn import app.dapk.st.matrix.common.isSignedIn
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.matrix.sync.SyncService
import app.dapk.st.profile.ProfileViewModel import app.dapk.st.profile.ProfileViewModel
import app.dapk.st.viewmodel.DapkViewModel import app.dapk.st.viewmodel.DapkViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -20,14 +19,13 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class HomeViewModel( class HomeViewModel(
private val chatEngine: ChatEngine,
private val credentialsProvider: CredentialsStore, private val credentialsProvider: CredentialsStore,
private val directoryViewModel: DirectoryViewModel, private val directoryViewModel: DirectoryViewModel,
private val loginViewModel: LoginViewModel, private val loginViewModel: LoginViewModel,
private val profileViewModel: ProfileViewModel, private val profileViewModel: ProfileViewModel,
private val profileService: ProfileService,
private val cacheCleaner: StoreCleaner, private val cacheCleaner: StoreCleaner,
private val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase, private val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
private val syncService: SyncService,
) : DapkViewModel<HomeScreenState, HomeEvent>( ) : DapkViewModel<HomeScreenState, HomeEvent>(
initialState = Loading initialState = Loading
) { ) {
@ -56,8 +54,8 @@ class HomeViewModel(
} }
private suspend fun initialHomeContent(): SignedIn { private suspend fun initialHomeContent(): SignedIn {
val me = profileService.me(forceRefresh = false) val me = chatEngine.me(forceRefresh = false)
val initialInvites = syncService.invites().first().size val initialInvites = chatEngine.invites().first().size
return SignedIn(Page.Directory, me, invites = initialInvites) return SignedIn(Page.Directory, me, invites = initialInvites)
} }
@ -70,7 +68,7 @@ class HomeViewModel(
private fun CoroutineScope.listenForInviteChanges() { private fun CoroutineScope.listenForInviteChanges() {
listenForInvitesJob?.cancel() listenForInvitesJob?.cancel()
listenForInvitesJob = syncService.invites() listenForInvitesJob = chatEngine.invites()
.onEach { invites -> .onEach { invites ->
when (val currentState = state) { when (val currentState = state) {
is SignedIn -> updateState { currentState.copy(invites = invites.size) } is SignedIn -> updateState { currentState.copy(invites = invites.size) }

View File

@ -1,12 +1,10 @@
applyAndroidComposeLibraryModule(project) applyAndroidComposeLibraryModule(project)
dependencies { dependencies {
implementation project(":chat-engine")
implementation project(":domains:android:compose-core") implementation project(":domains:android:compose-core")
implementation project(":domains:android:push") implementation project(":domains:android:push")
implementation project(":domains:android:viewmodel") implementation project(":domains:android:viewmodel")
implementation project(":matrix:services:auth")
implementation project(":matrix:services:profile")
implementation project(":matrix:services:crypto")
implementation project(":design-library") implementation project(":design-library")
implementation project(":core") implementation project(":core")
} }

View File

@ -2,18 +2,16 @@ package app.dapk.st.login
import app.dapk.st.core.ProvidableModule import app.dapk.st.core.ProvidableModule
import app.dapk.st.core.extensions.ErrorTracker import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.matrix.auth.AuthService import app.dapk.st.engine.ChatEngine
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.push.PushModule import app.dapk.st.push.PushModule
class LoginModule( class LoginModule(
private val authService: AuthService, private val chatEngine: ChatEngine,
private val pushModule: PushModule, private val pushModule: PushModule,
private val profileService: ProfileService,
private val errorTracker: ErrorTracker, private val errorTracker: ErrorTracker,
) : ProvidableModule { ) : ProvidableModule {
fun loginViewModel(): LoginViewModel { fun loginViewModel(): LoginViewModel {
return LoginViewModel(authService, pushModule.pushTokenRegistrar(), profileService, errorTracker) return LoginViewModel(chatEngine, pushModule.pushTokenRegistrar(), errorTracker)
} }
} }

View File

@ -3,10 +3,11 @@ package app.dapk.st.login
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.dapk.st.core.extensions.ErrorTracker import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.logP import app.dapk.st.core.logP
import app.dapk.st.engine.ChatEngine
import app.dapk.st.engine.LoginRequest
import app.dapk.st.engine.LoginResult
import app.dapk.st.login.LoginEvent.LoginComplete import app.dapk.st.login.LoginEvent.LoginComplete
import app.dapk.st.login.LoginScreenState.* import app.dapk.st.login.LoginScreenState.*
import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.push.PushTokenRegistrar import app.dapk.st.push.PushTokenRegistrar
import app.dapk.st.viewmodel.DapkViewModel import app.dapk.st.viewmodel.DapkViewModel
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -14,9 +15,8 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LoginViewModel( class LoginViewModel(
private val authService: AuthService, private val chatEngine: ChatEngine,
private val pushTokenRegistrar: PushTokenRegistrar, private val pushTokenRegistrar: PushTokenRegistrar,
private val profileService: ProfileService,
private val errorTracker: ErrorTracker, private val errorTracker: ErrorTracker,
) : DapkViewModel<LoginScreenState, LoginEvent>( ) : DapkViewModel<LoginScreenState, LoginEvent>(
initialState = Content(showServerUrl = false) initialState = Content(showServerUrl = false)
@ -28,8 +28,8 @@ class LoginViewModel(
state = Loading state = Loading
viewModelScope.launch { viewModelScope.launch {
logP("login") { logP("login") {
when (val result = authService.login(AuthService.LoginRequest(userName, password, serverUrl.takeIfNotEmpty()))) { when (val result = chatEngine.login(LoginRequest(userName, password, serverUrl.takeIfNotEmpty()))) {
is AuthService.LoginResult.Success -> { is LoginResult.Success -> {
runCatching { runCatching {
listOf( listOf(
async { pushTokenRegistrar.registerCurrentToken() }, async { pushTokenRegistrar.registerCurrentToken() },
@ -38,11 +38,13 @@ class LoginViewModel(
} }
_events.tryEmit(LoginComplete) _events.tryEmit(LoginComplete)
} }
is AuthService.LoginResult.Error -> {
is LoginResult.Error -> {
errorTracker.track(result.cause) errorTracker.track(result.cause)
state = Error(result.cause) state = Error(result.cause)
} }
AuthService.LoginResult.MissingWellKnown -> {
LoginResult.MissingWellKnown -> {
_events.tryEmit(LoginEvent.WellKnownMissing) _events.tryEmit(LoginEvent.WellKnownMissing)
state = Content(showServerUrl = true) state = Content(showServerUrl = true)
} }
@ -51,7 +53,7 @@ class LoginViewModel(
} }
} }
private suspend fun preloadMe() = profileService.me(forceRefresh = false) private suspend fun preloadMe() = chatEngine.me(forceRefresh = false)
fun start() { fun start() {
val showServerUrl = previousState?.let { it is Content && it.showServerUrl } ?: false val showServerUrl = previousState?.let { it is Content && it.showServerUrl } ?: false

View File

@ -1,6 +1,12 @@
package app.dapk.st.engine package app.dapk.st.engine
import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.sync.InviteMeta
import app.dapk.st.matrix.auth.AuthService.LoginRequest as MatrixLoginRequest
import app.dapk.st.matrix.auth.AuthService.LoginResult as MatrixLoginResult
import app.dapk.st.matrix.room.ProfileService.Me as MatrixMe
import app.dapk.st.matrix.sync.LastMessage as MatrixLastMessage import app.dapk.st.matrix.sync.LastMessage as MatrixLastMessage
import app.dapk.st.matrix.sync.RoomInvite as MatrixRoomInvite
import app.dapk.st.matrix.sync.RoomOverview as MatrixRoomOverview import app.dapk.st.matrix.sync.RoomOverview as MatrixRoomOverview
import app.dapk.st.matrix.sync.SyncService.SyncEvent.Typing as MatrixTyping import app.dapk.st.matrix.sync.SyncService.SyncEvent.Typing as MatrixTyping
@ -24,4 +30,35 @@ fun MatrixLastMessage.engine() = RoomOverview.LastMessage(
fun MatrixTyping.engine() = Typing( fun MatrixTyping.engine() = Typing(
this.roomId, this.roomId,
this.members, this.members,
) )
fun LoginRequest.engine() = MatrixLoginRequest(
this.userName,
this.password,
this.serverUrl
)
fun MatrixLoginResult.engine() = when (this) {
is AuthService.LoginResult.Error -> LoginResult.Error(this.cause)
AuthService.LoginResult.MissingWellKnown -> LoginResult.MissingWellKnown
is AuthService.LoginResult.Success -> LoginResult.Success(this.userCredentials)
}
fun MatrixMe.engine() = Me(
this.userId,
this.displayName,
this.avatarUrl,
this.homeServerUrl,
)
fun MatrixRoomInvite.engine() = RoomInvite(
this.from,
this.roomId,
this.inviteMeta.engine(),
)
fun InviteMeta.engine() = when (this) {
InviteMeta.DirectMessage -> RoomInvite.InviteMeta.DirectMessage
is InviteMeta.Room -> RoomInvite.InviteMeta.Room(this.roomName)
}

View File

@ -7,6 +7,7 @@ import app.dapk.st.core.SingletonFlows
import app.dapk.st.core.extensions.ErrorTracker import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.matrix.MatrixClient import app.dapk.st.matrix.MatrixClient
import app.dapk.st.matrix.auth.DeviceDisplayNameGenerator 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.auth.installAuthService
import app.dapk.st.matrix.common.* import app.dapk.st.matrix.common.*
import app.dapk.st.matrix.crypto.RoomMembersProvider import app.dapk.st.matrix.crypto.RoomMembersProvider
@ -27,13 +28,27 @@ import app.dapk.st.matrix.sync.internal.room.MessageDecrypter
import app.dapk.st.olm.DeviceKeyFactory import app.dapk.st.olm.DeviceKeyFactory
import app.dapk.st.olm.OlmStore import app.dapk.st.olm.OlmStore
import app.dapk.st.olm.OlmWrapper import app.dapk.st.olm.OlmWrapper
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.time.Clock import java.time.Clock
class MatrixEngine internal constructor( class MatrixEngine internal constructor(
private val directoryUseCase: Lazy<DirectoryUseCase>, private val directoryUseCase: Lazy<DirectoryUseCase>,
private val matrix: Lazy<MatrixClient>,
) : ChatEngine { ) : ChatEngine {
override fun directory() = directoryUseCase.value.state() override fun directory() = directoryUseCase.value.state()
override fun invites(): Flow<InviteState> {
return matrix.value.syncService().invites().map { it.map { it.engine() } }
}
override suspend fun login(request: LoginRequest): LoginResult {
return matrix.value.authService().login(request.engine()).engine()
}
override suspend fun me(forceRefresh: Boolean): Me {
return matrix.value.profileService().me(forceRefresh).engine()
}
class Factory { class Factory {
@ -57,28 +72,30 @@ class MatrixEngine internal constructor(
knownDeviceStore: KnownDeviceStore, knownDeviceStore: KnownDeviceStore,
olmStore: OlmStore, olmStore: OlmStore,
): ChatEngine { ): ChatEngine {
val matrix = MatrixFactory.createMatrix( val lazyMatrix = unsafeLazy {
base64, MatrixFactory.createMatrix(
buildMeta, base64,
logger, buildMeta,
nameGenerator, logger,
coroutineDispatchers, nameGenerator,
errorTracker, coroutineDispatchers,
imageContentReader, errorTracker,
backgroundScheduler, imageContentReader,
memberStore, backgroundScheduler,
roomStore, memberStore,
profileStore, roomStore,
syncStore, profileStore,
overviewStore, syncStore,
filterStore, overviewStore,
localEchoStore, filterStore,
credentialsStore, localEchoStore,
knownDeviceStore, credentialsStore,
olmStore knownDeviceStore,
) olmStore
)
}
val directoryUseCase = unsafeLazy { val directoryUseCase = unsafeLazy {
val matrix = lazyMatrix.value
DirectoryUseCase( DirectoryUseCase(
matrix.syncService(), matrix.syncService(),
matrix.messageService(), matrix.messageService(),
@ -88,7 +105,7 @@ class MatrixEngine internal constructor(
) )
} }
return MatrixEngine(directoryUseCase) return MatrixEngine(directoryUseCase, lazyMatrix)
} }