Adds attachment viewer when clicking on an attachment
This commit is contained in:
@@ -4,12 +4,16 @@ import androidx.compose.material.AlertDialog
|
|||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.NavHost
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
@@ -17,6 +21,7 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import net.buzzert.kordophone.backend.server.ChatRepository
|
import net.buzzert.kordophone.backend.server.ChatRepository
|
||||||
|
|
||||||
import net.buzzert.kordophonedroid.data.AppContainer
|
import net.buzzert.kordophonedroid.data.AppContainer
|
||||||
|
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewer
|
||||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTheme
|
import net.buzzert.kordophonedroid.ui.theme.KordophoneTheme
|
||||||
import net.buzzert.kordophonedroid.ui.conversationlist.ConversationListScreen
|
import net.buzzert.kordophonedroid.ui.conversationlist.ConversationListScreen
|
||||||
import net.buzzert.kordophonedroid.ui.messagelist.MessageListScreen
|
import net.buzzert.kordophonedroid.ui.messagelist.MessageListScreen
|
||||||
@@ -30,7 +35,13 @@ sealed class Destination(val route: String) {
|
|||||||
object MessageList : Destination("messages/{id}") {
|
object MessageList : Destination("messages/{id}") {
|
||||||
fun createRoute(data: String) = "messages/$data"
|
fun createRoute(data: String) = "messages/$data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object AttachmentViewer : Destination("attachment/{guid}") {
|
||||||
|
fun createRoute(guid: String) = "attachment/$guid"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val LocalNavController = compositionLocalOf<NavHostController> { error("No nav host") }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorDialog(title: String, body: String, onDismiss: () -> Unit) {
|
fun ErrorDialog(title: String, body: String, onDismiss: () -> Unit) {
|
||||||
@@ -61,29 +72,28 @@ fun KordophoneApp(
|
|||||||
errorVisible.value = error.value
|
errorVisible.value = error.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalNavController provides navController) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = Destination.ConversationList.route,
|
startDestination = Destination.ConversationList.route,
|
||||||
) {
|
) {
|
||||||
composable(route = Destination.ConversationList.route) {
|
composable(route = Destination.ConversationList.route) {
|
||||||
ConversationListScreen(onConversationSelected = {
|
ConversationListScreen()
|
||||||
navController.navigate(Destination.MessageList.createRoute(it))
|
|
||||||
}, onSettingsInvoked = {
|
|
||||||
navController.navigate(Destination.Settings.route)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(Destination.MessageList.route) {
|
composable(Destination.MessageList.route) {
|
||||||
val conversationID = it.arguments?.getString("id")!!
|
val conversationID = it.arguments?.getString("id")!!
|
||||||
MessageListScreen(conversationGUID = conversationID, backAction = {
|
MessageListScreen(conversationGUID = conversationID)
|
||||||
navController.popBackStack()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(Destination.Settings.route) {
|
composable(Destination.Settings.route) {
|
||||||
SettingsScreen(backAction = {
|
SettingsScreen()
|
||||||
navController.popBackStack()
|
}
|
||||||
})
|
|
||||||
|
composable(Destination.AttachmentViewer.route) {
|
||||||
|
val guid = it.arguments?.getString("guid")!!
|
||||||
|
AttachmentViewer(attachmentGuid = guid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package net.buzzert.kordophonedroid.ui.attachments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import coil.Coil
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.ImageLoaderFactory
|
||||||
|
import coil.decode.DataSource
|
||||||
|
import coil.decode.ImageSource
|
||||||
|
import coil.disk.DiskCache
|
||||||
|
import coil.fetch.FetchResult
|
||||||
|
import coil.fetch.Fetcher
|
||||||
|
import coil.fetch.SourceResult
|
||||||
|
import coil.memory.MemoryCache
|
||||||
|
import coil.request.Options
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import net.buzzert.kordophone.backend.server.ChatRepository
|
||||||
|
import net.buzzert.kordophonedroid.ui.messagelist.MVM_LOG
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class AttachmentFetchData(
|
||||||
|
val guid: String,
|
||||||
|
val preview: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AttachmentViewModel @Inject constructor(
|
||||||
|
private val repository: ChatRepository,
|
||||||
|
@ApplicationContext val application: Context,
|
||||||
|
) : ViewModel(), ImageLoaderFactory, Fetcher.Factory<AttachmentFetchData>
|
||||||
|
{
|
||||||
|
init {
|
||||||
|
// Register Coil image loader
|
||||||
|
Coil.setImageLoader(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newImageLoader(): ImageLoader {
|
||||||
|
val factory = this
|
||||||
|
return ImageLoader.Builder(application)
|
||||||
|
.memoryCache {
|
||||||
|
MemoryCache.Builder(application)
|
||||||
|
.maxSizePercent(0.25)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
.diskCache {
|
||||||
|
DiskCache.Builder()
|
||||||
|
.directory(application.cacheDir.resolve("attachments"))
|
||||||
|
.maxSizePercent(0.02)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
.components {
|
||||||
|
// Adds the FetcherFactory
|
||||||
|
add(factory)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(
|
||||||
|
data: AttachmentFetchData,
|
||||||
|
options: Options,
|
||||||
|
imageLoader: ImageLoader
|
||||||
|
): Fetcher {
|
||||||
|
return AttachmentFetcher(repository, application, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AttachmentFetcher(
|
||||||
|
val repository: ChatRepository,
|
||||||
|
val context: Context,
|
||||||
|
val data: AttachmentFetchData
|
||||||
|
): Fetcher {
|
||||||
|
override suspend fun fetch(): FetchResult {
|
||||||
|
Log.d(MVM_LOG, "Loading attachment ${data.guid} from network")
|
||||||
|
val source = repository.fetchAttachmentDataSource(data.guid, data.preview)
|
||||||
|
return SourceResult(
|
||||||
|
source = ImageSource(source, context),
|
||||||
|
dataSource = DataSource.NETWORK,
|
||||||
|
mimeType = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package net.buzzert.kordophonedroid.ui.attachments
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import net.buzzert.kordophonedroid.ui.LocalNavController
|
||||||
|
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AttachmentViewer(
|
||||||
|
attachmentGuid: String,
|
||||||
|
attachmentViewModel: AttachmentViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
Scaffold(topBar = {
|
||||||
|
KordophoneTopAppBar(
|
||||||
|
title = "Attachment",
|
||||||
|
backAction = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}) { padding ->
|
||||||
|
val data = AttachmentFetchData(attachmentGuid, preview = false)
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(data)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(padding)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.buzzert.kordophone.backend.model.Conversation
|
import net.buzzert.kordophone.backend.model.Conversation
|
||||||
|
import net.buzzert.kordophonedroid.ui.Destination
|
||||||
|
import net.buzzert.kordophonedroid.ui.LocalNavController
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
@@ -51,14 +53,10 @@ fun formatDateTime(dateTime: LocalDateTime): String {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ConversationListScreen(
|
fun ConversationListScreen(
|
||||||
viewModel: ConversationListViewModel = hiltViewModel(),
|
viewModel: ConversationListViewModel = hiltViewModel(),
|
||||||
onConversationSelected: (conversationID: String) -> Unit,
|
|
||||||
onSettingsInvoked: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val conversations by viewModel.conversations.collectAsStateWithLifecycle(initialValue = emptyList())
|
val conversations by viewModel.conversations.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
ConversationListView(
|
ConversationListView(
|
||||||
conversations = conversations,
|
conversations = conversations,
|
||||||
onConversationSelected = onConversationSelected,
|
|
||||||
onSettingsInvoked = onSettingsInvoked,
|
|
||||||
onRefresh = suspend { viewModel.refresh() }
|
onRefresh = suspend { viewModel.refresh() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -67,8 +65,6 @@ fun ConversationListScreen(
|
|||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
fun ConversationListView(
|
fun ConversationListView(
|
||||||
conversations: List<Conversation>,
|
conversations: List<Conversation>,
|
||||||
onConversationSelected: (conversationID: String) -> Unit,
|
|
||||||
onSettingsInvoked: () -> Unit,
|
|
||||||
onRefresh: suspend () -> Unit,
|
onRefresh: suspend () -> Unit,
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -84,6 +80,8 @@ fun ConversationListView(
|
|||||||
|
|
||||||
val refreshState = rememberPullRefreshState(refreshing = refreshing, onRefresh = ::refresh)
|
val refreshState = rememberPullRefreshState(refreshing = refreshing, onRefresh = ::refresh)
|
||||||
|
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val onSettingsInvoked = { navController.navigate(Destination.Settings.route) }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(title = { Text("Conversations") }, actions = {
|
TopAppBar(title = { Text("Conversations") }, actions = {
|
||||||
@@ -101,13 +99,18 @@ fun ConversationListView(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
items(conversations) { conversation ->
|
items(conversations) { conversation ->
|
||||||
|
val clickHandler = {
|
||||||
|
val route = Destination.MessageList.createRoute(conversation.guid)
|
||||||
|
navController.navigate(route)
|
||||||
|
}
|
||||||
|
|
||||||
ConversationListItem(
|
ConversationListItem(
|
||||||
name = conversation.formattedDisplayName(),
|
name = conversation.formattedDisplayName(),
|
||||||
id = conversation.guid,
|
id = conversation.guid,
|
||||||
isUnread = conversation.unreadCount > 0,
|
isUnread = conversation.unreadCount > 0,
|
||||||
lastMessagePreview = conversation.lastMessagePreview ?: "",
|
lastMessagePreview = conversation.lastMessagePreview ?: "",
|
||||||
date = conversation.date,
|
date = conversation.date,
|
||||||
onClick = { onConversationSelected(conversation.guid) }
|
onClick = clickHandler
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,5 +220,5 @@ fun ConversationListItemPreview() {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun ConversationListScreenPreview() {
|
fun ConversationListScreenPreview() {
|
||||||
ConversationListScreen(onConversationSelected = {}, onSettingsInvoked = {})
|
ConversationListScreen()
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package net.buzzert.kordophonedroid.ui.messagelist
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -42,6 +43,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.ImageLoaderFactory
|
import coil.ImageLoaderFactory
|
||||||
@@ -52,6 +54,10 @@ import coil.request.ImageRequest
|
|||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
import net.buzzert.kordophonedroid.R
|
import net.buzzert.kordophonedroid.R
|
||||||
|
import net.buzzert.kordophonedroid.ui.Destination
|
||||||
|
import net.buzzert.kordophonedroid.ui.LocalNavController
|
||||||
|
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
|
||||||
|
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel
|
||||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
@@ -83,8 +89,8 @@ sealed class MessageListItem: MessageMetadataProvider {
|
|||||||
@Composable
|
@Composable
|
||||||
fun MessageListScreen(
|
fun MessageListScreen(
|
||||||
conversationGUID: GUID,
|
conversationGUID: GUID,
|
||||||
backAction: () -> Unit,
|
|
||||||
viewModel: MessageListViewModel = hiltViewModel(),
|
viewModel: MessageListViewModel = hiltViewModel(),
|
||||||
|
attachmentViewModel: AttachmentViewModel = hiltViewModel() // unused, but initialized for Coil
|
||||||
) {
|
) {
|
||||||
viewModel.conversationGUID = conversationGUID
|
viewModel.conversationGUID = conversationGUID
|
||||||
|
|
||||||
@@ -97,7 +103,7 @@ fun MessageListScreen(
|
|||||||
|
|
||||||
val messages by viewModel.messages.collectAsStateWithLifecycle(initialValue = listOf())
|
val messages by viewModel.messages.collectAsStateWithLifecycle(initialValue = listOf())
|
||||||
|
|
||||||
var messageItems = mutableListOf<MessageListItem>()
|
val messageItems = mutableListOf<MessageListItem>()
|
||||||
for (message in messages) {
|
for (message in messages) {
|
||||||
val metadata = MessageMetadata(
|
val metadata = MessageMetadata(
|
||||||
fromMe = message.sender == null,
|
fromMe = message.sender == null,
|
||||||
@@ -125,7 +131,11 @@ fun MessageListScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(topBar = { KordophoneTopAppBar(title = viewModel.title, backAction = backAction) }) { padding ->
|
val navController = LocalNavController.current
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
KordophoneTopAppBar(title = viewModel.title, backAction = { navController.popBackStack() })
|
||||||
|
}) { padding ->
|
||||||
MessageTranscript(
|
MessageTranscript(
|
||||||
messages = messageItems,
|
messages = messageItems,
|
||||||
paddingValues = padding,
|
paddingValues = padding,
|
||||||
@@ -181,7 +191,6 @@ fun Messages(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
scrollState: LazyListState
|
scrollState: LazyListState
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
reverseLayout = true,
|
reverseLayout = true,
|
||||||
@@ -331,14 +340,13 @@ fun ImageBubble(
|
|||||||
) {
|
) {
|
||||||
val shape: RoundedCornerShape = if (mine) OutgoingChatBubbleShape else IncomingChatBubbleShape
|
val shape: RoundedCornerShape = if (mine) OutgoingChatBubbleShape else IncomingChatBubbleShape
|
||||||
val attachmentFetchData = AttachmentFetchData(guid, preview = true)
|
val attachmentFetchData = AttachmentFetchData(guid, preview = true)
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
|
||||||
BubbleScaffold(mine = mine, modifier = modifier) {
|
BubbleScaffold(mine = mine, modifier = modifier) {
|
||||||
SubcomposeAsyncImage(
|
SubcomposeAsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data(attachmentFetchData)
|
.data(attachmentFetchData)
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.diskCacheKey(attachmentFetchData.guid)
|
|
||||||
.memoryCacheKey(attachmentFetchData.guid)
|
|
||||||
.build(),
|
.build(),
|
||||||
loading = {
|
loading = {
|
||||||
Box(
|
Box(
|
||||||
@@ -357,6 +365,9 @@ fun ImageBubble(
|
|||||||
contentDescription = "Image attachment",
|
contentDescription = "Image attachment",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(shape)
|
.clip(shape)
|
||||||
|
.clickable {
|
||||||
|
navController.navigate(Destination.AttachmentViewer.createRoute(guid))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import net.buzzert.kordophone.backend.model.Conversation
|
|||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
import net.buzzert.kordophone.backend.model.Message
|
import net.buzzert.kordophone.backend.model.Message
|
||||||
import net.buzzert.kordophone.backend.server.ChatRepository
|
import net.buzzert.kordophone.backend.server.ChatRepository
|
||||||
|
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
|
||||||
|
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel
|
||||||
import okio.ByteString.Companion.toByteString
|
import okio.ByteString.Companion.toByteString
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
@@ -48,16 +50,10 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
|
|
||||||
const val MVM_LOG: String = "MessageListViewModel"
|
const val MVM_LOG: String = "MessageListViewModel"
|
||||||
|
|
||||||
data class AttachmentFetchData(
|
|
||||||
val guid: String,
|
|
||||||
val preview: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MessageListViewModel @Inject constructor(
|
class MessageListViewModel @Inject constructor(
|
||||||
private val repository: ChatRepository,
|
private val repository: ChatRepository
|
||||||
@ApplicationContext val application: Context,
|
) : ViewModel()
|
||||||
) : ViewModel(), ImageLoaderFactory, Fetcher.Factory<AttachmentFetchData>
|
|
||||||
{
|
{
|
||||||
var conversationGUID: GUID? = null
|
var conversationGUID: GUID? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -78,9 +74,6 @@ class MessageListViewModel @Inject constructor(
|
|||||||
init {
|
init {
|
||||||
// TODO: Need to handle settings changes here!!
|
// TODO: Need to handle settings changes here!!
|
||||||
|
|
||||||
// Register Coil image loader
|
|
||||||
Coil.setImageLoader(this)
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// Remove pending message after message is delivered.
|
// Remove pending message after message is delivered.
|
||||||
// By now, the repository should've committed this to the store.
|
// By now, the repository should've committed this to the store.
|
||||||
@@ -129,49 +122,5 @@ class MessageListViewModel @Inject constructor(
|
|||||||
fun synchronize() = viewModelScope.launch {
|
fun synchronize() = viewModelScope.launch {
|
||||||
repository.synchronizeConversation(conversation!!, limit = 100)
|
repository.synchronizeConversation(conversation!!, limit = 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun newImageLoader(): ImageLoader {
|
|
||||||
val factory = this
|
|
||||||
return ImageLoader.Builder(application)
|
|
||||||
.memoryCache {
|
|
||||||
MemoryCache.Builder(application)
|
|
||||||
.maxSizePercent(0.25)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
.diskCache {
|
|
||||||
DiskCache.Builder()
|
|
||||||
.directory(application.cacheDir.resolve("attachments"))
|
|
||||||
.maxSizePercent(0.02)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
.components {
|
|
||||||
// Adds the FetcherFactory
|
|
||||||
add(factory)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun create(
|
|
||||||
data: AttachmentFetchData,
|
|
||||||
options: Options,
|
|
||||||
imageLoader: ImageLoader
|
|
||||||
): Fetcher {
|
|
||||||
return AttachmentFetcher(repository, application, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AttachmentFetcher(
|
|
||||||
val repository: ChatRepository,
|
|
||||||
val context: Context,
|
|
||||||
val data: AttachmentFetchData
|
|
||||||
): Fetcher {
|
|
||||||
override suspend fun fetch(): FetchResult {
|
|
||||||
Log.d(MVM_LOG, "Loading attachment ${data.guid} from network")
|
|
||||||
val source = repository.fetchAttachmentDataSource(data.guid, data.preview)
|
|
||||||
return SourceResult(
|
|
||||||
source = ImageSource(source, context),
|
|
||||||
dataSource = DataSource.NETWORK,
|
|
||||||
mimeType = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -49,18 +49,19 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import net.buzzert.kordophonedroid.R
|
import net.buzzert.kordophonedroid.R
|
||||||
|
import net.buzzert.kordophonedroid.ui.LocalNavController
|
||||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
viewModel: SettingsViewModel = hiltViewModel(),
|
viewModel: SettingsViewModel = hiltViewModel(),
|
||||||
backAction: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
|
val navController = LocalNavController.current
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
KordophoneTopAppBar(
|
KordophoneTopAppBar(
|
||||||
title = "Settings",
|
title = "Settings",
|
||||||
backAction = backAction,
|
backAction = { navController.popBackStack() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -237,5 +238,5 @@ private fun EditDialog(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsPreview() {
|
fun SettingsPreview() {
|
||||||
SettingsScreen(backAction = {})
|
SettingsScreen()
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user