Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object AepUIConstants {
const val CARD_CLICKED = "Card clicked"
}

internal object DefaultStyle {
internal object DefaultAepUIStyle {
const val IMAGE_WIDTH = 100
const val IMAGE_PROGRESS_SPINNER_SIZE = 48
const val TITLE_TEXT_SIZE = 15
Expand All @@ -30,7 +30,10 @@ object AepUIConstants {
const val BUTTON_TEXT_SIZE = 13
val BUTTON_FONT_WEIGHT = FontWeight.Normal
const val SPACING = 8
const val DISMISS_BUTTON_SIZE = 13
}

const val DISMISS_BUTTON_SIZE = 13
internal object DefaultAepContainerStyle {
const val CIRCULAR_PROGRESS_WIDTH = 30
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui

import androidx.compose.runtime.mutableStateOf
import com.adobe.marketing.mobile.aepcomposeui.state.AepContainerUIState
import com.adobe.marketing.mobile.aepcomposeui.uimodels.AepContainerUITemplate

sealed class BaseContainerUI<T : AepContainerUITemplate, S : AepContainerUIState>(
private val template: T,
state: S
) : AepContainerUI<T, S> {
private val _state = mutableStateOf(state)

override fun getTemplate(): T {
return template
}

override fun getState(): S {
return _state.value
}

override fun updateState(newState: S) {
_state.value = newState
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui

import com.adobe.marketing.mobile.aepcomposeui.state.InboxContainerUIState
import com.adobe.marketing.mobile.aepcomposeui.uimodels.InboxContainerUITemplate

/**
* Implementation of the [AepContainerUI] interface used in rendering a UI for an [InboxContainerUITemplate].
*
* @param template The template associated with the inbox container UI.
* @param state The current state of the inbox container UI.
*/
class InboxContainerUI(
private val template: InboxContainerUITemplate,
state: InboxContainerUIState
) : BaseContainerUI<InboxContainerUITemplate, InboxContainerUIState> (template, state)
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ import com.adobe.marketing.mobile.messaging.ContentCardImageManager
*/
@Composable
internal fun AepAsyncImage(
image: AepImage?,
image: AepImage,
imageStyle: AepImageStyle = AepImageStyle(),
onSuccess: (Bitmap) -> Unit = {},
onError: (Throwable) -> Unit = {}
) {
val imageUrl = if (isSystemInDarkTheme() && image?.darkUrl != null)
image.darkUrl else image?.url
val imageUrl = if (isSystemInDarkTheme() && image.darkUrl != null)
image.darkUrl else image.url
var imageBitmap by remember { mutableStateOf<Bitmap?>(null) }
var isLoading by remember { mutableStateOf(true) }

Expand All @@ -76,11 +76,11 @@ internal fun AepAsyncImage(
if (isLoading) {
Box(
modifier = imageStyle.modifier ?: Modifier
.size(AepUIConstants.DefaultStyle.IMAGE_WIDTH.dp),
.size(AepUIConstants.DefaultAepUIStyle.IMAGE_WIDTH.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(AepUIConstants.DefaultStyle.IMAGE_PROGRESS_SPINNER_SIZE.dp),
modifier = Modifier.size(AepUIConstants.DefaultAepUIStyle.IMAGE_PROGRESS_SPINNER_SIZE.dp),
strokeWidth = 4.dp
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.components

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adobe.marketing.mobile.aepcomposeui.AepUIConstants

/**
* A composable function that displays a centered circular progress indicator.
**/
@Composable
fun AepCircularProgressIndicator() {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(AepUIConstants.DefaultAepContainerStyle.CIRCULAR_PROGRESS_WIDTH.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.components

import androidx.compose.runtime.Composable
import com.adobe.marketing.mobile.aepcomposeui.AepContainerUI
import com.adobe.marketing.mobile.aepcomposeui.InboxContainerUI
import com.adobe.marketing.mobile.aepcomposeui.observers.AepUIEventObserver
import com.adobe.marketing.mobile.aepcomposeui.style.AepUIStyle
import com.adobe.marketing.mobile.aepcomposeui.style.ContainerStyle

/**
* AEP Container Composable that renders the appropriate container based on the type of [AepContainerUI] provided.
*
* @param containerUi The AEP container UI model to be rendered.
* @param containerStyle The style to be applied to the container.
* @param itemsStyle The style to be applied to the cards within the container.
* @param cardUIEventListener An optional event listener for content card UI events.
*/
@Composable
fun AepContainer(
containerUi: AepContainerUI<*, *>,
containerStyle: ContainerStyle = ContainerStyle(),
itemsStyle: AepUIStyle = AepUIStyle(),
observer: AepUIEventObserver? = null
) {
when (containerUi) {
is InboxContainerUI -> {
InboxContainer(
ui = containerUi,
inboxContainerStyle = containerStyle.inboxContainerUIStyle,
itemsStyle = itemsStyle,
observer = observer
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.components

import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adobe.marketing.mobile.aepcomposeui.style.AepLazyColumnStyle

/**
* A composable function that displays a lazy column element with customizable properties.
*
* @param lazyColumnStyle The [AepLazyColumnStyle] to be applied to the lazy column element.
* @param content The content of the lazy column.
*/
@Composable
internal fun AepLazyColumn(
lazyColumnStyle: AepLazyColumnStyle = AepLazyColumnStyle(),
content: LazyListScope.() -> Unit
) {
LazyColumn(
modifier = lazyColumnStyle.modifier ?: Modifier,
contentPadding = lazyColumnStyle.contentPadding ?: PaddingValues(0.dp),
reverseLayout = lazyColumnStyle.reverseLayout ?: false,
verticalArrangement = lazyColumnStyle.verticalArrangement ?: getDefaultVerticalArrangement(lazyColumnStyle.reverseLayout ?: false),
horizontalAlignment = lazyColumnStyle.horizontalAlignment ?: Alignment.Start,
flingBehavior = lazyColumnStyle.flingBehavior ?: ScrollableDefaults.flingBehavior(),
userScrollEnabled = lazyColumnStyle.userScrollEnabled ?: true,
content = content
)
}

/**
* Helper function to get the default vertical arrangement based on reverse layout.
*/
private fun getDefaultVerticalArrangement(reverseLayout: Boolean): Arrangement.Vertical =
if (reverseLayout) Arrangement.Bottom else Arrangement.Top
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.components

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adobe.marketing.mobile.aepcomposeui.AepUI
import com.adobe.marketing.mobile.aepcomposeui.ImageOnlyUI
import com.adobe.marketing.mobile.aepcomposeui.LargeImageUI
import com.adobe.marketing.mobile.aepcomposeui.SmallImageUI
import com.adobe.marketing.mobile.aepcomposeui.observers.AepUIEventObserver
import com.adobe.marketing.mobile.aepcomposeui.style.AepImageStyle
import com.adobe.marketing.mobile.aepcomposeui.style.AepUIStyle
import com.adobe.marketing.mobile.aepcomposeui.uimodels.AepImage

/**
* Renders a list of AEP UI items within a LazyListScope.
*
* @param items The list of [AepUI] items to be rendered.
* @param itemsStyle The style to be applied to the items.
* @param unreadItemsStyle An optional style to be applied to unread items.
* @param unreadIcon An optional Triple containing the unread icon [AepImage], its [AepImageStyle], and its [Alignment].
* @param observer An optional observer that listens to UI events.
*/
fun LazyListScope.renderListItems(
items: List<AepUI<*, *>>,
itemsStyle: AepUIStyle,
unreadItemsStyle: AepUIStyle = itemsStyle,
unreadIcon: Triple<AepImage, AepImageStyle, Alignment>? = null,
observer: AepUIEventObserver?
) {
items(items = items) { aepUI ->
val state = aepUI.getState()
if (!state.dismissed) {
Box(modifier = Modifier.padding(0.dp)) {
val read = aepUI.getState().read
when (aepUI) {
is SmallImageUI -> {
// Use read or unread style based on UI state
val style =
if (read == false) unreadItemsStyle.smallImageUIStyle else itemsStyle.smallImageUIStyle
SmallImageCard(
ui = aepUI,
style = style,
observer = observer
)
}

is LargeImageUI -> {
// Use read or unread style based on UI state
val style =
if (read == false) unreadItemsStyle.largeImageUIStyle else itemsStyle.largeImageUIStyle
LargeImageCard(
ui = aepUI,
style = style,
observer = observer
)
}

is ImageOnlyUI -> {
// Use read or unread style based on UI state
val style =
if (read == false) unreadItemsStyle.imageOnlyUIStyle else itemsStyle.imageOnlyUIStyle
ImageOnlyCard(
ui = aepUI,
style = style,
observer = observer
)
}
}
// Display unread icon one is provided and if the item is unread
if (read != null && !read && unreadIcon != null) {
Box(modifier = Modifier.align(unreadIcon.third)) {
AepAsyncImage(
image = unreadIcon.first,
imageStyle = unreadIcon.second
)
}
}
}
}
}
}
Loading
Loading