Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:56:56 +01:00
parent 75dc487a7a
commit 39c29d175b
6317 changed files with 388324 additions and 2 deletions

View file

@ -0,0 +1,3 @@
## Core - UI - Compose - Common
This module contains common code for the compose UI.

View file

@ -0,0 +1,13 @@
plugins {
id(ThunderbirdPlugins.Library.androidCompose)
}
android {
namespace = "app.k9mail.core.ui.compose.common"
resourcePrefix = "core_ui_common_"
}
dependencies {
testImplementation(projects.core.ui.compose.testing)
testImplementation(projects.core.ui.compose.designsystem)
}

View file

@ -0,0 +1,16 @@
package app.k9mail.core.ui.compose.common.annotation
import androidx.compose.ui.tooling.preview.Preview
/**
* A marker annotation for device previews.
*
* It's used to provide previews for a set of different devices and form factors.
*/
@Preview(name = "Small phone", device = "spec:width=360dp,height=640dp,dpi=160")
@Preview(name = "Phone", device = "spec:width=411dp,height=891dp,dpi=420")
@Preview(name = "Phone landscape", device = "spec:width=891dp,height=411dp,dpi=420")
@Preview(name = "Foldable", device = "spec:width=673dp,height=841dp,dpi=420")
@Preview(name = "Tablet", device = "spec:width=1280dp,height=800dp,dpi=240")
@Preview(name = "Desktop", device = "spec:width=1920dp,height=1080dp,dpi=160")
annotation class PreviewDevices

View file

@ -0,0 +1,16 @@
package app.k9mail.core.ui.compose.common.annotation
import androidx.compose.ui.tooling.preview.Preview
/**
* A marker annotation for device previews with background.
*
* It's used to provide previews for a set of different devices and form factors.
*/
@Preview(name = "Small phone", device = "spec:width=360dp,height=640dp,dpi=160", showBackground = true)
@Preview(name = "Phone", device = "spec:width=411dp,height=891dp,dpi=420", showBackground = true)
@Preview(name = "Phone landscape", device = "spec:width=891dp,height=411dp,dpi=420", showBackground = true)
@Preview(name = "Foldable", device = "spec:width=673dp,height=841dp,dpi=420", showBackground = true)
@Preview(name = "Tablet", device = "spec:width=1280dp,height=800dp,dpi=240", showBackground = true)
@Preview(name = "Desktop", device = "spec:width=1920dp,height=1080dp,dpi=160", showBackground = true)
annotation class PreviewDevicesWithBackground

View file

@ -0,0 +1,31 @@
package app.k9mail.core.ui.compose.common.baseline
import androidx.compose.foundation.layout.RowScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.Dp
/**
* Adds a baseline to a Composable that typically doesn't have one.
*
* This can be used to align e.g. an icon to the baseline of some text next to it. See e.g. [RowScope.alignByBaseline].
*
* @param baseline The number of device-independent pixels (dp) the baseline is from the top of the Composable.
*/
fun Modifier.withBaseline(baseline: Dp) = layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val baselineInPx = baseline.roundToPx()
layout(
width = placeable.width,
height = placeable.height,
alignmentLines = mapOf(
FirstBaseline to baselineInPx,
LastBaseline to baselineInPx,
),
) {
placeable.placeRelative(x = 0, y = 0)
}
}

View file

@ -0,0 +1,11 @@
package app.k9mail.core.ui.compose.common.image
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
@Immutable
data class ImageWithBaseline(
val image: ImageVector,
val baseline: Dp,
)

View file

@ -0,0 +1,18 @@
package app.k9mail.core.ui.compose.common.image
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
/**
* An image with a coordinate to draw a (smaller) overlay icon on top of it.
*
* Example: An icon representing an Android permission with an overlay icon to indicate whether the permission has been
* granted.
*/
@Immutable
data class ImageWithOverlayCoordinate(
val image: ImageVector,
val overlayOffsetX: Dp,
val overlayOffsetY: Dp,
)

View file

@ -0,0 +1,27 @@
package app.k9mail.core.ui.compose.common.koin
import androidx.compose.runtime.Composable
import org.koin.compose.KoinContext
import org.koin.core.Koin
import org.koin.dsl.ModuleDeclaration
import org.koin.dsl.module
/**
* Helper to make Compose previews work when some dependencies are injected via Koin.
*/
fun koinPreview(moduleDeclaration: ModuleDeclaration): KoinPreview {
val koin = Koin().apply {
loadModules(listOf(module(moduleDeclaration = moduleDeclaration)))
}
return KoinPreview(koin)
}
class KoinPreview internal constructor(private val koin: Koin) {
@Composable
infix fun WithContent(content: @Composable () -> Unit) {
KoinContext(context = koin) {
content()
}
}
}

View file

@ -0,0 +1,75 @@
package app.k9mail.core.ui.compose.common.mvi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
* An abstract base ViewModel that implements [UnidirectionalViewModel] and provides basic
* functionality for managing state and effects.
*
* @param STATE The type that represents the state of the ViewModel. For example, the UI state of a screen can be
* represented as a state.
* @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel. For
* example, a button click can be represented as an event.
* @param EFFECT The type that represents side-effects that can occur in response to the state changes. For example,
* a navigation event can be represented as an effect.
*
* @param initialState The initial [STATE] of the ViewModel.
*/
abstract class BaseViewModel<STATE, EVENT, EFFECT>(
initialState: STATE,
) : ViewModel(),
UnidirectionalViewModel<STATE, EVENT, EFFECT> {
private val _state = MutableStateFlow(initialState)
override val state: StateFlow<STATE> = _state.asStateFlow()
private val _effect = MutableSharedFlow<EFFECT>()
override val effect: SharedFlow<EFFECT> = _effect.asSharedFlow()
private val handledOneTimeEvents = mutableSetOf<EVENT>()
/**
* Updates the [STATE] of the ViewModel.
*
* @param update A function that takes the current [STATE] and produces a new [STATE].
*/
protected fun updateState(update: (STATE) -> STATE) {
_state.update(update)
}
/**
* Emits a side effect.
*
* @param effect The [EFFECT] to emit.
*/
protected fun emitEffect(effect: EFFECT) {
viewModelScope.launch {
_effect.emit(effect)
}
}
/**
* Ensures that one-time events are only handled once.
*
* When you can't ensure that an event is only sent once, but you want the event to only be handled once, call this
* method. It will ensure [block] is only executed the first time this function is called. Subsequent calls with an
* [event] argument equal to that of a previous invocation will not execute [block].
*
* Multiple one-time events are supported.
*/
protected fun handleOneTimeEvent(event: EVENT, block: () -> Unit) {
if (event !in handledOneTimeEvents) {
handledOneTimeEvents.add(event)
block()
}
}
}

View file

@ -0,0 +1,147 @@
package app.k9mail.core.ui.compose.common.mvi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
/**
* Interface for a unidirectional view model with side-effects ([EFFECT]). It has a [STATE] and can handle [EVENT]'s.
*
* @param STATE The type that represents the state of the ViewModel. For example, the UI state of a screen can be
* represented as a state.
* @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel. For
* example, a button click can be represented as an event.
* @param EFFECT The type that represents side-effects that can occur in response to the state changes. For example,
* a navigation event can be represented as an effect.
*/
interface UnidirectionalViewModel<STATE, EVENT, EFFECT> {
/**
* The current [STATE] of the view model.
*/
val state: StateFlow<STATE>
/**
* The side-effects ([EFFECT]) produced by the view model.
*/
val effect: SharedFlow<EFFECT>
/**
* Handles an [EVENT] and updates the [STATE] of the view model.
*
* @param event The [EVENT] to handle.
*/
fun event(event: EVENT)
}
/**
* Data class representing a state and a dispatch function, used for destructuring in [observe].
*/
data class StateDispatch<STATE, EVENT>(
val state: State<STATE>,
val dispatch: (EVENT) -> Unit,
)
/**
* Composable function that observes a UnidirectionalViewModel and handles its side effects.
*
* Example usage:
* ```
* @Composable
* fun MyScreen(
* onNavigateNext: () -> Unit,
* onNavigateBack: () -> Unit,
* viewModel: MyUnidirectionalViewModel<MyState, MyEvent, MyEffect>,
* ) {
* val (state, dispatch) = viewModel.observe { effect ->
* when (effect) {
* MyEffect.OnBackPressed -> onNavigateBack()
* MyEffect.OnNextPressed -> onNavigateNext()
* }
* }
*
* MyContent(
* onNextClick = {
* dispatch(MyEvent.OnNext)
* },
* onBackClick = {
* dispatch(MyEvent.OnBack)
* },
* state = state.value,
* )
* }
* ```
*
* @param STATE The type that represents the state of the ViewModel.
* @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel.
* @param EFFECT The type that represents side-effects that can occur in response to the state changes.
*
* @param handleEffect A function to handle side effects ([EFFECT]).
*
* @return A [StateDispatch] containing the state and a dispatch function.
*/
@Composable
inline fun <reified STATE, EVENT, EFFECT> UnidirectionalViewModel<STATE, EVENT, EFFECT>.observe(
crossinline handleEffect: (EFFECT) -> Unit,
): StateDispatch<STATE, EVENT> {
val collectedState = state.collectAsStateWithLifecycle()
val dispatch: (EVENT) -> Unit = { event(it) }
LaunchedEffect(key1 = effect) {
effect.collect {
handleEffect(it)
}
}
return StateDispatch(
state = collectedState,
dispatch = dispatch,
)
}
/**
* Composable function that observes a UnidirectionalViewModel without handling side effects.
*
* Example usage:
* ```
* @Composable
* fun MyScreen(
* viewModel: MyUnidirectionalViewModel<MyState, MyEvent, MyEffect>,
* onNavigateNext: () -> Unit,
* onNavigateBack: () -> Unit,
* ) {
* val (state, dispatch) = viewModel.observeWithoutEffect()
*
* MyContent(
* onNextClick = {
* dispatch(MyEvent.OnNext)
* },
* onBackClick = {
* dispatch(MyEvent.OnBack)
* },
* state = state.value,
* )
* }
* ```
*
* @param STATE The type that represents the state of the ViewModel.
* @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel.
*
* @return A [StateDispatch] containing the state and a dispatch function.
*/
@Suppress("MaxLineLength")
@Composable
inline fun <reified STATE, EVENT, EFFECT> UnidirectionalViewModel<STATE, EVENT, EFFECT>.observeWithoutEffect(
// no effect handler
): StateDispatch<STATE, EVENT> {
val collectedState = state.collectAsStateWithLifecycle()
val dispatch: (EVENT) -> Unit = { event(it) }
return StateDispatch(
state = collectedState,
dispatch = dispatch,
)
}

