diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt index 483ec6a3c8..8531830af4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt @@ -11,6 +11,7 @@ import org.signal.donations.InAppPaymentType import org.thoughtcrime.securesms.MainActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity +import org.thoughtcrime.securesms.components.settings.app.chats.folders.CreateFoldersFragmentArgs import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.EditNotificationProfileScheduleFragmentArgs import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository @@ -68,6 +69,10 @@ class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent { StartLocation.USERNAME_LINK -> AppSettingsFragmentDirections.actionDirectToUsernameLinkSettings() StartLocation.RECOVER_USERNAME -> AppSettingsFragmentDirections.actionDirectToUsernameRecovery() StartLocation.REMOTE_BACKUPS -> AppSettingsFragmentDirections.actionDirectToRemoteBackupsSettingsFragment() + StartLocation.CHAT_FOLDERS -> AppSettingsFragmentDirections.actionDirectToChatFoldersFragment() + StartLocation.CREATE_CHAT_FOLDER -> AppSettingsFragmentDirections.actionDirectToCreateFoldersFragment( + CreateFoldersFragmentArgs.fromBundle(intent.getBundleExtra(START_ARGUMENTS)!!).folderId + ) } } @@ -198,6 +203,18 @@ class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent { @JvmStatic fun remoteBackups(context: Context): Intent = getIntentForStartLocation(context, StartLocation.REMOTE_BACKUPS) + @JvmStatic + fun chatFolders(context: Context): Intent = getIntentForStartLocation(context, StartLocation.CHAT_FOLDERS) + + @JvmStatic + fun createChatFolder(context: Context, id: Long = -1): Intent { + val arguments = CreateFoldersFragmentArgs.Builder(id) + .build() + .toBundle() + + return getIntentForStartLocation(context, StartLocation.CREATE_CHAT_FOLDER).putExtra(START_ARGUMENTS, arguments) + } + private fun getIntentForStartLocation(context: Context, startLocation: StartLocation): Intent { return Intent(context, AppSettingsActivity::class.java) .putExtra(ARG_NAV_GRAPH, R.navigation.app_settings_with_change_number) @@ -222,7 +239,9 @@ class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent { LINKED_DEVICES(13), USERNAME_LINK(14), RECOVER_USERNAME(15), - REMOTE_BACKUPS(16); + REMOTE_BACKUPS(16), + CHAT_FOLDERS(17), + CREATE_CHAT_FOLDER(18); companion object { fun fromCode(code: Int?): StartLocation { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFolderContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFolderContextMenu.kt new file mode 100644 index 0000000000..e80a1df2f4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFolderContextMenu.kt @@ -0,0 +1,122 @@ +package org.thoughtcrime.securesms.components.settings.app.chats.folders + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import org.signal.core.util.DimensionUnit +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.menu.ActionItem +import org.thoughtcrime.securesms.components.menu.SignalContextMenu + +/** + * A context menu shown when long pressing on a chat folder. + */ +object ChatFolderContextMenu { + + fun show( + context: Context, + anchorView: View, + rootView: ViewGroup = anchorView.rootView as ViewGroup, + folderType: ChatFolderRecord.FolderType, + onEdit: () -> Unit = {}, + onAdd: () -> Unit = {}, + onMuteAll: () -> Unit = {}, + onReadAll: () -> Unit = {}, + onDelete: () -> Unit = {}, + onReorder: () -> Unit = {} + ) { + show( + context = context, + anchorView = anchorView, + rootView = rootView, + folderType = folderType, + callbacks = object : Callbacks { + override fun onEdit() = onEdit() + override fun onAdd() = onAdd() + override fun onMuteAll() = onMuteAll() + override fun onReadAll() = onReadAll() + override fun onDelete() = onDelete() + override fun onReorder() = onReorder() + } + ) + } + + private fun show( + context: Context, + anchorView: View, + rootView: ViewGroup, + folderType: ChatFolderRecord.FolderType, + callbacks: Callbacks + ) { + val actions = mutableListOf().apply { + if (folderType == ChatFolderRecord.FolderType.ALL) { + add( + ActionItem(R.drawable.symbol_plus_24, context.getString(R.string.ChatFoldersFragment__add_new_folder)) { + callbacks.onAdd() + } + ) + add( + ActionItem(R.drawable.symbol_bell_slash_24, context.getString(R.string.ChatFoldersFragment__mute_all)) { + callbacks.onMuteAll() + } + ) + add( + ActionItem(R.drawable.symbol_chat_check, context.getString(R.string.ChatFoldersFragment__mark_all_read)) { + callbacks.onReadAll() + } + ) + add( + ActionItem(R.drawable.symbol_exchange_24, context.getString(R.string.ChatFoldersFragment__reorder_folder)) { + callbacks.onReorder() + } + ) + } else { + add( + ActionItem(R.drawable.symbol_edit_24, context.getString(R.string.ChatFoldersFragment__edit_folder)) { + callbacks.onEdit() + } + ) + add( + ActionItem(R.drawable.symbol_plus_24, context.getString(R.string.ChatFoldersFragment__add_new_folder)) { + callbacks.onAdd() + } + ) + add( + ActionItem(R.drawable.symbol_bell_slash_24, context.getString(R.string.ChatFoldersFragment__mute_all)) { + callbacks.onMuteAll() + } + ) + add( + ActionItem(R.drawable.symbol_chat_check, context.getString(R.string.ChatFoldersFragment__mark_all_read)) { + callbacks.onReadAll() + } + ) + add( + ActionItem(R.drawable.symbol_trash_24, context.getString(R.string.ChatFoldersFragment__delete_folder)) { + callbacks.onDelete() + } + ) + add( + ActionItem(R.drawable.symbol_exchange_24, context.getString(R.string.ChatFoldersFragment__reorder_folder)) { + callbacks.onReorder() + } + ) + } + } + + SignalContextMenu.Builder(anchorView, rootView) + .preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.START) + .preferredVerticalPosition(SignalContextMenu.VerticalPosition.BELOW) + .offsetY(DimensionUnit.DP.toPixels(8f).toInt()) + .show(actions) + } + + private interface Callbacks { + fun onEdit() + fun onAdd() + fun onMuteAll() + fun onReadAll() + fun onDelete() + fun onReorder() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersFragment.kt index c6079035ab..0e2a342685 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersFragment.kt @@ -77,8 +77,7 @@ class ChatFoldersFragment : ComposeFragment() { state = state, modifier = Modifier.padding(contentPadding), onFolderClicked = { - viewModel.setCurrentFolder(it) - navController.safeNavigate(R.id.action_chatFoldersFragment_to_createFoldersFragment) + navController.safeNavigate(ChatFoldersFragmentDirections.actionChatFoldersFragmentToCreateFoldersFragment(it.id)) }, onAdd = { folder -> Toast.makeText(requireContext(), getString(R.string.ChatFoldersFragment__folder_added, folder.name), Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersRepository.kt index 54d65b7673..c8fb037850 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersRepository.kt @@ -40,4 +40,8 @@ object ChatFoldersRepository { fun updatePositions(folders: List) { SignalDatabase.chatFolders.updatePositions(folders) } + + fun getFolder(id: Long): ChatFolderRecord { + return SignalDatabase.chatFolders.getChatFolder(id) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersViewModel.kt index fe85436910..420db6e0f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/ChatFoldersViewModel.kt @@ -28,7 +28,12 @@ class ChatFoldersViewModel : ViewModel() { val suggestedFolders = getSuggestedFolders(context, folders) internalState.update { - it.copy(folders = folders, suggestedFolders = suggestedFolders) + it.copy( + folders = folders, + suggestedFolders = suggestedFolders, + currentFolder = ChatFolderRecord(), + originalFolder = ChatFolderRecord() + ) } } } @@ -310,4 +315,13 @@ class ChatFoldersViewModel : ViewModel() { originalFolder.excludedRecipients != currentFolder.excludedRecipients } } + + fun setCurrentFolderId(folderId: Long) { + if (folderId != -1L) { + viewModelScope.launch(Dispatchers.IO) { + val folder = ChatFoldersRepository.getFolder(folderId) + setCurrentFolder(folder) + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/CreateFoldersFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/CreateFoldersFragment.kt index 296c22ed71..1b907ac69b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/CreateFoldersFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/folders/CreateFoldersFragment.kt @@ -27,6 +27,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -87,6 +88,12 @@ class CreateFoldersFragment : ComposeFragment() { val focusRequester = remember { FocusRequester() } val isNewFolder = state.originalFolder.id == -1L + LaunchedEffect(Unit) { + if (state.originalFolder == state.currentFolder) { + viewModel.setCurrentFolderId(arguments?.getLong(KEY_FOLDER_ID) ?: -1) + } + } + Scaffolds.Settings( title = if (isNewFolder) stringResource(id = R.string.CreateFoldersFragment__create_a_folder) else stringResource(id = R.string.CreateFoldersFragment__edit_folder), onNavigationClick = { @@ -143,6 +150,10 @@ class CreateFoldersFragment : ComposeFragment() { ) } } + + companion object { + private val KEY_FOLDER_ID = "folder_id" + } } @Composable diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ChatFolderAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ChatFolderAdapter.kt index 10f33091f5..82dd3d9285 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ChatFolderAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ChatFolderAdapter.kt @@ -6,6 +6,7 @@ import android.view.View import android.widget.TextView import androidx.core.content.ContextCompat import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderContextMenu import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter @@ -36,6 +37,20 @@ class ChatFolderAdapter(val callbacks: Callbacks) : MappingAdapter() { itemView.setOnClickListener { callbacks.onChatFolderClicked(model.chatFolder) } + itemView.setOnLongClickListener { view -> + ChatFolderContextMenu.show( + context = itemView.context, + anchorView = view, + folderType = model.chatFolder.folderType, + onEdit = { callbacks.onEdit(model.chatFolder) }, + onAdd = { callbacks.onAdd() }, + onMuteAll = { callbacks.onMuteAll(model.chatFolder) }, + onReadAll = { callbacks.onReadAll(model.chatFolder) }, + onDelete = { callbacks.onDelete(model.chatFolder) }, + onReorder = { callbacks.onReorder() } + ) + true + } if (model.isSelected) { itemView.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.signal_colorSurfaceVariant)) } else { @@ -54,5 +69,11 @@ class ChatFolderAdapter(val callbacks: Callbacks) : MappingAdapter() { interface Callbacks { fun onChatFolderClicked(chatFolder: ChatFolderRecord) + fun onEdit(chatFolder: ChatFolderRecord) + fun onAdd() + fun onMuteAll(chatFolder: ChatFolderRecord) + fun onReadAll(chatFolder: ChatFolderRecord) + fun onDelete(chatFolder: ChatFolderRecord) + fun onReorder() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 1b0a3858a9..c91ba37a3d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -1666,6 +1666,44 @@ public class ConversationListFragment extends MainFragment implements ActionMode viewModel.select(chatFolder); } + @Override + public void onEdit(@NonNull ChatFolderRecord chatFolder) { + startActivity(AppSettingsActivity.createChatFolder(requireContext(), chatFolder.getId())); + } + + @Override + public void onAdd() { + startActivity(AppSettingsActivity.createChatFolder(requireContext(), -1)); + } + + @Override + public void onMuteAll(@NonNull ChatFolderRecord chatFolder) { + MuteDialog.show(requireContext(), until -> viewModel.onMuteChatFolder(chatFolder, until)); + } + + @Override + public void onReadAll(@NonNull ChatFolderRecord chatFolder) { + if (chatFolder.getFolderType() == ChatFolderRecord.FolderType.ALL) { + handleMarkAllRead(); + } else { + viewModel.markChatFolderRead(chatFolder); + } + } + + @Override + public void onDelete(@NonNull ChatFolderRecord chatFolder) { + new MaterialAlertDialogBuilder(requireActivity()) + .setMessage(getString(R.string.CreateFoldersFragment__delete_this_chat_folder)) + .setPositiveButton(R.string.delete, (dialog, which) -> viewModel.deleteChatFolder(chatFolder)) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + @Override + public void onReorder() { + startActivity(AppSettingsActivity.chatFolders(requireContext())); + } + private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback { private static final long SWIPE_ANIMATION_DURATION = 175; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.kt index 8aa2abe23e..62a30fb54e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.kt @@ -31,7 +31,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.megaphone.Megaphone import org.thoughtcrime.securesms.megaphone.MegaphoneRepository import org.thoughtcrime.securesms.megaphone.Megaphones +import org.thoughtcrime.securesms.notifications.MarkReadReceiver import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.rx.RxStore import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState import java.util.concurrent.TimeUnit @@ -259,6 +262,41 @@ class ConversationListViewModel( } } + fun onMuteChatFolder(chatFolder: ChatFolderRecord, until: Long) { + viewModelScope.launch(Dispatchers.IO) { + val ids = SignalDatabase.threads.getRecipientIdsByChatFolder(chatFolder) + val recipientIds: List = ids.filter { id -> + Recipient.resolved(id).muteUntil != until + } + if (recipientIds.isNotEmpty()) { + SignalDatabase.recipients.setMuted(recipientIds, until) + } + } + } + + fun deleteChatFolder(chatFolder: ChatFolderRecord) { + viewModelScope.launch(Dispatchers.IO) { + SignalDatabase.chatFolders.deleteChatFolder(chatFolder) + val updatedFolders = folders.filter { folder -> folder.chatFolder.id != chatFolder.id } + + store.update { + it.copy( + currentFolder = updatedFolders.first().chatFolder, + chatFolders = updatedFolders + ) + } + } + } + + fun markChatFolderRead(chatFolder: ChatFolderRecord) { + viewModelScope.launch(Dispatchers.IO) { + val ids = SignalDatabase.threads.getThreadIdsByChatFolder(chatFolder) + val messageIds = SignalDatabase.threads.setRead(ids, false) + AppDependencies.messageNotifier.updateNotification(AppDependencies.application) + MarkReadReceiver.process(messageIds) + } + } + private data class ConversationListState( val chatFolders: List = emptyList(), val currentFolder: ChatFolderRecord = ChatFolderRecord(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt index 5fdcbe013e..17d182fe3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt @@ -9,6 +9,7 @@ import org.signal.core.util.groupBy import org.signal.core.util.insertInto import org.signal.core.util.readToList import org.signal.core.util.readToSingleInt +import org.signal.core.util.readToSingleObject import org.signal.core.util.requireBoolean import org.signal.core.util.requireInt import org.signal.core.util.requireLong @@ -117,6 +118,37 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat .run() } + /** + * Returns a single chat folder that corresponds to that id + */ + fun getChatFolder(id: Long): ChatFolderRecord { + val includedChats: Map> = getIncludedChats(id) + val excludedChats: Map> = getExcludedChats(id) + + val folder = readableDatabase + .select() + .from(ChatFolderTable.TABLE_NAME) + .where("${ChatFolderTable.ID} = ?", id) + .run() + .readToSingleObject { cursor -> + ChatFolderRecord( + id = id, + name = cursor.requireString(ChatFolderTable.NAME) ?: "", + position = cursor.requireInt(ChatFolderTable.POSITION), + showUnread = cursor.requireBoolean(ChatFolderTable.SHOW_UNREAD), + showMutedChats = cursor.requireBoolean(ChatFolderTable.SHOW_MUTED), + showIndividualChats = cursor.requireBoolean(ChatFolderTable.SHOW_INDIVIDUAL), + showGroupChats = cursor.requireBoolean(ChatFolderTable.SHOW_GROUPS), + isMuted = cursor.requireBoolean(ChatFolderTable.IS_MUTED), + folderType = ChatFolderRecord.FolderType.deserialize(cursor.requireInt(ChatFolderTable.FOLDER_TYPE)), + includedChats = includedChats[id] ?: emptyList(), + excludedChats = excludedChats[id] ?: emptyList() + ) + } + + return folder ?: ChatFolderRecord() + } + /** * Maps the chat folder ids to its corresponding chat folder */ @@ -158,13 +190,20 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat } /** - * Maps chat folder ids to all of its corresponding included chats + * Maps a chat folder id to all of its corresponding included chats. + * If an id is not specified, all chat folder ids will be mapped. */ - private fun getIncludedChats(): Map> { + private fun getIncludedChats(id: Long? = null): Map> { + val whereQuery = if (id != null) { + "${ChatFolderMembershipTable.MEMBERSHIP_TYPE} = ${MembershipType.INCLUDED.value} AND ${ChatFolderMembershipTable.CHAT_FOLDER_ID} = $id" + } else { + "${ChatFolderMembershipTable.MEMBERSHIP_TYPE} = ${MembershipType.INCLUDED.value}" + } + return readableDatabase .select() .from(ChatFolderMembershipTable.TABLE_NAME) - .where("${ChatFolderMembershipTable.MEMBERSHIP_TYPE} = ${MembershipType.INCLUDED.value}") + .where(whereQuery) .run() .groupBy { cursor -> cursor.requireLong(ChatFolderMembershipTable.CHAT_FOLDER_ID) to cursor.requireLong(ChatFolderMembershipTable.THREAD_ID) @@ -172,13 +211,20 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat } /** - * Maps the chat folder ids to all of its corresponding excluded chats + * Maps a chat folder id to all of its corresponding excluded chats. + * If an id is not specified, all chat folder ids will be mapped. */ - private fun getExcludedChats(): Map> { + private fun getExcludedChats(id: Long? = null): Map> { + val whereQuery = if (id != null) { + "${ChatFolderMembershipTable.MEMBERSHIP_TYPE} = ${MembershipType.EXCLUDED.value} AND ${ChatFolderMembershipTable.CHAT_FOLDER_ID} = $id" + } else { + "${ChatFolderMembershipTable.MEMBERSHIP_TYPE} = ${MembershipType.EXCLUDED.value}" + } + return readableDatabase .select() .from(ChatFolderMembershipTable.TABLE_NAME) - .where("${ChatFolderMembershipTable.MEMBERSHIP_TYPE} = ${MembershipType.EXCLUDED.value}") + .where(whereQuery) .run() .groupBy { cursor -> cursor.requireLong(ChatFolderMembershipTable.CHAT_FOLDER_ID) to cursor.requireLong(ChatFolderMembershipTable.THREAD_ID) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt index cfc51dc966..add4951c75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -1041,6 +1041,47 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } } + fun getThreadIdsByChatFolder(chatFolder: ChatFolderRecord): List { + val folderQuery = chatFolder.toQuery() + val query = + """ + SELECT ${TABLE_NAME}.$ID + FROM $TABLE_NAME + LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} + WHERE + $ACTIVE = 1 + $folderQuery + """ + return readableDatabase.rawQuery(query, null).readToList { cursor -> cursor.requireLong(ID) } + } + + fun getRecipientIdsByChatFolder(chatFolder: ChatFolderRecord): List { + return if (chatFolder.folderType == ChatFolderRecord.FolderType.ALL) { + readableDatabase + .select(RECIPIENT_ID) + .from(TABLE_NAME) + .where("$ACTIVE = 1") + .run() + .readToList { cursor -> + RecipientId.from(cursor.requireLong(RECIPIENT_ID)) + } + } else { + val folderQuery = chatFolder.toQuery() + val query = + """ + SELECT $RECIPIENT_ID + FROM $TABLE_NAME + LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} + WHERE + $ACTIVE = 1 + $folderQuery + """ + readableDatabase.rawQuery(query, null).readToList { cursor -> + RecipientId.from(cursor.requireLong(RECIPIENT_ID)) + } + } + } + /** * @return Pinned recipients, in order from top to bottom. */ diff --git a/app/src/main/res/drawable/symbol_chat_check.xml b/app/src/main/res/drawable/symbol_chat_check.xml new file mode 100644 index 0000000000..e0e5d84271 --- /dev/null +++ b/app/src/main/res/drawable/symbol_chat_check.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/symbol_exchange_24.xml b/app/src/main/res/drawable/symbol_exchange_24.xml new file mode 100644 index 0000000000..25eedddc5d --- /dev/null +++ b/app/src/main/res/drawable/symbol_exchange_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/navigation/app_settings.xml b/app/src/main/res/navigation/app_settings.xml index 21cfae37b9..b92fb855ad 100644 --- a/app/src/main/res/navigation/app_settings.xml +++ b/app/src/main/res/navigation/app_settings.xml @@ -368,6 +368,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dddfda66d1..df34a9f123 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5069,6 +5069,18 @@ Add %1$s folder added. + + Edit folder + + Delete folder + + Add new folder + + Mute all + + Reorder folders + + Mark all read %1$d chat type