Indicate when chats already belong in folder.

This commit is contained in:
Michelle Tang
2024-10-29 14:14:20 -04:00
committed by GitHub
parent 1b9129b4a0
commit 0b66e9409d
3 changed files with 75 additions and 14 deletions

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -14,15 +15,18 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -50,13 +54,22 @@ class AddToFolderBottomSheet private constructor() : ComposeBottomSheetDialogFra
companion object { companion object {
private const val ARG_FOLDERS = "argument.folders" private const val ARG_FOLDERS = "argument.folders"
private const val ARG_THREAD_ID = "argument.thread.id" private const val ARG_THREAD_ID = "argument.thread.id"
private const val ARG_IS_INDIVIDUAL_CHAT = "argument.is.individual.chat"
/**
* Shows a bottom sheet that allows a thread to be added to a folder.
*
* @param folders list of available folders to add a thread to
* @param threadId the thread that is going to be added
* @param isIndividualChat whether the thread is an individual/1:1 chat as opposed to a group chat
*/
@JvmStatic @JvmStatic
fun showChatFolderSheet(folders: List<ChatFolderRecord>, threadId: Long): ComposeBottomSheetDialogFragment { fun showChatFolderSheet(folders: List<ChatFolderRecord>, threadId: Long, isIndividualChat: Boolean): ComposeBottomSheetDialogFragment {
return AddToFolderBottomSheet().apply { return AddToFolderBottomSheet().apply {
arguments = bundleOf( arguments = bundleOf(
ARG_FOLDERS to folders, ARG_FOLDERS to folders,
ARG_THREAD_ID to threadId ARG_THREAD_ID to threadId,
ARG_IS_INDIVIDUAL_CHAT to isIndividualChat
) )
} }
} }
@@ -64,17 +77,22 @@ class AddToFolderBottomSheet private constructor() : ComposeBottomSheetDialogFra
@Composable @Composable
override fun SheetContent() { override fun SheetContent() {
val folders = arguments?.getParcelableArrayListCompat(ARG_FOLDERS, ChatFolderRecord::class.java)?.filter { it.folderType != ChatFolderRecord.FolderType.ALL } val folders = requireArguments().getParcelableArrayListCompat(ARG_FOLDERS, ChatFolderRecord::class.java)?.filter { it.folderType != ChatFolderRecord.FolderType.ALL }
val threadId = arguments?.getLong(ARG_THREAD_ID) val threadId = requireArguments().getLong(ARG_THREAD_ID)
val isIndividualChat = requireArguments().getBoolean(ARG_IS_INDIVIDUAL_CHAT)
AddToChatFolderSheetContent( AddToChatFolderSheetContent(
threadId = threadId,
isIndividualChat = isIndividualChat,
folders = remember { folders ?: emptyList() }, folders = remember { folders ?: emptyList() },
onClick = { folder -> onClick = { folder, isAlreadyAdded ->
if (threadId != null) { if (isAlreadyAdded) {
Toast.makeText(context, requireContext().getString(R.string.AddToFolderBottomSheet_this_chat_is_already, folder.name), Toast.LENGTH_SHORT).show()
} else {
viewModel.addToFolder(folder.id, threadId) viewModel.addToFolder(folder.id, threadId)
Toast.makeText(context, requireContext().getString(R.string.AddToFolderBottomSheet_added_to_s, folder.name), Toast.LENGTH_SHORT).show() Toast.makeText(context, requireContext().getString(R.string.AddToFolderBottomSheet_added_to_s, folder.name), Toast.LENGTH_SHORT).show()
}
dismissAllowingStateLoss() dismissAllowingStateLoss()
}
}, },
onCreate = { onCreate = {
requireContext().startActivity(AppSettingsActivity.createChatFolder(requireContext(), -1, threadId)) requireContext().startActivity(AppSettingsActivity.createChatFolder(requireContext(), -1, threadId))
@@ -86,8 +104,10 @@ class AddToFolderBottomSheet private constructor() : ComposeBottomSheetDialogFra
@Composable @Composable
private fun AddToChatFolderSheetContent( private fun AddToChatFolderSheetContent(
threadId: Long,
isIndividualChat: Boolean,
folders: List<ChatFolderRecord>, folders: List<ChatFolderRecord>,
onClick: (ChatFolderRecord) -> Unit = {}, onClick: (ChatFolderRecord, Boolean) -> Unit = { _, _ -> },
onCreate: () -> Unit = {} onCreate: () -> Unit = {}
) { ) {
Column( Column(
@@ -110,10 +130,16 @@ private fun AddToChatFolderSheetContent(
.background(color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(18.dp)) .background(color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(18.dp))
) { ) {
items(folders) { folder -> items(folders) { folder ->
val isIncludedViaChatType = (isIndividualChat && folder.showIndividualChats) || (!isIndividualChat && folder.showGroupChats)
val isIncludedExplicitly = folder.includedChats.contains(threadId)
val isExcludedExplicitly = folder.excludedChats.contains(threadId)
val isAlreadyAdded = (isIncludedExplicitly || isIncludedViaChatType) && !isExcludedExplicitly
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.clickable(onClick = { onClick(folder) }) .clickable(onClick = { onClick(folder, isAlreadyAdded) })
.padding(start = 24.dp) .padding(start = 24.dp)
.fillMaxWidth() .fillMaxWidth()
.defaultMinSize(minHeight = dimensionResource(id = R.dimen.chat_folder_row_height)) .defaultMinSize(minHeight = dimensionResource(id = R.dimen.chat_folder_row_height))
@@ -133,6 +159,24 @@ private fun AddToChatFolderSheetContent(
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 16.dp) modifier = Modifier.padding(start = 16.dp)
) )
Spacer(modifier = Modifier.weight(1f))
if (isAlreadyAdded) {
Icon(
painterResource(R.drawable.symbol_check_white_24),
contentDescription = null,
tint = Color.White,
modifier = Modifier
.padding(end = 16.dp)
.size(24.dp)
.background(
color = Color.Black.copy(.40f),
shape = CircleShape
)
.padding(4.dp)
)
}
} }
} }
@@ -171,7 +215,9 @@ private fun AddToChatFolderSheetContent(
private fun AddToChatFolderSheetContentPreview() { private fun AddToChatFolderSheetContentPreview() {
Previews.BottomSheetPreview { Previews.BottomSheetPreview {
AddToChatFolderSheetContent( AddToChatFolderSheetContent(
folders = listOf(ChatFolderRecord(name = "Friends"), ChatFolderRecord(name = "Work")) folders = listOf(ChatFolderRecord(name = "Friends"), ChatFolderRecord(name = "Work")),
threadId = 1,
isIndividualChat = false
) )
} }
} }

View File

@@ -33,6 +33,7 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -1066,7 +1067,10 @@ public class ConversationListFragment extends MainFragment implements ActionMode
private void onChatFoldersChanged(List<ChatFolderMappingModel> folders) { private void onChatFoldersChanged(List<ChatFolderMappingModel> folders) {
chatFolderList.setVisibility(folders.size() > 1 && !isArchived() ? View.VISIBLE : View.GONE); chatFolderList.setVisibility(folders.size() > 1 && !isArchived() ? View.VISIBLE : View.GONE);
chatFolderAdapter.submitList(new ArrayList<>(folders)); if (chatFolderList.getLayoutManager() != null) {
Parcelable savedState = chatFolderList.getLayoutManager().onSaveInstanceState();
chatFolderAdapter.submitList(new ArrayList<>(folders), () -> chatFolderList.getLayoutManager().onRestoreInstanceState(savedState));
}
} }
private void onMegaphoneChanged(@NonNull Megaphone megaphone) { private void onMegaphoneChanged(@NonNull Megaphone megaphone) {
@@ -1485,10 +1489,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode
if (conversation.getThreadRecord().isArchived()) { if (conversation.getThreadRecord().isArchived()) {
items.add(new ActionItem(R.drawable.symbol_archive_up_24, getResources().getString(R.string.ConversationListFragment_unarchive), () -> handleArchive(id, false))); items.add(new ActionItem(R.drawable.symbol_archive_up_24, getResources().getString(R.string.ConversationListFragment_unarchive), () -> handleArchive(id, false)));
} else { } else {
if (viewModel.getCurrentFolder().getFolderType() == ChatFolderRecord.FolderType.ALL) { if (viewModel.getCurrentFolder().getFolderType() == ChatFolderRecord.FolderType.ALL &&
conversation.getThreadRecord().getRecipient().isIndividual() ||
conversation.getThreadRecord().getRecipient().isPushV2Group()) {
List<ChatFolderRecord> folders = viewModel.getFolders().stream().map(ChatFolderMappingModel::getChatFolder).collect(Collectors.toList()); List<ChatFolderRecord> folders = viewModel.getFolders().stream().map(ChatFolderMappingModel::getChatFolder).collect(Collectors.toList());
items.add(new ActionItem(R.drawable.symbol_folder_add, getString(R.string.ConversationListFragment_add_to_folder), () -> items.add(new ActionItem(R.drawable.symbol_folder_add, getString(R.string.ConversationListFragment_add_to_folder), () ->
AddToFolderBottomSheet.showChatFolderSheet(folders, conversation.getThreadRecord().getThreadId()).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) AddToFolderBottomSheet.showChatFolderSheet(folders, conversation.getThreadRecord().getThreadId(), conversation.getThreadRecord().getRecipient().isIndividual()).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
)); ));
} else { } else {
items.add(new ActionItem(R.drawable.symbol_folder_minus, getString(R.string.ConversationListFragment_remove_from_folder), () -> viewModel.removeChatFromFolder(conversation.getThreadRecord().getThreadId()))); items.add(new ActionItem(R.drawable.symbol_folder_minus, getString(R.string.ConversationListFragment_remove_from_folder), () -> viewModel.removeChatFromFolder(conversation.getThreadRecord().getThreadId())));
@@ -1713,6 +1719,14 @@ public class ConversationListFragment extends MainFragment implements ActionMode
} }
} }
if (isScrolled()) {
list.smoothScrollToPosition(0);
}
if (oldIndex == newIndex) {
return;
}
if (oldIndex < newIndex) { if (oldIndex < newIndex) {
smoothScroller.setTargetPosition(Math.min(newIndex + 1, viewModel.getFolders().size())); smoothScroller.setTargetPosition(Math.min(newIndex + 1, viewModel.getFolders().size()));
} else { } else {
@@ -1724,7 +1738,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
} }
viewModel.select(chatFolder); viewModel.select(chatFolder);
list.smoothScrollToPosition(0);
} }
@Override @Override

View File

@@ -749,6 +749,8 @@
<string name="AddToFolderBottomSheet_choose_a_folder">Choose a folder</string> <string name="AddToFolderBottomSheet_choose_a_folder">Choose a folder</string>
<!-- Toast shown when a chat has been added to a folder, where %s is the name of the folder --> <!-- Toast shown when a chat has been added to a folder, where %s is the name of the folder -->
<string name="AddToFolderBottomSheet_added_to_s">Added to \"%1$s\"</string> <string name="AddToFolderBottomSheet_added_to_s">Added to \"%1$s\"</string>
<!-- Toast shown when a user tries to add a chat to a folder, but the folder already has that chat. %s is the name of the folder -->
<string name="AddToFolderBottomSheet_this_chat_is_already">This chat is already in the folder \"%1$s\"</string>
<!-- Show in conversation list overflow menu to open selection bottom sheet --> <!-- Show in conversation list overflow menu to open selection bottom sheet -->
<string name="ConversationListFragment__notification_profile">Notification profile</string> <string name="ConversationListFragment__notification_profile">Notification profile</string>