Merge develop into feature/fga/dagger_setup
This commit is contained in:
commit
4c88d8e3c2
214 changed files with 2662 additions and 1833 deletions
|
|
@ -23,8 +23,6 @@ dependencies {
|
|||
implementation(project(":libraries:elementresources"))
|
||||
implementation(libs.mavericks.compose)
|
||||
ksp(libs.showkase.processor)
|
||||
implementation(libs.timber)
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package io.element.android.x.features.login
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
|
@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
|
|||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("io.element.android.x.features.login.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@
|
|||
|
||||
package io.element.android.x.features.login
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
|
@ -10,8 +16,21 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.text.font.FontWeight
|
||||
|
|
@ -56,19 +75,22 @@ fun LoginScreen(
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginContent(
|
||||
state: LoginViewState,
|
||||
formState: LoginFormState,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeServer: () -> Unit = {},
|
||||
onLoginChanged: (String) -> Unit = {},
|
||||
onPasswordChanged: (String) -> Unit = {},
|
||||
onSubmitClicked: () -> Unit = {},
|
||||
onLoginWithSuccess: (MatrixClient) -> Unit = {},
|
||||
) {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -96,7 +118,7 @@ fun LoginContent(
|
|||
)
|
||||
// Form
|
||||
Column(
|
||||
//modifier = Modifier.weight(1f),
|
||||
// modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -66,4 +66,4 @@ class LoginViewModel @AssistedInject constructor(
|
|||
formState.value = formState.value.copy(login = name)
|
||||
setState { copy(loggedInClient = Uninitialized) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,4 @@ data class LoginFormState(
|
|||
companion object {
|
||||
val Default = LoginFormState("", "")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,27 @@
|
|||
package io.element.android.x.features.login.changeserver
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.material3.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -45,15 +59,18 @@ fun ChangeServerScreen(
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun ChangeServerContent(
|
||||
state: ChangeServerViewState,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeServer: (String) -> Unit = {},
|
||||
onChangeServerSubmit: () -> Unit = {},
|
||||
onChangeServerSuccess: () -> Unit = {},
|
||||
) {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -67,8 +84,7 @@ fun ChangeServerContent(
|
|||
state = scrollState,
|
||||
)
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
{
|
||||
) {
|
||||
val isError = state.changeServerAction is Fail
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -101,7 +117,7 @@ fun ChangeServerContent(
|
|||
)
|
||||
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.",
|
||||
"You choose your server and it’s easy to make one.", // TODO "Learn more.",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
|
|
|
|||
|
|
@ -48,4 +48,4 @@ class ChangeServerViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ data class ChangeServerViewState(
|
|||
val changeServerAction: Async<Unit> = Uninitialized,
|
||||
) : MavericksState {
|
||||
val submitEnabled = homeserver.isNotEmpty() && changeServerAction !is Loading
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package io.element.android.x.features.login.error
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.x.core.uri.isValidUrl
|
||||
import io.element.android.x.features.login.LoginFormState
|
||||
import io.element.android.x.element.resources.R as ElementR
|
||||
import io.element.android.x.features.login.LoginFormState
|
||||
|
||||
@Composable
|
||||
fun loginError(
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M23.04,3.84C23.04,1.7192 24.7593,0 26.88,0C41.0185,0 52.48,11.4615 52.48,25.6C52.48,27.7208 50.7608,29.44 48.64,29.44C46.5193,29.44 44.8,27.7208 44.8,25.6C44.8,15.7031 36.777,7.68 26.88,7.68C24.7593,7.68 23.04,5.9608 23.04,3.84Z"
|
||||
android:fillColor="#0DBD8B"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M40.96,60.16C40.96,62.2808 39.2407,64 37.12,64C22.9815,64 11.52,52.5385 11.52,38.4C11.52,36.2792 13.2392,34.56 15.36,34.56C17.4807,34.56 19.2,36.2792 19.2,38.4C19.2,48.2969 27.223,56.32 37.12,56.32C39.2407,56.32 40.96,58.0392 40.96,60.16Z"
|
||||
android:fillColor="#0DBD8B"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M3.84,40.96C1.7192,40.96 -0,39.2407 -0,37.12C-0,22.9815 11.4615,11.52 25.6,11.52C27.7208,11.52 29.44,13.2392 29.44,15.36C29.44,17.4807 27.7208,19.2 25.6,19.2C15.7031,19.2 7.68,27.223 7.68,37.12C7.68,39.2407 5.9608,40.96 3.84,40.96Z"
|
||||
android:fillColor="#0DBD8B"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M60.16,23.04C62.2808,23.04 64,24.7593 64,26.88C64,41.0185 52.5385,52.48 38.4,52.48C36.2792,52.48 34.56,50.7608 34.56,48.64C34.56,46.5193 36.2792,44.8 38.4,44.8C48.2969,44.8 56.32,36.777 56.32,26.88C56.32,24.7593 58.0392,23.04 60.16,23.04Z"
|
||||
android:fillColor="#0DBD8B"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
<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 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>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package io.element.android.x.features.login
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
|
|
@ -14,4 +13,4 @@ class ExampleUnitTest {
|
|||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ dependencies {
|
|||
implementation(project(":libraries:textcomposer"))
|
||||
implementation(libs.mavericks.compose)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.datetime)
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
implementation(libs.androidx.recyclerview)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package io.element.android.x.features.messages
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
|
@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
|
|||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("io.element.android.x.features.messages.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -9,12 +9,20 @@ import io.element.android.x.features.messages.model.AggregatedReaction
|
|||
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
|
||||
import io.element.android.x.features.messages.model.MessagesItemReactionState
|
||||
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||
import io.element.android.x.features.messages.model.content.*
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemEmoteContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemEncryptedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemImageContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemNoticeContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent
|
||||
import io.element.android.x.features.messages.util.invalidateLast
|
||||
import io.element.android.x.matrix.MatrixClient
|
||||
import io.element.android.x.matrix.media.MediaResolver
|
||||
import io.element.android.x.matrix.room.MatrixRoom
|
||||
import io.element.android.x.matrix.timeline.MatrixTimelineItem
|
||||
import kotlin.system.measureTimeMillis
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -28,7 +36,6 @@ import org.matrix.rustcomponents.sdk.FormattedBody
|
|||
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||
import org.matrix.rustcomponents.sdk.MessageType
|
||||
import timber.log.Timber
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class MessageTimelineItemStateFactory(
|
||||
private val client: MatrixClient,
|
||||
|
|
@ -83,7 +90,6 @@ class MessageTimelineItemStateFactory(
|
|||
timelineItemStates.emit(newTimelineItemStates)
|
||||
}
|
||||
|
||||
|
||||
private fun calculateAndApplyDiff(newTimelineItems: List<MatrixTimelineItem>) {
|
||||
val timeToDiff = measureTimeMillis {
|
||||
val diffCallback =
|
||||
|
|
@ -192,7 +198,6 @@ class MessageTimelineItemStateFactory(
|
|||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
else -> MessagesTimelineItemUnknownContent
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,5 +237,4 @@ class MessageTimelineItemStateFactory(
|
|||
.resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value))
|
||||
return AvatarData(name, model, size)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,26 @@
|
|||
|
||||
package io.element.android.x.features.messages
|
||||
|
||||
import Avatar
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
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.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
|
@ -20,8 +37,24 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.End
|
||||
import androidx.compose.ui.Alignment.Companion.Start
|
||||
|
|
@ -41,18 +74,40 @@ import com.airbnb.mvrx.compose.mavericksViewModel
|
|||
import io.element.android.x.core.compose.LogCompositions
|
||||
import io.element.android.x.core.compose.PairCombinedPreviewParameter
|
||||
import io.element.android.x.core.data.StableCharSequence
|
||||
import io.element.android.x.designsystem.components.avatar.Avatar
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.features.messages.components.*
|
||||
import io.element.android.x.features.messages.model.*
|
||||
import io.element.android.x.features.messages.model.content.*
|
||||
import io.element.android.x.features.messages.components.MessageEventBubble
|
||||
import io.element.android.x.features.messages.components.MessagesReactionsView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemEncryptedView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemImageView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemRedactedView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemTextView
|
||||
import io.element.android.x.features.messages.components.MessagesTimelineItemUnknownView
|
||||
import io.element.android.x.features.messages.components.TimelineItemActionsScreen
|
||||
import io.element.android.x.features.messages.model.AggregatedReaction
|
||||
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
|
||||
import io.element.android.x.features.messages.model.MessagesItemGroupPositionProvider
|
||||
import io.element.android.x.features.messages.model.MessagesItemReactionState
|
||||
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||
import io.element.android.x.features.messages.model.MessagesViewState
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemContentProvider
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemEncryptedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemImageContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent
|
||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
|
||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewState
|
||||
import io.element.android.x.textcomposer.MessageComposerMode
|
||||
import io.element.android.x.textcomposer.TextComposer
|
||||
import java.lang.Math.random
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.lang.Math.random
|
||||
|
||||
@Composable
|
||||
fun MessagesScreen(
|
||||
|
|
@ -61,7 +116,6 @@ fun MessagesScreen(
|
|||
viewModel: MessagesViewModel = mavericksViewModel(argsFactory = { roomId }),
|
||||
composerViewModel: MessageComposerViewModel = mavericksViewModel(argsFactory = { roomId })
|
||||
) {
|
||||
|
||||
fun onSendMessage(textMessage: String) {
|
||||
viewModel.sendMessage(textMessage)
|
||||
composerViewModel.updateText("")
|
||||
|
|
@ -88,7 +142,7 @@ fun MessagesScreen(
|
|||
MessagesScreenContent(
|
||||
roomTitle = roomTitle,
|
||||
roomAvatar = roomAvatar,
|
||||
timelineItems = timelineItems().orEmpty(),
|
||||
timelineItems = timelineItems().orEmpty().toImmutableList(),
|
||||
hasMoreToLoad = hasMoreToLoad,
|
||||
onReachedLoadMore = viewModel::loadMore,
|
||||
onBackPressed = onBackPressed,
|
||||
|
|
@ -130,7 +184,7 @@ fun MessagesScreen(
|
|||
fun MessagesScreenContent(
|
||||
roomTitle: String?,
|
||||
roomAvatar: AvatarData?,
|
||||
timelineItems: List<MessagesTimelineItemState>,
|
||||
timelineItems: ImmutableList<MessagesTimelineItemState>,
|
||||
hasMoreToLoad: Boolean,
|
||||
onReachedLoadMore: () -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
|
|
@ -146,9 +200,11 @@ fun MessagesScreenContent(
|
|||
composerCanSendMessage: Boolean,
|
||||
composerText: StableCharSequence?,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LogCompositions(tag = "MessagesScreen", msg = "Content")
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
MessagesTopAppBar(
|
||||
|
|
@ -187,7 +243,7 @@ fun MessagesScreenContent(
|
|||
|
||||
@Composable
|
||||
fun MessagesContent(
|
||||
timelineItems: List<MessagesTimelineItemState>,
|
||||
timelineItems: ImmutableList<MessagesTimelineItemState>,
|
||||
hasMoreToLoad: Boolean,
|
||||
onReachedLoadMore: () -> Unit,
|
||||
onSendMessage: (String) -> Unit,
|
||||
|
|
@ -203,7 +259,6 @@ fun MessagesContent(
|
|||
composerText: StableCharSequence?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
@ -249,9 +304,11 @@ fun MessagesContent(
|
|||
fun MessagesTopAppBar(
|
||||
roomTitle: String?,
|
||||
roomAvatar: AvatarData?,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onBackPressed: () -> Unit = {},
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackPressed) {
|
||||
Icon(
|
||||
|
|
@ -282,7 +339,7 @@ fun MessagesTopAppBar(
|
|||
@Composable
|
||||
fun TimelineItems(
|
||||
lazyListState: LazyListState,
|
||||
timelineItems: List<MessagesTimelineItemState>,
|
||||
timelineItems: ImmutableList<MessagesTimelineItemState>,
|
||||
highlightedEventId: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
hasMoreToLoad: Boolean = false,
|
||||
|
|
@ -322,7 +379,6 @@ fun TimelineItems(
|
|||
onLoadMore = onReachedLoadMore
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun MessagesTimelineItemState.key(): String {
|
||||
|
|
@ -339,7 +395,6 @@ private fun MessagesTimelineItemState.contentType(): Int {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun TimelineItemRow(
|
||||
timelineItem: MessagesTimelineItemState,
|
||||
|
|
@ -426,7 +481,6 @@ fun MessageEventRow(
|
|||
content = messageEvent.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
else -> TODO() /* compiler issue ? */
|
||||
}
|
||||
}
|
||||
MessagesReactionsView(
|
||||
|
|
@ -471,8 +525,8 @@ private fun MessageSenderInformation(
|
|||
@Composable
|
||||
internal fun BoxScope.MessagesScrollHelper(
|
||||
lazyListState: LazyListState,
|
||||
timelineItems: List<MessagesTimelineItemState>,
|
||||
onLoadMore: () -> Unit,
|
||||
timelineItems: ImmutableList<MessagesTimelineItemState>,
|
||||
onLoadMore: () -> Unit = {},
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val firstVisibleItemIndex by remember { derivedStateOf { lazyListState.firstVisibleItemIndex } }
|
||||
|
|
@ -526,7 +580,6 @@ internal fun BoxScope.MessagesScrollHelper(
|
|||
Icon(Icons.Default.ArrowDownward, "")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -543,7 +596,6 @@ internal fun MessagesLoadingMoreIndicator() {
|
|||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MessagesItemGroupPositionToMessagesTimelineItemContentProvider :
|
||||
|
|
@ -551,6 +603,7 @@ class MessagesItemGroupPositionToMessagesTimelineItemContentProvider :
|
|||
MessagesItemGroupPositionProvider() to MessagesTimelineItemContentProvider()
|
||||
)
|
||||
|
||||
@Suppress("PreviewPublic")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun TimelineItemsPreview(
|
||||
|
|
@ -559,7 +612,7 @@ fun TimelineItemsPreview(
|
|||
) {
|
||||
TimelineItems(
|
||||
lazyListState = LazyListState(),
|
||||
timelineItems = listOf(
|
||||
timelineItems = persistentListOf(
|
||||
// 3 items (First Middle Last) with isMine = false
|
||||
createMessageEvent(
|
||||
isMine = false,
|
||||
|
|
|
|||
|
|
@ -218,4 +218,4 @@ class MessagesViewModel @AssistedInject constructor(
|
|||
timeline.callback = null
|
||||
timeline.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.x.designsystem.*
|
||||
import io.element.android.x.designsystem.LocalIsDarkTheme
|
||||
import io.element.android.x.designsystem.MessageHighlightDark
|
||||
import io.element.android.x.designsystem.MessageHighlightLight
|
||||
import io.element.android.x.designsystem.SystemGrey5Dark
|
||||
import io.element.android.x.designsystem.SystemGrey5Light
|
||||
import io.element.android.x.designsystem.SystemGrey6Dark
|
||||
import io.element.android.x.designsystem.SystemGrey6Light
|
||||
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
|
||||
|
||||
private val BUBBLE_RADIUS = 16.dp
|
||||
|
|
@ -26,9 +32,9 @@ fun MessageEventBubble(
|
|||
interactionSource: MutableInteractionSource,
|
||||
isHighlighted: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
fun bubbleShape(): Shape {
|
||||
return when (groupPosition) {
|
||||
|
|
@ -102,4 +108,4 @@ fun MessageEventBubble(
|
|||
shape = bubbleShape,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fun MessagesReactionsView(
|
|||
reactionsState: MessagesItemReactionState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if(reactionsState.reactions.isEmpty()) return
|
||||
if (reactionsState.reactions.isEmpty()) return
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
mainAxisSpacing = 2.dp,
|
||||
|
|
@ -53,4 +53,4 @@ fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Mo
|
|||
Text(text = reaction.count, color = MaterialTheme.colorScheme.secondary, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,28 @@
|
|||
package io.element.android.x.features.messages.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ModalBottomSheetLayout
|
||||
import androidx.compose.material.ModalBottomSheetState
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.airbnb.mvrx.compose.collectAsState
|
||||
import io.element.android.x.designsystem.components.VectorIcon
|
||||
|
|
@ -21,7 +35,6 @@ import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
|||
import io.element.android.x.features.messages.model.MessagesViewState
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -63,7 +76,6 @@ fun TimelineItemActionsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
ModalBottomSheetLayout(
|
||||
modifier = modifier,
|
||||
sheetState = modalBottomSheetState,
|
||||
|
|
@ -71,46 +83,47 @@ fun TimelineItemActionsScreen(
|
|||
SheetContent(
|
||||
actionsSheetState = itemActionsSheetState(),
|
||||
onActionClicked = ::onItemActionClicked,
|
||||
modifier = Modifier.navigationBarsPadding().imePadding()
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.imePadding()
|
||||
)
|
||||
}
|
||||
) {}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SheetContent(
|
||||
actionsSheetState: MessagesItemActionsSheetState?,
|
||||
onActionClicked: (MessagesItemAction, MessagesTimelineItemState.MessageEvent) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
onActionClicked: (MessagesItemAction, MessagesTimelineItemState.MessageEvent) -> Unit = { _, _ -> },
|
||||
) {
|
||||
if (actionsSheetState == null || actionsSheetState.actions.isEmpty()) {
|
||||
// Crashes if sheetContent size is zero
|
||||
Box(modifier = modifier.size(1.dp))
|
||||
return
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
items(actionsSheetState.actions) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
onActionClicked(it, actionsSheetState.targetItem)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = it.title,
|
||||
color = if (it.destructive) MaterialTheme.colors.error else Color.Unspecified,
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
VectorIcon(
|
||||
resourceId = it.icon,
|
||||
tint = if (it.destructive) MaterialTheme.colors.error else LocalContentColor.current,
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
items(actionsSheetState.actions) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
onActionClicked(it, actionsSheetState.targetItem)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = it.title,
|
||||
color = if (it.destructive) MaterialTheme.colors.error else Color.Unspecified,
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
VectorIcon(
|
||||
resourceId = it.icon,
|
||||
tint = if (it.destructive) MaterialTheme.colors.error else LocalContentColor.current,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ fun MessagesTimelineItemEncryptedView(
|
|||
icon = Icons.Default.Warning,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -16,7 +15,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemImageContent
|
||||
|
|
@ -26,9 +24,9 @@ fun MessagesTimelineItemImageView(
|
|||
content: MessagesTimelineItemImageContent,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val widthPercent = if(content.aspectRatio > 1f){
|
||||
val widthPercent = if (content.aspectRatio > 1f) {
|
||||
1f
|
||||
}else {
|
||||
} else {
|
||||
0.7f
|
||||
}
|
||||
Box(
|
||||
|
|
@ -37,7 +35,6 @@ fun MessagesTimelineItemImageView(
|
|||
.aspectRatio(content.aspectRatio),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
||||
var isLoading = rememberSaveable(content.imageMeta) { mutableStateOf(true) }
|
||||
val context = LocalContext.current
|
||||
val model = ImageRequest.Builder(context)
|
||||
|
|
@ -52,4 +49,4 @@ fun MessagesTimelineItemImageView(
|
|||
onSuccess = { isLoading.value = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,4 +40,4 @@ fun MessagesTimelineItemInformativeView(
|
|||
text = text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ fun MessagesTimelineItemRedactedView(
|
|||
icon = Icons.Default.Delete,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ fun MessagesTimelineItemTextView(
|
|||
content: MessagesTimelineItemTextBasedContent,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit,
|
||||
onTextLongClicked: () -> Unit,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val htmlDocument = content.htmlDocument
|
||||
if (htmlDocument != null) {
|
||||
|
|
@ -74,4 +74,4 @@ private fun String.linkify(
|
|||
end = end
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ fun MessagesTimelineItemUnknownView(
|
|||
icon = Icons.Default.Info,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,25 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
|
|
@ -27,6 +35,7 @@ import io.element.android.x.designsystem.LinkColor
|
|||
import io.element.android.x.designsystem.components.ClickableLinkText
|
||||
import io.element.android.x.matrix.permalink.PermalinkData
|
||||
import io.element.android.x.matrix.permalink.PermalinkParser
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.Node
|
||||
|
|
@ -38,34 +47,33 @@ private const val chipId = "chip"
|
|||
fun HtmlDocument(
|
||||
document: Document,
|
||||
interactionSource: MutableInteractionSource,
|
||||
onTextClicked: () -> Unit,
|
||||
onTextLongClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
HtmlBody(
|
||||
body = document.body(),
|
||||
interactionSource = interactionSource,
|
||||
modifier = modifier,
|
||||
onTextClicked = onTextClicked,
|
||||
onTextLongClicked = onTextLongClicked,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HtmlBody(
|
||||
body: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit,
|
||||
onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
|
||||
@Composable
|
||||
fun NodesFlowRode(
|
||||
nodes: Iterator<Node>,
|
||||
onTextClicked: () -> Unit,
|
||||
onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) = FlowRow(
|
||||
mainAxisSpacing = 2.dp,
|
||||
crossAxisSpacing = 8.dp,
|
||||
|
|
@ -106,9 +114,9 @@ private fun HtmlBody(
|
|||
while (nodesIterator.hasNext()) {
|
||||
NodesFlowRode(
|
||||
nodes = nodesIterator,
|
||||
interactionSource = interactionSource,
|
||||
onTextClicked = onTextClicked,
|
||||
onTextLongClicked = onTextLongClicked,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -125,10 +133,10 @@ private fun Element.isInline(): Boolean {
|
|||
@Composable
|
||||
private fun HtmlBlock(
|
||||
element: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit,
|
||||
onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val blockModifier = modifier
|
||||
.padding(top = 4.dp)
|
||||
|
|
@ -183,10 +191,10 @@ private fun HtmlBlock(
|
|||
@Composable
|
||||
private fun HtmlInline(
|
||||
element: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit,
|
||||
onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
Box(modifier) {
|
||||
val styledText = buildAnnotatedString {
|
||||
|
|
@ -202,7 +210,10 @@ private fun HtmlInline(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun HtmlPreformatted(pre: Element, modifier: Modifier = Modifier) {
|
||||
private fun HtmlPreformatted(
|
||||
pre: Element,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val isCode = pre.firstElementChild()?.normalName() == "code"
|
||||
val backgroundColor =
|
||||
if (isCode) MaterialTheme.colorScheme.codeBackground() else Color.Unspecified
|
||||
|
|
@ -221,9 +232,10 @@ private fun HtmlPreformatted(pre: Element, modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun HtmlParagraph(
|
||||
paragraph: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
Box(modifier) {
|
||||
val styledText = buildAnnotatedString {
|
||||
|
|
@ -239,9 +251,10 @@ private fun HtmlParagraph(
|
|||
@Composable
|
||||
private fun HtmlBlockquote(
|
||||
blockquote: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val color = MaterialTheme.colorScheme.onBackground
|
||||
Box(
|
||||
|
|
@ -268,13 +281,13 @@ private fun HtmlBlockquote(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun HtmlHeading(
|
||||
heading: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val style = when (heading.normalName()) {
|
||||
"h1" -> MaterialTheme.typography.headlineLarge.copy(fontSize = 30.sp)
|
||||
|
|
@ -304,9 +317,10 @@ private fun HtmlHeading(
|
|||
@Composable
|
||||
private fun HtmlMxReply(
|
||||
mxReply: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val blockquote = mxReply.childNodes().firstOrNull() ?: return
|
||||
val shape = RoundedCornerShape(12.dp)
|
||||
|
|
@ -356,9 +370,10 @@ private fun HtmlMxReply(
|
|||
@Composable
|
||||
private fun HtmlOrderedList(
|
||||
orderedList: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
var number = 1
|
||||
val delimiter = "."
|
||||
|
|
@ -381,9 +396,10 @@ private fun HtmlOrderedList(
|
|||
@Composable
|
||||
private fun HtmlUnorderedList(
|
||||
unorderedList: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
) {
|
||||
val marker = "・"
|
||||
HtmlListItems(
|
||||
|
|
@ -402,14 +418,14 @@ private fun HtmlUnorderedList(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun HtmlListItems(
|
||||
list: Element,
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit, onTextLongClicked: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
content: @Composable (node: TextNode) -> Unit
|
||||
modifier: Modifier = Modifier,
|
||||
onTextClicked: () -> Unit = {},
|
||||
onTextLongClicked: () -> Unit = {},
|
||||
content: @Composable (node: TextNode) -> Unit = {}
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
for (node in list.children()) {
|
||||
|
|
@ -420,13 +436,12 @@ private fun HtmlListItems(
|
|||
}
|
||||
is Element -> HtmlBlock(
|
||||
element = innerNode,
|
||||
modifier = modifier.padding(start = 4.dp),
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
onTextClicked = onTextClicked, onTextLongClicked = onTextLongClicked,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -439,7 +454,6 @@ private fun AnnotatedString.Builder.appendInlineChildrenElements(
|
|||
childNodes: List<Node>,
|
||||
colors: ColorScheme
|
||||
) {
|
||||
|
||||
for (node in childNodes) {
|
||||
when (node) {
|
||||
is TextNode -> {
|
||||
|
|
@ -452,7 +466,6 @@ private fun AnnotatedString.Builder.appendInlineChildrenElements(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun AnnotatedString.Builder.appendInlineElement(element: Element, colors: ColorScheme) {
|
||||
when (element.normalName()) {
|
||||
"br" -> {
|
||||
|
|
@ -490,7 +503,6 @@ private fun AnnotatedString.Builder.appendInlineElement(element: Element, colors
|
|||
appendInlineChildrenElements(element.childNodes(), colors)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.appendLink(link: Element) {
|
||||
|
|
@ -521,13 +533,13 @@ private fun AnnotatedString.Builder.appendLink(link: Element) {
|
|||
@Composable
|
||||
private fun HtmlText(
|
||||
text: AnnotatedString,
|
||||
interactionSource: MutableInteractionSource,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
) {
|
||||
val inlineContentMap = emptyMap<String, InlineTextContent>()
|
||||
val inlineContentMap = persistentMapOf<String, InlineTextContent>()
|
||||
ClickableLinkText(
|
||||
text = text,
|
||||
linkAnnotationTag = "URL",
|
||||
|
|
@ -539,4 +551,3 @@ private fun HtmlText(
|
|||
onLongClick = onLongClick
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,5 +37,4 @@ internal class CacheInvalidator(private val itemStatesCache: MutableList<Message
|
|||
itemStatesCache.removeAt(position)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,5 +31,4 @@ internal class MatrixTimelineItemsDiffCallback(
|
|||
val newItem = newList.getOrNull(newItemPosition)
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@ sealed class MessagesItemAction(
|
|||
object Redact : MessagesItemAction("Redact", VectorIcons.Delete, destructive = true)
|
||||
object Reply : MessagesItemAction("Reply", VectorIcons.Reply)
|
||||
object Edit : MessagesItemAction("Edit", VectorIcons.Edit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ sealed interface MessagesItemGroupPosition {
|
|||
First, None -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class MessagesItemGroupPositionProvider : PreviewParameterProvider<MessagesItemGroupPosition> {
|
||||
|
|
@ -22,4 +21,4 @@ internal class MessagesItemGroupPositionProvider : PreviewParameterProvider<Mess
|
|||
MessagesItemGroupPosition.Last,
|
||||
MessagesItemGroupPosition.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import androidx.compose.runtime.Stable
|
|||
data class MessagesItemReactionState(
|
||||
val reactions: List<AggregatedReaction>
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class AggregatedReaction(
|
||||
val key: String,
|
||||
val count: String,
|
||||
val isHighlighted: Boolean = false
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,11 +23,5 @@ sealed interface MessagesTimelineItemState {
|
|||
val showSenderInformation = groupPosition.isNew() && !isMine
|
||||
|
||||
val safeSenderName: String = senderDisplayName ?: senderId
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package io.element.android.x.features.messages.model.content
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||
|
||||
sealed interface MessagesTimelineItemContent
|
||||
|
||||
|
|
@ -28,4 +26,4 @@ class MessagesTimelineItemContentProvider : PreviewParameterProvider<MessagesTim
|
|||
),
|
||||
MessagesTimelineItemUnknownContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ import org.jsoup.nodes.Document
|
|||
data class MessagesTimelineItemEmoteContent(
|
||||
override val body: String,
|
||||
override val htmlDocument: Document?
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ import org.matrix.rustcomponents.sdk.EncryptedMessage
|
|||
|
||||
data class MessagesTimelineItemEncryptedContent(
|
||||
val encryptedMessage: EncryptedMessage
|
||||
) : MessagesTimelineItemContent
|
||||
) : MessagesTimelineItemContent
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ data class MessagesTimelineItemImageContent(
|
|||
val imageMeta: MediaResolver.Meta,
|
||||
val blurhash: String?,
|
||||
val aspectRatio: Float
|
||||
) : MessagesTimelineItemContent
|
||||
) : MessagesTimelineItemContent
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ import org.jsoup.nodes.Document
|
|||
data class MessagesTimelineItemNoticeContent(
|
||||
override val body: String,
|
||||
override val htmlDocument: Document?
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
object MessagesTimelineItemRedactedContent : MessagesTimelineItemContent
|
||||
object MessagesTimelineItemRedactedContent : MessagesTimelineItemContent
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ import org.jsoup.nodes.Document
|
|||
sealed interface MessagesTimelineItemTextBasedContent : MessagesTimelineItemContent {
|
||||
val body: String
|
||||
val htmlDocument: Document?
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ import org.jsoup.nodes.Document
|
|||
data class MessagesTimelineItemTextContent(
|
||||
override val body: String,
|
||||
override val htmlDocument: Document?
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
) : MessagesTimelineItemTextBasedContent
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
package io.element.android.x.features.messages.model.content
|
||||
|
||||
object MessagesTimelineItemUnknownContent : MessagesTimelineItemContent
|
||||
object MessagesTimelineItemUnknownContent : MessagesTimelineItemContent
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ class MessageComposerViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,4 @@ data class MessageComposerViewState(
|
|||
// val voiceBroadcastState: VoiceBroadcastState? = null,
|
||||
val text: StableCharSequence? = null,
|
||||
val isFullScreen: Boolean = false,
|
||||
) : MavericksState
|
||||
) : MavericksState
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ internal inline fun <reified T> MutableList<T?>.invalidateLast() {
|
|||
if (indexOfLast > 0) {
|
||||
set(indexOfLast - 1, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package io.element.android.x.features.messages
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
|
|
@ -14,4 +13,4 @@ class ExampleUnitTest {
|
|||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ dependencies {
|
|||
implementation(project(":libraries:elementresources"))
|
||||
implementation(project(":libraries:designsystem"))
|
||||
implementation(libs.mavericks.compose)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.accompanist.pager)
|
||||
implementation(libs.accompanist.pagerindicator)
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package io.element.android.x.features.login
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
|
@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
|
|||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("io.element.android.x.features.login.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,24 @@
|
|||
package io.element.android.x.features.onboarding
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -42,19 +54,20 @@ fun OnBoardingScreen(
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun OnBoardingContent(
|
||||
state: OnBoardingViewState,
|
||||
onPageChanged: (Int) -> Unit,
|
||||
onSignUp: () -> Unit,
|
||||
onSignIn: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onPageChanged: (Int) -> Unit = {},
|
||||
onSignUp: () -> Unit = {},
|
||||
onSignIn: () -> Unit = {},
|
||||
) {
|
||||
val carrouselState = remember { SplashCarouselStateFactory().create() }
|
||||
val nbOfPages = carrouselState.items.size
|
||||
var key by remember { mutableStateOf(false) }
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
Box(
|
||||
|
|
@ -125,8 +138,11 @@ fun OnBoardingContent(
|
|||
@Composable
|
||||
fun OnBoardingPage(
|
||||
item: SplashCarouselState.Item,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
/*
|
||||
Image(
|
||||
painterResource(id = item.pageBackground),
|
||||
|
|
@ -164,4 +180,4 @@ fun OnBoardingPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ class OnBoardingViewModel(initialState: OnBoardingViewState) :
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.annotation.StringRes
|
||||
|
||||
data class SplashCarouselState(
|
||||
val items: List<Item>
|
||||
val items: List<Item>
|
||||
) {
|
||||
data class Item(
|
||||
@StringRes val title: Int,
|
||||
@StringRes val body: Int,
|
||||
@DrawableRes val image: Int,
|
||||
@DrawableRes val pageBackground: Int
|
||||
@StringRes val title: Int,
|
||||
@StringRes val body: Int,
|
||||
@DrawableRes val image: Int,
|
||||
@DrawableRes val pageBackground: Int
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package io.element.android.x.features.onboarding
|
|||
import androidx.annotation.DrawableRes
|
||||
import io.element.android.x.element.resources.R as ElementR
|
||||
|
||||
class SplashCarouselStateFactory() {
|
||||
class SplashCarouselStateFactory {
|
||||
fun create(): SplashCarouselState {
|
||||
val lightTheme = true
|
||||
fun background(@DrawableRes lightDrawable: Int) =
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package io.element.android.x.features.login
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
|
|
@ -14,4 +13,4 @@ class ExampleUnitTest {
|
|||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ dependencies {
|
|||
implementation(project(":libraries:matrix"))
|
||||
implementation(project(":libraries:designsystem"))
|
||||
implementation(libs.mavericks.compose)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.datetime)
|
||||
implementation(libs.accompanist.placeholder)
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package io.element.android.x.features.roomlist
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
|
@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
|
|||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("io.element.android.x.features.roomlist.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ package io.element.android.x.features.roomlist
|
|||
|
||||
import android.text.format.DateFormat
|
||||
import android.text.format.DateUtils
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.datetime.TimeZone
|
||||
import java.time.Period
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toJavaLocalDate
|
||||
import kotlinx.datetime.toJavaLocalDateTime
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
|
||||
class LastMessageFormatter(
|
||||
private val clock: Clock = Clock.System,
|
||||
|
|
@ -29,7 +35,6 @@ class LastMessageFormatter(
|
|||
DateTimeFormatter.ofPattern(pattern)
|
||||
}
|
||||
|
||||
|
||||
fun format(timestamp: Long?): String {
|
||||
if (timestamp == null) return ""
|
||||
val now: Instant = clock.now()
|
||||
|
|
@ -77,6 +82,4 @@ class LastMessageFormatter(
|
|||
DateUtils.FORMAT_SHOW_WEEKDAY
|
||||
).toString()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
|||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||
import io.element.android.x.features.roomlist.model.stubbedRoomSummaries
|
||||
import io.element.android.x.matrix.core.RoomId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun RoomListScreen(
|
||||
|
|
@ -53,7 +55,7 @@ fun RoomListScreen(
|
|||
val roomSummaries by viewModel.collectAsState(RoomListViewState::rooms)
|
||||
val matrixUser by viewModel.collectAsState(RoomListViewState::user)
|
||||
RoomListContent(
|
||||
roomSummaries = roomSummaries().orEmpty(),
|
||||
roomSummaries = roomSummaries().orEmpty().toImmutableList(),
|
||||
matrixUser = matrixUser(),
|
||||
onRoomClicked = onRoomClicked,
|
||||
onLogoutClicked = viewModel::logout,
|
||||
|
|
@ -66,16 +68,16 @@ fun RoomListScreen(
|
|||
|
||||
@Composable
|
||||
fun RoomListContent(
|
||||
roomSummaries: List<RoomListRoomSummary>,
|
||||
roomSummaries: ImmutableList<RoomListRoomSummary>,
|
||||
matrixUser: MatrixUser?,
|
||||
onRoomClicked: (RoomId) -> Unit,
|
||||
filter: String,
|
||||
onFilterChanged: (String) -> Unit,
|
||||
onLogoutClicked: () -> Unit,
|
||||
onScrollOver: (IntRange) -> Unit,
|
||||
isLoginOut: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onRoomClicked: (RoomId) -> Unit = {},
|
||||
onFilterChanged: (String) -> Unit = {},
|
||||
onLogoutClicked: () -> Unit = {},
|
||||
onScrollOver: (IntRange) -> Unit = {},
|
||||
) {
|
||||
|
||||
fun onRoomClicked(room: RoomListRoomSummary) {
|
||||
onRoomClicked(room.roomId)
|
||||
}
|
||||
|
|
@ -104,7 +106,7 @@ fun RoomListContent(
|
|||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
RoomListTopBar(
|
||||
matrixUser = matrixUser,
|
||||
|
|
@ -133,7 +135,7 @@ fun RoomListContent(
|
|||
}
|
||||
)
|
||||
if (isLoginOut) {
|
||||
ProgressDialog("Login out...")
|
||||
ProgressDialog(text = "Login out...")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,4 +178,3 @@ fun PreviewableDarkRoomListContent() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,17 +4,15 @@ import com.airbnb.mvrx.*
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.x.anvilannotations.ContributesViewModel
|
||||
import io.element.android.x.core.data.parallelMap
|
||||
import io.element.android.x.core.coroutine.parallelMap
|
||||
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.x.di.AppScope
|
||||
import io.element.android.x.di.SessionScope
|
||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummaryPlaceholders
|
||||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||
import io.element.android.x.matrix.Matrix
|
||||
import io.element.android.x.matrix.MatrixClient
|
||||
import io.element.android.x.matrix.media.MediaResolver
|
||||
import io.element.android.x.matrix.room.RoomSummary
|
||||
|
|
@ -112,8 +110,8 @@ class RoomListViewModel @AssistedInject constructor(
|
|||
copy(
|
||||
rooms = when {
|
||||
it is Loading ||
|
||||
// Note: this second case will prevent to handle correctly the empty case
|
||||
(it is Success && it().isEmpty() && filter.isEmpty()) -> {
|
||||
// Note: this second case will prevent to handle correctly the empty case
|
||||
(it is Success && it().isEmpty() && filter.isEmpty()) -> {
|
||||
// Show fake placeholders to avoid having empty screen
|
||||
Loading(RoomListRoomSummaryPlaceholders.createFakeList(size = 16))
|
||||
}
|
||||
|
|
@ -158,5 +156,4 @@ class RoomListViewModel @AssistedInject constructor(
|
|||
.resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value))
|
||||
return AvatarData(name, model, size)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
package io.element.android.x.features.roomlist.components
|
||||
|
||||
import Avatar
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.ContentAlpha
|
||||
|
|
@ -11,9 +10,23 @@ import androidx.compose.material.icons.filled.ArrowBack
|
|||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Logout
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
|
|
@ -24,6 +37,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.x.core.compose.LogCompositions
|
||||
import io.element.android.x.core.compose.textFieldState
|
||||
import io.element.android.x.designsystem.components.avatar.Avatar
|
||||
import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||
|
||||
|
|
@ -35,7 +49,6 @@ fun RoomListTopBar(
|
|||
onLogoutClicked: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior
|
||||
) {
|
||||
|
||||
LogCompositions(tag = "RoomListScreen", msg = "TopBar")
|
||||
var searchWidgetStateIsOpened by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -65,20 +78,20 @@ fun RoomListTopBar(
|
|||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchRoomListTopBar(
|
||||
text: String,
|
||||
onFilterChanged: (String) -> Unit,
|
||||
onCloseClicked: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
modifier: Modifier = Modifier,
|
||||
onFilterChanged: (String) -> Unit = {},
|
||||
onCloseClicked: () -> Unit = {},
|
||||
) {
|
||||
var filterState by textFieldState(stateValue = text)
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
TopAppBar(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
title = {
|
||||
TextField(
|
||||
|
|
@ -184,10 +197,13 @@ private fun DefaultRoomListTopBar(
|
|||
)
|
||||
// Log out confirmation dialog
|
||||
ConfirmationDialog(
|
||||
openDialog,
|
||||
isDisplayed = openDialog.value,
|
||||
title = "Log out",
|
||||
content = "Do you confirm you want to log out?",
|
||||
submitText = "Log out",
|
||||
onSubmitClicked = onLogoutClicked,
|
||||
onDismiss = {
|
||||
openDialog.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
package io.element.android.x.features.roomlist.components
|
||||
|
||||
import Avatar
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
|
|
@ -29,17 +36,17 @@ import androidx.compose.ui.unit.LayoutDirection
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.placeholder.material.placeholder
|
||||
import io.element.android.x.designsystem.components.avatar.Avatar
|
||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||
|
||||
private val minHeight = 72.dp
|
||||
|
||||
@Composable
|
||||
internal fun RoomSummaryRow(
|
||||
modifier: Modifier = Modifier,
|
||||
room: RoomListRoomSummary,
|
||||
onClick: (RoomListRoomSummary) -> Unit
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (RoomListRoomSummary) -> Unit = {},
|
||||
) {
|
||||
|
||||
val clickModifier = if (room.isPlaceholder) {
|
||||
modifier
|
||||
} else {
|
||||
|
|
@ -57,7 +64,6 @@ internal fun RoomSummaryRow(
|
|||
) {
|
||||
DefaultRoomSummaryRow(room = room)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ data class RoomListRoomSummary(
|
|||
val timestamp: String? = null,
|
||||
val lastMessage: CharSequence? = null,
|
||||
val avatarData: AvatarData = AvatarData(),
|
||||
val isPlaceholder: Boolean = false,)
|
||||
val isPlaceholder: Boolean = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package io.element.android.x.features.roomlist.model
|
|||
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
|
||||
|
||||
object RoomListRoomSummaryPlaceholders {
|
||||
|
||||
fun create(id: String): RoomListRoomSummary {
|
||||
|
|
@ -23,6 +22,4 @@ object RoomListRoomSummaryPlaceholders {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package io.element.android.x.features.roomlist.model
|
||||
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal fun stubbedRoomSummaries(): List<RoomListRoomSummary> {
|
||||
return listOf(
|
||||
internal fun stubbedRoomSummaries(): ImmutableList<RoomListRoomSummary> {
|
||||
return persistentListOf(
|
||||
RoomListRoomSummary(
|
||||
name = "Room",
|
||||
hasUnread = true,
|
||||
|
|
@ -22,4 +24,4 @@ internal fun stubbedRoomSummaries(): List<RoomListRoomSummary> {
|
|||
),
|
||||
RoomListRoomSummaryPlaceholders.create("roomId2")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package io.element.android.x.features.roomlist
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
|
|
@ -14,4 +13,4 @@ class ExampleUnitTest {
|
|||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue