Merge branch 'develop' into feature/fga/improve_node_architecture
This commit is contained in:
commit
7f3679a15e
463 changed files with 3133 additions and 1555 deletions
|
|
@ -36,7 +36,7 @@ dependencies {
|
|||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.elementresources)
|
||||
implementation(projects.libraries.testtags)
|
||||
|
|
@ -48,7 +48,7 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ package io.element.android.features.login.impl.changeserver
|
|||
sealed interface ChangeServerEvents {
|
||||
data class SetServer(val server: String) : ChangeServerEvents
|
||||
object Submit : ChangeServerEvents
|
||||
object ClearError : ChangeServerEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,20 @@
|
|||
|
||||
package io.element.android.features.login.impl.changeserver
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.login.util.LoginConstants
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
|
|
@ -32,18 +38,25 @@ class ChangeServerNode @AssistedInject constructor(
|
|||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: ChangeServerPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
private fun onSuccess() {
|
||||
navigateUp()
|
||||
}
|
||||
|
||||
private fun openLearnMorePage(context: Context) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(LoginConstants.SLIDING_SYNC_READ_MORE_URL))
|
||||
tryOrNull { context.startActivity(intent) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val context = LocalContext.current
|
||||
ChangeServerView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onChangeServerSuccess = this::onSuccess,
|
||||
onBackPressed = { navigateUp() },
|
||||
onLearnMoreClicked = { openLearnMorePage(context) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.features.login.util.LoginConstants
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.execute
|
||||
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChangeServerPresenter @Inject constructor(private val authenticationService: MatrixAuthenticationService) : Presenter<ChangeServerState> {
|
||||
|
|
@ -35,8 +38,9 @@ class ChangeServerPresenter @Inject constructor(private val authenticationServic
|
|||
@Composable
|
||||
override fun present(): ChangeServerState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
val homeserver = rememberSaveable {
|
||||
mutableStateOf(authenticationService.getHomeserverOrDefault())
|
||||
mutableStateOf(authenticationService.getHomeserverDetails().value?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL)
|
||||
}
|
||||
val changeServerAction: MutableState<Async<Unit>> = remember {
|
||||
mutableStateOf(Async.Uninitialized)
|
||||
|
|
@ -45,7 +49,10 @@ class ChangeServerPresenter @Inject constructor(private val authenticationServic
|
|||
fun handleEvents(event: ChangeServerEvents) {
|
||||
when (event) {
|
||||
is ChangeServerEvents.SetServer -> homeserver.value = event.server
|
||||
ChangeServerEvents.Submit -> localCoroutineScope.submit(homeserver.value, changeServerAction)
|
||||
ChangeServerEvents.Submit -> {
|
||||
localCoroutineScope.submit(homeserver, changeServerAction)
|
||||
}
|
||||
ChangeServerEvents.ClearError -> changeServerAction.value = Async.Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,9 +63,11 @@ class ChangeServerPresenter @Inject constructor(private val authenticationServic
|
|||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.submit(homeserver: String, changeServerAction: MutableState<Async<Unit>>) = launch {
|
||||
private fun CoroutineScope.submit(homeserverUrl: MutableState<String>, changeServerAction: MutableState<Async<Unit>>) = launch {
|
||||
suspend {
|
||||
authenticationService.setHomeserver(homeserver)
|
||||
val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value
|
||||
authenticationService.setHomeserver(domain)
|
||||
homeserverUrl.value = domain
|
||||
}.execute(changeServerAction)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,164 +19,240 @@ package io.element.android.features.login.impl.changeserver
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.features.login.R
|
||||
import io.element.android.features.login.impl.error.changeServerError
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.ElementTextStyles
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.LocalColors
|
||||
import io.element.android.libraries.designsystem.theme.components.BackButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationException
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChangeServerView(
|
||||
state: ChangeServerState,
|
||||
onLearnMoreClicked: () -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeServerSuccess: () -> Unit = {},
|
||||
) {
|
||||
val eventSink = state.eventSink
|
||||
val scrollState = rememberScrollState()
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(
|
||||
state = scrollState,
|
||||
)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
val isError = state.changeServerAction is Async.Failure
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 99.dp)
|
||||
.size(width = 81.dp, height = 73.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(32.dp)
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(width = 48.dp, height = 48.dp),
|
||||
// TODO Update with design input
|
||||
resourceId = R.drawable.ic_baseline_dataset_24,
|
||||
contentDescription = "",
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Your server",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 56.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 38.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 24.sp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = "A server is a home for all your data.\n" +
|
||||
"You choose your server and it’s easy to make one.", // TODO "Learn more.",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 16.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 16.sp,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
var homeserverFieldState by textFieldState(stateValue = state.homeserver)
|
||||
OutlinedTextField(
|
||||
value = homeserverFieldState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.changeServerServer)
|
||||
.padding(top = 200.dp),
|
||||
onValueChange = {
|
||||
homeserverFieldState = it
|
||||
eventSink(ChangeServerEvents.SetServer(it))
|
||||
},
|
||||
label = {
|
||||
Text(text = "Server")
|
||||
},
|
||||
isError = isError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { eventSink(ChangeServerEvents.Submit) }
|
||||
)
|
||||
)
|
||||
if (state.changeServerAction is Async.Failure) {
|
||||
Text(
|
||||
text = changeServerError(
|
||||
state.homeserver,
|
||||
state.changeServerAction.error
|
||||
),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
Button(
|
||||
onClick = { eventSink(ChangeServerEvents.Submit) },
|
||||
enabled = state.submitEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.changeServerContinue)
|
||||
.padding(top = 44.dp)
|
||||
) {
|
||||
Text(text = "Continue")
|
||||
}
|
||||
if (state.changeServerAction is Async.Success) {
|
||||
onChangeServerSuccess()
|
||||
}
|
||||
}
|
||||
if (state.changeServerAction is Async.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
val interactionEnabled by remember(state.changeServerAction) {
|
||||
derivedStateOf {
|
||||
state.changeServerAction !is Async.Loading
|
||||
}
|
||||
}
|
||||
val focusManager = LocalFocusManager.current
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {},
|
||||
navigationIcon = { BackButton(action = onBackPressed, enabled = interactionEnabled) }
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(
|
||||
state = scrollState,
|
||||
)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Spacer(Modifier.height(42.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 70.dp, height = 70.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.background(
|
||||
color = LocalColors.current.quinary,
|
||||
shape = RoundedCornerShape(14.dp)
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(width = 32.dp, height = 32.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
resourceId = R.drawable.ic_homeserver,
|
||||
contentDescription = "",
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = StringR.string.ftue_auth_choose_server_title),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTextStyles.Bold.title2,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = StringR.string.ex_choose_server_subtitle),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTextStyles.Regular.subheadline,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Text(
|
||||
stringResource(StringR.string.hs_url),
|
||||
style = ElementTextStyles.Regular.formHeader,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
var homeserverFieldState by textFieldState(stateValue = state.homeserver)
|
||||
TextField(
|
||||
value = homeserverFieldState,
|
||||
readOnly = !interactionEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.changeServerServer)
|
||||
.onTabOrEnterKeyFocusNext(focusManager),
|
||||
onValueChange = {
|
||||
homeserverFieldState = it
|
||||
eventSink(ChangeServerEvents.SetServer(it))
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { eventSink(ChangeServerEvents.Submit) }
|
||||
),
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
trailingIcon = if (homeserverFieldState.isNotEmpty()) {
|
||||
{
|
||||
IconButton(onClick = {
|
||||
homeserverFieldState = ""
|
||||
}, enabled = interactionEnabled) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(StringR.string.action_clear))
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
)
|
||||
if (state.changeServerAction is Async.Failure) {
|
||||
if (state.changeServerAction.error is AuthenticationException.SlidingSyncNotAvailable) {
|
||||
SlidingSyncNotSupportedDialog(onLearnMoreClicked = {
|
||||
onLearnMoreClicked()
|
||||
eventSink(ChangeServerEvents.ClearError)
|
||||
}, onDismiss = {
|
||||
eventSink(ChangeServerEvents.ClearError)
|
||||
})
|
||||
} else {
|
||||
ChangeServerErrorDialog(
|
||||
error = state.changeServerAction.error,
|
||||
onDismiss = {
|
||||
eventSink(ChangeServerEvents.ClearError)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(StringR.string.server_selection_server_footer),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
style = ElementTextStyles.Regular.caption1,
|
||||
textAlign = TextAlign.Start,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Button(
|
||||
onClick = { eventSink(ChangeServerEvents.Submit) },
|
||||
enabled = interactionEnabled && state.submitEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.changeServerContinue)
|
||||
) {
|
||||
Text(text = stringResource(id = StringR.string.login_continue), style = ElementTextStyles.Button)
|
||||
}
|
||||
if (state.changeServerAction is Async.Success) {
|
||||
onChangeServerSuccess()
|
||||
}
|
||||
}
|
||||
if (state.changeServerAction is Async.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ChangeServerErrorDialog(error: Throwable, onDismiss: () -> Unit) {
|
||||
ErrorDialog(
|
||||
content = error.localizedMessage ?: stringResource(id = StringR.string.unknown_error),
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SlidingSyncNotSupportedDialog(onLearnMoreClicked: () -> Unit, onDismiss: () -> Unit) {
|
||||
ConfirmationDialog(
|
||||
onDismiss = onDismiss,
|
||||
submitText = stringResource(StringR.string.action_learn_more),
|
||||
onSubmitClicked = onLearnMoreClicked,
|
||||
title = stringResource(StringR.string.server_selection_sliding_sync_alert_title),
|
||||
content = stringResource(StringR.string.server_selection_sliding_sync_alert_message),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
|
@ -191,5 +267,5 @@ internal fun ChangeServerViewDarkPreview(@PreviewParameter(ChangeServerStateProv
|
|||
|
||||
@Composable
|
||||
private fun ContentToPreview(state: ChangeServerState) {
|
||||
ChangeServerView(state = state)
|
||||
ChangeServerView(state = state, onBackPressed = {}, onLearnMoreClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
package io.element.android.features.login.impl.root
|
||||
|
||||
sealed interface LoginRootEvents {
|
||||
object RefreshHomeServer : LoginRootEvents
|
||||
data class SetLogin(val login: String) : LoginRootEvents
|
||||
data class SetPassword(val password: String) : LoginRootEvents
|
||||
object Submit : LoginRootEvents
|
||||
object ClearError : LoginRootEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,16 +47,11 @@ class LoginRootNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
OnLifecycleEvent { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> state.eventSink(LoginRootEvents.RefreshHomeServer)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
LoginRootScreen(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onChangeServer = this::onChangeHomeServer,
|
||||
onBackPressed = this::navigateUp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,25 +18,30 @@ package io.element.android.features.login.impl.root
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.features.login.util.LoginConstants
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginRootPresenter @Inject constructor(private val authenticationService: MatrixAuthenticationService) : Presenter<LoginRootState> {
|
||||
|
||||
private val defaultHomeserver = MatrixHomeServerDetails(LoginConstants.DEFAULT_HOMESERVER_URL, true, null)
|
||||
|
||||
@Composable
|
||||
override fun present(): LoginRootState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val homeserver = rememberSaveable {
|
||||
mutableStateOf(authenticationService.getHomeserverOrDefault())
|
||||
}
|
||||
val homeserver = authenticationService.getHomeserverDetails().collectAsState().value ?: defaultHomeserver
|
||||
val loggedInState: MutableState<LoggedInState> = remember {
|
||||
mutableStateOf(LoggedInState.NotLoggedIn)
|
||||
}
|
||||
|
|
@ -46,19 +51,19 @@ class LoginRootPresenter @Inject constructor(private val authenticationService:
|
|||
|
||||
fun handleEvents(event: LoginRootEvents) {
|
||||
when (event) {
|
||||
LoginRootEvents.RefreshHomeServer -> refreshHomeServer(homeserver)
|
||||
is LoginRootEvents.SetLogin -> updateFormState(formState) {
|
||||
copy(login = event.login)
|
||||
}
|
||||
is LoginRootEvents.SetPassword -> updateFormState(formState) {
|
||||
copy(password = event.password)
|
||||
}
|
||||
LoginRootEvents.Submit -> localCoroutineScope.submit(homeserver.value, formState.value, loggedInState)
|
||||
LoginRootEvents.Submit -> localCoroutineScope.submit(homeserver.url, formState.value, loggedInState)
|
||||
LoginRootEvents.ClearError -> loggedInState.value = LoggedInState.NotLoggedIn
|
||||
}
|
||||
}
|
||||
|
||||
return LoginRootState(
|
||||
homeserver = homeserver.value,
|
||||
homeserverDetails = homeserver,
|
||||
loggedInState = loggedInState.value,
|
||||
formState = formState.value,
|
||||
eventSink = ::handleEvents
|
||||
|
|
@ -83,7 +88,4 @@ class LoginRootPresenter @Inject constructor(private val authenticationService:
|
|||
formState.value = updateLambda(formState.value)
|
||||
}
|
||||
|
||||
private fun refreshHomeServer(homeserver: MutableState<String>) {
|
||||
homeserver.value = authenticationService.getHomeserverOrDefault()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,222 +16,332 @@
|
|||
|
||||
package io.element.android.features.login.impl.root
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.features.login.impl.error.loginError
|
||||
import io.element.android.libraries.designsystem.ElementTextStyles
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.BackButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.core.SessionId
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginRootScreen(
|
||||
state: LoginRootState,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeServer: () -> Unit = {},
|
||||
onLoginWithSuccess: (SessionId) -> Unit = {},
|
||||
onBackPressed: () -> Unit,
|
||||
) {
|
||||
val eventSink = state.eventSink
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding()
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
var loginFieldState by textFieldState(stateValue = state.formState.login)
|
||||
var passwordFieldState by textFieldState(stateValue = state.formState.password)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(
|
||||
state = scrollState,
|
||||
)
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
val isError = state.loggedInState is LoggedInState.ErrorLoggingIn
|
||||
// Title
|
||||
Text(
|
||||
text = stringResource(id = StringR.string.ftue_auth_welcome_back_title),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 48.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 24.sp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
val interactionEnabled by remember(state.loggedInState) {
|
||||
derivedStateOf {
|
||||
state.loggedInState != LoggedInState.LoggingIn
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {},
|
||||
navigationIcon = { BackButton(action = onBackPressed, enabled = interactionEnabled) },
|
||||
)
|
||||
// Form
|
||||
Column(
|
||||
// modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.homeserver,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onValueChange = { /* no op */ },
|
||||
enabled = false,
|
||||
label = {
|
||||
Text(text = "Server")
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Uri,
|
||||
),
|
||||
)
|
||||
Button(
|
||||
onClick = onChangeServer,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.testTag(TestTags.loginChangeServer)
|
||||
.padding(top = 8.dp, end = 8.dp),
|
||||
content = {
|
||||
Text(text = "Change")
|
||||
}
|
||||
)
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = loginFieldState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginEmailUsername)
|
||||
.padding(top = 60.dp),
|
||||
label = {
|
||||
Text(text = stringResource(id = StringR.string.login_signin_username_hint))
|
||||
},
|
||||
onValueChange = {
|
||||
loginFieldState = it
|
||||
eventSink(LoginRootEvents.SetLogin(it))
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Email,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
)
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
if (state.loggedInState is LoggedInState.LoggingIn) {
|
||||
// Ensure password is hidden when user submits the form
|
||||
passwordVisible = false
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = passwordFieldState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginPassword)
|
||||
.padding(top = 24.dp),
|
||||
onValueChange = {
|
||||
passwordFieldState = it
|
||||
eventSink(LoginRootEvents.SetPassword(it))
|
||||
},
|
||||
label = {
|
||||
Text(text = "Password")
|
||||
},
|
||||
isError = isError,
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
|
||||
val description =
|
||||
if (passwordVisible) "Hide password" else "Show password"
|
||||
}
|
||||
) { padding ->
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(imageVector = image, description)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { eventSink(LoginRootEvents.Submit) }
|
||||
),
|
||||
)
|
||||
if (state.loggedInState is LoggedInState.ErrorLoggingIn) {
|
||||
Text(
|
||||
text = loginError(state.formState, state.loggedInState.failure),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
// Submit
|
||||
Button(
|
||||
onClick = { eventSink(LoginRootEvents.Submit) },
|
||||
enabled = state.submitEnabled,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginContinue)
|
||||
.padding(vertical = 32.dp)
|
||||
.verticalScroll(state = scrollState)
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(text = "Continue")
|
||||
Spacer(Modifier.height(16.dp))
|
||||
// Title
|
||||
Text(
|
||||
text = stringResource(id = StringR.string.ftue_auth_welcome_back_title),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
style = ElementTextStyles.Bold.title1,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
|
||||
ChangeServerSection(
|
||||
interactionEnabled = interactionEnabled,
|
||||
homeserver = state.homeserverDetails.url,
|
||||
onChangeServer = onChangeServer
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(32.dp))
|
||||
|
||||
LoginForm(state = state, interactionEnabled = interactionEnabled)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
}
|
||||
when (val loggedInState = state.loggedInState) {
|
||||
is LoggedInState.LoggedIn -> onLoginWithSuccess(loggedInState.sessionId)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
if (state.loggedInState is LoggedInState.LoggingIn) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
|
||||
if (state.loggedInState is LoggedInState.LoggingIn) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun LoginRootScreenLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun LoginRootScreenDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||
internal fun ChangeServerSection(
|
||||
interactionEnabled: Boolean,
|
||||
homeserver: String,
|
||||
onChangeServer: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, bottom = 8.dp),
|
||||
text = stringResource(id = StringR.string.ftue_auth_sign_in_choose_server_header),
|
||||
style = ElementTextStyles.Regular.formHeader,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.testTag(TestTags.loginChangeServer)
|
||||
.clickable {
|
||||
if (interactionEnabled) {
|
||||
onChangeServer()
|
||||
}
|
||||
},
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = homeserver,
|
||||
style = ElementTextStyles.Bold.body,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.size(24.dp),
|
||||
onClick = {
|
||||
if (interactionEnabled) {
|
||||
onChangeServer()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.ChevronRight, contentDescription = null, tint = MaterialTheme.colorScheme.tertiary)
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
LoginRootScreen(
|
||||
state = aLoginRootState().copy(
|
||||
homeserver = "matrix.org",
|
||||
),
|
||||
internal fun LoginForm(
|
||||
state: LoginRootState,
|
||||
interactionEnabled: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var loginFieldState by textFieldState(stateValue = state.formState.login)
|
||||
var passwordFieldState by textFieldState(stateValue = state.formState.password)
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
val eventSink = state.eventSink
|
||||
Column(modifier) {
|
||||
Text(
|
||||
text = stringResource(StringR.string.login_form_title),
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
style = ElementTextStyles.Regular.formHeader
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
TextField(
|
||||
value = loginFieldState,
|
||||
readOnly = !interactionEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginEmailUsername)
|
||||
.onTabOrEnterKeyFocusNext(focusManager),
|
||||
label = {
|
||||
Text(text = stringResource(StringR.string.ex_login_username_hint))
|
||||
},
|
||||
onValueChange = {
|
||||
loginFieldState = it
|
||||
eventSink(LoginRootEvents.SetLogin(it))
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Email,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
keyboardActions = KeyboardActions(onNext = {
|
||||
focusManager.moveFocus(FocusDirection.Down)
|
||||
}),
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
trailingIcon = if (loginFieldState.isNotEmpty()) {
|
||||
{
|
||||
IconButton(onClick = {
|
||||
loginFieldState = ""
|
||||
}) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(StringR.string.action_clear))
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
)
|
||||
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
if (state.loggedInState is LoggedInState.LoggingIn) {
|
||||
// Ensure password is hidden when user submits the form
|
||||
passwordVisible = false
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
TextField(
|
||||
value = passwordFieldState,
|
||||
readOnly = !interactionEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginPassword)
|
||||
.onTabOrEnterKeyFocusNext(focusManager),
|
||||
onValueChange = {
|
||||
passwordFieldState = it
|
||||
eventSink(LoginRootEvents.SetPassword(it))
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(StringR.string.login_signup_password_hint))
|
||||
},
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
|
||||
val description =
|
||||
if (passwordVisible) stringResource(StringR.string.login_hide_password) else stringResource(StringR.string.login_show_password)
|
||||
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(imageVector = image, description)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { eventSink(LoginRootEvents.Submit) }
|
||||
),
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
if (state.loggedInState is LoggedInState.ErrorLoggingIn) {
|
||||
LoginErrorDialog(error = state.loggedInState.failure, onDismiss = {
|
||||
eventSink(LoginRootEvents.ClearError)
|
||||
})
|
||||
}
|
||||
Spacer(Modifier.height(28.dp))
|
||||
|
||||
// Submit
|
||||
Button(
|
||||
onClick = { eventSink(LoginRootEvents.Submit) },
|
||||
enabled = interactionEnabled && state.submitEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.loginContinue)
|
||||
) {
|
||||
Text(text = stringResource(StringR.string.login_continue), style = ElementTextStyles.Button)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) {
|
||||
ErrorDialog(
|
||||
content = error.localizedMessage ?: stringResource(id = StringR.string.unknown_error),
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun LoginRootScreenLightPreview(@PreviewParameter(LoginRootStateProvider::class) state: LoginRootState) =
|
||||
ElementPreviewLight { ContentToPreview(state) }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun LoginRootScreenDarkPreview(@PreviewParameter(LoginRootStateProvider::class) state: LoginRootState) =
|
||||
ElementPreviewDark { ContentToPreview(state) }
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview(state: LoginRootState) {
|
||||
LoginRootScreen(
|
||||
state = state,
|
||||
onBackPressed = {}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
package io.element.android.features.login.impl.root
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.element.android.libraries.matrix.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
data class LoginRootState(
|
||||
val homeserver: String,
|
||||
val homeserverDetails: MatrixHomeServerDetails,
|
||||
val loggedInState: LoggedInState,
|
||||
val formState: LoginFormState,
|
||||
val eventSink: (LoginRootEvents) -> Unit
|
||||
|
|
|
|||
|
|
@ -16,8 +16,24 @@
|
|||
|
||||
package io.element.android.features.login.impl.root
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
open class LoginRootStateProvider : PreviewParameterProvider<LoginRootState> {
|
||||
override val values: Sequence<LoginRootState>
|
||||
get() = sequenceOf(
|
||||
aLoginRootState(),
|
||||
aLoginRootState().copy(homeserverDetails = MatrixHomeServerDetails("some-custom-server.com", true, null)),
|
||||
aLoginRootState().copy(formState = LoginFormState("user", "pass")),
|
||||
aLoginRootState().copy(formState = LoginFormState("user", "pass"), loggedInState = LoggedInState.LoggingIn),
|
||||
aLoginRootState().copy(formState = LoginFormState("user", "pass"), loggedInState = LoggedInState.ErrorLoggingIn(Throwable())),
|
||||
aLoginRootState().copy(formState = LoginFormState("user", "pass"), loggedInState = LoggedInState.LoggedIn(SessionId("1234"))),
|
||||
)
|
||||
}
|
||||
|
||||
fun aLoginRootState() = LoginRootState(
|
||||
homeserver = "",
|
||||
homeserverDetails = MatrixHomeServerDetails("matrix.org", true, null),
|
||||
loggedInState = LoggedInState.NotLoggedIn,
|
||||
formState = LoginFormState.Default,
|
||||
eventSink = {}
|
||||
|
|
|
|||
|
|
@ -14,19 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.fakes
|
||||
package io.element.android.features.login.util
|
||||
|
||||
import io.element.android.libraries.dateformatter.DaySeparatorFormatter
|
||||
object LoginConstants {
|
||||
|
||||
class FakeDaySeparatorFormatter : DaySeparatorFormatter {
|
||||
const val DEFAULT_HOMESERVER_URL = "matrix.org"
|
||||
const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md"
|
||||
|
||||
private var format = ""
|
||||
|
||||
fun givenFormat(format: String) {
|
||||
this.format = format
|
||||
}
|
||||
|
||||
override fun format(timestamp: Long): String {
|
||||
return format
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<!--
|
||||
~ Copyright (c) 2022 New Vector Ltd
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM11,17H7v-4h4V17zM11,11H7V7h4V11zM17,17h-4v-4h4V17zM17,11h-4V7h4V11z" />
|
||||
</vector>
|
||||
13
features/login/src/main/res/drawable/ic_homeserver.xml
Normal file
13
features/login/src/main/res/drawable/ic_homeserver.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="42dp"
|
||||
android:height="42dp"
|
||||
android:viewportWidth="42"
|
||||
android:viewportHeight="42">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h42v42h-42z"/>
|
||||
<path
|
||||
android:pathData="M33.25,22.75H8.75C6.825,22.75 5.25,24.325 5.25,26.25V33.25C5.25,35.175 6.825,36.75 8.75,36.75H33.25C35.175,36.75 36.75,35.175 36.75,33.25V26.25C36.75,24.325 35.175,22.75 33.25,22.75ZM12.25,33.25C10.325,33.25 8.75,31.675 8.75,29.75C8.75,27.825 10.325,26.25 12.25,26.25C14.175,26.25 15.75,27.825 15.75,29.75C15.75,31.675 14.175,33.25 12.25,33.25ZM33.25,5.25H8.75C6.825,5.25 5.25,6.825 5.25,8.75V15.75C5.25,17.675 6.825,19.25 8.75,19.25H33.25C35.175,19.25 36.75,17.675 36.75,15.75V8.75C36.75,6.825 35.175,5.25 33.25,5.25ZM12.25,15.75C10.325,15.75 8.75,14.175 8.75,12.25C8.75,10.325 10.325,8.75 12.25,8.75C14.175,8.75 15.75,10.325 15.75,12.25C15.75,14.175 14.175,15.75 12.25,15.75Z"
|
||||
android:fillColor="#737D8C"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -25,8 +25,11 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.login.impl.changeserver.ChangeServerEvents
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrixtest.A_HOMESERVER
|
||||
import io.element.android.libraries.matrixtest.auth.FakeAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -41,7 +44,23 @@ class ChangeServerPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER)
|
||||
assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER_URL)
|
||||
assertThat(initialState.submitEnabled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - authentication service can provide a homeserver`() = runTest {
|
||||
val presenter = ChangeServerPresenter(
|
||||
FakeAuthenticationService().apply {
|
||||
givenHomeserver(A_HOMESERVER.copy(url = A_HOMESERVER_URL_2))
|
||||
},
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER_URL_2)
|
||||
assertThat(initialState.submitEnabled).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -80,4 +99,70 @@ class ChangeServerPresenterTest {
|
|||
assertThat(successState.changeServerAction).isInstanceOf(Async.Success::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - submit parses URL`() = runTest {
|
||||
val presenter = ChangeServerPresenter(
|
||||
FakeAuthenticationService(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val longUrl = "https://matrix.org/.well-known/"
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ChangeServerEvents.SetServer(longUrl))
|
||||
awaitItem()
|
||||
initialState.eventSink.invoke(ChangeServerEvents.Submit)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.submitEnabled).isFalse()
|
||||
assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java)
|
||||
awaitItem() // Skip changing the url to the parsed domain
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.submitEnabled).isTrue()
|
||||
assertThat(successState.changeServerAction).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(successState.homeserver).isEqualTo("matrix.org")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - submit fails`() = runTest {
|
||||
val authServer = FakeAuthenticationService()
|
||||
val presenter = ChangeServerPresenter(authServer)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
authServer.givenChangeServerError(Throwable())
|
||||
initialState.eventSink.invoke(ChangeServerEvents.Submit)
|
||||
val failureState = awaitItem()
|
||||
assertThat(failureState.submitEnabled).isTrue()
|
||||
assertThat(failureState.changeServerAction).isInstanceOf(Async.Failure::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - clear error`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val presenter = ChangeServerPresenter(
|
||||
authenticationService,
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Submit will return an error
|
||||
authenticationService.givenChangeServerError(A_THROWABLE)
|
||||
initialState.eventSink(ChangeServerEvents.Submit)
|
||||
|
||||
// Check an error was returned
|
||||
val submittedState = awaitItem()
|
||||
assertThat(submittedState.changeServerAction).isEqualTo(Async.Failure<Unit>(A_THROWABLE))
|
||||
|
||||
// Assert the error is then cleared
|
||||
submittedState.eventSink(ChangeServerEvents.ClearError)
|
||||
val clearedState = awaitItem()
|
||||
assertThat(clearedState.changeServerAction).isEqualTo(Async.Uninitialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,14 +26,12 @@ import io.element.android.features.login.impl.root.LoggedInState
|
|||
import io.element.android.features.login.impl.root.LoginFormState
|
||||
import io.element.android.features.login.impl.root.LoginRootEvents
|
||||
import io.element.android.features.login.impl.root.LoginRootPresenter
|
||||
import io.element.android.libraries.matrix.core.SessionId
|
||||
import io.element.android.libraries.matrixtest.A_HOMESERVER
|
||||
import io.element.android.libraries.matrixtest.A_HOMESERVER_2
|
||||
import io.element.android.libraries.matrixtest.A_PASSWORD
|
||||
import io.element.android.libraries.matrixtest.A_SESSION_ID
|
||||
import io.element.android.libraries.matrixtest.A_THROWABLE
|
||||
import io.element.android.libraries.matrixtest.A_USER_NAME
|
||||
import io.element.android.libraries.matrixtest.auth.FakeAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||
import io.element.android.libraries.matrix.test.A_PASSWORD
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -48,7 +46,7 @@ class LoginRootPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER)
|
||||
assertThat(initialState.homeserverDetails).isEqualTo(A_HOMESERVER)
|
||||
assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn)
|
||||
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
|
||||
assertThat(initialState.submitEnabled).isFalse()
|
||||
|
|
@ -92,7 +90,7 @@ class LoginRootPresenterTest {
|
|||
val submitState = awaitItem()
|
||||
assertThat(submitState.loggedInState).isEqualTo(LoggedInState.LoggingIn)
|
||||
val loggedInState = awaitItem()
|
||||
assertThat(loggedInState.loggedInState).isEqualTo(LoggedInState.LoggedIn(SessionId(A_SESSION_ID)))
|
||||
assertThat(loggedInState.loggedInState).isEqualTo(LoggedInState.LoggedIn(A_SESSION_ID))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +118,7 @@ class LoginRootPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - refresh server`() = runTest {
|
||||
fun `present - clear error`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val presenter = LoginRootPresenter(
|
||||
authenticationService,
|
||||
|
|
@ -129,11 +127,20 @@ class LoginRootPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER_2)
|
||||
initialState.eventSink.invoke(LoginRootEvents.RefreshHomeServer)
|
||||
val refreshedState = awaitItem()
|
||||
assertThat(refreshedState.homeserver).isEqualTo(A_HOMESERVER_2)
|
||||
|
||||
// Submit will return an error
|
||||
authenticationService.givenLoginError(A_THROWABLE)
|
||||
initialState.eventSink(LoginRootEvents.Submit)
|
||||
awaitItem() // Skip LoggingIn state
|
||||
|
||||
// Check an error was returned
|
||||
val submittedState = awaitItem()
|
||||
assertThat(submittedState.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE))
|
||||
|
||||
// Assert the error is then cleared
|
||||
submittedState.eventSink(LoginRootEvents.ClearError)
|
||||
val clearedState = awaitItem()
|
||||
assertThat(clearedState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ dependencies {
|
|||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.matrix)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.elementresources)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
|
@ -46,7 +46,7 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.execute
|
||||
import io.element.android.libraries.matrix.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ import app.cash.molecule.moleculeFlow
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrix.core.SessionId
|
||||
import io.element.android.libraries.matrixtest.A_THROWABLE
|
||||
import io.element.android.libraries.matrixtest.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -34,7 +34,7 @@ class LogoutPreferencePresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = LogoutPreferencePresenter(
|
||||
FakeMatrixClient(SessionId("sessionId")),
|
||||
FakeMatrixClient(A_SESSION_ID),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -47,7 +47,7 @@ class LogoutPreferencePresenterTest {
|
|||
@Test
|
||||
fun `present - logout`() = runTest {
|
||||
val presenter = LogoutPreferencePresenter(
|
||||
FakeMatrixClient(SessionId("sessionId")),
|
||||
FakeMatrixClient(A_SESSION_ID),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -63,7 +63,7 @@ class LogoutPreferencePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - logout with error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient(SessionId("sessionId"))
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID)
|
||||
val presenter = LogoutPreferencePresenter(
|
||||
matrixClient,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,16 +25,5 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ dependencies {
|
|||
api(projects.features.messages.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.textcomposer)
|
||||
implementation(projects.libraries.dateformatter)
|
||||
implementation(projects.libraries.dateformatter.api)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.datetime)
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
|
|
@ -52,7 +52,8 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.dateformatter.test)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
ksp(libs.showkase.processor)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListState
|
|||
import io.element.android.features.messages.impl.textcomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineState
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@Immutable
|
||||
data class MessagesState(
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
|||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||
import io.element.android.libraries.core.data.StableCharSequence
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
|
||||
open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.data.StableCharSequence
|
||||
import io.element.android.libraries.core.data.toStableCharSequence
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline
|
||||
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
||||
sealed interface TimelineEvents {
|
||||
object LoadMore : TimelineEvents
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ package io.element.android.features.messages.impl.timeline
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
package io.element.android.features.messages.impl.timeline.diff
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
|
||||
internal class MatrixTimelineItemsDiffCallback(
|
||||
private val oldList: List<MatrixTimelineItem>,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli
|
|||
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline.factories.event
|
||||
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemNoticeContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
||||
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
||||
import io.element.android.libraries.matrix.media.MediaResolver
|
||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||
import org.matrix.rustcomponents.sdk.Message
|
||||
import org.matrix.rustcomponents.sdk.MessageType
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemGrou
|
|||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.matrix.rustcomponents.sdk.ProfileTimelineDetails
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.timeline.factories.virtual
|
|||
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||
import io.element.android.libraries.dateformatter.DaySeparatorFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
|
||||
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
|
|||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemUnknownVirtualModel
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import androidx.compose.runtime.Immutable
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItem {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import io.element.android.libraries.matrix.media.MediaResolver
|
||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||
|
||||
data class TimelineItemImageContent(
|
||||
val body: String,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.media.MediaResolver
|
||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||
|
||||
open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineItemImageContent> {
|
||||
override val values: Sequence<TimelineItemImageContent>
|
||||
|
|
|
|||
|
|
@ -22,18 +22,18 @@ import app.cash.molecule.RecompositionClock
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.fixtures.aMessageEvent
|
||||
import io.element.android.features.messages.fixtures.aTimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.MessagesEvents
|
||||
import io.element.android.features.messages.impl.MessagesPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrixtest.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrixtest.A_ROOM_ID
|
||||
import io.element.android.libraries.matrixtest.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrixtest.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrixtest.A_MESSAGE
|
||||
import io.element.android.libraries.matrixtest.A_USER_ID
|
||||
import io.element.android.libraries.matrixtest.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrixtest.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrixtest.A_MESSAGE
|
||||
import io.element.android.libraries.matrixtest.A_USER_ID
|
||||
import io.element.android.libraries.matrixtest.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal fun aMessageEvent(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package io.element.android.features.messages.fixtures
|
||||
|
||||
import io.element.android.features.messages.fakes.FakeDaySeparatorFormatter
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFactory
|
||||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory
|
||||
|
|
@ -31,6 +30,7 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli
|
|||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemEventFactory
|
||||
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemDaySeparatorFactory
|
||||
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory
|
||||
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
||||
|
||||
internal fun aTimelineItemsFactory() = TimelineItemsFactory(
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ import io.element.android.features.messages.impl.textcomposer.MessageComposerEve
|
|||
import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.textcomposer.MessageComposerState
|
||||
import io.element.android.libraries.core.data.StableCharSequence
|
||||
import io.element.android.libraries.matrixtest.ANOTHER_MESSAGE
|
||||
import io.element.android.libraries.matrixtest.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrixtest.A_MESSAGE
|
||||
import io.element.android.libraries.matrixtest.A_REPLY
|
||||
import io.element.android.libraries.matrixtest.A_USER_NAME
|
||||
import io.element.android.libraries.matrixtest.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_REPLY
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.messages.fixtures.aTimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
import io.element.android.libraries.matrixtest.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrixtest.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrixtest.timeline.FakeMatrixTimeline
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
ksp(libs.showkase.processor)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import io.element.android.features.logout.LogoutPreferencePresenter
|
|||
import io.element.android.features.preferences.impl.root.PreferencesRootPresenter
|
||||
import io.element.android.features.rageshake.preferences.RageshakePreferencesPresenter
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrixtest.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(libs.test.mockk)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.rageshake.crash.ui.A_CRASH_DATA
|
||||
import io.element.android.features.rageshake.crash.ui.FakeCrashDataStore
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrixtest.A_FAILURE_REASON
|
||||
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ package io.element.android.features.rageshake.bugreport
|
|||
import io.element.android.features.rageshake.reporter.BugReporter
|
||||
import io.element.android.features.rageshake.reporter.BugReporterListener
|
||||
import io.element.android.features.rageshake.reporter.ReportType
|
||||
import io.element.android.libraries.matrixtest.A_FAILURE_REASON
|
||||
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import io.element.android.features.rageshake.preferences.FakeRageShake
|
|||
import io.element.android.features.rageshake.preferences.FakeRageshakeDataStore
|
||||
import io.element.android.features.rageshake.preferences.RageshakePreferencesPresenter
|
||||
import io.element.android.features.rageshake.screenshot.ImageResult
|
||||
import io.element.android.libraries.matrixtest.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@ dependencies {
|
|||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.elementresources)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.dateformatter)
|
||||
implementation(projects.libraries.dateformatter.api)
|
||||
implementation(libs.accompanist.placeholder)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
|
|
@ -49,9 +49,8 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
|
||||
testImplementation(testFixtures(projects.libraries.matrix))
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
implementation(projects.libraries.dateformatter.test)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.roomlist.api
|
|||
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
interface RoomListEntryPoint : FeatureEntryPoint {
|
||||
interface Callback : Plugin {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import dagger.assisted.AssistedInject
|
|||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class RoomListNode @AssistedInject constructor(
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.parallelMap
|
||||
import io.element.android.libraries.dateformatter.LastMessageFormatter
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageFormatter
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.MatrixClient
|
||||
import io.element.android.libraries.matrix.room.RoomSummary
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -103,14 +104,14 @@ class RoomListPresenter @Inject constructor(
|
|||
val userDisplayName = client.loadUserDisplayName().getOrNull()
|
||||
val avatarData =
|
||||
AvatarData(
|
||||
id = client.userId().value,
|
||||
id = client.sessionId.value,
|
||||
name = userDisplayName,
|
||||
url = userAvatarUrl,
|
||||
size = AvatarSize.SMALL
|
||||
)
|
||||
matrixUser.value = MatrixUser(
|
||||
id = client.userId(),
|
||||
username = userDisplayName ?: client.userId().value,
|
||||
id = UserId(client.sessionId.value),
|
||||
username = userDisplayName ?: client.sessionId.value,
|
||||
avatarData = avatarData,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.roomlist.impl
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@Immutable
|
||||
data class RoomListRoomSummary(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.roomlist.impl
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> {
|
||||
override val values: Sequence<RoomListRoomSummary>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.roomlist.impl
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||
import io.element.android.libraries.matrix.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||
import io.element.android.libraries.matrix.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.roomlist
|
||||
|
||||
import io.element.android.libraries.dateformatter.LastMessageFormatter
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageFormatter
|
||||
|
||||
class FakeLastMessageFormatter : LastMessageFormatter {
|
||||
private var format = ""
|
||||
|
|
|
|||
|
|
@ -22,22 +22,22 @@ import app.cash.molecule.RecompositionClock
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.roomlist.impl.RoomListPresenter
|
||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
||||
import io.element.android.features.roomlist.impl.RoomListPresenter
|
||||
import io.element.android.features.roomlist.impl.RoomListRoomSummary
|
||||
import io.element.android.libraries.dateformatter.LastMessageFormatter
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageFormatter
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.core.SessionId
|
||||
import io.element.android.libraries.matrixtest.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrixtest.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrixtest.A_MESSAGE
|
||||
import io.element.android.libraries.matrixtest.A_ROOM_ID
|
||||
import io.element.android.libraries.matrixtest.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrixtest.A_USER_ID
|
||||
import io.element.android.libraries.matrixtest.A_USER_NAME
|
||||
import io.element.android.libraries.matrixtest.FakeMatrixClient
|
||||
import io.element.android.libraries.matrixtest.room.FakeRoomSummaryDataSource
|
||||
import io.element.android.libraries.matrixtest.room.aRoomSummaryFilled
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -47,9 +47,7 @@ class RoomListPresenterTests {
|
|||
@Test
|
||||
fun `present - should start with no user and then load user with success`() = runTest {
|
||||
val presenter = RoomListPresenter(
|
||||
FakeMatrixClient(
|
||||
SessionId("sessionId")
|
||||
),
|
||||
FakeMatrixClient(A_SESSION_ID),
|
||||
createDateFormatter()
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
|
|
@ -70,7 +68,7 @@ class RoomListPresenterTests {
|
|||
fun `present - should start with no user and then load user with error`() = runTest {
|
||||
val presenter = RoomListPresenter(
|
||||
FakeMatrixClient(
|
||||
SessionId("sessionId"),
|
||||
A_SESSION_ID,
|
||||
userDisplayName = Result.failure(AN_EXCEPTION),
|
||||
userAvatarURLString = Result.failure(AN_EXCEPTION),
|
||||
),
|
||||
|
|
@ -91,9 +89,7 @@ class RoomListPresenterTests {
|
|||
@Test
|
||||
fun `present - should filter room with success`() = runTest {
|
||||
val presenter = RoomListPresenter(
|
||||
FakeMatrixClient(
|
||||
SessionId("sessionId")
|
||||
),
|
||||
FakeMatrixClient(A_SESSION_ID),
|
||||
createDateFormatter()
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
|
|
@ -113,7 +109,7 @@ class RoomListPresenterTests {
|
|||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
||||
val presenter = RoomListPresenter(
|
||||
FakeMatrixClient(
|
||||
sessionId = SessionId("sessionId"),
|
||||
sessionId = A_SESSION_ID,
|
||||
roomSummaryDataSource = roomSummaryDataSource
|
||||
),
|
||||
createDateFormatter()
|
||||
|
|
@ -139,7 +135,7 @@ class RoomListPresenterTests {
|
|||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
||||
val presenter = RoomListPresenter(
|
||||
FakeMatrixClient(
|
||||
sessionId = SessionId("sessionId"),
|
||||
sessionId = A_SESSION_ID,
|
||||
roomSummaryDataSource = roomSummaryDataSource
|
||||
),
|
||||
createDateFormatter()
|
||||
|
|
@ -170,7 +166,7 @@ class RoomListPresenterTests {
|
|||
val roomSummaryDataSource = FakeRoomSummaryDataSource()
|
||||
val presenter = RoomListPresenter(
|
||||
FakeMatrixClient(
|
||||
sessionId = SessionId("sessionId"),
|
||||
sessionId = A_SESSION_ID,
|
||||
roomSummaryDataSource = roomSummaryDataSource
|
||||
),
|
||||
createDateFormatter()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ dependencies {
|
|||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.elementresources)
|
||||
|
|
@ -48,7 +48,7 @@ dependencies {
|
|||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrixtest)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue