Skip to content

Commit

Permalink
Support AddToFolder for Multiple Selected Threads
Browse files Browse the repository at this point in the history
  • Loading branch information
Sagar0-0 committed Feb 6, 2025
1 parent 5973e96 commit f952222
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent {
StartLocation.CHAT_FOLDERS -> AppSettingsFragmentDirections.actionDirectToChatFoldersFragment()
StartLocation.CREATE_CHAT_FOLDER -> AppSettingsFragmentDirections.actionDirectToCreateFoldersFragment(
CreateFoldersFragmentArgs.fromBundle(intent.getBundleExtra(START_ARGUMENTS)!!).folderId,
CreateFoldersFragmentArgs.fromBundle(intent.getBundleExtra(START_ARGUMENTS)!!).threadId
CreateFoldersFragmentArgs.fromBundle(intent.getBundleExtra(START_ARGUMENTS)!!).threadIds
)
StartLocation.BACKUPS_SETTINGS -> AppSettingsFragmentDirections.actionDirectToBackupsSettingsFragment()
}
Expand Down Expand Up @@ -209,8 +209,8 @@ class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent {
fun chatFolders(context: Context): Intent = getIntentForStartLocation(context, StartLocation.CHAT_FOLDERS)

@JvmStatic
fun createChatFolder(context: Context, id: Long = -1, threadId: Long?): Intent {
val arguments = CreateFoldersFragmentArgs.Builder(id, threadId ?: -1)
fun createChatFolder(context: Context, id: Long = -1, threadIds: LongArray?): Intent {
val arguments = CreateFoldersFragmentArgs.Builder(id, threadIds ?: longArrayOf())
.build()
.toBundle()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ChatFoldersFragment : ComposeFragment() {
navController = navController,
modifier = Modifier.padding(contentPadding),
onFolderClicked = {
navController.safeNavigate(ChatFoldersFragmentDirections.actionChatFoldersFragmentToCreateFoldersFragment(it.id, -1))
navController.safeNavigate(ChatFoldersFragmentDirections.actionChatFoldersFragmentToCreateFoldersFragment(it.id, null))
},
onAdd = { folder ->
Toast.makeText(requireContext(), getString(R.string.ChatFoldersFragment__folder_added, folder.name), Toast.LENGTH_SHORT).show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,22 +225,27 @@ class ChatFoldersViewModel : ViewModel() {
}
}

fun addThreadToIncludedChat(threadId: Long?) {
if (threadId == null || threadId == -1L) {
fun addThreadsToFolder(threadIds: LongArray?) {
if (threadIds == null || threadIds.isEmpty()) {
return
}
viewModelScope.launch {
val updatedFolder = internalState.value.currentFolder
val recipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
if (recipient != null) {
internalState.update {
it.copy(
currentFolder = updatedFolder.copy(
includedRecipients = setOf(recipient)
)
)
val includedRecipients = mutableSetOf<Recipient>()
threadIds.forEach { threadId ->
val recipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
if(recipient != null) {
includedRecipients.add(recipient)
}
}

internalState.update {
it.copy(
currentFolder = updatedFolder.copy(
includedRecipients = includedRecipients
)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class CreateFoldersFragment : ComposeFragment() {
LaunchedEffect(Unit) {
if (state.originalFolder == state.currentFolder) {
viewModel.setCurrentFolderId(arguments?.getLong(KEY_FOLDER_ID) ?: -1)
viewModel.addThreadToIncludedChat(arguments?.getLong(KEY_THREAD_ID))
viewModel.addThreadsToFolder(arguments?.getLongArray(KEY_THREAD_IDS))
}
}

Expand Down Expand Up @@ -170,7 +170,7 @@ class CreateFoldersFragment : ComposeFragment() {

companion object {
private val KEY_FOLDER_ID = "folder_id"
private val KEY_THREAD_ID = "thread_id"
private val KEY_THREAD_IDS = "thread_ids"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,34 @@ import org.thoughtcrime.securesms.util.viewModel
/**
* Bottom sheet shown when choosing to add a chat to a folder
*/
class AddToFolderBottomSheet private constructor() : ComposeBottomSheetDialogFragment() {
class AddToFolderBottomSheet private constructor(private val onDismissListener: OnDismissListener) : ComposeBottomSheetDialogFragment() {

override val peekHeightPercentage: Float = 1f

interface OnDismissListener {
fun onDismiss()
}

private val viewModel by viewModel { ConversationListViewModel(isArchived = false) }

companion object {
private const val ARG_FOLDERS = "argument.folders"
private const val ARG_THREAD_ID = "argument.thread.id"
private const val ARG_THREAD_IDS = "argument.thread.ids"
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 threadIds list of threads that are going to be added
* @param isIndividualChat whether the thread is an individual/1:1 chat as opposed to a group chat
*/
@JvmStatic
fun showChatFolderSheet(folders: List<ChatFolderRecord>, threadId: Long, isIndividualChat: Boolean): ComposeBottomSheetDialogFragment {
return AddToFolderBottomSheet().apply {
fun showChatFolderSheet(folders: List<ChatFolderRecord>, threadIds: List<Long>, isIndividualChat: Boolean, onDismissListener: OnDismissListener): ComposeBottomSheetDialogFragment {
return AddToFolderBottomSheet(onDismissListener).apply {
arguments = bundleOf(
ARG_FOLDERS to folders,
ARG_THREAD_ID to threadId,
ARG_THREAD_IDS to threadIds.toLongArray(),
ARG_IS_INDIVIDUAL_CHAT to isIndividualChat
)
}
Expand All @@ -78,33 +82,35 @@ class AddToFolderBottomSheet private constructor() : ComposeBottomSheetDialogFra
@Composable
override fun SheetContent() {
val folders = requireArguments().getParcelableArrayListCompat(ARG_FOLDERS, ChatFolderRecord::class.java)?.filter { it.folderType != ChatFolderRecord.FolderType.ALL }
val threadId = requireArguments().getLong(ARG_THREAD_ID)
val threadIds = requireArguments().getLongArray(ARG_THREAD_IDS)?.asList() ?: throw IllegalArgumentException("At least one ThreadId is expected!")
val isIndividualChat = requireArguments().getBoolean(ARG_IS_INDIVIDUAL_CHAT)

AddToChatFolderSheetContent(
threadId = threadId,
threadIds = threadIds,
isIndividualChat = isIndividualChat,
folders = remember { folders ?: emptyList() },
onClick = { folder, isAlreadyAdded ->
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, threadIds)
Toast.makeText(context, requireContext().getString(R.string.AddToFolderBottomSheet_added_to_s, folder.name), Toast.LENGTH_SHORT).show()
dismissAllowingStateLoss()
onDismissListener.onDismiss()
}
},
onCreate = {
requireContext().startActivity(AppSettingsActivity.createChatFolder(requireContext(), -1, threadId))
requireContext().startActivity(AppSettingsActivity.createChatFolder(requireContext(), -1, threadIds.toLongArray()))
dismissAllowingStateLoss()
onDismissListener.onDismiss()
}
)
}
}

@Composable
private fun AddToChatFolderSheetContent(
threadId: Long,
threadIds: List<Long>,
isIndividualChat: Boolean,
folders: List<ChatFolderRecord>,
onClick: (ChatFolderRecord, Boolean) -> Unit = { _, _ -> },
Expand All @@ -131,8 +137,8 @@ private fun AddToChatFolderSheetContent(
) {
items(folders) { folder ->
val isIncludedViaChatType = (isIndividualChat && folder.showIndividualChats) || (!isIndividualChat && folder.showGroupChats)
val isIncludedExplicitly = folder.includedChats.contains(threadId)
val isExcludedExplicitly = folder.excludedChats.contains(threadId)
val isIncludedExplicitly = folder.includedChats.containsAll(threadIds)
val isExcludedExplicitly = folder.excludedChats.containsAll(threadIds)

val isAlreadyAdded = (isIncludedExplicitly || isIncludedViaChatType) && !isExcludedExplicitly

Expand Down Expand Up @@ -216,7 +222,7 @@ private fun AddToChatFolderSheetContentPreview() {
Previews.BottomSheetPreview {
AddToChatFolderSheetContent(
folders = listOf(ChatFolderRecord(name = "Friends"), ChatFolderRecord(name = "Work")),
threadId = 1,
threadIds = listOf(1),
isIndividualChat = false
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@
import org.thoughtcrime.securesms.megaphone.MegaphoneViewBuilder;
import org.thoughtcrime.securesms.megaphone.Megaphones;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment;
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment;
Expand Down Expand Up @@ -1508,9 +1507,10 @@ public boolean onConversationLongClick(@NonNull Conversation conversation, @NonN
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<Conversation> conversations = new ArrayList<>();
conversations.add(conversation);
items.add(new ActionItem(R.drawable.symbol_folder_add, getString(R.string.ConversationListFragment_add_to_folder), () ->
AddToFolderBottomSheet.showChatFolderSheet(folders, conversation.getThreadRecord().getThreadId(), conversation.getThreadRecord().getRecipient().isIndividual()).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
showAddToFolderBottomSheet(conversations)
));
} else if (viewModel.getCurrentFolder().getFolderType() != ChatFolderRecord.FolderType.ALL) {
items.add(new ActionItem(R.drawable.symbol_folder_minus, getString(R.string.ConversationListFragment_remove_from_folder), () -> viewModel.removeChatFromFolder(conversation.getThreadRecord().getThreadId())));
Expand Down Expand Up @@ -1580,6 +1580,27 @@ public void onEvent(MessageSender.MessageSentEvent event) {
closeSearchIfOpen();
}

private void showAddToFolderBottomSheet(List<Conversation> conversations) {
boolean isIndividual = false;
if(conversations.size()==1) {
isIndividual = conversations.get(0).getThreadRecord().getRecipient().isIndividual();
}
showAddToFolderBottomSheet(
conversations.stream().map(conversation -> conversation.getThreadRecord().getThreadId()).collect(Collectors.toList()),
isIndividual
);
}

private void showAddToFolderBottomSheet(List<Long> threadIds, Boolean isIndividual) {
List<ChatFolderRecord> folders = viewModel.getFolders().stream().map(ChatFolderMappingModel::getChatFolder).collect(Collectors.toList());
AddToFolderBottomSheet.showChatFolderSheet(
folders,
threadIds,
isIndividual,
this::endActionModeIfActive
).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
}

private void updateMultiSelectState() {
int count = viewModel.currentSelectedConversations().size();
boolean hasUnread = Stream.of(viewModel.currentSelectedConversations()).anyMatch(conversation -> !conversation.getThreadRecord().isRead());
Expand Down Expand Up @@ -1626,6 +1647,10 @@ private void updateMultiSelectState() {

items.add(new ActionItem(R.drawable.symbol_check_circle_24, getString(R.string.ConversationListFragment_select_all), viewModel::onSelectAllClick));

items.add(new ActionItem(R.drawable.symbol_folder_add, getString(R.string.ConversationListFragment_add_to_folder), () -> {
showAddToFolderBottomSheet(new ArrayList<>(selectionIds),false);
}));

bottomActionBar.setItems(items);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,13 @@ class ConversationListViewModel(
}
}

fun addToFolder(folderId: Long, threadId: Long) {
fun addToFolder(folderId: Long, threadIds: List<Long>) {
viewModelScope.launch(Dispatchers.IO) {
SignalDatabase.chatFolders.addToFolder(folderId, threadId)
val threadIdsAndIsIncluded = threadIds.map { threadId ->
val isAlreadyIncluded = folders.find { it.chatFolder.id == folderId }?.chatFolder?.includedChats?.contains(threadId) ?: false
Pair(threadId,isAlreadyIncluded)
}
SignalDatabase.chatFolders.addToFolderIfNotIncluded(folderId, threadIdsAndIsIncluded)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,15 +380,19 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat
/**
* Adds a thread to a chat folder
*/
fun addToFolder(folderId: Long, threadId: Long) {
fun addToFolderIfNotIncluded(folderId: Long, threadIdsAndIsIncluded: List<Pair<Long, Boolean>>) {
writableDatabase.withinTransaction { db ->
db.insertInto(ChatFolderMembershipTable.TABLE_NAME)
.values(
ChatFolderMembershipTable.CHAT_FOLDER_ID to folderId,
ChatFolderMembershipTable.THREAD_ID to threadId,
ChatFolderMembershipTable.MEMBERSHIP_TYPE to MembershipType.INCLUDED.value
)
.run(SQLiteDatabase.CONFLICT_REPLACE)
threadIdsAndIsIncluded.forEach { (threadId, isIncluded) ->
if (!isIncluded) {
db.insertInto(ChatFolderMembershipTable.TABLE_NAME)
.values(
ChatFolderMembershipTable.CHAT_FOLDER_ID to folderId,
ChatFolderMembershipTable.THREAD_ID to threadId,
ChatFolderMembershipTable.MEMBERSHIP_TYPE to MembershipType.INCLUDED.value
)
.run(SQLiteDatabase.CONFLICT_REPLACE)
}
}

AppDependencies.databaseObserver.notifyChatFolderObservers()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,9 @@
app:argType="long" />

<argument
android:name="thread_id"
app:argType="long" />
android:name="thread_ids"
app:argType="long[]"
app:nullable="true" />

<action
android:id="@+id/action_createFoldersFragment_to_chooseChatsFragment"
Expand Down

0 comments on commit f952222

Please sign in to comment.