This commit is contained in:
ganfra 2022-11-29 21:28:25 +01:00
commit 5244e2fac6
28 changed files with 330 additions and 45 deletions

View file

@ -125,4 +125,7 @@ dependencies {
debugImplementation "androidx.compose.ui:ui-tooling"
debugImplementation "androidx.compose.ui:ui-test-manifest"
implementation 'com.airbnb.android:mavericks-compose:3.0.1'
implementation("com.airbnb.android:showkase:1.0.0-beta14")
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}

View file

@ -13,13 +13,23 @@ import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.material3.MaterialTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.navigation.NavHostController
import com.airbnb.android.showkase.models.Showkase
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.animations.defaults.RootNavGraphDefaultAnimations
@ -40,8 +50,38 @@ class MainActivity : ComponentActivity() {
// FIXME Scrolling is broken on login screens. Commenting this line fixes the issue.
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ElementXTheme {
MainScreen(viewModel = viewModel)
Box(modifier = Modifier.fillMaxSize()) {
ElementXTheme {
MainScreen(viewModel = viewModel)
}
ShowkaseButton(
onClick = { startActivity(Showkase.getBrowserIntent(this@MainActivity)) }
)
}
}
}
}
@Composable
private fun ShowkaseButton(
onClick: () -> Unit = {}
) {
val showkaseButtonVisible = remember { mutableStateOf(true) }
if (showkaseButtonVisible.value) {
Button(
modifier = Modifier
.padding(top = 32.dp, start = 16.dp),
onClick = onClick
) {
Text(text = "Showkase Browser")
IconButton(
modifier = Modifier
.padding(start = 8.dp)
.size(16.dp),
onClick = { showkaseButtonVisible.value = false },
) {
Icon(imageVector = Icons.Filled.Close, contentDescription = "")
}
}
}
@ -122,6 +162,6 @@ private fun LogNavigation(navController: NavHostController) {
@Composable
@Preview
private fun MainContentPreview() {
fun MainContentPreview() {
MainContent(startRoute = OnBoardingScreenNavigationDestination)
}

View file

@ -0,0 +1,7 @@
package io.element.android.x
import com.airbnb.android.showkase.annotation.ShowkaseRoot
import com.airbnb.android.showkase.annotation.ShowkaseRootModule
@ShowkaseRoot
class ElementRootModule : ShowkaseRootModule

View file

@ -1,8 +1,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// TODO Convert to .kts
plugins {
id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
id 'com.google.devtools.ksp' version '1.7.20-1.0.7' apply false
}
task clean(type: Delete) {

View file

@ -1,6 +1,14 @@
### VersionCatalog
### Jetpack Compose
https://developer.android.com/jetpack/compose/mental-model
https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md#api-guidelines-for-jetpack-compose
https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md#api-guidelines-for-jetpack-compose
Preview
https://alexzh.com/jetpack-compose-preview/
https://github.com/airbnb/Showkase

View file

@ -1,5 +1,7 @@
plugins {
id("io.element.android-compose")
// TODO Move to common config
id("com.google.devtools.ksp") version "1.7.20-1.0.7"
}
android {
@ -16,4 +18,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
// TODO Move to common config
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}

View file

@ -202,7 +202,7 @@ fun LoginContent(
@Composable
@Preview
private fun LoginContentPreview() {
fun LoginContentPreview() {
ElementXTheme(darkTheme = false) {
LoginContent(
state = LoginViewState(

View file

@ -150,7 +150,7 @@ fun ChangeServerContent(
@Composable
@Preview
private fun ChangeServerContentPreview() {
fun ChangeServerContentPreview() {
ChangeServerContent(
state = ChangeServerViewState(homeserver = "matrix.org"),
)

View file

@ -1,5 +1,7 @@
plugins {
id("io.element.android-compose")
// TODO Move to common config
id("com.google.devtools.ksp") version "1.7.20-1.0.7"
}
android {
@ -20,4 +22,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
// TODO Move to common config
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}

View file

@ -24,17 +24,19 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
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 androidx.compose.ui.zIndex
import com.airbnb.mvrx.compose.collectAsState
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.AvatarData
import io.element.android.x.features.messages.components.*
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.*
import io.element.android.x.features.messages.model.content.*
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
import io.element.android.x.features.messages.textcomposer.MessageComposerViewState
@ -42,6 +44,7 @@ import io.element.android.x.textcomposer.TextComposer
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import timber.log.Timber
import java.lang.Math.random
private val COMPOSER_HEIGHT = 112.dp
@ -253,11 +256,11 @@ fun MessagesTopAppBar(
fun TimelineItems(
lazyListState: LazyListState,
timelineItems: List<MessagesTimelineItemState>,
hasMoreToLoad: Boolean,
onClick: (MessagesTimelineItemState.MessageEvent) -> Unit,
onLongClick: ((MessagesTimelineItemState.MessageEvent)) -> Unit,
onReachedLoadMore: () -> Unit,
modifier: Modifier = Modifier,
hasMoreToLoad: Boolean = false,
onClick: (MessagesTimelineItemState.MessageEvent) -> Unit = {},
onLongClick: ((MessagesTimelineItemState.MessageEvent)) -> Unit = {},
onReachedLoadMore: () -> Unit = {},
) {
Box(modifier = modifier.fillMaxWidth()) {
LazyColumn(
@ -385,6 +388,7 @@ fun MessageEventRow(
content = messageEvent.content,
modifier = contentModifier
)
else -> TODO() /* compiler issue ? */
}
}
MessagesReactionsView(
@ -502,4 +506,76 @@ internal fun MessagesLoadingMoreIndicator() {
)
}
}
}
class MessagesItemGroupPositionToMessagesTimelineItemContentProvider :
PairCombinedPreviewParameter<MessagesItemGroupPosition, MessagesTimelineItemContent>(
MessagesItemGroupPositionProvider() to MessagesTimelineItemContentProvider()
)
@Preview(showBackground = true)
@Composable
fun TimelineItemsPreview(
@PreviewParameter(MessagesTimelineItemContentProvider::class)
content: MessagesTimelineItemContent
) {
TimelineItems(
lazyListState = LazyListState(),
timelineItems = listOf(
// 3 items (First Middle Last) with isMine = false
createMessageEvent(
isMine = false,
content = content,
groupPosition = MessagesItemGroupPosition.First
),
createMessageEvent(
isMine = false,
content = content,
groupPosition = MessagesItemGroupPosition.Middle
),
createMessageEvent(
isMine = false,
content = content,
groupPosition = MessagesItemGroupPosition.Last
),
// 3 items (First Middle Last) with isMine = true
createMessageEvent(
isMine = true,
content = content,
groupPosition = MessagesItemGroupPosition.First
),
createMessageEvent(
isMine = true,
content = content,
groupPosition = MessagesItemGroupPosition.Middle
),
createMessageEvent(
isMine = true,
content = content,
groupPosition = MessagesItemGroupPosition.Last
),
),
hasMoreToLoad = true,
)
}
private fun createMessageEvent(
isMine: Boolean,
content: MessagesTimelineItemContent,
groupPosition: MessagesItemGroupPosition
): MessagesTimelineItemState {
return MessagesTimelineItemState.MessageEvent(
id = random().toString(),
senderId = "senderId",
senderAvatar = AvatarData("sender"),
content = content,
reactionsState = MessagesItemReactionState(
listOf(
AggregatedReaction("👍", "1")
)
),
isMine = isMine,
senderDisplayName = "sender",
groupPosition = groupPosition,
)
}

View file

@ -9,6 +9,7 @@ import io.element.android.x.features.messages.model.MessagesItemAction
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
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.MessagesTimelineItemRedactedContent
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.MatrixInstance
import io.element.android.x.matrix.media.MediaResolver
@ -104,13 +105,19 @@ class MessagesViewModel(
fun computeActionsSheetState(messagesTimelineItemState: MessagesTimelineItemState.MessageEvent) {
suspend {
val actions = mutableListOf(
MessagesItemAction.Forward,
MessagesItemAction.Copy,
)
if (messagesTimelineItemState.isMine) {
actions.add(MessagesItemAction.Redact)
}
val actions =
if (messagesTimelineItemState.content is MessagesTimelineItemRedactedContent) {
emptyList()
} else {
mutableListOf(
MessagesItemAction.Forward,
MessagesItemAction.Copy,
).also {
if (messagesTimelineItemState.isMine) {
it.add(MessagesItemAction.Redact)
}
}
}
MessagesItemActionsSheetState(
targetItem = messagesTimelineItemState,
actions = actions

View file

@ -1,5 +1,7 @@
package io.element.android.x.features.messages.model
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
sealed interface MessagesItemGroupPosition {
object First : MessagesItemGroupPosition
object Middle : MessagesItemGroupPosition
@ -12,3 +14,12 @@ sealed interface MessagesItemGroupPosition {
}
}
internal class MessagesItemGroupPositionProvider : PreviewParameterProvider<MessagesItemGroupPosition> {
override val values = sequenceOf(
MessagesItemGroupPosition.First,
MessagesItemGroupPosition.Middle,
MessagesItemGroupPosition.Last,
MessagesItemGroupPosition.None,
)
}

View file

@ -9,7 +9,7 @@ sealed interface MessagesTimelineItemState {
) : MessagesTimelineItemState
data class MessageEvent(
val id: String = "",
val id: String,
val senderId: String,
val senderDisplayName: String?,
val senderAvatar: AvatarData,

View file

@ -1,3 +1,31 @@
package io.element.android.x.features.messages.model.content
sealed interface MessagesTimelineItemContent
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
class MessagesTimelineItemContentProvider : PreviewParameterProvider<MessagesTimelineItemContent> {
override val values = sequenceOf(
MessagesTimelineItemEmoteContent(
body = "Emote",
formattedBody = FormattedBody(MessageFormat.HTML, "Formatted emote")
),
MessagesTimelineItemEncryptedContent(
encryptedMessage = EncryptedMessage.Unknown
),
// TODO MessagesTimelineItemImageContent(),
MessagesTimelineItemNoticeContent(
body = "Notice",
formattedBody = FormattedBody(MessageFormat.HTML, "Formatted notice")
),
MessagesTimelineItemRedactedContent,
MessagesTimelineItemTextContent(
body = "Text",
formattedBody = FormattedBody(MessageFormat.HTML, "Formatted text")
),
MessagesTimelineItemUnknownContent,
)
}

View file

@ -1,5 +1,7 @@
plugins {
id("io.element.android-compose")
// TODO Move to common config
id("com.google.devtools.ksp") version "1.7.20-1.0.7"
}
android {
@ -17,4 +19,6 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
// TODO Move to common config
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}

View file

@ -1,5 +1,7 @@
plugins {
id("io.element.android-compose")
// TODO Move to common config
id("com.google.devtools.ksp") version "1.7.20-1.0.7"
}
android {
@ -17,4 +19,6 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
// TODO Move to common config
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}

View file

@ -145,7 +145,7 @@ private fun LazyListState.isScrolled(): Boolean {
@Preview
@Composable
private fun PreviewableRoomListContent() {
fun PreviewableRoomListContent() {
ElementXTheme(darkTheme = false) {
RoomListContent(
roomSummaries = stubbedRoomSummaries(),
@ -162,7 +162,7 @@ private fun PreviewableRoomListContent() {
@Preview
@Composable
private fun PreviewableDarkRoomListContent() {
fun PreviewableDarkRoomListContent() {
ElementXTheme(darkTheme = true) {
RoomListContent(
roomSummaries = stubbedRoomSummaries(),

View file

@ -3,6 +3,7 @@
android_gradle_plugin = "7.3.1"
firebase_gradle_plugin = "3.0.2"
kotlin = "1.7.20"
ksp = "1.7.20-1.0.7"
# AndroidX
material = "1.6.1"
@ -92,3 +93,5 @@ wysiwyg = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
[bundles]
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

View file

@ -1,6 +1,7 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'io.element.android-compose'
}
android {

View file

@ -0,0 +1,14 @@
package io.element.android.x.core.compose
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class PairCombinedPreviewParameter<T1, T2>(
private val provider: Pair<PreviewParameterProvider<T1>, PreviewParameterProvider<T2>>
) : PreviewParameterProvider<Pair<T1, T2>> {
override val values: Sequence<Pair<T1, T2>>
get() = provider.first.values.flatMap { first ->
provider.second.values.map { second ->
first to second
}
}
}

View file

@ -1,5 +1,7 @@
plugins {
id("io.element.android-compose")
// TODO Move to common config
id("com.google.devtools.ksp") version "1.7.20-1.0.7"
}
android {
@ -9,5 +11,7 @@ android {
// Should not be there, but this is a POC
implementation("io.coil-kt:coil-compose:2.2.1")
implementation(libs.accompanist.systemui)
// TODO Move to common config
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}
}

View file

@ -1,8 +1,11 @@
package io.element.android.x.designsystem
import androidx.compose.ui.graphics.Color
import com.airbnb.android.showkase.annotation.ShowkaseColor
@ShowkaseColor(name = "LightGrey", group = "Material Design")
val LightGrey = Color(0x993C3C43)
@ShowkaseColor(name = "DarkGrey", group = "Material Design")
val DarkGrey = Color(0x99EBEBF5)
val AvatarGradientStart = Color(0xFF4CA1AF)

View file

@ -7,24 +7,31 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
import com.airbnb.android.showkase.annotation.ShowkaseTypography
@ShowkaseTypography(name = "Body Large", group = "Element")
val bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
@ShowkaseTypography(name = "Headline Small", group = "Element")
val headlineSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
headlineSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
bodyLarge = bodyLarge,
headlineSmall = headlineSmall,
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,

View file

@ -51,6 +51,6 @@ fun ProgressDialog(text: String? = null, onDismiss: () -> Unit = {}) {
@Composable
@Preview
private fun ProgressDialogPreview() {
fun ProgressDialogPreview() {
ProgressDialog("test dialog content")
}

View file

@ -71,7 +71,7 @@ fun ConfirmationDialog(
@Composable
@Preview
private fun ConfirmationDialogPreview() {
fun ConfirmationDialogPreview() {
ConfirmationDialog(
isDisplayed = remember { mutableStateOf(true) },
title = "Title",

View file

@ -1,5 +1,7 @@
plugins {
id("io.element.android-compose")
// TODO Move to common config
id("com.google.devtools.ksp") version "1.7.20-1.0.7"
}
android {
@ -15,4 +17,6 @@ dependencies {
implementation(libs.wysiwyg)
implementation(libs.androidx.constraintlayout)
implementation("com.google.android.material:material:1.7.0")
// TODO Move to common config
ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}

View file

@ -3,8 +3,18 @@ package io.element.android.x.textcomposer
import android.graphics.Color
import android.net.Uri
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
@ -20,6 +30,10 @@ fun TextComposer(
composerCanSendMessage: Boolean,
composerText: String?,
) {
if (LocalInspectionMode.current) {
FakeComposer(modifier)
return
}
val isInDarkMode = isSystemInDarkTheme()
AndroidView(
modifier = modifier,
@ -75,10 +89,28 @@ fun TextComposer(
)
}
@Composable
private fun FakeComposer(modifier: Modifier) {
// AndroidView is not Available in this mode, just render a Text
Box(
modifier = modifier
.fillMaxWidth()
.height(80.dp)
) {
Text(
modifier = Modifier
.align(Alignment.Center),
textAlign = TextAlign.Center,
text = "Composer Preview",
fontSize = 20.sp
)
}
}
private fun MessageComposerView.setup(isDarkMode: Boolean) {
val editTextColor = if(isDarkMode){
val editTextColor = if (isDarkMode) {
Color.WHITE
}else{
} else {
Color.BLACK
}
editText.setTextColor(editTextColor)
@ -87,3 +119,16 @@ private fun MessageComposerView.setup(isDarkMode: Boolean) {
emojiButton?.isVisible = true
sendButton.isVisible = true
}
@Preview
@Composable
fun TextComposerPreview() {
TextComposer(
onSendMessage = {},
fullscreen = false,
onFullscreenToggle = { },
onComposerTextChange = {},
composerCanSendMessage = true,
composerText = "Message",
)
}

View file

@ -5,6 +5,8 @@ import extension.proguardConfig
plugins {
id("com.android.library")
id("kotlin-android")
// alias(libs.plugins.ksp)
// id("com.google.devtools.ksp") // version "1.7.20-1.0.7"
}
android {
@ -28,4 +30,6 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("com.airbnb.android:showkase:1.0.0-beta14")
// ksp("com.airbnb.android:showkase-processor:1.0.0-beta14")
}