View file

@ -0,0 +1,20 @@
package app.k9mail.core.ui.compose.common.padding
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.common.window.WindowSizeClass
import app.k9mail.core.ui.compose.common.window.getWindowSizeInfo
@Composable
fun calculateResponsiveWidthPadding(): PaddingValues {
val windowSizeInfo = getWindowSizeInfo()
val horizontalPadding = when (windowSizeInfo.screenWidthSizeClass) {
WindowSizeClass.Compact -> 0.dp
WindowSizeClass.Medium -> (windowSizeInfo.screenWidth - WindowSizeClass.COMPACT_MAX_WIDTH.dp) / 2
WindowSizeClass.Expanded -> (windowSizeInfo.screenWidth - WindowSizeClass.MEDIUM_MAX_WIDTH.dp) / 2
}
return PaddingValues(horizontal = horizontalPadding)
}

View file

@ -0,0 +1,38 @@
package app.k9mail.core.ui.compose.common.resources
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
private const val PLACE_HOLDER = "{placeHolder}"
/**
* Loads a string resource with a single string parameter that will be replaced with the given [AnnotatedString].
*/
@Composable
@ReadOnlyComposable
fun annotatedStringResource(@StringRes id: Int, argument: AnnotatedString): AnnotatedString {
val stringWithPlaceHolder = stringResource(id, PLACE_HOLDER)
return buildAnnotatedString {
// In Android Studio previews loading string resources with formatting is not working
if (LocalInspectionMode.current) {
append(stringWithPlaceHolder)
return@buildAnnotatedString
}
val placeHolderStartIndex = stringWithPlaceHolder.indexOf(PLACE_HOLDER)
require(placeHolderStartIndex != -1)
append(text = stringWithPlaceHolder, start = 0, end = placeHolderStartIndex)
append(argument)
append(
text = stringWithPlaceHolder,
start = placeHolderStartIndex + PLACE_HOLDER.length,
end = stringWithPlaceHolder.length,
)
}
}

View file

@ -0,0 +1,18 @@
package app.k9mail.core.ui.compose.common.text
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
/**
* Converts a [String] into an [AnnotatedString] with all of the text being bold.
*/
fun String.bold(): AnnotatedString {
return buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(this@bold)
}
}
}

View file

@ -0,0 +1,16 @@
package app.k9mail.core.ui.compose.common.visibility
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.semantics.clearAndSetSemantics
/**
* Sets a composable to be fully transparent when it should be hidden (but still present for layout purposes).
*/
fun Modifier.hide(hide: Boolean): Modifier {
return if (hide) {
alpha(0f).clearAndSetSemantics {}
} else {
alpha(1f)
}
}

View file

@ -0,0 +1,38 @@
package app.k9mail.core.ui.compose.common.window
/**
* WindowSizeClass as defined by supporting different screen sizes.
*
* See: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes
*/
enum class WindowSizeClass {
Compact,
Medium,
Expanded,
;
companion object {
const val COMPACT_MAX_WIDTH = 600
const val COMPACT_MAX_HEIGHT = 480
const val MEDIUM_MAX_WIDTH = 840
const val MEDIUM_MAX_HEIGHT = 900
fun fromWidth(width: Int): WindowSizeClass {
return when {
width < COMPACT_MAX_WIDTH -> Compact
width < MEDIUM_MAX_WIDTH -> Medium
else -> Expanded
}
}
fun fromHeight(height: Int): WindowSizeClass {
return when {
height < COMPACT_MAX_HEIGHT -> Compact
height < MEDIUM_MAX_HEIGHT -> Medium
else -> Expanded
}
}
}
}

View file

@ -0,0 +1,30 @@
package app.k9mail.core.ui.compose.common.window
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Returns the current window size info based on current Configuration.
*/
@Composable
fun getWindowSizeInfo(): WindowSizeInfo {
val configuration = LocalConfiguration.current
return WindowSizeInfo(
screenWidthSizeClass = WindowSizeClass.fromWidth(configuration.screenWidthDp),
screenHeightSizeClass = WindowSizeClass.fromHeight(configuration.screenHeightDp),
screenWidth = configuration.screenWidthDp.dp,
screenHeight = configuration.screenHeightDp.dp,
)
}
@Immutable
data class WindowSizeInfo(
val screenWidthSizeClass: WindowSizeClass,
val screenHeightSizeClass: WindowSizeClass,
val screenWidth: Dp,
val screenHeight: Dp,
)

View file

@ -0,0 +1,28 @@
package net.thunderbird.core.ui.compose.common.date
import androidx.compose.runtime.staticCompositionLocalOf
import kotlinx.datetime.format.DayOfWeekNames
import kotlinx.datetime.format.MonthNames
/**
* Configuration for date and time formatting.
*
* @property monthNames The names of the months.
* @property dayOfWeekNames The names of the days of the week.
*/
data class DateTimeConfiguration(
val monthNames: MonthNames,
val dayOfWeekNames: DayOfWeekNames,
)
/**
* CompositionLocal that provides the default [DateTimeConfiguration] for date and time formatting.
* This configuration uses abbreviated English month and day of the week names by default.
* It can be overridden at a lower level in the composition tree to customize date and time formatting.
*/
val LocalDateTimeConfiguration = staticCompositionLocalOf {
DateTimeConfiguration(
monthNames = MonthNames.ENGLISH_ABBREVIATED,
dayOfWeekNames = DayOfWeekNames.ENGLISH_ABBREVIATED,
)
}

View file

@ -0,0 +1,17 @@
package net.thunderbird.core.ui.compose.common.modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
/**
* Adds a test tag to the element with testTagsAsResourceId set to true.
* This allows the element to be found by its test tag during UI testing.
*
* @param tag The test tag to be assigned to the element.
* @return A [Modifier] with the test tag applied.
*/
fun Modifier.testTagAsResourceId(tag: String): Modifier = this
.semantics { testTagsAsResourceId = true }
.testTag(tag)

View file

@ -0,0 +1,33 @@
package app.k9mail.core.ui.compose.common.koin
import androidx.compose.runtime.Composable
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
import app.k9mail.core.ui.compose.testing.ComposeTest
import app.k9mail.core.ui.compose.testing.onNodeWithText
import app.k9mail.core.ui.compose.testing.setContentWithTheme
import kotlin.test.Test
import org.koin.compose.koinInject
class KoinPreviewTest : ComposeTest() {
@Test
fun `koinPreview should make dependencies available in WithContent block`() = runComposeTest {
val injectString = "Test"
setContentWithTheme {
koinPreview {
factory { injectString }
} WithContent {
TestComposable()
}
}
onNodeWithText(injectString).assertExists()
}
}
@Composable
private fun TestComposable(
injected: String = koinInject(),
) {
TextBodyLarge(text = injected)
}

View file

@ -0,0 +1,109 @@
package app.k9mail.core.ui.compose.common.mvi
import app.cash.turbine.test
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isTrue
import kotlinx.coroutines.test.runTest
import net.thunderbird.core.testing.coroutines.MainDispatcherRule
import org.junit.Rule
import org.junit.Test
class BaseViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun `should emit initial state`() = runTest {
val viewModel = TestBaseViewModel()
assertThat(viewModel.state.value).isEqualTo("Initial state")
}
@Test
fun `should update state`() = runTest {
val viewModel = TestBaseViewModel()
viewModel.event("Test event")
assertThat(viewModel.state.value).isEqualTo("Test event")
viewModel.event("Another test event")
assertThat(viewModel.state.value).isEqualTo("Another test event")
}
@Test
fun `should emit effects`() = runTest {
val viewModel = TestBaseViewModel()
viewModel.effect.test {
viewModel.event("Test effect")
assertThat(awaitItem()).isEqualTo("Test effect")
viewModel.event("Another test effect")
assertThat(awaitItem()).isEqualTo("Another test effect")
}
}
@Test
fun `handleOneTimeEvent() should execute block`() = runTest {
val viewModel = TestBaseViewModel()
var eventHandled = false
viewModel.callHandleOneTimeEvent(event = "event") {
eventHandled = true
}
assertThat(eventHandled).isTrue()
}
@Test
fun `handleOneTimeEvent() should execute block only once`() = runTest {
val viewModel = TestBaseViewModel()
var eventHandledCount = 0
repeat(2) {
viewModel.callHandleOneTimeEvent(event = "event") {
eventHandledCount++
}
}
assertThat(eventHandledCount).isEqualTo(1)
}
@Test
fun `handleOneTimeEvent() should support multiple one-time events`() = runTest {
val viewModel = TestBaseViewModel()
var eventOneHandled = false
var eventTwoHandled = false
viewModel.callHandleOneTimeEvent(event = "eventOne") {
eventOneHandled = true
}
assertThat(eventOneHandled).isTrue()
assertThat(eventTwoHandled).isFalse()
viewModel.callHandleOneTimeEvent(event = "eventTwo") {
eventTwoHandled = true
}
assertThat(eventOneHandled).isTrue()
assertThat(eventTwoHandled).isTrue()
}
private class TestBaseViewModel : BaseViewModel<String, String, String>("Initial state") {
override fun event(event: String) {
updateState { event }
emitEffect(event)
}
fun callHandleOneTimeEvent(event: String, block: () -> Unit) {
handleOneTimeEvent(event, block)
}
}
}

View file

@ -0,0 +1,78 @@
package app.k9mail.core.ui.compose.common.mvi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.k9mail.core.ui.compose.testing.ComposeTest
import app.k9mail.core.ui.compose.testing.setContent
import assertk.assertThat
import assertk.assertions.isEqualTo
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import net.thunderbird.core.testing.coroutines.MainDispatcherRule
import org.junit.Rule
import org.junit.Test
class UnidirectionalViewModelKtTest : ComposeTest() {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun `observe should emit state changes, allow event dispatch and expose effects`() = runTest {
val viewModel = TestViewModel()
val effects = mutableListOf<TestEffect>()
lateinit var stateDispatch: StateDispatch<TestState, TestEvent>
setContent {
stateDispatch = viewModel.observe { effect ->
effects.add(effect)
}
}
val (state, dispatch) = stateDispatch
// Initial state
assertThat(state.value.data).isEqualTo("TestState: Initial")
// Dispatch an event
dispatch(TestEvent("Event 1"))
assertThat(state.value.data).isEqualTo("TestState: Event 1")
assertThat(effects.last().result).isEqualTo("TestEffect: Event 1")
// Dispatch another event
dispatch(TestEvent("Event 2"))
assertThat(state.value.data).isEqualTo("TestState: Event 2")
assertThat(effects.last().result).isEqualTo("TestEffect: Event 2")
}
private data class TestState(val data: String)
private data class TestEvent(val action: String)
private data class TestEffect(val result: String)
private class TestViewModel(
initialState: TestState = TestState("TestState: Initial"),
) : ViewModel(), UnidirectionalViewModel<TestState, TestEvent, TestEffect> {
private val _state = MutableStateFlow(initialState)
override val state: StateFlow<TestState> = _state.asStateFlow()
private val _effect = MutableSharedFlow<TestEffect>()
override val effect: SharedFlow<TestEffect> = _effect.asSharedFlow()
override fun event(event: TestEvent) {
_state.update { it.copy(data = "TestState: ${event.action}") }
viewModelScope.launch {
_effect.emit(TestEffect(result = "TestEffect: ${event.action}"))
}
}
}
}

