mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 19:18:37 +00:00
Indicate when chats already belong in folder.
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user