Fix various bugs for chat folders.

This commit is contained in:
Michelle Tang
2024-10-21 11:02:40 -07:00
committed by Greyson Parrelli
parent b519bf6772
commit dd4fcffec4
18 changed files with 228 additions and 144 deletions

View File

@@ -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)
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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))
}
}

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
)
}
}

View File

@@ -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.

View File

@@ -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)
}
}

View File

@@ -4,4 +4,5 @@ import androidx.recyclerview.widget.RecyclerView
interface Material3OnScrollHelperBinder {
fun bindScrollHelper(recyclerView: RecyclerView)
fun bindScrollHelper(recyclerView: RecyclerView, chatFolders: RecyclerView, setChatFolder: (Int) -> Unit)
}

View File

@@ -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)
}
}