View file

@ -0,0 +1,28 @@
package app.k9mail.core.ui.compose.common.resources
import androidx.compose.ui.text.buildAnnotatedString
import app.k9mail.core.ui.compose.common.test.R
import app.k9mail.core.ui.compose.common.text.bold
import app.k9mail.core.ui.compose.testing.ComposeTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import org.junit.Test
class StringResourcesTest : ComposeTest() {
@Test
fun `annotatedStringResource() with bold text`() = runComposeTest {
val argument = "text".bold()
setContent {
val result = annotatedStringResource(id = R.string.StringResourcesTest, argument)
assertThat(result).isEqualTo(
buildAnnotatedString {
append("prefix ")
append(argument)
append(" suffix")
},
)
}
}
}

View file

@ -0,0 +1,27 @@
package app.k9mail.core.ui.compose.common.text
import androidx.compose.ui.text.AnnotatedString.Range
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.isEqualTo
import kotlin.test.Test
class AnnotatedStringsTest {
@Test
fun bold() {
val input = "text"
val result = input.bold()
assertThat(result.toString()).isEqualTo(input)
assertThat(result.spanStyles).containsExactly(
Range(
item = SpanStyle(fontWeight = FontWeight.Bold),
start = 0,
end = input.length,
),
)
}
}

View file

@ -0,0 +1,80 @@
package app.k9mail.core.ui.compose.common.window
import assertk.assertThat
import assertk.assertions.isEqualTo
import org.junit.Test
class WindowSizeClassTest {
@Test
fun `should return compact when width is less than 600`() {
val width = 599
val windowSizeClass = WindowSizeClass.fromWidth(width)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Compact)
}
@Test
fun `should return medium when width is 600`() {
val width = 600
val windowSizeClass = WindowSizeClass.fromWidth(width)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
}
@Test
fun `should return medium when width is less than 840`() {
val width = 839
val windowSizeClass = WindowSizeClass.fromWidth(width)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
}
@Test
fun `should return expanded when width is 840`() {
val width = 840
val windowSizeClass = WindowSizeClass.fromWidth(width)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Expanded)
}
@Test
fun `should return compact when height is less than 480`() {
val height = 479
val windowSizeClass = WindowSizeClass.fromHeight(height)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Compact)
}
@Test
fun `should return medium when height is 480`() {
val height = 480
val windowSizeClass = WindowSizeClass.fromHeight(height)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
}
@Test
fun `should return medium when height is less than 900`() {
val height = 899
val windowSizeClass = WindowSizeClass.fromHeight(height)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
}
@Test
fun `should return expanded when height is 900`() {
val height = 900
val windowSizeClass = WindowSizeClass.fromHeight(height)
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Expanded)
}
}

View file

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="StringResourcesTest">prefix %s suffix</string>
</resources>

View file

