Skip to content
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

feat: View conversations, read/unread messages, conversation deleting #56

Merged
merged 10 commits into from
Sep 20, 2023
18 changes: 15 additions & 3 deletions app/schemas/tech.relaycorp.letro.storage.LetroDatabase/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "12466b7fa30a962804c156ee00283227",
"identityHash": "94e396e1f0c1805cb42e862839856f3f",
"entities": [
{
"tableName": "account",
Expand Down Expand Up @@ -110,7 +110,7 @@
},
{
"tableName": "conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`keyId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `conversationId` BLOB NOT NULL, `ownerVeraId` TEXT NOT NULL, `contactVeraId` TEXT NOT NULL, `subject` TEXT)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`keyId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `conversationId` BLOB NOT NULL, `ownerVeraId` TEXT NOT NULL, `contactVeraId` TEXT NOT NULL, `isRead` INTEGER NOT NULL, `subject` TEXT, `isArchived` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "keyId",
Expand All @@ -136,11 +136,23 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isRead",
"columnName": "isRead",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isArchived",
"columnName": "isArchived",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
Expand Down Expand Up @@ -212,7 +224,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '12466b7fa30a962804c156ee00283227')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '94e396e1f0c1805cb42e862839856f3f')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import tech.relaycorp.letro.ui.theme.LargeProminent
import tech.relaycorp.letro.ui.theme.LetroColor
import tech.relaycorp.letro.ui.theme.SmallProminent
import tech.relaycorp.letro.ui.utils.SnackbarStringsProvider
import tech.relaycorp.letro.utils.ext.showSnackbar

