diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt index 402faf1d7cbe9f5957ff7c72058c2dbbe797150a..911a05eec12604178e961ae9ec1b5489ed6919d6 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt @@ -1,6 +1,5 @@ package org.briarproject.briar.desktop.contact -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -19,7 +18,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -30,36 +28,38 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.identity.AuthorId -import org.briarproject.briar.desktop.theme.DarkColors import org.briarproject.briar.desktop.theme.outline import org.briarproject.briar.desktop.theme.selectedCard import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.HorizontalDivider import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.PreviewUtils.preview import org.briarproject.briar.desktop.utils.TimeUtils.getFormattedTimestamp import java.time.Instant -@Preview -@Composable -fun PreviewContactCard() { - MaterialTheme(colors = DarkColors) { - Surface { - ContactCard( - ContactItem( - contactId = ContactId(0), - authorId = AuthorId(ByteArray(0)), - name = "Paul", - alias = "UI Master", - isConnected = true, - isEmpty = false, - unread = 3, - timestamp = Instant.now().epochSecond - ), - {}, false - ) - } - } +fun main() = preview( + "name" to "Paul", + "alias" to "UI Master", + "isConnected" to true, + "isEmpty" to false, + "unread" to 3, + "timestamp" to Instant.now().toEpochMilli(), + "selected" to false, +) { + ContactCard( + ContactItem( + contactId = ContactId(0), + authorId = AuthorId(getRandomId()), + name = getStringParameter("name"), + alias = getStringParameter("alias"), + isConnected = getBooleanParameter("isConnected"), + isEmpty = getBooleanParameter("isEmpty"), + unread = getIntParameter("unread"), + timestamp = getLongParameter("timestamp") + ), + {}, getBooleanParameter("selected") + ) } @Composable diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt index 47f6711eb5ce775da9a575d60b64f0b315c0b551..044eba7d4187c805e1a212668d2b8a61419839b8 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt @@ -19,10 +19,37 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.briarproject.bramble.api.sync.GroupId +import org.briarproject.bramble.api.sync.MessageId import org.briarproject.briar.desktop.theme.awayMsgBubble import org.briarproject.briar.desktop.theme.localMsgBubble import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.PreviewUtils.preview import org.briarproject.briar.desktop.utils.TimeUtils +import java.time.Instant + +fun main() = preview( + "text" to "Lorem ipsum dolor sit amet.", + "time" to Instant.now().toEpochMilli(), + "isIncoming" to false, + "isRead" to false, + "isSent" to false, + "isSeen" to false, +) { + TextBubble( + ConversationMessageItem( + text = getStringParameter("text"), + id = MessageId(getRandomId()), + groupId = GroupId(getRandomId()), + time = getLongParameter("time"), + autoDeleteTimer = 0, + isIncoming = getBooleanParameter("isIncoming"), + isRead = getBooleanParameter("isRead"), + isSent = getBooleanParameter("isSent"), + isSeen = getBooleanParameter("isSeen"), + ) + ) +} @Composable fun TextBubble(m: ConversationMessageItem) { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..d03fd2bc25692a112236c9fbbf7efd4ea3ca0723 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt @@ -0,0 +1,134 @@ +package org.briarproject.briar.desktop.utils + +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Done +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.singleWindowApplication +import org.briarproject.bramble.api.UniqueId +import org.briarproject.briar.desktop.theme.DarkColors +import org.briarproject.briar.desktop.theme.LightColors +import kotlin.random.Random + +object PreviewUtils { + + class PreviewScope { + private val random = Random(0) + + val parameters = mutableMapOf<String, State<Any>>() + + private inline fun <reified T> getDatatype(name: String): T { + val state = parameters[name] ?: throw IllegalArgumentException("No parameter found with name '$name'") + if (state.value !is T) throw IllegalArgumentException("Parameter '$name' is not of type ${T::class.simpleName}") + return state.value as T + } + + fun getStringParameter(name: String) = getDatatype<String>(name) + + fun getBooleanParameter(name: String) = getDatatype<Boolean>(name) + + fun getIntParameter(name: String) = getDatatype<Int>(name) + + fun getLongParameter(name: String) = getDatatype<Long>(name) + + @Composable + fun getRandomId() = + remember { random.nextBytes(UniqueId.LENGTH) } + } + + @Composable + private fun <T : Any> PreviewScope.addParameter( + name: String, + initial: T, + editField: @Composable (MutableState<T>) -> Unit + ) { + val value = remember { mutableStateOf(initial) } + + Row { + Text("$name: ") + editField(value) + } + + parameters[name] = value + } + + @Composable + private fun PreviewScope.addStringParameter(name: String, initial: String) = addParameter(name, initial) { value -> + BasicTextField(value.value, { value.value = it }) + } + + @Composable + private fun PreviewScope.addBooleanParameter(name: String, initial: Boolean) = + addParameter(name, initial) { value -> + Box(modifier = Modifier.size(15.dp).border(1.dp, Color.Black).clickable { value.value = !value.value }) { + if (value.value) Icon(Icons.Filled.Done, "") + } + } + + @Composable + private fun PreviewScope.addIntParameter(name: String, initial: Int) = addParameter(name, initial) { value -> + BasicTextField(value.value.toString(), { value.value = it.toInt() }) + } + + @Composable + private fun PreviewScope.addLongParameter(name: String, initial: Long) = addParameter(name, initial) { value -> + BasicTextField(value.value.toString(), { value.value = it.toLong() }) + } + + /** + * Open an interactive preview of the composable specified by [content]. + * All [parameters] passed to this function will be changeable on the fly. + * They can be retrieved as [State] using [PreviewScope.getStringParameter] or similar functions + * and used inside the composable [content]. + */ + fun preview( + vararg parameters: Pair<String, Any>, + content: @Composable PreviewScope.() -> Unit + ) { + val scope = PreviewScope() + + singleWindowApplication(title = "Interactive Preview") { + Column { + Column(Modifier.padding(10.dp)) { + scope.addBooleanParameter("darkTheme", true) + parameters.forEach { (name, initial) -> + when (initial) { + is String -> scope.addStringParameter(name, initial) + is Boolean -> scope.addBooleanParameter(name, initial) + is Int -> scope.addIntParameter(name, initial) + is Long -> scope.addLongParameter(name, initial) + else -> throw IllegalArgumentException("Type ${initial::class.simpleName} is not supported for previewing.") + } + } + } + + MaterialTheme(colors = if (scope.getBooleanParameter("darkTheme")) DarkColors else LightColors) { + Surface(Modifier.fillMaxSize(1f)) { + Column(Modifier.padding(10.dp)) { + content(scope) + } + } + } + } + } + } +}