@ -0,0 +1,39 @@
## Core - UI - Compose - Design system
Uses [`:core:ui:compose:theme`](../theme/README.md)
## Background
[Jetpack Compose](https://developer.android.com/jetpack/compose) is a declarative UI toolkit for Android that provides a modern and efficient way to build UIs for Android apps. In this context, design systems and atomic design can help designers and developers create more scalable, maintainable, and reusable UIs.
### Design system
A design system is a collection of guidelines, principles, and tools that help teams create consistent and cohesive visual designs and user experiences.
It typically includes a set of reusable components, such as icons, typography, color palettes, and layouts, that can be combined and customized to create new designs.
The design system also provides documentation and resources for designers and developers to ensure that the designs are implemented consistently and efficiently across all platforms and devices.
The goal of a design system is to streamline the design process, improve design quality, and maintain brand consistency.
An example is Google's [Material Design](https://m3.material.io/) that is used to develop cohesive apps.
### Atomic Design
![Atomic design](assets/images/atomic_design.svg)
Atomic design is a methodology for creating user interfaces (UI) in a design system by breaking them down into smaller, reusable components.
These components are classified into five categories based on their level of abstraction: **atoms**, **molecules**, **organisms**, **templates**, and **pages**.
- **Atoms** are the smallest building blocks, such as buttons, labels, and input fields and could be combined to create more complex components.
- **Molecules** are groups of atoms that work together, like search bars, forms or menus
- **Organisms** are more complex components that combine molecules and atoms, such as headers or cards.
- **Templates** are pages with placeholders for components
- **Pages** are the final UI
By using atomic design, designers and developers can create more consistent and reusable UIs.
This can save time and improve the overall quality, as well as facilitate collaboration between team members.
## Acknowledgement
- [Atomic Design Methodology | Atomic Design by Brad Frost](https://atomicdesign.bradfrost.com/chapter-2/)
- [Atomic Design: Getting Started | Blog | We Are Mobile First](https://www.wearemobilefirst.com/blog/atomic-design)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,33 @@
plugins {
id(ThunderbirdPlugins.Library.androidCompose)
alias(libs.plugins.kotlin.parcelize)
}
android {
namespace = "app.k9mail.core.ui.compose.designsystem"
resourcePrefix = "designsystem_"
}
dependencies {
api(projects.core.ui.compose.theme2.common)
debugApi(projects.core.ui.compose.theme2.k9mail)
debugApi(projects.core.ui.compose.theme2.thunderbird)
implementation(libs.androidx.autofill)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material.icons.extended)
// Landscapist imports a lot of dependencies that we don't need. We exclude them here.
implementation(libs.lanscapist.coil) {
exclude(group = "io.coil-kt", module = "coil-gif")
exclude(group = "io.coil-kt", module = "coil-video")
exclude(group = "io.coil-kt.coil3", module = "coil-network-ktor3")
exclude(group = "io.ktor")
}
testImplementation(projects.core.ui.compose.testing)
}

View file

@ -0,0 +1,13 @@
package app.k9mail.core.ui.compose.designsystem
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.content.res.Configuration.UI_MODE_TYPE_NORMAL
import androidx.compose.ui.tooling.preview.Preview
@Preview(name = "Light", device = "spec:width=673dp,height=841dp,orientation=landscape")
@Preview(
name = "Dark",
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
device = "spec:width=673dp,height=841dp,orientation=landscape",
)
annotation class PreviewLightDarkLandscape

View file

@ -0,0 +1,108 @@
package app.k9mail.core.ui.compose.designsystem
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.theme2.MainTheme
/**
* A Composable function that displays a preview of the content in both Thunderbird and K-9 Mail themes.
*
* It uses the current system theme (light or dark) for both previews.
*
* @param modifier The modifier to be applied to the layout.
* @param useRow Whether to display the previews in a row or column. Defaults to `false` (column).
* @param useScrim Whether to display a scrim behind the content. Defaults to `false`.
* @param scrimAlpha The alpha value for the scrim. Defaults to `0.8f`.
* @param scrimPadding The padding for the scrim. Defaults to `MainTheme.spacings.triple`.
* @param arrangement The arrangement for the previews. Defaults to `Arrangement.spacedBy(MainTheme.spacings.triple)`.
* @param content The content to be displayed in the previews.
*
* @see app.k9mail.core.ui.compose.theme2.default.defaultThemeSpacings for MainTheme.spacings
*/
@Composable
fun PreviewWithThemesLightDark(
modifier: Modifier = Modifier,
useRow: Boolean = false,
useScrim: Boolean = false,
scrimAlpha: Float = 0.8f,
scrimPadding: PaddingValues = PaddingValues(24.dp),
arrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(24.dp),
content: @Composable () -> Unit,
) {
val movableContent = remember {
movableContentOf {
PreviewWithThemeLightDark(
themeType = PreviewThemeType.THUNDERBIRD,
useScrim = useScrim,
scrimAlpha = scrimAlpha,
scrimPadding = scrimPadding,
content = content,
)
PreviewWithThemeLightDark(
themeType = PreviewThemeType.K9MAIL,
useScrim = useScrim,
scrimAlpha = scrimAlpha,
scrimPadding = scrimPadding,
content = content,
)
}
}
if (useRow) {
Row(
horizontalArrangement = arrangement,
modifier = modifier,
) {
movableContent()
}
} else {
Column(
verticalArrangement = arrangement,
modifier = modifier,
) {
movableContent()
}
}
}
@Suppress("ModifierMissing")
@Composable
fun PreviewWithThemeLightDark(
themeType: PreviewThemeType = PreviewThemeType.THUNDERBIRD,
useScrim: Boolean = false,
scrimAlpha: Float = 0f,
scrimPadding: PaddingValues = PaddingValues(0.dp),
content: @Composable (() -> Unit),
) {
val movableContent = remember { movableContentOf { content() } }
PreviewWithTheme(
themeType = themeType,
isDarkTheme = isSystemInDarkTheme(),
) {
PreviewSurface {
if (useScrim) {
Box(
modifier = Modifier
.background(MainTheme.colors.scrim.copy(alpha = scrimAlpha))
.padding(scrimPadding),
) {
movableContent()
}
} else {
movableContent()
}
PreviewHeader(themeName = themeType.name)
}
}
}

View file

@ -0,0 +1,127 @@
package app.k9mail.core.ui.compose.designsystem
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
import app.k9mail.core.ui.compose.theme2.MainTheme
import app.k9mail.core.ui.compose.theme2.k9mail.K9MailTheme2
import app.k9mail.core.ui.compose.theme2.thunderbird.ThunderbirdTheme2
@Composable
fun PreviewWithThemes(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Column(
modifier = modifier,
) {
K9MailTheme2 {
PreviewSurface {
Column {
PreviewHeader(themeName = "K9Theme Light")
content()
}
}
}
K9MailTheme2(darkTheme = true) {
PreviewSurface {
Column {
PreviewHeader(themeName = "K9Theme Dark")
content()
}
}
}
ThunderbirdTheme2 {
PreviewSurface {
Column {
PreviewHeader(themeName = "ThunderbirdTheme Light")
content()
}
}
}
ThunderbirdTheme2(darkTheme = true) {
PreviewSurface {
Column {
PreviewHeader(themeName = "ThunderbirdTheme Dark")
content()
}
}
}
}
}
enum class PreviewThemeType {
K9MAIL,
THUNDERBIRD,
}
@Composable
fun PreviewWithTheme(
themeType: PreviewThemeType = PreviewThemeType.THUNDERBIRD,
isDarkTheme: Boolean = false,
content: @Composable () -> Unit,
) {
when (themeType) {
PreviewThemeType.K9MAIL -> {
PreviewWithK9MailTheme(isDarkTheme, content)
}
PreviewThemeType.THUNDERBIRD -> {
PreviewWithThunderbirdTheme(isDarkTheme, content)
}
}
}
@Composable
private fun PreviewWithK9MailTheme(
isDarkTheme: Boolean,
content: @Composable () -> Unit,
) {
K9MailTheme2(
darkTheme = isDarkTheme,
content = content,
)
}
@Composable
private fun PreviewWithThunderbirdTheme(
isDarkTheme: Boolean,
content: @Composable () -> Unit,
) {
ThunderbirdTheme2(
darkTheme = isDarkTheme,
content = content,
)
}
@Composable
internal fun PreviewHeader(
themeName: String,
) {
Surface(
color = MainTheme.colors.primary,
) {
Text(
text = themeName,
fontSize = 4.sp,
modifier = Modifier.padding(
start = MainTheme.spacings.half,
end = MainTheme.spacings.half,
),
)
}
}
@Composable
internal fun PreviewSurface(
content: @Composable () -> Unit,
) {
Surface(
color = MainTheme.colors.surface,
content = content,
)
}

View file

@ -0,0 +1,28 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun CheckboxPreview() {
PreviewWithThemes {
Checkbox(
checked = true,
onCheckedChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun CheckboxDisabledPreview() {
PreviewWithThemes {
Checkbox(
checked = true,
onCheckedChange = {},
enabled = false,
)
}
}

View file

@ -0,0 +1,27 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun CircularProgressIndicatorPreview() {
PreviewWithThemes {
CircularProgressIndicator(
progress = { 0.75f },
)
}
}
@Composable
@Preview(showBackground = true)
internal fun CircularProgressIndicatorColoredPreview() {
PreviewWithThemes {
CircularProgressIndicator(
progress = { 0.75f },
color = MainTheme.colors.secondary,
)
}
}

View file

@ -0,0 +1,26 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun DividerHorizontalPreview() {
PreviewWithThemes {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(MainTheme.spacings.double),
) {
DividerHorizontal(
modifier = Modifier.fillMaxWidth(),
)
}
}
}

View file

@ -0,0 +1,26 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun DividerVerticalPreview() {
PreviewWithThemes {
Row(
modifier = Modifier
.fillMaxHeight()
.padding(MainTheme.spacings.double),
) {
DividerVertical(
modifier = Modifier.fillMaxHeight(),
)
}
}
}

View file

@ -0,0 +1,48 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
import kotlinx.collections.immutable.persistentListOf
val choice = persistentListOf(
Pair("1", "Native Android"),
Pair("2", "Native iOS"),
Pair("3", "KMM"),
Pair("4", "Flutter"),
)
@Composable
@Preview(showBackground = true)
internal fun RadioGroupSelectedPreview() {
PreviewWithThemes {
var selectedOption by remember { mutableStateOf(choice[0]) }
RadioGroup(
onClick = { selectedOption = it },
options = choice,
optionTitle = { it.second },
selectedOption = selectedOption,
modifier = Modifier.padding(MainTheme.spacings.default),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun RadioGroupUnSelectedPreview() {
PreviewWithThemes {
RadioGroup(
onClick = {},
options = choice,
optionTitle = { it.second },
modifier = Modifier.padding(MainTheme.spacings.default),
)
}
}

View file

@ -0,0 +1,37 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun SurfacePreview() {
PreviewWithThemes {
Surface(
modifier = Modifier
.requiredHeight(MainTheme.sizes.larger)
.requiredWidth(MainTheme.sizes.larger),
content = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SurfaceWithShapePreview() {
PreviewWithThemes {
Surface(
modifier = Modifier
.requiredHeight(MainTheme.sizes.larger)
.requiredWidth(MainTheme.sizes.larger),
shape = MainTheme.shapes.small,
color = MainTheme.colors.primary,
content = {},
)
}
}

View file

@ -0,0 +1,28 @@
package app.k9mail.core.ui.compose.designsystem.atom
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun SwitchPreview() {
PreviewWithThemes {
Switch(
checked = true,
onCheckedChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SwitchDisabledPreview() {
PreviewWithThemes {
Switch(
checked = true,
onCheckedChange = {},
enabled = false,
)
}
}

View file

@ -0,0 +1,39 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun ButtonElevatedPreview() {
PreviewWithThemes {
ButtonElevated(
text = "Button Elevated",
onClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonElevatedDisabledPreview() {
PreviewWithThemes {
ButtonElevated(
text = "Button Elevated Disabled",
onClick = {},
enabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonElevatedMultiLinePreview() {
PreviewWithThemes {
ButtonElevated(
text = "First\nSecond line",
onClick = {},
)
}
}

View file

@ -0,0 +1,39 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun ButtonFilledPreview() {
PreviewWithThemes {
ButtonFilled(
text = "Button Filled",
onClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonFilledDisabledPreview() {
PreviewWithThemes {
ButtonFilled(
text = "Button Filled Disabled",
onClick = {},
enabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonFilledMultiLinePreview() {
PreviewWithThemes {
ButtonFilled(
text = "First\nSecond line",
onClick = {},
)
}
}

View file

@ -0,0 +1,39 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun ButtonFilledTonalPreview() {
PreviewWithThemes {
ButtonFilledTonal(
text = "Button Filled Tonal",
onClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonFilledTonalDisabledPreview() {
PreviewWithThemes {
ButtonFilledTonal(
text = "Button Filled Tonal Disabled",
onClick = {},
enabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonFilledTonalMultiLinePreview() {
PreviewWithThemes {
ButtonFilledTonal(
text = "First\nSecond line",
onClick = {},
)
}
}

View file

@ -0,0 +1,28 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
@Composable
@Preview(showBackground = true)
internal fun ButtonIconPreview() {
PreviewWithThemes {
ButtonIcon(
onClick = { },
imageVector = Icons.Outlined.Info,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonIconFilledPreview() {
PreviewWithThemes {
ButtonIcon(
onClick = { },
imageVector = Icons.Filled.Cancel,
)
}
}

View file

@ -0,0 +1,39 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun ButtonOutlinedPreview() {
PreviewWithThemes {
ButtonOutlined(
text = "Button Outlined",
onClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonOutlinedDisabledPreview() {
PreviewWithThemes {
ButtonOutlined(
text = "Button Outlined Disabled",
onClick = {},
enabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonOutlinedMultiLinePreview() {
PreviewWithThemes {
ButtonOutlined(
text = "First\nSecond line",
onClick = {},
)
}
}

View file

@ -0,0 +1,55 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import kotlinx.collections.immutable.persistentListOf
private val options = persistentListOf<String>(
"Option 1",
"Option 2",
"Option 3",
)
@Composable
@Preview(showBackground = true)
internal fun ButtonSegmentedSingleChoicePreview() {
PreviewWithThemes {
ButtonSegmentedSingleChoice(
modifier = Modifier,
onClick = {},
options = options,
optionTitle = { it },
selectedOption = null,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonSegmentedSingleChoiceWithSelectionPreview() {
PreviewWithThemes {
ButtonSegmentedSingleChoice(
modifier = Modifier,
onClick = {},
options = options,
optionTitle = { it },
selectedOption = options[1],
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonSegmentedSingleChoiceEmptyPreview() {
PreviewWithThemes {
ButtonSegmentedSingleChoice(
modifier = Modifier,
onClick = {},
options = persistentListOf<String>(),
optionTitle = { it },
selectedOption = null,
)
}
}

View file

@ -0,0 +1,52 @@
package app.k9mail.core.ui.compose.designsystem.atom.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun ButtonTextPreview() {
PreviewWithThemes {
ButtonText(
text = "Button Text",
onClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonTextColoredPreview() {
PreviewWithThemes {
ButtonText(
text = "Button Text Colored",
onClick = {},
color = Color.Magenta,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonTextDisabledPreview() {
PreviewWithThemes {
ButtonText(
text = "Button Text Disabled",
onClick = {},
enabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ButtonTextMultiLinePreview() {
PreviewWithThemes {
ButtonText(
text = "First\nSecond line",
onClick = {},
)
}
}

View file

@ -0,0 +1,22 @@
package app.k9mail.core.ui.compose.designsystem.atom.card
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun CardElevatedPreview() {
PreviewWithThemes {
CardElevated {
Box(modifier = Modifier.padding(MainTheme.spacings.double)) {
TextBodyMedium("Text in card")
}
}
}
}

View file

@ -0,0 +1,22 @@
package app.k9mail.core.ui.compose.designsystem.atom.card
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun CardFilledPreview() {
PreviewWithThemes {
CardFilled {
Box(modifier = Modifier.padding(MainTheme.spacings.double)) {
TextBodyMedium("Text in card")
}
}
}
}

View file

@ -0,0 +1,25 @@
package app.k9mail.core.ui.compose.designsystem.atom.card
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun CardOutlinedPreview() {
PreviewWithThemesLightDark {
Surface(modifier = Modifier.padding(MainTheme.spacings.quadruple)) {
CardOutlined {
Box(modifier = Modifier.padding(MainTheme.spacings.double)) {
TextBodyMedium("Text in card")
}
}
}
}
}

View file

@ -0,0 +1,27 @@
package app.k9mail.core.ui.compose.designsystem.atom.icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Preview(showBackground = true)
@Composable
internal fun IconPreview() {
PreviewWithThemes {
Icon(
imageVector = Icons.Outlined.Info,
)
}
}
@Preview(showBackground = true)
@Composable
internal fun IconTintedPreview() {
PreviewWithThemes {
Icon(
imageVector = Icons.Outlined.Info,
tint = Color.Magenta,
)
}
}

View file

@ -0,0 +1,76 @@
package app.k9mail.core.ui.compose.designsystem.atom.image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun FixedScaleImageBottomCenterPreview() {
PreviewWithTheme {
Box(
modifier = Modifier
.width(MainTheme.sizes.huge)
.height(MainTheme.sizes.huge),
) {
FixedScaleImage(
id = MainTheme.images.logo,
alignment = Alignment.BottomCenter,
)
}
}
}
@Composable
@Preview(showBackground = true)
internal fun FixedScaleImageCroppedPreview() {
PreviewWithTheme {
Box(
modifier = Modifier
.width(MainTheme.sizes.medium)
.height(MainTheme.sizes.medium),
) {
FixedScaleImage(
id = MainTheme.images.logo,
)
}
}
}
@Composable
@Preview(showBackground = true)
internal fun FixedScaleImageHorizontallyCroppedPreview() {
PreviewWithTheme {
Box(
modifier = Modifier
.width(MainTheme.sizes.huge)
.height(MainTheme.sizes.medium),
) {
FixedScaleImage(
id = MainTheme.images.logo,
)
}
}
}
@Composable
@Preview(showBackground = true)
internal fun FixedScaleImageVerticallyCroppedPreview() {
PreviewWithTheme {
Box(
modifier = Modifier
.width(MainTheme.sizes.medium)
.height(MainTheme.sizes.huge),
) {
FixedScaleImage(
id = MainTheme.images.logo,
)
}
}
}

View file

@ -0,0 +1,23 @@
package app.k9mail.core.ui.compose.designsystem.atom.image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
fun RemoteImagePreview() {
PreviewWithTheme {
val painter = rememberVectorPainter(Icons.Outlined.AccountCircle)
RemoteImage(
url = "",
modifier = Modifier.size(MainTheme.sizes.large),
previewPlaceholder = painter,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextBodyLargePreview() {
PreviewWithThemes {
TextBodyLarge(
text = "Text Body Large",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodyLargeWithAnnotatedStringPreview() {
PreviewWithThemes {
TextBodyLarge(
text = buildAnnotatedString {
append("Text Body Large ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodyLargeWithColorPreview() {
PreviewWithThemes {
TextBodyLarge(
text = "Text Body Large with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodyLargeWithTextAlignPreview() {
PreviewWithThemes {
TextBodyLarge(
text = "Text Body Large with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextBodyMediumPreview() {
PreviewWithThemes {
TextBodyMedium(
text = "Text Body Medium",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodyMediumWithAnnotatedStringPreview() {
PreviewWithThemes {
TextBodyMedium(
text = buildAnnotatedString {
append("Text Body Medium ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodyMediumWithColorPreview() {
PreviewWithThemes {
TextBodyMedium(
text = "Text Body Medium with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodyMediumWithTextAlignPreview() {
PreviewWithThemes {
TextBodyMedium(
text = "Text Body Medium with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextBodySmallPreview() {
PreviewWithThemes {
TextBodySmall(
text = "Text Body Small",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodySmallWithAnnotatedStringPreview() {
PreviewWithThemes {
TextBodySmall(
text = buildAnnotatedString {
append("Text Body Small ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodySmallWithColorPreview() {
PreviewWithThemes {
TextBodySmall(
text = "Text Body Small with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextBodySmallWithTextAlignPreview() {
PreviewWithThemes {
TextBodySmall(
text = "Text Body Small with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextDisplayLargePreview() {
PreviewWithThemes {
TextDisplayLarge(
text = "Text Display Large",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplayLargeWithAnnotatedStringPreview() {
PreviewWithThemes {
TextDisplayLarge(
text = buildAnnotatedString {
append("Text Display Large ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplayLargeWithColorPreview() {
PreviewWithThemes {
TextDisplayLarge(
text = "Text Display Large with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplayLargeWithTextAlignPreview() {
PreviewWithThemes {
TextDisplayLarge(
text = "Text Display Large with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextDisplayMediumPreview() {
PreviewWithThemes {
TextDisplayMedium(
text = "Text Display Medium",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplayMediumWithAnnotatedStringPreview() {
PreviewWithThemes {
TextDisplayMedium(
text = buildAnnotatedString {
append("Text Display Medium ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplayMediumWithColorPreview() {
PreviewWithThemes {
TextDisplayMedium(
text = "Text Display Medium with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplayMediumWithTextAlignPreview() {
PreviewWithThemes {
TextDisplayMedium(
text = "Text Display Medium with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextDisplaySmallPreview() {
PreviewWithThemes {
TextDisplaySmall(
text = "Text Display Small",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplaySmallWithAnnotatedStringPreview() {
PreviewWithThemes {
TextDisplaySmall(
text = buildAnnotatedString {
append("Text Display Small ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplaySmallWithColorPreview() {
PreviewWithThemes {
TextDisplaySmall(
text = "Text Display Small with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextDisplaySmallWithTextAlignPreview() {
PreviewWithThemes {
TextDisplaySmall(
text = "Text Display Small with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineLargePreview() {
PreviewWithThemes {
TextHeadlineLarge(
text = "Text Headline Large",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineLargeWithAnnotatedStringPreview() {
PreviewWithThemes {
TextHeadlineLarge(
text = buildAnnotatedString {
append("Text Headline Large ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineLargeWithColorPreview() {
PreviewWithThemes {
TextHeadlineLarge(
text = "Text Headline Large with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineLargeWithTextAlignPreview() {
PreviewWithThemes {
TextHeadlineLarge(
text = "Text Headline Large with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineMediumPreview() {
PreviewWithThemes {
TextHeadlineMedium(
text = "Text Headline Medium",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineMediumWithAnnotatedStringPreview() {
PreviewWithThemes {
TextHeadlineMedium(
text = buildAnnotatedString {
append("Text Headline Medium ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineMediumWithColorPreview() {
PreviewWithThemes {
TextHeadlineMedium(
text = "Text Headline Medium with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineMediumWithTextAlignPreview() {
PreviewWithThemes {
TextHeadlineMedium(
text = "Text Headline Medium with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineSmallPreview() {
PreviewWithThemes {
TextHeadlineSmall(
text = "Text Headline Small",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineSmallWithAnnotatedStringPreview() {
PreviewWithThemes {
TextHeadlineSmall(
text = buildAnnotatedString {
append("Text Headline Small ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineSmallWithColorPreview() {
PreviewWithThemes {
TextHeadlineSmall(
text = "Text Headline Small with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextHeadlineSmallWithTextAlignPreview() {
PreviewWithThemes {
TextHeadlineSmall(
text = "Text Headline Small with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextLabelLargePreview() {
PreviewWithThemes {
TextLabelLarge(
text = "Text Label Large",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelLargeWithAnnotatedStringPreview() {
PreviewWithThemes {
TextLabelLarge(
text = buildAnnotatedString {
append("Text Label Large ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelLargeWithColorPreview() {
PreviewWithThemes {
TextLabelLarge(
text = "Text Label Large with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelLargeWithTextAlignPreview() {
PreviewWithThemes {
TextLabelLarge(
text = "Text Label Large with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextLabelMediumPreview() {
PreviewWithThemes {
TextLabelMedium(
text = "Text Label Medium",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelMediumWithAnnotatedStringPreview() {
PreviewWithThemes {
TextLabelMedium(
text = buildAnnotatedString {
append("Text Label Medium ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelMediumWithColorPreview() {
PreviewWithThemes {
TextLabelMedium(
text = "Text Label Medium with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelMediumWithTextAlignPreview() {
PreviewWithThemes {
TextLabelMedium(
text = "Text Label Medium with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextLabelSmallPreview() {
PreviewWithThemes {
TextLabelSmall(
text = "Text Label Small",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelSmallWithAnnotatedStringPreview() {
PreviewWithThemes {
TextLabelSmall(
text = buildAnnotatedString {
append("Text Label Small ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelSmallWithColorPreview() {
PreviewWithThemes {
TextLabelSmall(
text = "Text Label Small with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextLabelSmallWithTextAlignPreview() {
PreviewWithThemes {
TextLabelSmall(
text = "Text Label Small with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextTitleLargePreview() {
PreviewWithThemes {
TextTitleLarge(
text = "Text Title Large",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleLargeWithAnnotatedStringPreview() {
PreviewWithThemes {
TextTitleLarge(
text = buildAnnotatedString {
append("Text Title Large ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleLargeWithColorPreview() {
PreviewWithThemes {
TextTitleLarge(
text = "Text Title Large with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleLargeWithTextAlignPreview() {
PreviewWithThemes {
TextTitleLarge(
text = "Text Title Large with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextTitleMediumPreview() {
PreviewWithThemes {
TextTitleMedium(
text = "Text Title Medium",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleMediumWithAnnotatedStringPreview() {
PreviewWithThemes {
TextTitleMedium(
text = buildAnnotatedString {
append("Text Title Medium ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleMediumWithColorPreview() {
PreviewWithThemes {
TextTitleMedium(
text = "Text Title Medium with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleMediumWithTextAlignPreview() {
PreviewWithThemes {
TextTitleMedium(
text = "Text Title Medium with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,58 @@
package app.k9mail.core.ui.compose.designsystem.atom.text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun TextTitleSmallPreview() {
PreviewWithThemes {
TextTitleSmall(
text = "Text Title Small",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleSmallWithAnnotatedStringPreview() {
PreviewWithThemes {
TextTitleSmall(
text = buildAnnotatedString {
append("Text Title Small ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleSmallWithColorPreview() {
PreviewWithThemes {
TextTitleSmall(
text = "Text Title Small with color",
color = MainTheme.colors.primary,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextTitleSmallWithTextAlignPreview() {
PreviewWithThemes {
TextTitleSmall(
text = "Text Title Small with TextAlign End",
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,38 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun TextFieldLabelPreview() {
PreviewWithThemes {
TextFieldLabel(
label = "Label",
isRequired = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldLabelRequiredPreview() {
PreviewWithThemes {
TextFieldLabel(
label = "Label",
isRequired = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldLabelRequiredEmptyLabelPreview() {
PreviewWithThemes {
TextFieldLabel(
label = "",
isRequired = true,
)
}
}

View file

@ -0,0 +1,52 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedEmailAddressPreview() {
PreviewWithThemes {
TextFieldOutlinedEmailAddress(
value = "Input text",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedEmailAddressWithLabelPreview() {
PreviewWithThemes {
TextFieldOutlinedEmailAddress(
value = "Input text",
label = "Label",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedEmailDisabledPreview() {
PreviewWithThemes {
TextFieldOutlinedEmailAddress(
value = "Input text",
onValueChange = {},
isEnabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedEmailErrorPreview() {
PreviewWithThemes {
TextFieldOutlinedEmailAddress(
value = "Input text",
onValueChange = {},
hasError = true,
)
}
}

View file

@ -0,0 +1,28 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedFakeSelectPreview() {
PreviewWithThemes {
TextFieldOutlinedFakeSelect(
text = "Current value",
onClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedFakeSelectPreviewWithLabel() {
PreviewWithThemes {
TextFieldOutlinedFakeSelect(
text = "Current value",
onClick = {},
label = "Label",
)
}
}

View file

@ -0,0 +1,52 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedNumberPreview() {
PreviewWithThemes {
TextFieldOutlinedNumber(
value = 123L,
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedNumberWithLabelPreview() {
PreviewWithThemes {
TextFieldOutlinedNumber(
value = 123L,
label = "Label",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedNumberDisabledPreview() {
PreviewWithThemes {
TextFieldOutlinedNumber(
value = 123L,
onValueChange = {},
isEnabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedNumberErrorPreview() {
PreviewWithThemes {
TextFieldOutlinedNumber(
value = 123L,
onValueChange = {},
hasError = true,
)
}
}

View file

@ -0,0 +1,52 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedPasswordPreview() {
PreviewWithThemes {
TextFieldOutlinedPassword(
value = "Input text",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedPasswordWithLabelPreview() {
PreviewWithThemes {
TextFieldOutlinedPassword(
value = "Input text",
label = "Label",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedPasswordDisabledPreview() {
PreviewWithThemes {
TextFieldOutlinedPassword(
value = "Input text",
onValueChange = {},
isEnabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedPasswordErrorPreview() {
PreviewWithThemes {
TextFieldOutlinedPassword(
value = "Input text",
onValueChange = {},
hasError = true,
)
}
}

View file

@ -0,0 +1,80 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedPreview() {
PreviewWithThemes {
TextFieldOutlined(
value = "Input text",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedWithLabelPreview() {
PreviewWithThemes {
TextFieldOutlined(
value = "Input text",
onValueChange = {},
label = "Label",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedDisabledPreview() {
PreviewWithThemes {
TextFieldOutlined(
value = "Input text",
onValueChange = {},
isEnabled = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedErrorPreview() {
PreviewWithThemes {
TextFieldOutlined(
value = "Input text",
onValueChange = {},
hasError = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedRequiredPreview() {
PreviewWithThemes {
TextFieldOutlined(
value = "",
onValueChange = {},
label = "Label",
isRequired = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedWithTrailingIconPreview() {
PreviewWithThemes {
TextFieldOutlined(
value = "",
onValueChange = {},
trailingIcon = { Icon(imageVector = Icons.Outlined.AccountCircle) },
isRequired = true,
)
}
}

View file

@ -0,0 +1,31 @@
package app.k9mail.core.ui.compose.designsystem.atom.textfield
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import kotlinx.collections.immutable.persistentListOf
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedSelectPreview() {
PreviewWithThemes {
TextFieldOutlinedSelect(
options = persistentListOf("Option 1", "Option 2", "Option 3"),
selectedOption = "Option 1",
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextFieldOutlinedSelectPreviewWithLabel() {
PreviewWithThemes {
TextFieldOutlinedSelect(
options = persistentListOf("Option 1", "Option 2", "Option 3"),
selectedOption = "Option 1",
onValueChange = {},
label = "Label",
)
}
}

View file

@ -0,0 +1,91 @@
package app.k9mail.core.ui.compose.designsystem.molecule
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleMedium
@Composable
@Preview(showBackground = true)
internal fun ContentLoadingErrorViewContentPreview() {
PreviewWithThemes {
DefaultContentLoadingErrorView(
state = ContentLoadingErrorState.Content,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ContentLoadingErrorViewLoadingPreview() {
PreviewWithThemes {
DefaultContentLoadingErrorView(
state = ContentLoadingErrorState.Loading,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ContentLoadingErrorViewErrorPreview() {
PreviewWithThemes {
DefaultContentLoadingErrorView(
state = ContentLoadingErrorState.Error,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ContentLoadingErrorViewInteractivePreview() {
PreviewWithThemes {
val state = remember {
mutableStateOf<ContentLoadingErrorState>(ContentLoadingErrorState.Loading)
}
DefaultContentLoadingErrorView(
state = state.value,
modifier = Modifier
.clickable {
when (state.value) {
ContentLoadingErrorState.Loading -> {
state.value = ContentLoadingErrorState.Content
}
ContentLoadingErrorState.Content -> {
state.value = ContentLoadingErrorState.Error
}
ContentLoadingErrorState.Error -> {
state.value = ContentLoadingErrorState.Loading
}
}
},
)
}
}
@Composable
private fun DefaultContentLoadingErrorView(
state: ContentLoadingErrorState,
modifier: Modifier = Modifier,
) {
ContentLoadingErrorView(
state = state,
error = {
TextTitleMedium(text = "Error")
},
loading = {
TextTitleMedium(text = "Loading...")
},
content = {
TextTitleMedium(text = "Content")
},
modifier = modifier.fillMaxSize(),
)
}

View file

@ -0,0 +1,79 @@
package app.k9mail.core.ui.compose.designsystem.molecule
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleMedium
@Composable
@Preview(showBackground = true)
fun ContentLoadingViewPreview() {
PreviewWithThemes {
DefaultContentLoadingView(
state = ContentLoadingState.Content,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ContentLoadingViewLoadingPreview() {
PreviewWithThemes {
DefaultContentLoadingView(
state = ContentLoadingState.Loading,
)
}
}
@Composable
private fun DefaultContentLoadingView(
state: ContentLoadingState,
modifier: Modifier = Modifier,
) {
ContentLoadingView(
state = state,
loading = {
TextTitleMedium(text = "Loading...")
},
content = {
TextTitleMedium(text = "Content")
},
modifier = modifier.fillMaxSize(),
)
}
@Composable
@Preview(showBackground = true)
internal fun ContentLoadingViewInteractivePreview() {
PreviewWithThemes {
val state = remember {
mutableStateOf(State(isLoading = true, content = "Hello world"))
}
ContentLoadingView(
state = state.value,
loading = {
TextTitleMedium(text = "Loading...")
},
content = { targetState ->
TextTitleMedium(text = targetState.content)
},
modifier = Modifier
.clickable {
val currentValue = state.value
state.value = currentValue.copy(isLoading = currentValue.isLoading.not())
}
.fillMaxSize(),
)
}
}
private data class State(
override val isLoading: Boolean,
val content: String,
) : LoadingState

View file

@ -0,0 +1,49 @@
package app.k9mail.core.ui.compose.designsystem.molecule
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun ErrorViewPreview() {
PreviewWithThemes {
ErrorView(
title = "Error",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ErrorViewWithMessagePreview() {
PreviewWithThemes {
ErrorView(
title = "Error",
message = "Something went wrong.",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ErrorViewWithRetryPreview() {
PreviewWithThemes {
ErrorView(
title = "Error",
onRetry = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun ErrorViewWithRetryAndMessagePreview() {
PreviewWithThemes {
ErrorView(
title = "Error",
message = "Something went wrong.",
onRetry = {},
)
}
}

View file

@ -0,0 +1,23 @@
package app.k9mail.core.ui.compose.designsystem.molecule
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun LoadingViewPreview() {
PreviewWithThemes {
LoadingView()
}
}
@Composable
@Preview(showBackground = true)
internal fun LoadingViewWithMessagePreview() {
PreviewWithThemes {
LoadingView(
message = "Loading ...",
)
}
}

View file

@ -0,0 +1,45 @@
package app.k9mail.core.ui.compose.designsystem.molecule
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun PullToRefreshBoxPreview() {
PreviewWithThemes {
PullToRefreshBox(
isRefreshing = false,
onRefresh = {},
modifier = Modifier.fillMaxWidth()
.height(MainTheme.sizes.medium),
) {
Surface {
TextBodyLarge("Pull to refresh")
}
}
}
}
@Composable
@Preview(showBackground = true)
internal fun PullToRefreshBoxRefreshingPreview() {
PreviewWithThemes {
PullToRefreshBox(
isRefreshing = true,
onRefresh = {},
modifier = Modifier.fillMaxWidth()
.height(MainTheme.sizes.medium),
) {
Surface {
TextBodyLarge("Refreshing ...")
}
}
}
}

View file

@ -0,0 +1,87 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputPreview() {
PreviewWithThemes {
AdvancedTextInput(
onTextChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputIsRequiredPreview() {
PreviewWithThemes {
AdvancedTextInput(
onTextChange = {},
label = "Text input is required",
isRequired = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithErrorPreview() {
PreviewWithThemes {
AdvancedTextInput(
onTextChange = {},
errorMessage = "Text input error",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithAnnotatedStringPreview() {
PreviewWithThemes {
AdvancedTextInput(
onTextChange = {},
text = TextFieldValue(
annotatedString = buildAnnotatedString {
append("Text input with ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Annotated")
}
},
),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithSelectionPreview() {
PreviewWithThemes {
AdvancedTextInput(
onTextChange = {},
text = TextFieldValue("Text input with selection", selection = TextRange(0, 4)),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AdvancedTextInputWithCompositionPreview() {
PreviewWithThemes {
AdvancedTextInput(
onTextChange = {},
text = TextFieldValue(
text = "Text input with composition",
composition = TextRange(0, 4),
),
)
}
}

View file

@ -0,0 +1,42 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun CheckboxInputPreview() {
PreviewWithThemes {
CheckboxInput(
text = "CheckboxInput",
checked = false,
onCheckedChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun CheckboxInputWithErrorPreview() {
PreviewWithThemes {
CheckboxInput(
text = "CheckboxInput",
checked = false,
onCheckedChange = {},
errorMessage = "Error message",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun CheckboxInputCheckedPreview() {
PreviewWithThemes {
CheckboxInput(
text = "CheckboxInput",
checked = true,
onCheckedChange = {},
)
}
}

View file

@ -0,0 +1,26 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun EmailAddressInputPreview() {
PreviewWithThemes {
EmailAddressInput(
onEmailAddressChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun EmailAddressInputWithErrorPreview() {
PreviewWithThemes {
EmailAddressInput(
onEmailAddressChange = {},
errorMessage = "Email address error",
)
}
}

View file

@ -0,0 +1,40 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlined
@Composable
@Preview(showBackground = true)
internal fun InputLayoutPreview() {
PreviewWithThemes {
InputLayout {
TextFieldOutlined(value = "InputLayout", onValueChange = {})
}
}
}
@Composable
@Preview(showBackground = true)
internal fun InputLayoutWithErrorPreview() {
PreviewWithThemes {
InputLayout(
errorMessage = "Error message",
) {
TextFieldOutlined(value = "InputLayout", onValueChange = {})
}
}
}
@Composable
@Preview(showBackground = true)
internal fun InputLayoutWithWarningPreview() {
PreviewWithThemes {
InputLayout(
warningMessage = "Warning message",
) {
TextFieldOutlined(value = "InputLayout", onValueChange = {})
}
}
}

View file

@ -0,0 +1,38 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun NumberInputPreview() {
PreviewWithThemes {
NumberInput(
onValueChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun NumberInputIsRequiredPreview() {
PreviewWithThemes {
NumberInput(
onValueChange = {},
label = "Text input is required",
isRequired = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun NumberInputWithErrorPreview() {
PreviewWithThemes {
NumberInput(
onValueChange = {},
errorMessage = "Text input error",
)
}
}

View file

@ -0,0 +1,26 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun PasswordInputPreview() {
PreviewWithThemes {
PasswordInput(
onPasswordChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun PasswordInputWithErrorPreview() {
PreviewWithThemes {
PasswordInput(
onPasswordChange = {},
errorMessage = "Password error",
)
}
}

View file

@ -0,0 +1,18 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import kotlinx.collections.immutable.persistentListOf
@Composable
@Preview(showBackground = true)
internal fun SelectInputPreview() {
PreviewWithThemes {
SelectInput(
options = persistentListOf("Option 1", "Option 2", "Option 3"),
selectedOption = "Option 1",
onOptionChange = {},
)
}
}

View file

@ -0,0 +1,42 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun SwitchInputPreview() {
PreviewWithThemes {
SwitchInput(
text = "SwitchInput",
checked = false,
onCheckedChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SwitchInputWithErrorPreview() {
PreviewWithThemes {
SwitchInput(
text = "SwitchInput",
checked = false,
onCheckedChange = {},
errorMessage = "Error message",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SwitchInputCheckedPreview() {
PreviewWithThemes {
SwitchInput(
text = "SwitchInput",
checked = true,
onCheckedChange = {},
)
}
}

View file

@ -0,0 +1,38 @@
package app.k9mail.core.ui.compose.designsystem.molecule.input
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun TextInputPreview() {
PreviewWithThemes {
TextInput(
onTextChange = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextInputIsRequiredPreview() {
PreviewWithThemes {
TextInput(
onTextChange = {},
label = "Text input is required",
isRequired = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TextInputWithErrorPreview() {
PreviewWithThemes {
TextInput(
onTextChange = {},
errorMessage = "Text input error",
)
}
}

View file

@ -0,0 +1,28 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun NotificationActionButtonPreview() {
PreviewWithThemesLightDark {
Column(
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
) {
NotificationActionButton(
onClick = {},
text = "Sign in",
)
NotificationActionButton(
onClick = {},
text = "View support article",
isExternalLink = true,
)
}
}
}

View file

@ -0,0 +1,80 @@
package app.k9mail.core.ui.compose.designsystem.organism
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun AlertDialogPreview() {
PreviewWithTheme {
AlertDialog(
title = "Title",
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
confirmText = "Accept",
onConfirmClick = {},
onDismissRequest = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AlertDialogWithIconPreview() {
PreviewWithTheme {
AlertDialog(
icon = Icons.Outlined.Info,
title = "Title",
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
confirmText = "Accept",
onConfirmClick = {},
onDismissRequest = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AlertDialogWithCancelPreview() {
PreviewWithTheme {
AlertDialog(
icon = Icons.Outlined.Info,
title = "Title",
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
confirmText = "Accept",
dismissText = "Cancel",
onConfirmClick = {},
onDismissRequest = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AlertDialogWithCustomContentPreview() {
PreviewWithTheme {
AlertDialog(
icon = Icons.Outlined.Info,
title = "Title",
confirmText = "Accept",
dismissText = "Cancel",
onConfirmClick = {},
onDismissRequest = {},
) {
Column(
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
) {
TextBodyMedium("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
TextBodyMedium("Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
}
}
}
}

View file

@ -0,0 +1,147 @@
package app.k9mail.core.ui.compose.designsystem.organism
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.designsystem.PreviewLightDarkLandscape
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.designsystem.atom.text.TextHeadlineSmall
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDarkLandscape
@Composable
private fun BasicDialogPreview() {
PreviewWithThemesLightDark(
useRow = true,
useScrim = true,
scrimPadding = PaddingValues(32.dp),
arrangement = Arrangement.spacedBy(24.dp),
) {
BasicDialogContent(
headline = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
modifier = Modifier.fillMaxWidth(),
) {
Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
TextHeadlineSmall(text = "Reset settings?")
}
},
supportingText = {
TextBodyMedium(
text = "This will reset your app preferences back to their default settings. " +
"The following accounts will also be signed out:",
color = MainTheme.colors.onSurfaceVariant,
)
},
content = {
Column(
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
modifier = Modifier
.fillMaxWidth()
.padding(
start = MainTheme.spacings.triple,
end = MainTheme.spacings.triple,
),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
) {
Box(
modifier = Modifier
.size(MainTheme.sizes.iconAvatar)
.background(color = MainTheme.colors.primary, shape = CircleShape),
)
Text(text = "Account 1")
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
) {
Box(
modifier = Modifier
.size(MainTheme.sizes.iconAvatar)
.background(color = MainTheme.colors.primary, shape = CircleShape),
)
Text(text = "Account 2")
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
) {
Box(
modifier = Modifier
.size(MainTheme.sizes.iconAvatar)
.background(color = MainTheme.colors.primary, shape = CircleShape),
)
Text(text = "Account 3")
}
}
},
buttons = {
TextButton(onClick = {}) {
Text(text = "Cancel")
}
TextButton(onClick = {}) {
Text(text = "Accept")
}
},
showDividers = true,
modifier = Modifier.width(300.dp),
)
}
}
@PreviewLightDarkLandscape
@Composable
private fun PreviewOnlySupportingText() {
PreviewWithThemesLightDark(
useRow = true,
useScrim = true,
scrimPadding = PaddingValues(32.dp),
arrangement = Arrangement.spacedBy(24.dp),
) {
BasicDialogContent(
headline = {
TextHeadlineSmall(text = "Email can not be archived")
},
supportingText = {
TextBodyMedium(
text = "Configure archive folder now",
color = MainTheme.colors.onSurfaceVariant,
)
},
content = null,
buttons = {
TextButton(onClick = {}) {
Text(text = "Skip for now")
}
TextButton(onClick = {}) {
Text(text = "Set archive folder")
}
},
showDividers = false,
modifier = Modifier.width(300.dp),
)
}
}

View file

@ -0,0 +1,79 @@
package app.k9mail.core.ui.compose.designsystem.organism
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonIcon
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
@Composable
@Preview(showBackground = true)
internal fun SubtitleTopAppBarPreview() {
PreviewWithThemes {
SubtitleTopAppBar(
title = "Title",
subtitle = "Subtitle",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SubtitleTopAppBarWithLongSubtitlePreview() {
PreviewWithThemes {
SubtitleTopAppBar(
title = "Title",
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SubtitleTopAppBarWithActionsPreview() {
PreviewWithThemes {
SubtitleTopAppBar(
title = "Title",
subtitle = "Subtitle",
actions = {
ButtonIcon(
onClick = {},
imageVector = Icons.Outlined.Info,
)
ButtonIcon(
onClick = {},
imageVector = Icons.Outlined.Check,
)
ButtonIcon(
onClick = {},
imageVector = Icons.Outlined.Visibility,
)
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SubtitleTopAppBarWithMenuButtonPreview() {
PreviewWithThemes {
SubtitleTopAppBarWithMenuButton(
title = "Title",
subtitle = "Subtitle",
onMenuClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SubtitleTopAppBarWithBackButtonPreview() {
PreviewWithThemes {
SubtitleTopAppBarWithBackButton(
title = "Title",
subtitle = "Subtitle",
onBackClick = {},
)
}
}

View file

@ -0,0 +1,63 @@
package app.k9mail.core.ui.compose.designsystem.organism
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonIcon
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
@Composable
@Preview(showBackground = true)
internal fun TopAppBarPreview() {
PreviewWithThemes {
TopAppBar(
title = "Title",
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TopAppBarWithActionsPreview() {
PreviewWithThemes {
TopAppBar(
title = "Title",
actions = {
ButtonIcon(
onClick = {},
imageVector = Icons.Outlined.Info,
)
ButtonIcon(
onClick = {},
imageVector = Icons.Outlined.Check,
)
ButtonIcon(
onClick = {},
imageVector = Icons.Outlined.Visibility,
)
},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TopAppBarWithMenuButtonPreview() {
PreviewWithThemes {
TopAppBarWithMenuButton(
title = "Title",
onMenuClick = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun TopAppBarWithBackButtonPreview() {
PreviewWithThemes {
TopAppBarWithBackButton(
title = "Title",
onBackClick = {},
)
}
}

View file

@ -0,0 +1,68 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.global
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.designsystem.atom.icon.outlined.Warning
import app.k9mail.core.ui.compose.designsystem.organism.banner.global.BannerGlobalNotificationCard
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun BannerGlobalNotificationCardStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
BannerGlobalNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Warning) },
text = "Offline. No internet connection found.",
action = {
ButtonText(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun BannerGlobalNotificationCardAnnotatedStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
BannerGlobalNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Warning) },
text = buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Black)) {
append("Offline. ")
}
append("No internet connection found.")
},
action = {
ButtonText(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}

View file

@ -0,0 +1,63 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.global
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
import app.k9mail.core.ui.compose.designsystem.organism.banner.global.ErrorBannerGlobalNotificationCard
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun ErrorBannerGlobalNotificationCardStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
ErrorBannerGlobalNotificationCard(
text = "Offline. No internet connection found.",
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun ErrorBannerGlobalNotificationCardAnnotatedStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
ErrorBannerGlobalNotificationCard(
text = buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Black)) {
append("Offline. ")
}
append("No internet connection found.")
},
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}

View file

@ -0,0 +1,120 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.global
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
import app.k9mail.core.ui.compose.designsystem.organism.banner.global.InfoBannerGlobalNotificationCard
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun InfoBannerGlobalNotificationCardStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
InfoBannerGlobalNotificationCard(
text = "Offline. No internet connection found.",
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun InfoBannerGlobalNotificationCardNoActionPreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
InfoBannerGlobalNotificationCard(
text = "Offline. No internet connection found.",
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun InfoBannerGlobalNotificationCardLongTextPreview(
@PreviewParameter(LoremIpsum::class) text: String,
) {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
InfoBannerGlobalNotificationCard(
text = text,
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun InfoBannerGlobalNotificationCardLongNoActionTextPreview(
@PreviewParameter(LoremIpsum::class) text: String,
) {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
InfoBannerGlobalNotificationCard(
text = text,
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun InfoBannerGlobalNotificationCardAnnotatedStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
InfoBannerGlobalNotificationCard(
text = buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Black)) {
append("Offline. ")
}
append("No internet connection found.")
},
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}

View file

@ -0,0 +1,120 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.global
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
import app.k9mail.core.ui.compose.designsystem.organism.banner.global.SuccessBannerGlobalNotificationCard
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun SuccessBannerGlobalNotificationCardStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
SuccessBannerGlobalNotificationCard(
text = "What an awesome notification, isn't it?",
action = {
NotificationActionButton(
text = "Action",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun SuccessBannerGlobalNotificationCardNoActionPreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
SuccessBannerGlobalNotificationCard(
text = "What an awesome notification, isn't it?",
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun SuccessBannerGlobalNotificationCardLongTextPreview(
@PreviewParameter(LoremIpsum::class) text: String,
) {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
SuccessBannerGlobalNotificationCard(
text = text,
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun SuccessBannerGlobalNotificationCardLongNoActionTextPreview(
@PreviewParameter(LoremIpsum::class) text: String,
) {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
SuccessBannerGlobalNotificationCard(
text = text,
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun SuccessBannerGlobalNotificationCardAnnotatedStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
SuccessBannerGlobalNotificationCard(
text = buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Black)) {
append("Offline. ")
}
append("No internet connection found.")
},
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}

View file

@ -0,0 +1,120 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.global
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
import app.k9mail.core.ui.compose.designsystem.organism.banner.global.WarningBannerGlobalNotificationCard
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun WarningBannerGlobalNotificationCardStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
WarningBannerGlobalNotificationCard(
text = "Offline. No internet connection found.",
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun WarningBannerGlobalNotificationCardNoActionPreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
WarningBannerGlobalNotificationCard(
text = "Offline. No internet connection found.",
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun WarningBannerGlobalNotificationCardLongTextPreview(
@PreviewParameter(LoremIpsum::class) text: String,
) {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
WarningBannerGlobalNotificationCard(
text = text,
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun WarningBannerGlobalNotificationCardLongNoActionTextPreview(
@PreviewParameter(LoremIpsum::class) text: String,
) {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
WarningBannerGlobalNotificationCard(
text = text,
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}
@PreviewLightDark
@Composable
private fun WarningBannerGlobalNotificationCardAnnotatedStringTitlePreview() {
PreviewWithThemesLightDark {
Surface(
modifier = Modifier.fillMaxWidth(),
) {
WarningBannerGlobalNotificationCard(
text = buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Black)) {
append("Offline. ")
}
append("No internet connection found.")
},
action = {
NotificationActionButton(
text = "Retry",
onClick = {},
)
},
modifier = Modifier.padding(top = MainTheme.spacings.quadruple),
)
}
}
}

View file

@ -0,0 +1,151 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.inline
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleMedium
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
import app.k9mail.core.ui.compose.theme2.MainTheme
@PreviewLightDark
@Composable
private fun BannerInlineNotificationCardCustomTitleAndDescriptionPreview() {
PreviewWithThemesLightDark {
Surface(modifier = Modifier.padding(MainTheme.spacings.triple)) {
BannerInlineNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Report) },
title = {
TextTitleMedium(text = "Authentication required")
},
supportingText = {
TextBodyMedium(text = "Sign in to authenticate username@domain3.example")
},
actions = {
NotificationActionButton(text = "Support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Sign in", onClick = {})
},
modifier = Modifier.padding(
vertical = MainTheme.spacings.quadruple,
horizontal = MainTheme.spacings.default,
),
)
}
}
}
@PreviewLightDark
@Composable
private fun BannerInlineNotificationCardTextPreview() {
PreviewWithThemesLightDark {
Surface(modifier = Modifier.padding(MainTheme.spacings.triple)) {
BannerInlineNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Report) },
title = "Missing encryption key",
supportingText = "To dismiss this error, disable encryption for this account or ensure " +
"encryption key is available in openKeychain app.",
actions = {
NotificationActionButton(text = "Support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Disable encryption", onClick = {})
},
modifier = Modifier.padding(
vertical = MainTheme.spacings.quadruple,
horizontal = MainTheme.spacings.default,
),
)
}
}
}
@PreviewLightDark
@Composable
private fun BannerInlineNotificationCardAnnotatedStringPreview() {
PreviewWithThemesLightDark {
Surface(modifier = Modifier.padding(MainTheme.spacings.triple)) {
BannerInlineNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Report) },
title = buildAnnotatedString {
withStyle(style = SpanStyle(color = MainTheme.colors.tertiaryContainer)) {
append("Missing encryption key")
}
},
supportingText = buildAnnotatedString {
append("To dismiss this error, ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Black)) {
append("disable encryption for this account or ensure encryption key is available")
}
append("in openKeychain app.")
},
actions = {
NotificationActionButton(text = "Support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Disable encryption", onClick = {})
},
modifier = Modifier.padding(
vertical = MainTheme.spacings.quadruple,
horizontal = MainTheme.spacings.default,
),
)
}
}
}
@PreviewLightDark
@Composable
private fun BannerInlineNotificationClippedCardTextPreview() {
PreviewWithThemesLightDark {
Surface(modifier = Modifier.padding(MainTheme.spacings.triple)) {
BannerInlineNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Report) },
title = "Vestibulum tempor sed massa eget fermentum. Vivamus ut vitae aliquam e augue. " +
"Sed nec tincidunt arcu",
supportingText = "scelerisque fermentum. In lobortis pellentesque aliquet. Curabitur quam " +
"felis, sodales in leo ac, sodales rutrum quam. Quisque et odio id ex varius porta. " +
"Vestibulum tortor nibh, porta venenatis velit",
actions = {
NotificationActionButton(text = "Support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Disable encryption", onClick = {})
},
modifier = Modifier.padding(
vertical = MainTheme.spacings.quadruple,
horizontal = MainTheme.spacings.default,
),
behaviour = BannerInlineNotificationCardBehaviour.Clipped,
)
}
}
}
@PreviewLightDark
@Composable
private fun BannerInlineNotificationExpandedCardTextPreview() {
PreviewWithThemesLightDark {
Surface(modifier = Modifier.padding(MainTheme.spacings.triple)) {
BannerInlineNotificationCard(
icon = { Icon(imageVector = Icons.Outlined.Report) },
title = "Vestibulum tempor sed massa eget fermentum. Vivamus ut vitae aliquam e augue. " +
"Sed nec tincidunt arcu",
supportingText = "scelerisque fermentum. In lobortis pellentesque aliquet. Curabitur quam " +
"felis, sodales in leo ac, sodales rutrum quam. Quisque et odio id ex varius porta. " +
"Vestibulum tortor nibh, porta venenatis velit",
actions = {
NotificationActionButton(text = "Support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Disable encryption", onClick = {})
},
modifier = Modifier.padding(
vertical = MainTheme.spacings.quadruple,
horizontal = MainTheme.spacings.default,
),
behaviour = BannerInlineNotificationCardBehaviour.Expanded,
)
}
}
}

View file

@ -0,0 +1,59 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.inline
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
@PreviewLightDark
@Composable
private fun ErrorBannerInlineNotificationCardPreviewPreview() {
PreviewWithThemesLightDark {
ErrorBannerInlineNotificationCard(
title = "Notification title",
supportingText = "Supporting text",
actions = {
NotificationActionButton(text = "View support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Action 1", onClick = {})
},
)
}
}
@PreviewLightDark
@Composable
private fun ErrorBannerInlineNotificationCardLongTextClippedPreviewPreview() {
val title = remember { LoremIpsum(words = 20).values.joinToString(" ") }
val supportingText = remember { LoremIpsum(words = 60).values.joinToString(" ") }
PreviewWithThemesLightDark {
ErrorBannerInlineNotificationCard(
title = title,
supportingText = supportingText,
actions = {
NotificationActionButton(text = "View support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Action 1", onClick = {})
},
behaviour = BannerInlineNotificationCardBehaviour.Clipped,
)
}
}
@PreviewLightDark
@Composable
private fun ErrorBannerInlineNotificationCardLongTextExpandedPreviewPreview() {
val title = remember { LoremIpsum(words = 20).values.joinToString(" ") }
val supportingText = remember { LoremIpsum(words = 60).values.joinToString(" ") }
PreviewWithThemesLightDark {
ErrorBannerInlineNotificationCard(
title = title,
supportingText = supportingText,
actions = {
NotificationActionButton(text = "View support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Action 1", onClick = {})
},
behaviour = BannerInlineNotificationCardBehaviour.Expanded,
)
}
}

View file

@ -0,0 +1,59 @@
package app.k9mail.core.ui.compose.designsystem.organism.banner.inline
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import app.k9mail.core.ui.compose.designsystem.molecule.notification.NotificationActionButton
@PreviewLightDark
@Composable
private fun InfoBannerInlineNotificationCardPreviewPreview() {
PreviewWithThemesLightDark {
InfoBannerInlineNotificationCard(
title = "Notification title",
supportingText = "Supporting text",
actions = {
NotificationActionButton(text = "View support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Action 1", onClick = {})
},
)
}
}
@PreviewLightDark
@Composable
private fun InfoBannerInlineNotificationCardLongTextClippedPreviewPreview() {
val title = remember { LoremIpsum(words = 20).values.joinToString(" ") }
val supportingText = remember { LoremIpsum(words = 60).values.joinToString(" ") }
PreviewWithThemesLightDark {
InfoBannerInlineNotificationCard(
title = title,
supportingText = supportingText,
actions = {
NotificationActionButton(text = "View support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Action 1", onClick = {})
},
behaviour = BannerInlineNotificationCardBehaviour.Clipped,
)
}
}
@PreviewLightDark
@Composable
private fun InfoBannerInlineNotificationCardLongTextExpandedPreviewPreview() {
val title = remember { LoremIpsum(words = 20).values.joinToString(" ") }
val supportingText = remember { LoremIpsum(words = 60).values.joinToString(" ") }
PreviewWithThemesLightDark {
InfoBannerInlineNotificationCard(
title = title,
supportingText = supportingText,
actions = {
NotificationActionButton(text = "View support article", onClick = {}, isExternalLink = true)
NotificationActionButton(text = "Action 1", onClick = {})
},
behaviour = BannerInlineNotificationCardBehaviour.Expanded,
)
}
}

Some files were not shown because too many files have changed in this diff Show more