Skip to content

Adds ViewableFile implementation #445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -52,7 +52,7 @@ import com.arcgismaps.mapping.popup.Popup
import com.arcgismaps.mapping.popup.TextPopupElement
import com.arcgismaps.toolkit.popup.internal.element.attachment.AttachmentsElementState
import com.arcgismaps.toolkit.popup.internal.element.attachment.AttachmentsPopupElement
import com.arcgismaps.toolkit.popup.internal.element.attachment.rememberAttachmentElementState
import com.arcgismaps.toolkit.popup.internal.element.attachment.rememberAttachmentsElementState
import com.arcgismaps.toolkit.popup.internal.element.fieldselement.FieldsElementState
import com.arcgismaps.toolkit.popup.internal.element.fieldselement.FieldsPopupElement
import com.arcgismaps.toolkit.popup.internal.element.fieldselement.rememberFieldsElementState
Expand All @@ -61,6 +61,7 @@ import com.arcgismaps.toolkit.popup.internal.element.state.mutablePopupElementSt
import com.arcgismaps.toolkit.popup.internal.element.textelement.TextElementState
import com.arcgismaps.toolkit.popup.internal.element.textelement.TextPopupElement
import com.arcgismaps.toolkit.popup.internal.element.textelement.rememberTextElementState
import com.arcgismaps.toolkit.popup.internal.fileviewer.ViewableFile

@Immutable
private data class PopupState(@Stable val popup: Popup)
Expand Down Expand Up @@ -133,7 +134,7 @@ private fun Popup(popupState: PopupState, evaluated: Boolean, modifier: Modifier
}

