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.matrix.MatrixClient
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.RoomMembersProvider
@ -171,9 +170,8 @@ internal class FeatureModules internal constructor(
}
val loginModule by unsafeLazy {
LoginModule(
matrixModules.auth,
matrixModules.engine,
domainModules.pushModule,
matrixModules.profile,
trackingModule.errorTracker
)
}
@ -191,7 +189,7 @@ internal class FeatureModules internal constructor(
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 {
SettingsModule(
storeModule.value,
@ -473,7 +471,6 @@ internal class MatrixModules(
}
}
val auth by unsafeLazy { matrix.authService() }
val push by unsafeLazy { matrix.pushService() }
val sync by unsafeLazy { matrix.syncService() }
val message by unsafeLazy { matrix.messageService() }

View File

@ -5,8 +5,4 @@ plugins {
dependencies {
api Dependencies.mavenCentral.kotlinCoroutinesCore
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
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
interface ChatEngine {
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)
dependencies {
implementation project(":matrix:services:profile")
implementation project(":matrix:services:crypto")
implementation project(":matrix:services:sync")
implementation project(":chat-engine")
implementation project(":features:directory")
implementation project(":features:login")
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.directory.DirectoryViewModel
import app.dapk.st.domain.StoreModule
import app.dapk.st.engine.ChatEngine
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
class HomeModule(
private val chatEngine: ChatEngine,
private val storeModule: StoreModule,
private val profileService: ProfileService,
private val syncService: SyncService,
private val buildMeta: BuildMeta,
) : ProvidableModule {
fun homeViewModel(directory: DirectoryViewModel, login: LoginViewModel, profileViewModel: ProfileViewModel): HomeViewModel {
return HomeViewModel(
chatEngine,
storeModule.credentialsStore(),
directory,
login,
profileViewModel,
profileService,
storeModule.cacheCleaner(),
BetaVersionUpgradeUseCase(
storeModule.applicationStore(),
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.Settings
import androidx.compose.ui.graphics.vector.ImageVector
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.engine.Me
sealed interface HomeScreenState {
object Loading : 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) {
Directory(Icons.Filled.Menu),

View File

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

View File

@ -1,12 +1,10 @@
applyAndroidComposeLibraryModule(project)
dependencies {
implementation project(":chat-engine")
implementation project(":domains:android:compose-core")
implementation project(":domains:android:push")
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(":core")
}

View File

@ -2,18 +2,16 @@ package app.dapk.st.login
import app.dapk.st.core.ProvidableModule
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.engine.ChatEngine
import app.dapk.st.push.PushModule
class LoginModule(
private val authService: AuthService,
private val chatEngine: ChatEngine,
private val pushModule: PushModule,
private val profileService: ProfileService,
private val errorTracker: ErrorTracker,
) : ProvidableModule {
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 app.dapk.st.core.extensions.ErrorTracker
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.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.viewmodel.DapkViewModel
import kotlinx.coroutines.async
@ -14,9 +15,8 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
class LoginViewModel(
private val authService: AuthService,
private val chatEngine: ChatEngine,
private val pushTokenRegistrar: PushTokenRegistrar,
private val profileService: ProfileService,
private val errorTracker: ErrorTracker,
) : DapkViewModel<LoginScreenState, LoginEvent>(
initialState = Content(showServerUrl = false)
@ -28,8 +28,8 @@ class LoginViewModel(
state = Loading
viewModelScope.launch {
logP("login") {
when (val result = authService.login(AuthService.LoginRequest(userName, password, serverUrl.takeIfNotEmpty()))) {
is AuthService.LoginResult.Success -> {
when (val result = chatEngine.login(LoginRequest(userName, password, serverUrl.takeIfNotEmpty()))) {
is LoginResult.Success -> {
runCatching {
listOf(
async { pushTokenRegistrar.registerCurrentToken() },
@ -38,11 +38,13 @@ class LoginViewModel(
}
_events.tryEmit(LoginComplete)
}
is AuthService.LoginResult.Error -> {
is LoginResult.Error -> {
errorTracker.track(result.cause)
state = Error(result.cause)
}
AuthService.LoginResult.MissingWellKnown -> {
LoginResult.MissingWellKnown -> {
_events.tryEmit(LoginEvent.WellKnownMissing)
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() {
val showServerUrl = previousState?.let { it is Content && it.showServerUrl } ?: false

View File

@ -1,6 +1,12 @@
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.RoomInvite as MatrixRoomInvite
import app.dapk.st.matrix.sync.RoomOverview as MatrixRoomOverview
import app.dapk.st.matrix.sync.SyncService.SyncEvent.Typing as MatrixTyping
@ -25,3 +31,34 @@ fun MatrixTyping.engine() = Typing(
this.roomId,
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.matrix.MatrixClient
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.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.OlmStore
import app.dapk.st.olm.OlmWrapper
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.time.Clock
class MatrixEngine internal constructor(
private val directoryUseCase: Lazy<DirectoryUseCase>,
private val matrix: Lazy<MatrixClient>,
) : ChatEngine {
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 {
@ -57,7 +72,8 @@ class MatrixEngine internal constructor(
knownDeviceStore: KnownDeviceStore,
olmStore: OlmStore,
): ChatEngine {
val matrix = MatrixFactory.createMatrix(
val lazyMatrix = unsafeLazy {
MatrixFactory.createMatrix(
base64,
buildMeta,
logger,
@ -77,8 +93,9 @@ class MatrixEngine internal constructor(
knownDeviceStore,
olmStore
)
}
val directoryUseCase = unsafeLazy {
val matrix = lazyMatrix.value
DirectoryUseCase(
matrix.syncService(),
matrix.messageService(),
@ -88,7 +105,7 @@ class MatrixEngine internal constructor(
)
}
return MatrixEngine(directoryUseCase)
return MatrixEngine(directoryUseCase, lazyMatrix)
}