@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand All @@ -54,9 +55,7 @@ fun ContactsScreen(

LaunchedEffect(Unit) {
viewModel.showContactDeletedSnackbarSignal.collect {
snackbarHostState.showSnackbar(
message = snackbarStringsProvider.contactDeleted,
)
snackbarHostState.showSnackbar(this, snackbarStringsProvider.contactDeleted)
}
}

Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/tech/relaycorp/letro/di/MainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,29 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import tech.relaycorp.letro.ui.utils.ConversationsStringsProvider
import tech.relaycorp.letro.ui.utils.ConversationsStringsProviderImpl
import tech.relaycorp.letro.ui.utils.SnackbarStringsProvider
import tech.relaycorp.letro.ui.utils.SnackbarStringsProviderImpl
import tech.relaycorp.letro.ui.utils.StringsProvider
import tech.relaycorp.letro.ui.utils.StringsProviderImpl

@Module
@InstallIn(ActivityComponent::class)
interface MainModule {

@Binds
fun bindStringsProvider(
impl: StringsProviderImpl,
): StringsProvider

@Binds
fun bindSnackbarStringsProvider(
impl: SnackbarStringsProviderImpl,
): SnackbarStringsProvider

@Binds
fun bindConversationsStringsProvider(
impl: ConversationsStringsProviderImpl,
): ConversationsStringsProvider
}
11 changes: 8 additions & 3 deletions app/src/main/java/tech/relaycorp/letro/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import androidx.hilt.navigation.compose.hiltViewModel
import tech.relaycorp.letro.contacts.ContactsViewModel
import tech.relaycorp.letro.contacts.model.Contact
import tech.relaycorp.letro.contacts.ui.ContactsScreen
import tech.relaycorp.letro.home.tabs.LetroTabs
import tech.relaycorp.letro.messages.list.ConversationsListScreen
import tech.relaycorp.letro.messages.list.ConversationsViewModel
import tech.relaycorp.letro.ui.utils.SnackbarStringsProvider
import tech.relaycorp.letro.messages.model.ExtendedConversation
import tech.relaycorp.letro.ui.utils.StringsProvider

@Composable
fun HomeScreen(
homeViewModel: HomeViewModel,
snackbarStringsProvider: SnackbarStringsProvider,
stringsProvider: StringsProvider,
onConversationClick: (ExtendedConversation) -> Unit,
onEditContactClick: (Contact) -> Unit,
snackbarHostState: SnackbarHostState,
conversationsViewModel: ConversationsViewModel = hiltViewModel(),
Expand All @@ -38,13 +41,15 @@ fun HomeScreen(
when (uiState.currentTab) {
TAB_CHATS -> Column {
ConversationsListScreen(
conversationsStringsProvider = stringsProvider.conversations,
onConversationClick = onConversationClick,
viewModel = conversationsViewModel,
)
}
TAB_CONTACTS -> ContactsScreen(
viewModel = contactsViewModel,
snackbarHostState = snackbarHostState,
snackbarStringsProvider = snackbarStringsProvider,
snackbarStringsProvider = stringsProvider.snackbar,
onEditContactClick = { onEditContactClick(it) },
)
TAB_NOTIFICATIONS -> Column {
Expand Down
31 changes: 30 additions & 1 deletion app/src/main/java/tech/relaycorp/letro/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tech.relaycorp.letro.messages.model.ExtendedConversation
import tech.relaycorp.letro.messages.repository.ConversationsRepository
import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor() : ViewModel() {
class HomeViewModel @Inject constructor(
private val conversationsRepository: ConversationsRepository,
) : ViewModel() {

private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState>
Expand All @@ -22,6 +26,14 @@ class HomeViewModel @Inject constructor() : ViewModel() {
val createNewMessageSignal: SharedFlow<Unit>
get() = _createNewMessageSignal

init {
viewModelScope.launch {
conversationsRepository.conversations.collect {
updateTabBadges(it)
}
}
}

fun onTabClick(index: Int) {
viewModelScope.launch {
_uiState.update {
Expand Down Expand Up @@ -60,6 +72,22 @@ class HomeViewModel @Inject constructor() : ViewModel() {
}
}

private fun updateTabBadges(conversations: List<ExtendedConversation>) {
val unreadConversationsCount = conversations.count { !it.isRead }
val badge = when {
unreadConversationsCount > 9 -> "9+"
unreadConversationsCount > 0 -> unreadConversationsCount.toString()
else -> null
}
_uiState.update {
it.copy(
tabCounters = mapOf(
TAB_CHATS to badge,
),
)
}
}

private fun getFloatingActionButtonConfig(tabIndex: Int) = when (tabIndex) {
TAB_CHATS -> HomeFloatingActionButtonConfig.ChatListFloatingActionButtonConfig
TAB_CONTACTS -> HomeFloatingActionButtonConfig.ContactsFloatingActionButtonConfig
Expand All @@ -70,6 +98,7 @@ class HomeViewModel @Inject constructor() : ViewModel() {

data class HomeUiState(
val currentTab: Int = TAB_CHATS,
val tabCounters: Map<Int, String?> = emptyMap(),
val floatingActionButtonConfig: HomeFloatingActionButtonConfig? = HomeFloatingActionButtonConfig.ChatListFloatingActionButtonConfig,
val isAddContactFloatingMenuVisible: Boolean = false,
)
Expand Down
134 changes: 134 additions & 0 deletions app/src/main/java/tech/relaycorp/letro/home/tabs/LetroTabs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package tech.relaycorp.letro.home.tabs

import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import tech.relaycorp.letro.R
import tech.relaycorp.letro.home.HomeViewModel
import tech.relaycorp.letro.ui.theme.LetroColor

@SuppressLint
@Composable
fun LetroTabs(
viewModel: HomeViewModel,
modifier: Modifier = Modifier,
) {
val uiState by viewModel.uiState.collectAsState()

val tabTitles = listOf(
stringResource(id = R.string.top_bar_tab_conversations),
stringResource(id = R.string.top_bar_tab_contacts),
stringResource(id = R.string.top_bar_tab_notifications),
)
val tabCounters = uiState.tabCounters

ScrollableTabRow(
selectedTabIndex = uiState.currentTab,
containerColor = LetroColor.SurfaceContainerHigh,
contentColor = LetroColor.OnSurfaceContainerHigh,
edgePadding = 9.dp,
indicator = {
TabRowDefaults.Indicator(
color = LetroColor.OnSurfaceContainerHigh,
modifier = Modifier.tabIndicatorOffset(it[uiState.currentTab]),
)
},
modifier = modifier,
) {
tabTitles.forEachIndexed { index, title ->
BadgedTab(
selected = uiState.currentTab == index,
onClick = {
viewModel.onTabClick(index)
},
text = title,
badge = tabCounters[index],
modifier = Modifier
.padding(horizontal = 0.dp)
.alpha(if (uiState.currentTab == index) 1f else 0.6f),
)
}
}
}

@Composable
private fun BadgedTab(
modifier: Modifier = Modifier,
selected: Boolean,
onClick: () -> Unit,
text: String,
badge: String? = null,
) {
Tab(
selected = selected,
onClick = { onClick() },
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = text,
style = MaterialTheme.typography.labelLarge,
color = LetroColor.OnSurfaceContainerHigh,
maxLines = 1,
)
if (badge != null) {
Spacer(modifier = Modifier.width(4.dp))
TabBadge(text = badge)
}
}
},
selectedContentColor = LetroColor.OnSurfaceContainerHigh,
unselectedContentColor = MaterialTheme.colorScheme.error,
modifier = modifier,
)
}

@Composable
private fun TabBadge(
text: String,
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = LetroColor.OnSurfaceContainerHigh,
shape = CircleShape,
)
.size(16.dp),
) {
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
color = LetroColor.SurfaceContainerHigh,
maxLines = 1,
)
}
}

@Preview(showBackground = true)
@Composable
fun PreviewTabBadge() {
TabBadge(text = "1")
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ fun CreateNewMessageScreen(
value = recipientTextFieldValueState,
textStyle = MaterialTheme.typography.bodyLarge,
onValueChange = {
if (uiState.showRecipientAsChip) {
return@LetroTextField
}
recipientTextFieldValueState = it
viewModel.onRecipientTextChanged(it.text)
},
Expand Down
Loading
Loading