@Composable
private fun PopupBody(popupState: PopupState) {
private fun PopupBody(popupState: PopupState, onFileClicked: (ViewableFile?) -> Unit = {}) {
val popup = popupState.popup
val lazyListState = rememberLazyListState()
val states = rememberStates(popup)
Expand All @@ -155,8 +156,8 @@ private fun PopupBody(popupState: PopupState) {

is AttachmentsPopupElement -> {
AttachmentsPopupElement(
state = entry.state as AttachmentsElementState
)
state = entry.state as AttachmentsElementState,
onFileClicked)
}

is FieldsPopupElement -> {
Expand Down Expand Up @@ -217,7 +218,7 @@ internal fun rememberStates(
is AttachmentsPopupElement -> {
states.add(
element,
rememberAttachmentElementState(popup = popup, element = element)
rememberAttachmentsElementState(popup = popup, element = element)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.arcgismaps.LoadStatus
import com.arcgismaps.mapping.popup.PopupAttachmentType
import com.arcgismaps.toolkit.popup.internal.fileviewer.ViewableFile
import kotlinx.coroutines.flow.MutableStateFlow

@Composable
internal fun AttachmentTile(
state: PopupAttachmentState
state: PopupAttachmentState,
onClicked: (ViewableFile) -> Unit = {}
) {
val loadStatus by state.loadStatus.collectAsState()
val coroutineScope = rememberCoroutineScope()
Expand All @@ -84,37 +86,39 @@ internal fun AttachmentTile(
if (loadStatus is LoadStatus.NotLoaded || loadStatus is LoadStatus.FailedToLoad) {
// load attachment
state.loadAttachment(coroutineScope)
} else if (loadStatus is LoadStatus.Loaded) {
onClicked(state)
}
// TODO open attachment viewer in `else` here
}
) {
when (loadStatus) {
LoadStatus.Loaded -> LoadedView(
thumbnail = thumbnail,
attachmentType = state.type,
attachmentType = state.popupAttachmentType,
title = state.name
)

LoadStatus.Loading -> DefaultView(
title = state.name,
size = state.size,
attachmentType = state.type,
attachmentType = state.popupAttachmentType,
isLoading = true,
isError = false
)

LoadStatus.NotLoaded -> DefaultView(
title = state.name,
size = state.size,
attachmentType = state.type,
attachmentType = state.popupAttachmentType,
isLoading = false,
isError = false
)

is LoadStatus.FailedToLoad -> DefaultView(
title = state.name,
size = state.size,
attachmentType = state.type,
attachmentType = state.popupAttachmentType,
isLoading = false,
isError = true
)
Expand Down Expand Up @@ -268,7 +272,7 @@ internal fun PreviewAttachmentTile() {
state = PopupAttachmentState(
name = "Some attachment",
size = 1234L,
type = PopupAttachmentType.Other,
popupAttachmentType = PopupAttachmentType.Other,
loadStatus = MutableStateFlow(LoadStatus.NotLoaded),
onLoadAttachment = { Result.success(Unit) },
onLoadThumbnail = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import com.arcgismaps.mapping.popup.AttachmentsPopupElement
import com.arcgismaps.mapping.popup.Popup
import com.arcgismaps.mapping.popup.PopupAttachment
import com.arcgismaps.mapping.popup.PopupAttachmentType
import com.arcgismaps.toolkit.popup.internal.fileviewer.ViewableFile
import com.arcgismaps.toolkit.popup.internal.fileviewer.toViewableFileType
import com.arcgismaps.toolkit.popup.internal.element.state.PopupElementState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -51,10 +53,10 @@ internal class AttachmentsElementState(
val description: String,
val title: String,
val attachments: List<PopupAttachmentState>,
override val id : Int = createId(),
override val id: Int = createId(),
) : PopupElementState() {

constructor(attachmentPopupElement: AttachmentsPopupElement): this(
constructor(attachmentPopupElement: AttachmentsPopupElement) : this(
description = attachmentPopupElement.description,
title = attachmentPopupElement.title,
attachments = attachmentPopupElement.attachments.map { PopupAttachmentState(it) }
Expand All @@ -76,7 +78,7 @@ internal class AttachmentsElementState(
}

@Composable
internal fun rememberAttachmentElementState(
internal fun rememberAttachmentsElementState(
element: AttachmentsPopupElement,
popup: Popup
): AttachmentsElementState {
Expand All @@ -94,7 +96,7 @@ internal fun rememberAttachmentElementState(
it.attachments
.filter { state ->
state.loadStatus.value == LoadStatus.Loaded
&& state.type == PopupAttachmentType.Image
&& state.popupAttachmentType == PopupAttachmentType.Image
}
.forEach { state ->
state.loadAttachment(scope)
Expand All @@ -113,32 +115,37 @@ internal fun rememberAttachmentElementState(
* @param onLoadThumbnail A function that loads the thumbnail of the attachment.
*/
internal class PopupAttachmentState(
val name: String,
val size: Long,
val type: PopupAttachmentType,
override val name: String,
override val size: Long,
val popupAttachmentType: PopupAttachmentType,
val loadStatus: StateFlow<LoadStatus>,
private val onLoadAttachment: suspend () -> Result<Unit>,
private val onLoadThumbnail: (suspend () -> Result<BitmapDrawable?>)? = null
) {
) : ViewableFile(name, size, "", popupAttachmentType.toViewableFileType()) {
private val _thumbnail: MutableState<ImageBitmap?> = mutableStateOf(null)
private lateinit var _attachment: PopupAttachment

/**
* The thumbnail of the attachment. This is `null` until [loadAttachment] is called.
*/
val thumbnail: State<ImageBitmap?> = _thumbnail
override val path: String
get() = _attachment.filePath

constructor(attachment: PopupAttachment) : this(
name = attachment.name,
size = attachment.size,
type = attachment.type,
popupAttachmentType = attachment.type,
loadStatus = attachment.loadStatus,
onLoadAttachment = attachment::retryLoad,
onLoadThumbnail = if (attachment.type == PopupAttachmentType.Image) {
attachment::createFullImage
} else {
null
}
)
) {
_attachment = attachment
}

/**
* Loads the attachment and its thumbnail.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.arcgismaps.LoadStatus
import com.arcgismaps.mapping.popup.PopupAttachmentType
import com.arcgismaps.toolkit.popup.internal.fileviewer.ViewableFile
import com.arcgismaps.toolkit.popup.internal.ui.ExpandableCard
import kotlinx.coroutines.flow.MutableStateFlow

@Composable
internal fun AttachmentsPopupElement(
state: AttachmentsElementState
state: AttachmentsElementState,
onSelectedAttachment: (ViewableFile?) -> Unit = {}
) {
AttachmentsPopupElement(
title = state.title,
description = state.description,
stateId = state.id,
attachments = state.attachments
attachments = state.attachments,
onSelectedAttachment = onSelectedAttachment
)
}

Expand All @@ -49,7 +52,8 @@ private fun AttachmentsPopupElement(
description: String,
title: String,
@Suppress("UNUSED_PARAMETER") stateId: Int,
attachments: List<PopupAttachmentState>
attachments: List<PopupAttachmentState>,
onSelectedAttachment: (ViewableFile?) -> Unit = {}
) {
ExpandableCard(
title = title,
Expand All @@ -59,19 +63,23 @@ private fun AttachmentsPopupElement(
modifier = Modifier.padding(AttachmentsElementDefaults.shapes().galleryPadding)
) {
val listState = rememberLazyListState()
AttachmentGallery(listState, attachments)
AttachmentGallery(listState, attachments, onSelectedAttachment)
}
}
}

@Composable
private fun AttachmentGallery(state: LazyListState, attachments: List<PopupAttachmentState>) {
private fun AttachmentGallery(
state: LazyListState,
attachments: List<PopupAttachmentState>,
onSelectedAttachment: (ViewableFile) -> Unit = {}
) {
LazyRow(
state = state,
horizontalArrangement = Arrangement.spacedBy(15.dp),
) {
items(attachments, key = { it.name + it.type + it.size }) {
AttachmentTile(it)
items(attachments, key = { it.name + it.popupAttachmentType + it.size }) {
AttachmentTile(it, onSelectedAttachment)
}
}
}
Expand All @@ -85,8 +93,8 @@ private fun AttachmentsPopupElementPreview() {
stateId = 1,
attachments = listOf(
PopupAttachmentState(
"Photo 1.jpg",
2024,
name = "Photo 1.jpg",
size = 2024,
PopupAttachmentType.Image,
MutableStateFlow(LoadStatus.Loaded),
{ Result.success(Unit) },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.arcgismaps.toolkit.popup.internal.fileviewer

import android.os.Parcel
import android.os.Parcelable
import com.arcgismaps.mapping.popup.PopupAttachmentType
import kotlinx.parcelize.Parceler
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.TypeParceler

/**
* A file that can be viewed in the [FileViewer].
*/
@Parcelize
internal open class ViewableFile(
open val name: String,
open val size: Long,
open val path: String,
@TypeParceler<ViewableFileType, ViewableFileTypeParceler>() open val type: ViewableFileType
): Parcelable

private object ViewableFileTypeParceler: Parceler<ViewableFileType> {
override fun create(parcel: Parcel): ViewableFileType {
return when (parcel.readInt()) {
0 -> ViewableFileType.Image
1 -> ViewableFileType.Video
else -> ViewableFileType.Other
}
}

override fun ViewableFileType.write(parcel: Parcel, flags: Int) {
parcel.writeInt(
when (this) {
ViewableFileType.Image -> 0
ViewableFileType.Video -> 1
ViewableFileType.Other -> 2
}
)
}
}

internal sealed class ViewableFileType {
data object Image : ViewableFileType()
data object Video : ViewableFileType()
data object Other : ViewableFileType()
}

internal fun PopupAttachmentType.toViewableFileType(): ViewableFileType = when (this) {
PopupAttachmentType.Image -> ViewableFileType.Image
PopupAttachmentType.Video -> ViewableFileType.Video
PopupAttachmentType.Document,
PopupAttachmentType.Other -> ViewableFileType.Other
}