Fix various bugs for chat folders.
This commit is contained in:
parent
b519bf6772
commit
dd4fcffec4
18 changed files with 228 additions and 144 deletions
|
@ -19,7 +19,8 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
@Composable
|
||||
fun AvatarImage(
|
||||
recipient: Recipient,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
useProfile: Boolean = true
|
||||
) {
|
||||
if (LocalInspectionMode.current) {
|
||||
Spacer(
|
||||
|
@ -31,7 +32,11 @@ fun AvatarImage(
|
|||
factory = ::AvatarImageView,
|
||||
modifier = modifier.background(color = Color.Transparent, shape = CircleShape)
|
||||
) {
|
||||
it.setAvatarUsingProfile(recipient)
|
||||
if (useProfile) {
|
||||
it.setAvatarUsingProfile(recipient)
|
||||
} else {
|
||||
it.setAvatar(recipient)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,25 +18,27 @@ object ChatFolderContextMenu {
|
|||
anchorView: View,
|
||||
rootView: ViewGroup = anchorView.rootView as ViewGroup,
|
||||
folderType: ChatFolderRecord.FolderType,
|
||||
unreadCount: Int,
|
||||
isMuted: Boolean,
|
||||
onEdit: () -> Unit = {},
|
||||
onAdd: () -> Unit = {},
|
||||
onMuteAll: () -> Unit = {},
|
||||
onUnmuteAll: () -> Unit = {},
|
||||
onReadAll: () -> Unit = {},
|
||||
onDelete: () -> Unit = {},
|
||||
onReorder: () -> Unit = {}
|
||||
onFolderSettings: () -> Unit = {}
|
||||
) {
|
||||
show(
|
||||
context = context,
|
||||
anchorView = anchorView,
|
||||
rootView = rootView,
|
||||
folderType = folderType,
|
||||
unreadCount = unreadCount,
|
||||
isMuted = isMuted,
|
||||
callbacks = object : Callbacks {
|
||||
override fun onEdit() = onEdit()
|
||||
override fun onAdd() = onAdd()
|
||||
override fun onMuteAll() = onMuteAll()
|
||||
override fun onUnmuteAll() = onUnmuteAll()
|
||||
override fun onReadAll() = onReadAll()
|
||||
override fun onDelete() = onDelete()
|
||||
override fun onReorder() = onReorder()
|
||||
override fun onFolderSettings() = onFolderSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -45,29 +47,38 @@ object ChatFolderContextMenu {
|
|||
context: Context,
|
||||
anchorView: View,
|
||||
rootView: ViewGroup,
|
||||
unreadCount: Int,
|
||||
isMuted: Boolean,
|
||||
folderType: ChatFolderRecord.FolderType,
|
||||
callbacks: Callbacks
|
||||
) {
|
||||
val actions = mutableListOf<ActionItem>().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()
|
||||
}
|
||||
)
|
||||
if (unreadCount > 0) {
|
||||
add(
|
||||
ActionItem(R.drawable.symbol_chat_check, context.getString(R.string.ChatFoldersFragment__mark_all_read)) {
|
||||
callbacks.onReadAll()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (isMuted) {
|
||||
add(
|
||||
ActionItem(R.drawable.symbol_exchange_24, context.getString(R.string.ChatFoldersFragment__reorder_folder)) {
|
||||
callbacks.onReorder()
|
||||
ActionItem(R.drawable.symbol_bell_24, context.getString(R.string.ChatFoldersFragment__unmute_all)) {
|
||||
callbacks.onUnmuteAll()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
ActionItem(R.drawable.symbol_bell_slash_24, context.getString(R.string.ChatFoldersFragment__mute_all)) {
|
||||
callbacks.onMuteAll()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (folderType == ChatFolderRecord.FolderType.ALL) {
|
||||
add(
|
||||
ActionItem(R.drawable.symbol_folder_settings, context.getString(R.string.conversation_list_fragment__folder_settings)) {
|
||||
callbacks.onFolderSettings()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
@ -76,31 +87,6 @@ object ChatFolderContextMenu {
|
|||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,10 +99,9 @@ object ChatFolderContextMenu {
|
|||
|
||||
private interface Callbacks {
|
||||
fun onEdit()
|
||||
fun onAdd()
|
||||
fun onMuteAll()
|
||||
fun onUnmuteAll()
|
||||
fun onReadAll()
|
||||
fun onDelete()
|
||||
fun onReorder()
|
||||
fun onFolderSettings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,24 +104,22 @@ fun FoldersScreen(
|
|||
}
|
||||
|
||||
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||
Column(modifier = Modifier.padding(start = 24.dp)) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.ChatFoldersFragment__organize_your_chats),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 12.dp, bottom = 12.dp, end = 12.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.ChatFoldersFragment__folders),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 12.dp)
|
||||
)
|
||||
FolderRow(
|
||||
icon = R.drawable.symbol_plus_compact_16,
|
||||
title = stringResource(R.string.ChatFoldersFragment__create_a_folder),
|
||||
onClick = { onFolderClicked(ChatFolderRecord()) }
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.ChatFoldersFragment__organize_your_chats),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 12.dp, bottom = 12.dp, end = 12.dp, start = 24.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.ChatFoldersFragment__folders),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 12.dp, start = 24.dp)
|
||||
)
|
||||
FolderRow(
|
||||
icon = R.drawable.symbol_plus_compact_16,
|
||||
title = stringResource(R.string.ChatFoldersFragment__create_a_folder),
|
||||
onClick = { onFolderClicked(ChatFolderRecord()) }
|
||||
)
|
||||
|
||||
val columnHeight = dimensionResource(id = R.dimen.chat_folder_row_height).value * state.folders.size
|
||||
LazyColumn(
|
||||
|
@ -142,8 +140,7 @@ fun FoldersScreen(
|
|||
{ onFolderClicked(folder) }
|
||||
} else null,
|
||||
elevation = elevation,
|
||||
showDragHandle = true,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
showDragHandle = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -167,8 +164,7 @@ fun FoldersScreen(
|
|||
icon = R.drawable.symbol_chat_badge_24,
|
||||
title = title,
|
||||
subtitle = stringResource(R.string.ChatFoldersFragment__unread_messages),
|
||||
onAdd = { onAdd(chatFolder) },
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onAdd = { onAdd(chatFolder) }
|
||||
)
|
||||
}
|
||||
ChatFolderRecord.FolderType.INDIVIDUAL -> {
|
||||
|
@ -177,8 +173,7 @@ fun FoldersScreen(
|
|||
icon = R.drawable.symbol_person_light_24,
|
||||
title = title,
|
||||
subtitle = stringResource(R.string.ChatFoldersFragment__only_direct_messages),
|
||||
onAdd = { onAdd(chatFolder) },
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onAdd = { onAdd(chatFolder) }
|
||||
)
|
||||
}
|
||||
ChatFolderRecord.FolderType.GROUP -> {
|
||||
|
@ -187,8 +182,7 @@ fun FoldersScreen(
|
|||
icon = R.drawable.symbol_group_light_20,
|
||||
title = title,
|
||||
subtitle = stringResource(R.string.ChatFoldersFragment__only_group_messages),
|
||||
onAdd = { onAdd(chatFolder) },
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onAdd = { onAdd(chatFolder) }
|
||||
)
|
||||
}
|
||||
ChatFolderRecord.FolderType.ALL -> {
|
||||
|
@ -239,17 +233,19 @@ fun FolderRow(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = if (onClick != null) {
|
||||
modifier
|
||||
.padding(end = 12.dp)
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = dimensionResource(id = R.dimen.chat_folder_row_height))
|
||||
.shadow(elevation = elevation)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(start = 24.dp, end = 12.dp)
|
||||
} else {
|
||||
modifier
|
||||
.padding(end = 12.dp)
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = dimensionResource(id = R.dimen.chat_folder_row_height))
|
||||
.shadow(elevation = elevation)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(start = 24.dp, end = 12.dp)
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
|
|
|
@ -7,8 +7,8 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
|||
*/
|
||||
object ChatFoldersRepository {
|
||||
|
||||
fun getCurrentFolders(includeUnreadCount: Boolean = false): List<ChatFolderRecord> {
|
||||
return SignalDatabase.chatFolders.getChatFolders(includeUnreadCount)
|
||||
fun getCurrentFolders(includeUnreadAndMutedCounts: Boolean = false): List<ChatFolderRecord> {
|
||||
return SignalDatabase.chatFolders.getChatFolders(includeUnreadAndMutedCounts)
|
||||
}
|
||||
|
||||
fun createFolder(folder: ChatFolderRecord) {
|
||||
|
|
|
@ -24,7 +24,7 @@ class ChatFoldersViewModel : ViewModel() {
|
|||
|
||||
fun loadCurrentFolders(context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val folders = ChatFoldersRepository.getCurrentFolders(includeUnreadCount = false)
|
||||
val folders = ChatFoldersRepository.getCurrentFolders(includeUnreadAndMutedCounts = false)
|
||||
val suggestedFolders = getSuggestedFolders(context, folders)
|
||||
|
||||
internalState.update {
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
|
@ -229,16 +230,14 @@ fun CreateFolderScreen(
|
|||
FolderRow(
|
||||
icon = R.drawable.symbol_plus_compact_16,
|
||||
title = stringResource(R.string.CreateFoldersFragment__add_chats),
|
||||
onClick = onAddChat,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onClick = onAddChat
|
||||
)
|
||||
|
||||
if (state.currentFolder.showIndividualChats) {
|
||||
FolderRow(
|
||||
icon = R.drawable.symbol_person_light_24,
|
||||
title = stringResource(R.string.ChatFoldersFragment__one_on_one_chats),
|
||||
onClick = onAddChat,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onClick = onAddChat
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -246,8 +245,7 @@ fun CreateFolderScreen(
|
|||
FolderRow(
|
||||
icon = R.drawable.symbol_group_light_20,
|
||||
title = stringResource(R.string.ChatFoldersFragment__groups),
|
||||
onClick = onAddChat,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onClick = onAddChat
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -277,8 +275,7 @@ fun CreateFolderScreen(
|
|||
FolderRow(
|
||||
icon = R.drawable.symbol_plus_compact_16,
|
||||
title = stringResource(R.string.CreateFoldersFragment__exclude_chats),
|
||||
onClick = onRemoveChat,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
onClick = onRemoveChat
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -334,6 +331,9 @@ fun CreateFolderScreen(
|
|||
}
|
||||
} else if (!isNewFolder) {
|
||||
Buttons.MediumTonal(
|
||||
colors = ButtonDefaults.filledTonalButtonColors(
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
),
|
||||
enabled = hasChanges,
|
||||
onClick = { onCreateConfirmed(true) },
|
||||
modifier = modifier
|
||||
|
@ -451,10 +451,11 @@ fun ChatRow(
|
|||
recipient = recipient,
|
||||
modifier = Modifier
|
||||
.padding(start = 24.dp, end = 16.dp)
|
||||
.size(40.dp)
|
||||
.size(40.dp),
|
||||
useProfile = false
|
||||
)
|
||||
}
|
||||
|
||||
Text(text = recipient.getShortDisplayName(LocalContext.current))
|
||||
Text(text = if (recipient.isSelf) stringResource(id = R.string.note_to_self) else recipient.getShortDisplayName(LocalContext.current))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ object SelectedContacts {
|
|||
private val chip: ContactChip = itemView.findViewById(R.id.contact_chip)
|
||||
|
||||
override fun bind(model: RecipientModel) {
|
||||
chip.text = model.recipient.getShortDisplayName(context)
|
||||
chip.text = if (model.recipient.isSelf) context.getString(R.string.note_to_self) else model.recipient.getShortDisplayName(context)
|
||||
chip.setContact(model.selectedContact)
|
||||
chip.isCloseIconVisible = true
|
||||
chip.setOnCloseIconClickListener {
|
||||
|
|
|
@ -42,17 +42,18 @@ class ChatFolderAdapter(val callbacks: Callbacks) : MappingAdapter() {
|
|||
context = itemView.context,
|
||||
anchorView = view,
|
||||
folderType = model.chatFolder.folderType,
|
||||
unreadCount = folder.unreadCount,
|
||||
isMuted = folder.isMuted,
|
||||
onEdit = { callbacks.onEdit(model.chatFolder) },
|
||||
onAdd = { callbacks.onAdd() },
|
||||
onMuteAll = { callbacks.onMuteAll(model.chatFolder) },
|
||||
onUnmuteAll = { callbacks.onUnmuteAll(model.chatFolder) },
|
||||
onReadAll = { callbacks.onReadAll(model.chatFolder) },
|
||||
onDelete = { callbacks.onDelete(model.chatFolder) },
|
||||
onReorder = { callbacks.onReorder() }
|
||||
onFolderSettings = { callbacks.onFolderSettings() }
|
||||
)
|
||||
true
|
||||
}
|
||||
if (model.isSelected) {
|
||||
itemView.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.signal_colorSurfaceVariant))
|
||||
itemView.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.signal_colorSurface2))
|
||||
} else {
|
||||
itemView.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.transparent))
|
||||
}
|
||||
|
@ -70,10 +71,9 @@ class ChatFolderAdapter(val callbacks: Callbacks) : MappingAdapter() {
|
|||
interface Callbacks {
|
||||
fun onChatFolderClicked(chatFolder: ChatFolderRecord)
|
||||
fun onEdit(chatFolder: ChatFolderRecord)
|
||||
fun onAdd()
|
||||
fun onMuteAll(chatFolder: ChatFolderRecord)
|
||||
fun onUnmuteAll(chatFolder: ChatFolderRecord)
|
||||
fun onReadAll(chatFolder: ChatFolderRecord)
|
||||
fun onDelete(chatFolder: ChatFolderRecord)
|
||||
fun onReorder()
|
||||
fun onFolderSettings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
|
@ -64,6 +65,7 @@ import androidx.lifecycle.ViewModelProvider;
|
|||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.airbnb.lottie.SimpleColorFilter;
|
||||
|
@ -243,6 +245,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
private SignalContextMenu activeContextMenu;
|
||||
private LifecycleDisposable lifecycleDisposable;
|
||||
private ChatFolderAdapter chatFolderAdapter;
|
||||
private RecyclerView.SmoothScroller smoothScroller;
|
||||
|
||||
protected ConversationListArchiveItemDecoration archiveDecoration;
|
||||
protected ConversationListItemAnimator itemAnimator;
|
||||
|
@ -458,7 +461,22 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
}
|
||||
}));
|
||||
|
||||
requireCallback().bindScrollHelper(list);
|
||||
requireCallback().bindScrollHelper(list, chatFolderList, color -> {
|
||||
for (int i = 0; i < chatFolderList.getChildCount(); i++) {
|
||||
View child = chatFolderList.getChildAt(i);
|
||||
if (child != null && child.isSelected()) {
|
||||
child.setBackgroundTintList(ColorStateList.valueOf(color));
|
||||
}
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
smoothScroller = new LinearSmoothScroller(requireContext()) {
|
||||
@Override
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
return 150;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1048,6 +1066,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
}
|
||||
|
||||
private void onChatFoldersChanged(List<ChatFolderMappingModel> folders) {
|
||||
chatFolderList.setVisibility(folders.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
chatFolderAdapter.submitList(new ArrayList<>(folders));
|
||||
}
|
||||
|
||||
|
@ -1673,7 +1692,35 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
|
||||
@Override
|
||||
public void onChatFolderClicked(@NonNull ChatFolderRecord chatFolder) {
|
||||
int oldIndex = -1;
|
||||
int newIndex = -1;
|
||||
|
||||
for (int i = 0; i < viewModel.getFolders().size(); i++) {
|
||||
if (oldIndex != -1 && newIndex != -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
ChatFolderMappingModel folder = viewModel.getFolders().get(i);
|
||||
if (folder.isSelected()) {
|
||||
oldIndex = i;
|
||||
}
|
||||
if (folder.getChatFolder().getId() == chatFolder.getId()) {
|
||||
newIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldIndex < newIndex) {
|
||||
smoothScroller.setTargetPosition(Math.min(newIndex + 1, viewModel.getFolders().size()));
|
||||
} else {
|
||||
smoothScroller.setTargetPosition(Math.max(newIndex - 1, 0));
|
||||
}
|
||||
|
||||
if (chatFolderList.getLayoutManager() != null) {
|
||||
chatFolderList.getLayoutManager().startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
viewModel.select(chatFolder);
|
||||
list.smoothScrollToPosition(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1682,13 +1729,13 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onAdd() {
|
||||
startActivity(AppSettingsActivity.createChatFolder(requireContext(), -1));
|
||||
public void onMuteAll(@NonNull ChatFolderRecord chatFolder) {
|
||||
MuteDialog.show(requireContext(), until -> viewModel.onUpdateMute(chatFolder, until));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMuteAll(@NonNull ChatFolderRecord chatFolder) {
|
||||
MuteDialog.show(requireContext(), until -> viewModel.onMuteChatFolder(chatFolder, until));
|
||||
public void onUnmuteAll(@NonNull ChatFolderRecord chatFolder) {
|
||||
viewModel.onUpdateMute(chatFolder, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1701,16 +1748,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
}
|
||||
|
||||
@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() {
|
||||
public void onFolderSettings() {
|
||||
startActivity(AppSettingsActivity.chatFolders(requireContext()));
|
||||
}
|
||||
|
||||
|
@ -1763,6 +1801,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
if (viewHolder.itemView instanceof ConversationListItemAction ||
|
||||
viewHolder instanceof ConversationListAdapter.HeaderViewHolder ||
|
||||
viewHolder instanceof ClearFilterViewHolder ||
|
||||
viewHolder instanceof ConversationListAdapter.EmptyFolderViewHolder ||
|
||||
actionMode != null ||
|
||||
viewHolder.itemView.isSelected() ||
|
||||
activeAdapter == searchAdapter)
|
||||
|
|
|
@ -8,7 +8,6 @@ import io.reactivex.rxjava3.core.BackpressureStrategy
|
|||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.Flowables
|
||||
import io.reactivex.rxjava3.kotlin.addTo
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -88,6 +87,7 @@ class ConversationListViewModel(
|
|||
conversationListDataSource = store
|
||||
.stateFlowable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.filter { it.currentFolder.id != -1L }
|
||||
.map { it.filterRequest to it.currentFolder }
|
||||
.distinctUntilChanged()
|
||||
.map { (filterRequest, folder) ->
|
||||
|
@ -115,7 +115,7 @@ class ConversationListViewModel(
|
|||
.subscribe { controller.onDataInvalidated() }
|
||||
.addTo(disposables)
|
||||
|
||||
Flowables.combineLatest(
|
||||
Flowable.merge(
|
||||
RxDatabaseObserver
|
||||
.conversationList
|
||||
.debounce(250, TimeUnit.MILLISECONDS),
|
||||
|
@ -219,7 +219,7 @@ class ConversationListViewModel(
|
|||
|
||||
private fun loadCurrentFolders() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val folders = ChatFoldersRepository.getCurrentFolders(includeUnreadCount = true)
|
||||
val folders = ChatFoldersRepository.getCurrentFolders(includeUnreadAndMutedCounts = true)
|
||||
|
||||
val selectedFolderId = if (currentFolder.id == -1L) {
|
||||
folders.firstOrNull()?.id
|
||||
|
@ -262,7 +262,7 @@ class ConversationListViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onMuteChatFolder(chatFolder: ChatFolderRecord, until: Long) {
|
||||
fun onUpdateMute(chatFolder: ChatFolderRecord, until: Long) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val ids = SignalDatabase.threads.getRecipientIdsByChatFolder(chatFolder)
|
||||
val recipientIds: List<RecipientId> = ids.filter { id ->
|
||||
|
@ -274,20 +274,6 @@ class ConversationListViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -152,7 +152,7 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat
|
|||
/**
|
||||
* Maps the chat folder ids to its corresponding chat folder
|
||||
*/
|
||||
fun getChatFolders(includeUnreads: Boolean = false): List<ChatFolderRecord> {
|
||||
fun getChatFolders(includeUnreadAndMutedCount: Boolean = false): List<ChatFolderRecord> {
|
||||
val includedChats: Map<Long, List<Long>> = getIncludedChats()
|
||||
val excludedChats: Map<Long, List<Long>> = getExcludedChats()
|
||||
|
||||
|
@ -178,10 +178,11 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat
|
|||
)
|
||||
}
|
||||
|
||||
if (includeUnreads) {
|
||||
if (includeUnreadAndMutedCount) {
|
||||
return folders.map { folder ->
|
||||
folder.copy(
|
||||
unreadCount = SignalDatabase.threads.getUnreadCountByChatFolder(folder)
|
||||
unreadCount = SignalDatabase.threads.getUnreadCountByChatFolder(folder),
|
||||
isMuted = !SignalDatabase.threads.hasUnmutedChatsInFolder(folder)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.signal.core.util.exists
|
|||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.or
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToSingleBoolean
|
||||
import org.signal.core.util.readToSingleInt
|
||||
import org.signal.core.util.readToSingleLong
|
||||
import org.signal.core.util.requireBoolean
|
||||
|
@ -631,6 +632,26 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||
return allCount + forcedUnreadCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not there are any unmuted chats in a chat folder
|
||||
*/
|
||||
fun hasUnmutedChatsInFolder(folder: ChatFolderRecord): Boolean {
|
||||
val chatFolderQuery = folder.toQuery()
|
||||
|
||||
val unmutedChats =
|
||||
"""
|
||||
SELECT COUNT(${RecipientTable.MUTE_UNTIL})
|
||||
FROM $TABLE_NAME
|
||||
LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
|
||||
WHERE
|
||||
$ARCHIVED = 0 AND
|
||||
${RecipientTable.MUTE_UNTIL} = 0
|
||||
$chatFolderQuery
|
||||
"""
|
||||
|
||||
return readableDatabase.rawQuery(unmutedChats, null).readToSingleBoolean()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of unread messages across all threads within a chat folder
|
||||
* Threads that are forced-unread count as 1.
|
||||
|
|
|
@ -386,4 +386,14 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
|||
viewLifecycleOwner
|
||||
).attach(recyclerView)
|
||||
}
|
||||
|
||||
override fun bindScrollHelper(recyclerView: RecyclerView, chatFolders: RecyclerView, setChatFolder: (Int) -> Unit) {
|
||||
Material3OnScrollHelper(
|
||||
activity = requireActivity(),
|
||||
views = listOf(_toolbarBackground, chatFolders),
|
||||
viewStubs = listOf(_searchToolbar),
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
setChatFolderColor = setChatFolder
|
||||
).attach(recyclerView)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,5 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
|
||||
interface Material3OnScrollHelperBinder {
|
||||
fun bindScrollHelper(recyclerView: RecyclerView)
|
||||
fun bindScrollHelper(recyclerView: RecyclerView, chatFolders: RecyclerView, setChatFolder: (Int) -> Unit)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ open class Material3OnScrollHelper(
|
|||
private val context: Context,
|
||||
private val setStatusBarColor: (Int) -> Unit,
|
||||
private val getStatusBarColor: () -> Int,
|
||||
private val setChatFolderColor: (Int) -> Unit = {},
|
||||
private val views: List<View>,
|
||||
private val viewStubs: List<Stub<out View>> = emptyList(),
|
||||
lifecycleOwner: LifecycleOwner
|
||||
|
@ -33,16 +34,39 @@ open class Material3OnScrollHelper(
|
|||
constructor(activity: Activity, view: View, lifecycleOwner: LifecycleOwner) : this(activity = activity, views = listOf(view), lifecycleOwner = lifecycleOwner)
|
||||
|
||||
constructor(activity: Activity, views: List<View>, viewStubs: List<Stub<out View>> = emptyList(), lifecycleOwner: LifecycleOwner) : this(
|
||||
activity = activity,
|
||||
views = views,
|
||||
viewStubs = viewStubs,
|
||||
lifecycleOwner = lifecycleOwner,
|
||||
setChatFolderColor = {}
|
||||
)
|
||||
|
||||
constructor(
|
||||
activity: Activity,
|
||||
views: List<View>,
|
||||
viewStubs: List<Stub<out View>> = emptyList(),
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
setChatFolderColor: (Int) -> Unit = {}
|
||||
) : this(
|
||||
context = activity,
|
||||
setStatusBarColor = { WindowUtil.setStatusBarColor(activity.window, it) },
|
||||
getStatusBarColor = { WindowUtil.getStatusBarColor(activity.window) },
|
||||
setChatFolderColor = setChatFolderColor,
|
||||
views = views,
|
||||
viewStubs = viewStubs,
|
||||
lifecycleOwner = lifecycleOwner
|
||||
)
|
||||
|
||||
open val activeColorSet: ColorSet = ColorSet(R.color.signal_colorSurface2)
|
||||
open val inactiveColorSet: ColorSet = ColorSet(R.color.signal_colorBackground)
|
||||
open val activeColorSet: ColorSet = ColorSet(
|
||||
toolbarColorRes = R.color.signal_colorSurface2,
|
||||
statusBarColorRes = R.color.signal_colorSurface2,
|
||||
chatFolderColorRes = R.color.signal_colorBackground
|
||||
)
|
||||
open val inactiveColorSet: ColorSet = ColorSet(
|
||||
toolbarColorRes = R.color.signal_colorBackground,
|
||||
statusBarColorRes = R.color.signal_colorBackground,
|
||||
chatFolderColorRes = R.color.signal_colorSurface2
|
||||
)
|
||||
|
||||
protected var previousStatusBarColor: Int = getStatusBarColor()
|
||||
|
||||
|
@ -94,6 +118,7 @@ open class Material3OnScrollHelper(
|
|||
val colorSet = if (active == true) activeColorSet else inactiveColorSet
|
||||
setToolbarColor(ContextCompat.getColor(context, colorSet.toolbarColorRes))
|
||||
setStatusBarColor(ContextCompat.getColor(context, colorSet.statusBarColorRes))
|
||||
setChatFolderColor(ContextCompat.getColor(context, colorSet.chatFolderColorRes))
|
||||
}
|
||||
|
||||
private fun updateActiveState(isActive: Boolean) {
|
||||
|
@ -118,12 +143,15 @@ open class Material3OnScrollHelper(
|
|||
val endToolbarColor = ContextCompat.getColor(context, endColorSet.toolbarColorRes)
|
||||
val startStatusBarColor = ContextCompat.getColor(context, startColorSet.statusBarColorRes)
|
||||
val endStatusBarColor = ContextCompat.getColor(context, endColorSet.statusBarColorRes)
|
||||
val startChatFolderColor = ContextCompat.getColor(context, startColorSet.chatFolderColorRes)
|
||||
val endChatFolderColor = ContextCompat.getColor(context, endColorSet.chatFolderColorRes)
|
||||
|
||||
animator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
duration = 200
|
||||
addUpdateListener {
|
||||
setToolbarColor(ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, startToolbarColor, endToolbarColor))
|
||||
setStatusBarColor(ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, startStatusBarColor, endStatusBarColor))
|
||||
setChatFolderColor(ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, startChatFolderColor, endChatFolderColor))
|
||||
}
|
||||
start()
|
||||
}
|
||||
|
@ -157,8 +185,10 @@ open class Material3OnScrollHelper(
|
|||
*/
|
||||
data class ColorSet(
|
||||
@ColorRes val toolbarColorRes: Int,
|
||||
@ColorRes val statusBarColorRes: Int
|
||||
@ColorRes val statusBarColorRes: Int,
|
||||
@ColorRes val chatFolderColorRes: Int
|
||||
) {
|
||||
constructor(@ColorRes color: Int) : this(color, color)
|
||||
constructor(@ColorRes toolbarColorRes: Int, @ColorRes statusBarColorRes: Int) : this(toolbarColorRes, statusBarColorRes, toolbarColorRes)
|
||||
}
|
||||
}
|
||||
|
|
13
app/src/main/res/drawable/symbol_folder_settings.xml
Normal file
13
app/src/main/res/drawable/symbol_folder_settings.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12.774,4.125L10.989,4.125C10.576,4.125 10.178,3.968 9.877,3.685L9.72,3.538C9.094,2.951 8.269,2.625 7.411,2.625H5C3.136,2.625 1.625,4.136 1.625,6V13.202V13.263V15.237V15.237C1.625,16.046 1.625,16.706 1.669,17.242C1.714,17.796 1.811,18.294 2.047,18.759C2.419,19.488 3.012,20.081 3.741,20.453C4.206,20.69 4.704,20.786 5.258,20.831C5.794,20.875 6.454,20.875 7.263,20.875H16.737C17.546,20.875 18.206,20.875 18.742,20.831C19.296,20.786 19.794,20.69 20.259,20.453C20.988,20.081 21.581,19.488 21.953,18.759C22.19,18.294 22.286,17.796 22.331,17.242C22.375,16.706 22.375,16.045 22.375,15.237L22.375,11.556C21.837,11.884 21.247,12.135 20.621,12.296C20.625,12.585 20.625,12.916 20.625,13.3V15.2C20.625,16.055 20.624,16.643 20.587,17.099C20.551,17.545 20.484,17.788 20.393,17.965C20.19,18.365 19.865,18.69 19.465,18.893C19.288,18.984 19.045,19.051 18.599,19.087C18.143,19.124 17.555,19.125 16.7,19.125H7.3C6.445,19.125 5.857,19.124 5.401,19.087C4.955,19.051 4.712,18.984 4.535,18.893C4.135,18.69 3.81,18.365 3.607,17.965C3.516,17.788 3.449,17.545 3.413,17.099C3.376,16.643 3.375,16.055 3.375,15.2V13.3C3.375,12.446 3.376,11.857 3.413,11.401C3.449,10.955 3.516,10.712 3.607,10.535C3.81,10.135 4.135,9.81 4.535,9.607C4.712,9.516 4.955,9.449 5.401,9.413C5.857,9.376 6.445,9.375 7.3,9.375H13.444C13.117,8.838 12.866,8.25 12.705,7.625H7.263C6.454,7.625 5.794,7.625 5.258,7.669C4.704,7.714 4.206,7.811 3.741,8.047C3.451,8.195 3.183,8.377 2.942,8.589L2.861,8.515C3.284,8.087 3.375,7.907 3.375,7.538V6C3.375,5.103 4.103,4.375 5,4.375H7.411C7.824,4.375 8.222,4.532 8.523,4.815L8.68,4.962C9.306,5.549 10.131,5.875 10.989,5.875L12.501,5.875C12.513,5.268 12.607,4.681 12.774,4.125Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M18.583,11C18.261,11 18,10.788 17.93,10.476L17.719,9.569C17.608,9.538 17.497,9.498 17.367,9.427L16.563,9.911C16.292,10.083 15.97,10.073 15.729,9.831L15.176,9.256C14.955,9.034 14.925,8.702 15.095,8.419L15.568,7.613C15.528,7.502 15.477,7.411 15.427,7.29L14.523,7.079C14.211,6.998 14,6.746 14,6.413V5.607C14,5.294 14.211,5.042 14.523,4.962L15.417,4.74C15.467,4.619 15.518,4.508 15.558,4.417L15.055,3.601C14.884,3.319 14.945,2.986 15.166,2.754L15.739,2.2C15.97,1.968 16.302,1.927 16.553,2.079L17.347,2.583C17.467,2.522 17.578,2.482 17.719,2.431L17.93,1.534C18,1.222 18.261,1 18.583,1H19.397C19.729,1 19.98,1.212 20.06,1.524L20.281,2.452C20.392,2.492 20.503,2.522 20.623,2.583L21.437,2.079C21.698,1.917 22.02,1.968 22.251,2.2L22.834,2.754C23.055,2.976 23.085,3.319 22.915,3.601L22.422,4.417C22.462,4.508 22.513,4.609 22.563,4.74L23.457,4.962C23.779,5.042 24,5.284 24,5.607V6.413C24,6.746 23.789,6.998 23.457,7.079L22.553,7.29C22.513,7.411 22.482,7.502 22.412,7.613L22.884,8.409C23.065,8.702 23.045,9.024 22.814,9.256L22.251,9.831C22.01,10.073 21.688,10.083 21.417,9.911L20.623,9.427C20.513,9.488 20.392,9.538 20.271,9.569L20.06,10.476C19.98,10.798 19.729,11 19.397,11H18.583ZM18.995,7.563C19.839,7.563 20.543,6.867 20.543,6.01C20.543,5.153 19.839,4.448 18.995,4.448C18.141,4.448 17.437,5.153 17.437,6.01C17.437,6.867 18.141,7.563 18.995,7.563Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/chat_folder_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="52dp"
|
||||
android:clipToPadding="false"
|
||||
|
|
|
@ -5101,14 +5101,10 @@
|
|||
<string name="ChatFoldersFragment__folder_added">%1$s folder added.</string>
|
||||
<!-- Option in context menu to edit the folder -->
|
||||
<string name="ChatFoldersFragment__edit_folder">Edit folder</string>
|
||||
<!-- Option in context menu to delete the folder -->
|
||||
<string name="ChatFoldersFragment__delete_folder">Delete folder</string>
|
||||
<!-- Option in context menu to add a new folder -->
|
||||
<string name="ChatFoldersFragment__add_new_folder">Add new folder</string>
|
||||
<!-- Option in context menu to mute all chats in the folder -->
|
||||
<string name="ChatFoldersFragment__mute_all">Mute all</string>
|
||||
<!-- Option in context menu to reorder the positions of the folder -->
|
||||
<string name="ChatFoldersFragment__reorder_folder">Reorder folders</string>
|
||||
<!-- Option in context menu to unmute all chats in the folder -->
|
||||
<string name="ChatFoldersFragment__unmute_all">Unmute all</string>
|
||||
<!-- Option in context menu to mark all of the chats in a folder as read -->
|
||||
<string name="ChatFoldersFragment__mark_all_read">Mark all read</string>
|
||||
<!-- Text describing the number of chat types in a folder -->
|
||||
|
|
Loading…
Add table
Reference in a new issue