Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -290,7 +290,7 @@ class PrivMxClient : IPrivMxClient, AutoCloseable {
return data
}

override fun getFilesAsByteArrayFromStore(storeId: String?, limit: Long, skip: Long): List<ByteArray> {
override fun getFilesAsByteArrayFromStore(storeId: String?, limit: Long, skip: Long): List<Pair<ByteArray, Long>> {
val files = storeApi!!.listFiles(
storeId,
skip,
Expand All @@ -300,7 +300,9 @@ class PrivMxClient : IPrivMxClient, AutoCloseable {

return files.readItems.mapNotNull { file ->
try {
getFileAsByteArrayFromStore(file.info.fileId)
val byteArray = getFileAsByteArrayFromStore(file.info.fileId)
val creationTimestamp = file.info.createDate
Pair(byteArray, creationTimestamp)
} catch (e: Exception) {
e.printStackTrace()
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ interface IPrivMxClient {
)

fun getFileAsByteArrayFromStore(fileId: String): ByteArray
fun getFilesAsByteArrayFromStore(storeId: String?, limit: Long, skip: Long): List<ByteArray>
fun getFilesAsByteArrayFromStore(storeId: String?, limit: Long, skip: Long): List<Pair<ByteArray, Long>>
fun sendByteArrayToStore(storeId: String, content: ByteArray): String
fun retrieveMessagesFromThread(
threadId: String, startIndex: Int, pageSize: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class FileCabinetService(
storeId: String?,
limit: Long,
skip: Long
): List<ByteArray> {
): List<Pair<ByteArray, Long>> {
return privMxClient.getFilesAsByteArrayFromStore(storeId, limit, skip)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ interface IFileCabinetService {
imageByteArray: ByteArray
)

fun getImagesFromFamilyGroupStoreAsByteArray(storeId: String?, limit: Long, skip: Long) : List<ByteArray>
fun getImagesFromFamilyGroupStoreAsByteArray(storeId: String?, limit: Long, skip: Long) : List<Pair<ByteArray, Long>>
suspend fun restoreFileCabinetMembership()
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package com.github.familyvault.ui.screens.main.filesCabinet

import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.foundation.lazy.grid.items as gridItems
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import com.github.familyvault.services.IFileCabinetService
import com.github.familyvault.services.IImagePickerService
Expand All @@ -20,11 +18,15 @@ import com.github.familyvault.ui.components.LoaderWithText
import com.github.familyvault.ui.components.filesCabinet.LoadingCard
import com.github.familyvault.ui.components.filesCabinet.PhotoCard
import com.github.familyvault.ui.theme.AdditionalTheme
import com.github.familyvault.utils.TimeFormatter
import familyvault.composeapp.generated.resources.Res
import familyvault.composeapp.generated.resources.loading
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.koinInject

Expand All @@ -34,60 +36,80 @@ fun PhotosTabContent() {
val imagePicker = koinInject<IImagePickerService>()
val storeId = fileCabinetService.retrieveFileCabinetStoreId()

var imageByteArrays by remember { mutableStateOf<List<ByteArray>>(emptyList()) }
var imageData by remember { mutableStateOf<List<Pair<ByteArray, Long>>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) }
var fullScreenImage by remember { mutableStateOf<ImageBitmap?>(null) }

LaunchedEffect(Unit) {
isLoading = true
imageByteArrays = withContext(Dispatchers.IO) {
imageData = withContext(Dispatchers.IO) {
fileCabinetService.getImagesFromFamilyGroupStoreAsByteArray(
storeId = storeId,
limit = 30,
skip = 0
).filterNotNull()
)
}
isLoading = false
}

if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
LoaderWithText(
stringResource(Res.string.loading), modifier = Modifier.fillMaxSize()
stringResource(Res.string.loading),
modifier = Modifier.fillMaxSize()
)
}
} else {
val groupedByDate = remember(imageData) {
imageData
.sortedByDescending { it.second }
.groupBy { (_, timestamp) -> TimeFormatter.formatDate(timestamp) }
}

LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(AdditionalTheme.spacings.small),
horizontalArrangement = Arrangement.spacedBy(AdditionalTheme.spacings.small),
contentPadding = PaddingValues(AdditionalTheme.spacings.small)
) {
items(imageByteArrays.size) { index ->
val imageBytes = imageByteArrays[index]

val imageBitmapState = produceState<ImageBitmap?>(initialValue = null, imageBytes) {
withContext(Dispatchers.IO) {
value = imagePicker.getBitmapFromBytes(imageBytes)
}
groupedByDate.forEach { (date, images) ->
item(span = { GridItemSpan(maxLineSpan) }) {
Text(
text = date,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.fillMaxWidth()
.padding(AdditionalTheme.spacings.medium)
)
}

val bitmap = imageBitmapState.value
if (bitmap == null) {
LoadingCard()
} else {
PhotoCard(
imageBitmap = bitmap,
onClick = { fullScreenImage = bitmap }
)
gridItems(images) { (imageBytes, _) ->
val imageBitmapState =
produceState<ImageBitmap?>(initialValue = null, imageBytes) {
withContext(Dispatchers.IO) {
value = imagePicker.getBitmapFromBytes(imageBytes)
}
}

val bitmap = imageBitmapState.value
if (bitmap == null) {
LoadingCard()
} else {
PhotoCard(
imageBitmap = bitmap,
onClick = { fullScreenImage = bitmap }
)
}
}
}
}
}

if (fullScreenImage != null) {
fullScreenImage?.let { image ->
FullScreenImage(
imageBitmap = fullScreenImage!!,
imageBitmap = image,
onDismiss = { fullScreenImage = null }
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.github.familyvault.utils

import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime

object TimeFormatter {
fun formatTime(time: LocalDateTime): String =
Expand All @@ -15,4 +18,13 @@ object TimeFormatter {

return minuteString
}

fun formatDate(timestamp: Long): String {
val fileDateTime = Instant.fromEpochMilliseconds(timestamp)
.toLocalDateTime(TimeZone.currentSystemDefault())
val fileDate = fileDateTime.date

Comment thread
6ajmon marked this conversation as resolved.
Outdated
val monthName = fileDate.month.name.lowercase().replaceFirstChar { it.uppercase() }
return "${fileDate.dayOfMonth} $monthName ${fileDate.year}"
}
}