diff --git a/fluxer_app/src/features/app/components/AppNotificationBadge.ts b/fluxer_app/src/features/app/components/AppNotificationBadge.ts index b5879c1e3..784fe829e 100644 --- a/fluxer_app/src/features/app/components/AppNotificationBadge.ts +++ b/fluxer_app/src/features/app/components/AppNotificationBadge.ts @@ -75,8 +75,8 @@ const setBadge = (badge: number): void => { export const AppBadge: React.FC = observer(() => { const relationships = Relationships.getRelationships(); const unreadMessageBadgeEnabled = Notification.unreadMessageBadgeEnabled; - const mentionCount = GuildReadState.getTotalMentionCount(); - const hasUnread = GuildReadState.hasAnyUnread; + const mentionCount = GuildReadState.mentionCountAcrossGuilds(); + const hasUnread = GuildReadState.anyGuildUnread; const pendingCount = RuntimeConfig.directMessagesDisabled ? 0 : relationships.filter((relationship) => relationship.type === RelationshipTypes.INCOMING_REQUEST).length; diff --git a/fluxer_app/src/features/app/components/bottomsheets/FavoritesGuildHeaderBottomSheet.tsx b/fluxer_app/src/features/app/components/bottomsheets/FavoritesGuildHeaderBottomSheet.tsx index ee623fe16..a72152ee4 100644 --- a/fluxer_app/src/features/app/components/bottomsheets/FavoritesGuildHeaderBottomSheet.tsx +++ b/fluxer_app/src/features/app/components/bottomsheets/FavoritesGuildHeaderBottomSheet.tsx @@ -34,7 +34,7 @@ export const FavoritesGuildHeaderBottomSheet: React.FC { const {i18n} = useLingui(); const hideMutedChannels = Favorites.hideMutedChannels; - const settings = UserGuildSettings.getSettings(FAVORITES_GUILD_ID); + const settings = UserGuildSettings.getSettingsForScope(FAVORITES_GUILD_ID); const isMuted = settings?.muted ?? false; const handleAddChannel = () => { ModalCommands.pushAfterBottomSheetClose( diff --git a/fluxer_app/src/features/app/components/floating/UnreadChannelsContent.tsx b/fluxer_app/src/features/app/components/floating/UnreadChannelsContent.tsx index ce5cdf7de..30da80800 100644 --- a/fluxer_app/src/features/app/components/floating/UnreadChannelsContent.tsx +++ b/fluxer_app/src/features/app/components/floating/UnreadChannelsContent.tsx @@ -360,7 +360,7 @@ const UnreadChannelCard = observer(function UnreadChannelCard({ const messageGroupSpacing = Accessibility.getMessageGroupSpacingValue(messageDisplayCompact); const mentionCount = ReadStates.getMentionCount(channel.id); const isCollapsed = UnreadChannels.isCollapsed(channel.id); - const isMuted = UserGuildSettings.isGuildOrCategoryOrChannelMuted(channel.guildId ?? null, channel.id); + const isMuted = UserGuildSettings.isMutedAtAnyLevel(channel.guildId ?? null, channel.id); const previewMessages = previewData?.messages ?? []; const oldestUnreadMessageId = previewData?.oldestUnreadMessageId ?? null; const totalUnreadCount = ReadStates.getUnreadCount(channel.id); diff --git a/fluxer_app/src/features/app/components/layout/ChannelItem.tsx b/fluxer_app/src/features/app/components/layout/ChannelItem.tsx index 2cdc38e89..7c83d5407 100644 --- a/fluxer_app/src/features/app/components/layout/ChannelItem.tsx +++ b/fluxer_app/src/features/app/components/layout/ChannelItem.tsx @@ -238,7 +238,7 @@ export const ChannelItem = observer( const canInvite = InviteUtils.canInviteToChannel(channel.id, channel.guildId); const mobileLayout = MobileLayout; const isMuted = UserGuildSettings.isGuildOrChannelMuted(guild.id, channel.id); - const isChannelDirectlyMuted = UserGuildSettings.isChannelMuted(guild.id, channel.id); + const isChannelDirectlyMuted = UserGuildSettings.isChannelDirectlyMuted(guild.id, channel.id); const currentUserCount = channelIsVoice && channel.userLimit != null && channel.userLimit > 0 ? Object.keys(MediaEngine.getAllVoiceStatesInChannel(guild.id, channel.id)).length diff --git a/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx b/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx index 0b22bf33f..1d26de6b6 100644 --- a/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx +++ b/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx @@ -164,7 +164,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr const {i18n} = useLingui(); const channels = Channels.getGuildChannels(guild.id); const location = useLocation(); - const userGuildSettings = UserGuildSettings.getSettings(guild.id); + const userGuildSettings = UserGuildSettings.getSettingsForScope(guild.id); const isDraggingAnything = useDragLayer((monitor) => { if (!monitor.isDragging()) return false; const itemType = monitor.getItemType(); @@ -337,7 +337,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr const unreadCount = ReadStates.getUnreadCount(channelId); const mentionCount = ReadStates.getMentionCount(channelId); const isMuted = - UserGuildSettings.isCategoryMuted(guild.id, channelId) || UserGuildSettings.isChannelMuted(guild.id, channelId); + UserGuildSettings.isParentCategoryMuted(guild.id, channelId) || UserGuildSettings.isChannelDirectlyMuted(guild.id, channelId); const channel = Channels.getChannel(channelId); const unreadBadgesLevel = channel ? UserGuildSettings.resolvedUnreadBadgesLevel({ @@ -360,13 +360,13 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr for (const group of channelGroups) { const isCollapsed = group.category ? (collapsedCategories?.has(group.category.id) ?? false) : false; const isNullSpace = !group.category; - const isCategoryMuted = group.category ? UserGuildSettings.isChannelMuted(guild.id, group.category.id) : false; + const isCategoryMuted = group.category ? UserGuildSettings.isChannelDirectlyMuted(guild.id, group.category.id) : false; let filteredTextChannels: Array; let filteredVoiceChannels: Array; if (hideMutedChannels) { filteredTextChannels = []; for (const ch of group.textChannels) { - const isMuted = UserGuildSettings.isChannelMuted(guild.id, ch.id); + const isMuted = UserGuildSettings.isChannelDirectlyMuted(guild.id, ch.id); if ( shouldShowChannelWhenHidingMutedChannels({ isMuted, @@ -380,7 +380,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr } filteredVoiceChannels = []; for (const ch of group.voiceChannels) { - const isMuted = UserGuildSettings.isChannelMuted(guild.id, ch.id); + const isMuted = UserGuildSettings.isChannelDirectlyMuted(guild.id, ch.id); if ( shouldShowChannelWhenHidingMutedChannels({ isMuted, diff --git a/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx b/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx index 05ee0ffa9..dc8b14c6f 100644 --- a/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx +++ b/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx @@ -206,11 +206,11 @@ const FavoriteChannelResolvedItem = observer( const channelDisplayName = channel.isPrivate() ? ChannelUtils.getDMDisplayName(channel) : channel.name; const displayName = favoriteChannel.nickname || channelDisplayName || i18n._(UNKNOWN_CHANNEL_DESCRIPTOR); const isChannelDirectlyMuted = channel.guildId - ? UserGuildSettings.isChannelMuted(channel.guildId, channel.id) + ? UserGuildSettings.isChannelDirectlyMuted(channel.guildId, channel.id) : false; const isMuted = isChannelDirectlyMuted || - (channel.guildId ? UserGuildSettings.isCategoryMuted(channel.guildId, channel.id) : false); + (channel.guildId ? UserGuildSettings.isParentCategoryMuted(channel.guildId, channel.id) : false); const unreadBadgesLevel = channel.guildId ? UserGuildSettings.resolvedUnreadBadgesLevel({ id: channel.id, diff --git a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx index 649d05591..1820985bf 100644 --- a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx +++ b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx @@ -665,7 +665,7 @@ function getDMScrollIndicatorSeverity(channelId: string): ScrollIndicatorSeverit const unreadState = getChannelUnreadState({ unreadCount: ReadStates.getPrivateChannelUnreadCount(channelId), mentionCount, - isMuted: UserGuildSettings.isChannelMuted(null, channelId), + isMuted: UserGuildSettings.isChannelDirectlyMuted(null, channelId), showFadedUnreadOnMutedChannels: Accessibility.showFadedUnreadOnMutedChannels, }); if (mentionCount > 0) return 'mention'; @@ -674,8 +674,8 @@ function getDMScrollIndicatorSeverity(channelId: string): ScrollIndicatorSeverit } function getGuildScrollIndicatorSeverity(guildId: string): ScrollIndicatorSeverity | null { - if (GuildReadState.getMentionCount(guildId) > 0) return 'mention'; - if (GuildReadState.hasUnread(guildId)) return 'unread'; + if (GuildReadState.mentionCountForGuild(guildId) > 0) return 'mention'; + if (GuildReadState.guildIsUnread(guildId)) return 'unread'; return null; } @@ -1327,10 +1327,10 @@ const GUILD_RAIL_INDICATORS_BY_TOKEN: Record): SkeletonGuildRailItemIndicator { let hasUnread = false; for (const guildId of guildIds) { - if (GuildReadState.getMentionCount(guildId) > 0) { + if (GuildReadState.mentionCountForGuild(guildId) > 0) { return SkeletonGuildRailItemIndicator.MENTION; } - if (GuildReadState.hasUnread(guildId)) { + if (GuildReadState.guildIsUnread(guildId)) { hasUnread = true; } } diff --git a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx index dc25d64c9..337858ec3 100644 --- a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx +++ b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx @@ -316,8 +316,8 @@ export const GuildFolderItem = observer((props: GuildFolderItemProps) => { }) as CSSProperties, [folderColor], ); - const hasUnreadMessages = guilds.some((guild) => GuildReadState.hasUnread(guild.id)); - const totalMentionCount = guilds.reduce((sum, guild) => sum + GuildReadState.getMentionCount(guild.id), 0); + const hasUnreadMessages = guilds.some((guild) => GuildReadState.guildIsUnread(guild.id)); + const totalMentionCount = guilds.reduce((sum, guild) => sum + GuildReadState.mentionCountForGuild(guild.id), 0); let folderScrollTargetRef: React.RefCallback | null = null; if (registerScrollTarget != null) { folderScrollTargetRef = registerScrollTarget(folderId); diff --git a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx index 80d6f524a..f802ded0c 100644 --- a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx +++ b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx @@ -72,7 +72,7 @@ export function resolveDMListItemUnreadState(channelId: string): ChannelUnreadSt return getChannelUnreadState({ unreadCount: ReadStates.getUnreadCount(channelId), mentionCount: ReadStates.getMentionCount(channelId), - isMuted: UserGuildSettings.isChannelMuted(null, channelId), + isMuted: UserGuildSettings.isChannelDirectlyMuted(null, channelId), showFadedUnreadOnMutedChannels: Accessibility.showFadedUnreadOnMutedChannels, unreadBadgesLevel: null, }); @@ -124,7 +124,7 @@ const ResolvedDMListItem = observer(function ResolvedDMListItem({ const contextMenuOpen = useContextMenuHoverState(buttonRef, !isMobileExperience); const [isFocused, setIsFocused] = useState(false); const {keyboardModeEnabled} = KeyboardMode; - const isMuted = UserGuildSettings.isChannelMuted(null, channel.id); + const isMuted = UserGuildSettings.isChannelDirectlyMuted(null, channel.id); const mentionCount = ReadStates.getMentionCount(channel.id); const unreadState = resolveDMListItemUnreadState(channel.id); const directMessageName = recipient ? NicknameUtils.getNickname(recipient, null, channel.id) : null; diff --git a/fluxer_app/src/features/app/components/layout/sidebar_nav/UseGuildListItemState.ts b/fluxer_app/src/features/app/components/layout/sidebar_nav/UseGuildListItemState.ts index a3fa780f0..1c5b6c8f3 100644 --- a/fluxer_app/src/features/app/components/layout/sidebar_nav/UseGuildListItemState.ts +++ b/fluxer_app/src/features/app/components/layout/sidebar_nav/UseGuildListItemState.ts @@ -107,9 +107,9 @@ export function useGuildListItemState({ isSelected, selectedGuildIndex, }: UseGuildListItemStateOptions): GuildListItemState { - const hasUnreadMessages = GuildReadState.hasUnread(guild.id); - const mentionCount = GuildReadState.getMentionCount(guild.id); - const settings = UserGuildSettings.getSettings(guild.id); + const hasUnreadMessages = GuildReadState.guildIsUnread(guild.id); + const mentionCount = GuildReadState.mentionCountForGuild(guild.id); + const settings = UserGuildSettings.getSettingsForScope(guild.id); const isMuted = settings.muted; const muteConfig = settings.mute_config; let muteEndTime: string | null = null; diff --git a/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx b/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx index d81e11794..bc4290c7c 100644 --- a/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx +++ b/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx @@ -132,7 +132,7 @@ const ResolvedDMListItem = observer(function ResolvedDMListItem({ : TypingIndicator.isTyping(channel.id, recipientId); const hasUnreadMessages = () => ReadStates.hasUnread(channel.id); const isMobile = MobileLayout.isMobileLayout(); - const isMuted = UserGuildSettings.isChannelMuted(null, channel.id); + const isMuted = UserGuildSettings.isChannelDirectlyMuted(null, channel.id); const mentionCount = ReadStates.getMentionCount(channel.id); const unreadState = getChannelUnreadState({ unreadCount: ReadStates.getUnreadCount(channel.id), diff --git a/fluxer_app/src/features/guild/components/modals/GuildNotificationSettingsModal.tsx b/fluxer_app/src/features/guild/components/modals/GuildNotificationSettingsModal.tsx index fa878dbf4..0655554dc 100644 --- a/fluxer_app/src/features/guild/components/modals/GuildNotificationSettingsModal.tsx +++ b/fluxer_app/src/features/guild/components/modals/GuildNotificationSettingsModal.tsx @@ -166,7 +166,7 @@ export const GuildNotificationSettingsModal = observer(({guildId}: {guildId: str const {i18n} = useLingui(); const muteChannelLabel = i18n._(MUTE_CHANNEL_DESCRIPTOR); const guild = Guilds.getGuild(guildId); - const settings = UserGuildSettings.getSettings(guildId); + const settings = UserGuildSettings.getSettingsForScope(guildId); if (!guild || !settings) return null; const channels = Channels.getGuildChannels(guildId); const categories = channels.filter((c) => c.type === ChannelTypes.GUILD_CATEGORY); diff --git a/fluxer_app/src/features/guild/components/popouts/GuildHeaderPopout.tsx b/fluxer_app/src/features/guild/components/popouts/GuildHeaderPopout.tsx index 4ae671b8e..346930b6a 100644 --- a/fluxer_app/src/features/guild/components/popouts/GuildHeaderPopout.tsx +++ b/fluxer_app/src/features/guild/components/popouts/GuildHeaderPopout.tsx @@ -171,11 +171,11 @@ export const GuildHeaderPopout = observer(({guild}: {guild: Guild}) => { canCreateExpressions || canBanMembers; const canEditCommunityProfile = Users.getCurrentUser()?.isClaimed() ?? true; - const settings = UserGuildSettings.getSettings(guild.id); + const settings = UserGuildSettings.getSettingsForScope(guild.id); const hideMutedChannels = settings?.hide_muted_channels ?? false; const handleToggleHideMutedChannels = useCallback( (checked: boolean) => { - const currentSettings = UserGuildSettings.getSettings(guild.id); + const currentSettings = UserGuildSettings.getSettingsForScope(guild.id); const currentValue = currentSettings?.hide_muted_channels ?? false; if (checked === currentValue) return; UserGuildSettingsCommands.toggleHideMutedChannels(guild.id); diff --git a/fluxer_app/src/features/guild/state/GuildReadState.ts b/fluxer_app/src/features/guild/state/GuildReadState.ts index 5b950abd7..46dbdf028 100644 --- a/fluxer_app/src/features/guild/state/GuildReadState.ts +++ b/fluxer_app/src/features/guild/state/GuildReadState.ts @@ -20,18 +20,18 @@ const PRIVATE_CHANNEL_SENTINEL = ME; class GuildReadStateEntry { unread = observable.box(false); - unreadChannelId = observable.box(null); + firstUnreadChannelId = observable.box(null); mentionCount = observable.box(0); mentionChannels = observable.set(new Set()); - sentinel = observable.box(0); + changeTicket = observable.box(0); - incrementSentinel(): void { - this.sentinel.set(this.sentinel.get() + 1); + bumpChangeTicket(): void { + this.changeTicket.set(this.changeTicket.get() + 1); } reset(): void { this.unread.set(false); - this.unreadChannelId.set(null); + this.firstUnreadChannelId.set(null); this.mentionCount.set(0); this.mentionChannels.clear(); } @@ -43,12 +43,12 @@ type ContributeChannel = { guildId?: string | null; parentId?: string | null; isPrivate(): boolean; - isGuildVocal?(): boolean; + isGuildAudioChannel?(): boolean; }; function isChannelMutedForUnread(channel: ContributeChannel): boolean { if (channel.isPrivate()) return false; - return UserGuildSettings.isGuildOrCategoryOrChannelMuted(channel.guildId ?? null, channel.id); + return UserGuildSettings.isMutedAtAnyLevel(channel.guildId ?? null, channel.id); } function resolveUnreadBadgesLevel(channel: ContributeChannel): number | null { @@ -77,7 +77,7 @@ function getChannelContribution(channel: ContributeChannel, channelId: string): class GuildReadState { private readonly guildStates = observable.map(new Map()); - private readonly unreadGuilds = observable.set(new Set()); + private readonly unreadGuildIds = observable.set(new Set()); updateCounter = 0; private readStateReactionInstalled = false; @@ -156,9 +156,9 @@ class GuildReadState { this.updateCounter++; } - private incrementSentinel(guildId: string | null): void { + private bumpChangeTicket(guildId: string | null): void { const state = this.getOrCreate(guildId); - state.incrementSentinel(); + state.bumpChangeTicket(); this.notifyChange(); } @@ -166,18 +166,18 @@ class GuildReadState { const id = guildId ?? PRIVATE_CHANNEL_SENTINEL; const state = this.getOrCreate(id); const previousUnread = state.unread.get(); - const previousUnreadChannelId = state.unreadChannelId.get(); + const previousFirstUnread = state.firstUnreadChannelId.get(); const previousMentionCount = state.mentionCount.get(); const mentionChannelUpdates = new Map(); let foundUnread = false; - let shouldClearUnreadChannelId = false; - let nextUnreadChannelId = previousUnreadChannelId; + let shouldClearFirstUnread = false; + let nextFirstUnread = previousFirstUnread; for (const channelId of channelIds) { const channel = Channels.getChannel(channelId); if (channel == null) { mentionChannelUpdates.set(channelId, false); - if (nextUnreadChannelId === channelId) { - shouldClearUnreadChannelId = true; + if (nextFirstUnread === channelId) { + shouldClearFirstUnread = true; } continue; } @@ -194,14 +194,14 @@ class GuildReadState { mentionChannelUpdates.set(channelId, contribution.mentionAllowed); if (guildId != null && !foundUnread && contribution.unreadAllowed) { foundUnread = true; - nextUnreadChannelId = channelId; - } else if (!contribution.unreadAllowed && nextUnreadChannelId === channelId) { - shouldClearUnreadChannelId = true; + nextFirstUnread = channelId; + } else if (!contribution.unreadAllowed && nextFirstUnread === channelId) { + shouldClearFirstUnread = true; } } const nextUnread = foundUnread; - if (!nextUnread && shouldClearUnreadChannelId) { - nextUnreadChannelId = null; + if (!nextUnread && shouldClearFirstUnread) { + nextFirstUnread = null; } if (previousUnread && !nextUnread) { return this.recomputeAll(guildId); @@ -219,9 +219,7 @@ class GuildReadState { } } const changed = - nextUnread !== previousUnread || - nextUnreadChannelId !== previousUnreadChannelId || - mentionTotal !== previousMentionCount; + nextUnread !== previousUnread || nextFirstUnread !== previousFirstUnread || mentionTotal !== previousMentionCount; if (!changed && mentionChannelUpdates.size === 0) { return false; } @@ -237,16 +235,16 @@ class GuildReadState { return; } state.unread.set(nextUnread); - state.unreadChannelId.set(nextUnreadChannelId); + state.firstUnreadChannelId.set(nextFirstUnread); state.mentionCount.set(mentionTotal); if (id !== PRIVATE_CHANNEL_SENTINEL) { if (nextUnread) { - this.unreadGuilds.add(id as GuildId); + this.unreadGuildIds.add(id as GuildId); } else { - this.unreadGuilds.delete(id as GuildId); + this.unreadGuildIds.delete(id as GuildId); } } - this.incrementSentinel(guildId); + this.bumpChangeTicket(guildId); }); return changed; } @@ -264,11 +262,11 @@ class GuildReadState { } if (!newState.unread.get() && contribution.unreadAllowed) { newState.unread.set(true); - newState.unreadChannelId.set(channel.id as ChannelId); + newState.firstUnreadChannelId.set(channel.id as ChannelId); } } } else { - const isGuildMuted = UserGuildSettings.isMuted(guildId); + const isGuildMuted = UserGuildSettings.isWholeGuildMuted(guildId); if (isGuildMuted && skipIfMuted) { return false; } @@ -281,7 +279,7 @@ class GuildReadState { } if (!newState.unread.get() && contribution.unreadAllowed) { newState.unread.set(true); - newState.unreadChannelId.set(channel.id as ChannelId); + newState.firstUnreadChannelId.set(channel.id as ChannelId); } } } @@ -292,7 +290,7 @@ class GuildReadState { private commitState(guildId: string, newState: GuildReadStateEntry, prevState: GuildReadStateEntry): boolean { if ( newState.unread.get() === prevState.unread.get() && - newState.unreadChannelId.get() === prevState.unreadChannelId.get() && + newState.firstUnreadChannelId.get() === prevState.firstUnreadChannelId.get() && newState.mentionCount.get() === prevState.mentionCount.get() ) { return false; @@ -301,12 +299,12 @@ class GuildReadState { this.guildStates.set(guildId as GuildId, newState); if (guildId !== PRIVATE_CHANNEL_SENTINEL) { if (newState.unread.get()) { - this.unreadGuilds.add(guildId as GuildId); + this.unreadGuildIds.add(guildId as GuildId); } else { - this.unreadGuilds.delete(guildId as GuildId); + this.unreadGuildIds.delete(guildId as GuildId); } } - this.incrementSentinel(guildId === PRIVATE_CHANNEL_SENTINEL ? null : guildId); + this.bumpChangeTicket(guildId === PRIVATE_CHANNEL_SENTINEL ? null : guildId); }); return true; } @@ -335,21 +333,21 @@ class GuildReadState { } } - get hasAnyUnread(): boolean { - return this.unreadGuilds.size > 0; + get anyGuildUnread(): boolean { + return this.unreadGuildIds.size > 0; } - hasUnread(guildId: string): boolean { - return this.unreadGuilds.has(guildId as GuildId); + guildIsUnread(guildId: string): boolean { + return this.unreadGuildIds.has(guildId as GuildId); } - getMentionCount(guildId: string | null): number { + mentionCountForGuild(guildId: string | null): number { const id = guildId ?? PRIVATE_CHANNEL_SENTINEL; const state = this.guildStates.get(id as GuildId); return state?.mentionCount.get() ?? 0; } - getTotalMentionCount(excludePrivate = false): number { + mentionCountAcrossGuilds(excludePrivate = false): number { let total = 0; for (const [guildId, state] of this.guildStates.entries()) { if (excludePrivate && guildId === PRIVATE_CHANNEL_SENTINEL) continue; @@ -358,22 +356,22 @@ class GuildReadState { return total; } - getPrivateChannelMentionCount(): number { + directMessageMentionCount(): number { const state = this.guildStates.get(PRIVATE_CHANNEL_SENTINEL as GuildId); return state?.mentionCount.get() ?? 0; } - getMentionCountForPrivateChannel(channelId: string): number { + directMessageMentionCountFor(channelId: string): number { return ReadStates.getMentionCount(channelId); } - getGuildChangeSentinel(guildId: string | null): number { + guildChangeTicketFor(guildId: string | null): number { const id = guildId ?? PRIVATE_CHANNEL_SENTINEL; const state = this.guildStates.get(id as GuildId); - return state?.sentinel.get() ?? 0; + return state?.changeTicket.get() ?? 0; } - getGuildHasUnreadIgnoreMuted(guildId: string): boolean { + guildUnreadIgnoringMute(guildId: string): boolean { const channels = Channels.getGuildChannels(guildId); for (const channel of channels) { if (!GUILD_TEXT_BASED_CHANNEL_TYPES.has(channel.type)) { @@ -388,7 +386,7 @@ class GuildReadState { handleConnectionOpen(): void { this.guildStates.clear(); - this.unreadGuilds.clear(); + this.unreadGuildIds.clear(); this.updateCounter = 0; this.recomputeAll(null); for (const guildId of Guilds.getGuildIds()) { @@ -411,7 +409,7 @@ class GuildReadState { }; }): void { this.guildStates.delete(action.guild.id as GuildId); - this.unreadGuilds.delete(action.guild.id as GuildId); + this.unreadGuildIds.delete(action.guild.id as GuildId); this.notifyChange(); } @@ -459,7 +457,7 @@ class GuildReadState { this.handleConnectionOpen(); } - handleWindowFocus(): void { + handleWindowFocused(): void { this.notifyChange(); } diff --git a/fluxer_app/src/features/messaging/models/MessagingMessage.ts b/fluxer_app/src/features/messaging/models/MessagingMessage.ts index 8a0f2a130..da2cffffa 100644 --- a/fluxer_app/src/features/messaging/models/MessagingMessage.ts +++ b/fluxer_app/src/features/messaging/models/MessagingMessage.ts @@ -553,12 +553,12 @@ export class Message { export const messageMentionsCurrentUser = (message: WireMessage): boolean => { const channel = Channels.getChannel(message.channel_id); if (!channel) return false; - if (message.mention_everyone && !UserGuildSettings.isSuppressEveryoneEnabled(channel.guildId ?? null)) return true; + if (message.mention_everyone && !UserGuildSettings.isEveryoneMentionSuppressed(channel.guildId ?? null)) return true; if (message.mentions?.some((user) => user.id === Authentication.currentUserId)) { return true; } if (!channel.guildId) return false; - if (UserGuildSettings.isSuppressRolesEnabled(channel.guildId)) return false; + if (UserGuildSettings.isRoleMentionSuppressed(channel.guildId)) return false; const guild = Guilds.getGuild(channel.guildId); if (!guild) return false; const guildMember = GuildMembers.getMember(guild.id, Authentication.currentUserId); diff --git a/fluxer_app/src/features/messaging/utils/MessageUtils.ts b/fluxer_app/src/features/messaging/utils/MessageUtils.ts index a278302fc..e2cd69819 100644 --- a/fluxer_app/src/features/messaging/utils/MessageUtils.ts +++ b/fluxer_app/src/features/messaging/utils/MessageUtils.ts @@ -16,7 +16,7 @@ export function isMentioned(user: User, message: Message): boolean { logger.warn(`${message.channelId} does not exist!`); return false; } - const suppressEveryone = UserGuildSettings.isSuppressEveryoneEnabled(channel.guildId ?? null); + const suppressEveryone = UserGuildSettings.isEveryoneMentionSuppressed(channel.guildId ?? null); const mentionEveryone = message.mentionEveryone && !suppressEveryone; if (mentionEveryone) { return true; @@ -35,6 +35,6 @@ export function isMentioned(user: User, message: Message): boolean { if (!guildMember) { return false; } - const suppressRoles = UserGuildSettings.isSuppressRolesEnabled(channel.guildId); + const suppressRoles = UserGuildSettings.isRoleMentionSuppressed(channel.guildId); return !suppressRoles && message.mentionRoles.some((roleId) => guildMember.roles.has(roleId)); } diff --git a/fluxer_app/src/features/notification/hooks/useMuteSheet.ts b/fluxer_app/src/features/notification/hooks/useMuteSheet.ts index 40b826932..37d741311 100644 --- a/fluxer_app/src/features/notification/hooks/useMuteSheet.ts +++ b/fluxer_app/src/features/notification/hooks/useMuteSheet.ts @@ -43,7 +43,7 @@ export function useMuteSheet(params: UseMuteSheetParams): UseMuteSheetReturn { const additionalMutePayload = isGuildMode ? undefined : params.additionalMutePayload; const {onMuteSuccess, onUnmuteSuccess, onClose} = params; const muteConfig = isGuildMode - ? UserGuildSettings.getSettings(guildId)?.mute_config + ? UserGuildSettings.getSettingsForScope(guildId)?.mute_config : UserGuildSettings.getChannelOverride(guildId, channelId!)?.mute_config; const openMuteSheet = useCallback(() => { setMuteSheetOpen(true); diff --git a/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts b/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts index 200887608..f129b2a9e 100644 --- a/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts +++ b/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts @@ -231,8 +231,8 @@ export class ReadStateEntry { const userId = currentUser.id; const guildId = this.guildId; const channelId = this.channelId; - const suppressEveryone = recomputeMentions ? UserGuildSettings.isSuppressEveryoneEnabled(guildId) : false; - const suppressRoles = recomputeMentions ? UserGuildSettings.isSuppressRolesEnabled(guildId) : false; + const suppressEveryone = recomputeMentions ? UserGuildSettings.isEveryoneMentionSuppressed(guildId) : false; + const suppressRoles = recomputeMentions ? UserGuildSettings.isRoleMentionSuppressed(guildId) : false; const isMuted = recomputeMentions ? UserGuildSettings.isGuildOrChannelMuted(guildId, channelId) : false; const member = recomputeMentions && guildId ? GuildMembers.getMember(guildId, userId) : null; const memberRoles = member?.roles ?? null; @@ -285,8 +285,8 @@ export class ReadStateEntry { shouldMentionFor(message: MessageModel | WireMessage, userId: string, isPrivate: boolean): boolean { const authorBlocked = Relationships.isBlocked(message.author.id); - const suppressEveryone = UserGuildSettings.isSuppressEveryoneEnabled(this.guildId); - const suppressRoles = UserGuildSettings.isSuppressRolesEnabled(this.guildId); + const suppressEveryone = UserGuildSettings.isEveryoneMentionSuppressed(this.guildId); + const suppressRoles = UserGuildSettings.isRoleMentionSuppressed(this.guildId); const mentions = message.mentions; const mentionEveryone = 'mentionEveryone' in message ? message.mentionEveryone : message.mention_everyone; const mentionRoles = 'mentionRoles' in message ? message.mentionRoles : message.mention_roles; diff --git a/fluxer_app/src/features/ui/action_menu/ChannelListContextMenu.tsx b/fluxer_app/src/features/ui/action_menu/ChannelListContextMenu.tsx index 9326d267a..ef173719a 100644 --- a/fluxer_app/src/features/ui/action_menu/ChannelListContextMenu.tsx +++ b/fluxer_app/src/features/ui/action_menu/ChannelListContextMenu.tsx @@ -38,7 +38,7 @@ export const ChannelListContextMenu: React.FC = obs }); const invitableChannelId = InviteUtils.getInvitableChannelId(guild.id); const canInvite = InviteUtils.canInviteToChannel(invitableChannelId, guild.id); - const hideMutedChannels = UserGuildSettings.getSettings(guild.id)?.hide_muted_channels ?? false; + const hideMutedChannels = UserGuildSettings.getSettingsForScope(guild.id)?.hide_muted_channels ?? false; const handleToggleHideMutedChannels = useCallback(() => { UserGuildSettingsCommands.toggleHideMutedChannels(guild.id); }, [guild.id]); diff --git a/fluxer_app/src/features/ui/action_menu/FavoritesGuildContextMenu.tsx b/fluxer_app/src/features/ui/action_menu/FavoritesGuildContextMenu.tsx index e1838eae6..7bced5e4c 100644 --- a/fluxer_app/src/features/ui/action_menu/FavoritesGuildContextMenu.tsx +++ b/fluxer_app/src/features/ui/action_menu/FavoritesGuildContextMenu.tsx @@ -22,7 +22,7 @@ interface FavoritesGuildContextMenuProps { export const FavoritesGuildContextMenu: React.FC = observer(({onClose}) => { const {i18n} = useLingui(); - const settings = UserGuildSettings.getSettings(FAVORITES_GUILD_ID); + const settings = UserGuildSettings.getSettingsForScope(FAVORITES_GUILD_ID); const isMuted = settings?.muted ?? false; const handleToggleMute = () => { UserGuildSettingsCommands.updateGuildSettings(FAVORITES_GUILD_ID, {muted: !isMuted}); diff --git a/fluxer_app/src/features/ui/action_menu/GuildFolderContextMenu.tsx b/fluxer_app/src/features/ui/action_menu/GuildFolderContextMenu.tsx index 6670cfea8..7e93b68fc 100644 --- a/fluxer_app/src/features/ui/action_menu/GuildFolderContextMenu.tsx +++ b/fluxer_app/src/features/ui/action_menu/GuildFolderContextMenu.tsx @@ -34,7 +34,7 @@ interface GuildFolderContextMenuProps { } function guildHasUnreadState(guildId: string): boolean { - return GuildReadState.hasUnread(guildId) || GuildReadState.getGuildHasUnreadIgnoreMuted(guildId); + return GuildReadState.guildIsUnread(guildId) || GuildReadState.guildUnreadIgnoringMute(guildId); } export const GuildFolderContextMenu: React.FC = observer(({folder, guilds, onClose}) => { diff --git a/fluxer_app/src/features/ui/action_menu/items/CategoryMenuData.tsx b/fluxer_app/src/features/ui/action_menu/items/CategoryMenuData.tsx index ab5094f1c..cb8f4b5ab 100644 --- a/fluxer_app/src/features/ui/action_menu/items/CategoryMenuData.tsx +++ b/fluxer_app/src/features/ui/action_menu/items/CategoryMenuData.tsx @@ -132,11 +132,11 @@ function getCategoryMenuState(category: Channel): CategoryMenuState { (ch) => ch.parentId === category.id && ch.type !== ChannelTypes.GUILD_CATEGORY, ); const hasUnread = channelsInCategory.some((ch) => ReadStates.hasUnread(ch.id)); - const isCollapsed = UserGuildSettings.isChannelCollapsed(guildId, category.id); + const isCollapsed = UserGuildSettings.isChannelSectionCollapsed(guildId, category.id); const categoryIds = channels.filter((ch) => ch.type === ChannelTypes.GUILD_CATEGORY).map((ch) => ch.id); const allCategoriesCollapsed = categoryIds.length > 0 - ? categoryIds.every((categoryId) => UserGuildSettings.isChannelCollapsed(guildId, categoryId)) + ? categoryIds.every((categoryId) => UserGuildSettings.isChannelSectionCollapsed(guildId, categoryId)) : false; const categoryOverride = UserGuildSettings.getChannelOverride(guildId, category.id); const isMuted = categoryOverride?.muted ?? false; diff --git a/fluxer_app/src/features/ui/action_menu/items/CategoryMenuItems.tsx b/fluxer_app/src/features/ui/action_menu/items/CategoryMenuItems.tsx index 6f6064735..b68b1a944 100644 --- a/fluxer_app/src/features/ui/action_menu/items/CategoryMenuItems.tsx +++ b/fluxer_app/src/features/ui/action_menu/items/CategoryMenuItems.tsx @@ -113,7 +113,7 @@ export const MarkCategoryAsReadMenuItem: React.FC = obser export const CollapseCategoryMenuItem: React.FC = observer(({category, onClose}) => { const {i18n} = useLingui(); const guildId = category.guildId!; - const isCollapsed = UserGuildSettings.isChannelCollapsed(guildId, category.id); + const isCollapsed = UserGuildSettings.isChannelSectionCollapsed(guildId, category.id); const handleToggleCollapse = useCallback(() => { UserGuildSettingsCommands.toggleChannelCollapsed(guildId, category.id); onClose(); @@ -138,7 +138,7 @@ export const CollapseAllCategoriesMenuItem: React.FC = ob ); const allCategoriesCollapsed = useMemo(() => { if (categoryIds.length === 0) return false; - return categoryIds.every((categoryId) => UserGuildSettings.isChannelCollapsed(guildId, categoryId)); + return categoryIds.every((categoryId) => UserGuildSettings.isChannelSectionCollapsed(guildId, categoryId)); }, [guildId, categoryIds]); const handleToggleCollapseAll = useCallback(() => { UserGuildSettingsCommands.toggleAllCategoriesCollapsed(guildId, categoryIds); diff --git a/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx b/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx index e3d02816c..41c96f1d1 100644 --- a/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx +++ b/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx @@ -164,7 +164,7 @@ export function useGuildMenuData(guild: Guild, options: UseGuildMenuDataOptions) const isOwner = guild.isOwner(Authentication.currentUserId); const developerMode = UserSettings.developerMode; const canEditCommunityProfile = Users.getCurrentUser()?.isClaimed() ?? true; - const settings = UserGuildSettings.getSettings(guild.id); + const settings = UserGuildSettings.getSettingsForScope(guild.id); const hideMutedChannels = settings?.hide_muted_channels ?? false; const isMuted = settings?.muted ?? false; const muteConfig = settings?.mute_config; @@ -298,7 +298,7 @@ export function useGuildMenuData(guild: Guild, options: UseGuildMenuDataOptions) ModalCommands.runAfterBottomSheetClose(onClose, () => openReportGuildModal({i18n, guild})); }, handleToggleHideMutedChannels: (checked: boolean) => { - const currentSettings = UserGuildSettings.getSettings(guild.id); + const currentSettings = UserGuildSettings.getSettingsForScope(guild.id); const currentValue = currentSettings?.hide_muted_channels ?? false; if (checked === currentValue) return; UserGuildSettingsCommands.toggleHideMutedChannels(guild.id); diff --git a/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx b/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx index 2d82e2ccd..305da2a25 100644 --- a/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx +++ b/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx @@ -148,7 +148,7 @@ export const InvitePeopleMenuItem: React.FC = observer(({gui }); export const MuteCommunityMenuItem: React.FC = observer(({guild, onClose}) => { const {i18n} = useLingui(); - const settings = UserGuildSettings.getSettings(guild.id); + const settings = UserGuildSettings.getSettingsForScope(guild.id); const isMuted = settings?.muted ?? false; const muteConfig = settings?.mute_config; const mutedText = getMutedText(isMuted, muteConfig); @@ -219,7 +219,7 @@ export const MuteCommunityMenuItem: React.FC = observer(({gu }); export const NotificationSettingsMenuItem: React.FC = observer(({guild, onClose}) => { const {i18n} = useLingui(); - const settings = UserGuildSettings.getSettings(guild.id); + const settings = UserGuildSettings.getSettingsForScope(guild.id); const suppressEveryone = settings?.suppress_everyone ?? false; const suppressRoles = settings?.suppress_roles ?? false; const mobilePush = settings?.mobile_push ?? true; @@ -323,11 +323,11 @@ export const NotificationSettingsMenuItem: React.FC = observ }); export const HideMutedChannelsMenuItem: React.FC = observer(({guild}) => { const {i18n} = useLingui(); - const settings = UserGuildSettings.getSettings(guild.id); + const settings = UserGuildSettings.getSettingsForScope(guild.id); const hideMutedChannels = settings?.hide_muted_channels ?? false; const handleToggle = useCallback( (checked: boolean) => { - const currentSettings = UserGuildSettings.getSettings(guild.id); + const currentSettings = UserGuildSettings.getSettingsForScope(guild.id); const currentValue = currentSettings?.hide_muted_channels ?? false; if (checked === currentValue) return; UserGuildSettingsCommands.toggleHideMutedChannels(guild.id); diff --git a/fluxer_app/src/features/ui/state/Notification.ts b/fluxer_app/src/features/ui/state/Notification.ts index 9badfbd59..694d1ffcc 100644 --- a/fluxer_app/src/features/ui/state/Notification.ts +++ b/fluxer_app/src/features/ui/state/Notification.ts @@ -223,7 +223,7 @@ class NotificationState { } private shouldNotifyBasedOnSettings(channel: Channel, messageRecord: Message, currentUser: User): boolean { - const level = UserGuildSettings.resolvedMessageNotifications({ + const level = UserGuildSettings.resolveEffectiveMessageNotifications({ id: channel.id, guildId: channel.guildId, parentId: channel.parentId ?? undefined, @@ -261,7 +261,7 @@ class NotificationState { return null; } if ( - UserGuildSettings.allowNoMessages({ + UserGuildSettings.resolvesToNoMessages({ id: channel.id, guildId: channel.guildId, parentId: channel.parentId ?? undefined, diff --git a/fluxer_app/src/features/user/commands/UserGuildSettingsCommands.ts b/fluxer_app/src/features/user/commands/UserGuildSettingsCommands.ts index 5cfe14e37..c2dc30e2a 100644 --- a/fluxer_app/src/features/user/commands/UserGuildSettingsCommands.ts +++ b/fluxer_app/src/features/user/commands/UserGuildSettingsCommands.ts @@ -127,7 +127,7 @@ export function updateGuildSettings( updates: UserGuildSettingsPartial, options?: PersistenceOptions, ): void { - UserGuildSettings.getSettings(guildId); + UserGuildSettings.getSettingsForScope(guildId); UserGuildSettings.updateGuildSettings(guildId, updates as Partial); scheduleUpdate(guildId, updates, options); } @@ -139,7 +139,7 @@ export function bulkUpdateGuildSettings( const uniqueGuildIds = Array.from(new Set(guildIds)); if (uniqueGuildIds.length === 0) return; for (const guildId of uniqueGuildIds) { - UserGuildSettings.getSettings(guildId); + UserGuildSettings.getSettingsForScope(guildId); UserGuildSettings.updateGuildSettings(guildId, updates as Partial); const key = getUpdateKey(guildId); addPendingPayload(key, updates); @@ -148,7 +148,7 @@ export function bulkUpdateGuildSettings( } export function toggleHideMutedChannels(guildId: string | null): void { - const currentSettings = UserGuildSettings.getSettings(guildId); + const currentSettings = UserGuildSettings.getSettingsForScope(guildId); const newValue = !currentSettings.hide_muted_channels; updateGuildSettings(guildId, {hide_muted_channels: newValue}); } @@ -159,7 +159,7 @@ export function updateChannelOverride( override: ChannelOverrideUpdate, options?: PersistenceOptions, ): void { - const currentSettings = UserGuildSettings.getSettings(guildId); + const currentSettings = UserGuildSettings.getSettingsForScope(guildId); const currentOverride = UserGuildSettings.getChannelOverride(guildId, channelId); let newOverride: ChannelOverride | null = null; if (override != null) { @@ -182,7 +182,7 @@ export function bulkUpdateChannelOverrides( ): void { const uniqueChannelIds = Array.from(new Set(channelIds)); if (uniqueChannelIds.length === 0) return; - const currentSettings = UserGuildSettings.getSettings(guildId); + const currentSettings = UserGuildSettings.getSettingsForScope(guildId); const newChannelOverrides: Record = {...(currentSettings.channel_overrides ?? {})}; for (const channelId of uniqueChannelIds) { const currentOverride = UserGuildSettings.getChannelOverride(guildId, channelId); @@ -196,7 +196,7 @@ export function bulkUpdateChannelOverrides( } export function toggleChannelCollapsed(guildId: string | null, channelId: string): void { - const isCollapsed = UserGuildSettings.isChannelCollapsed(guildId, channelId); + const isCollapsed = UserGuildSettings.isChannelSectionCollapsed(guildId, channelId); updateChannelOverride(guildId, channelId, {collapsed: !isCollapsed}); } @@ -228,7 +228,7 @@ export function updateUnreadBadgesLevel( } export function toggleChannelMuted(guildId: string | null, channelId: string, options?: PersistenceOptions): void { - const isMuted = UserGuildSettings.isChannelMuted(guildId, channelId); + const isMuted = UserGuildSettings.isChannelDirectlyMuted(guildId, channelId); updateChannelOverride(guildId, channelId, {muted: !isMuted}, options); } @@ -236,13 +236,13 @@ export function toggleAllCategoriesCollapsed(guildId: string | null, categoryIds const uniqueCategoryIds = Array.from(new Set(categoryIds)); if (uniqueCategoryIds.length === 0) return; const allCollapsed = uniqueCategoryIds.every((categoryId) => - UserGuildSettings.isChannelCollapsed(guildId, categoryId), + UserGuildSettings.isChannelSectionCollapsed(guildId, categoryId), ); const newCollapsedState = !allCollapsed; for (const categoryId of uniqueCategoryIds) { UserGuildSettings.updateChannelOverride(guildId, categoryId, {collapsed: newCollapsedState}); } - const currentSettings = UserGuildSettings.getSettings(guildId); + const currentSettings = UserGuildSettings.getSettingsForScope(guildId); const newChannelOverrides: Record = {...(currentSettings.channel_overrides ?? {})}; for (const categoryId of uniqueCategoryIds) { const currentOverride = newChannelOverrides[categoryId]; diff --git a/fluxer_app/src/features/user/state/UserGuildSettings.ts b/fluxer_app/src/features/user/state/UserGuildSettings.ts index aba5684dc..8b90d6437 100644 --- a/fluxer_app/src/features/user/state/UserGuildSettings.ts +++ b/fluxer_app/src/features/user/state/UserGuildSettings.ts @@ -27,7 +27,7 @@ export interface ChannelOverride { unread_badges?: number | null; } -interface GuildSettings { +interface StoredGuildSettings { guild_id: string; suppress_everyone: boolean; suppress_roles: boolean; @@ -63,7 +63,7 @@ export interface GatewayGuildSettings { unread_badges?: number | null; } -const DEFAULT_GUILD_SETTINGS: Omit = { +const DEFAULT_GUILD_SETTINGS: Omit = { suppress_everyone: false, suppress_roles: false, mute_scheduled_events: false, @@ -95,7 +95,7 @@ function parseEndTime(end_time?: string): number | null { return Number.isFinite(ms) ? ms : null; } -function isMuted(config: { +function isMuteActive(config: { muted?: boolean; mute_config?: { end_time?: string; @@ -114,8 +114,8 @@ interface UnreadInboxReadState { } class UserGuildSettings { - private readonly guildSettings = observable.map(); - private readonly mutedChannels = observable.map>(); + private readonly guildSettings = observable.map(); + private readonly mutedChannelsByGuild = observable.map>(); updateCounter = 0; private readonly pendingGuildUpdates = new Set(); private readonly guildMuteTimers = new Map(); @@ -149,7 +149,7 @@ class UserGuildSettings { this.pendingGuildUpdates.add(guildId as GuildId | null); } - private getDefaultSettings(guildId: string): GuildSettings { + private getDefaultSettings(guildId: string): StoredGuildSettings { return { ...DEFAULT_GUILD_SETTINGS, guild_id: guildId, @@ -159,12 +159,12 @@ class UserGuildSettings { }; } - private getStoredSettings(guildId: string | null): GuildSettings | undefined { + private getStoredSettings(guildId: string | null): StoredGuildSettings | undefined { const key = this.storageKeyFor(guildId); return this.guildSettings.get(key as GuildId); } - private ensureGuildSettings(guildId: string | null): GuildSettings { + private ensureGuildSettings(guildId: string | null): StoredGuildSettings { const key = this.storageKeyFor(guildId); let settings = this.getStoredSettings(key); if (settings == null) { @@ -174,12 +174,12 @@ class UserGuildSettings { return settings; } - getGuildSettings(guildId: string | null): GuildSettings { + getGuildSettings(guildId: string | null): StoredGuildSettings { const key = this.storageKeyFor(guildId); return this.getStoredSettings(key) ?? this.getDefaultSettings(key); } - getSettings(guildId: string | null): GuildSettings { + getSettingsForScope(guildId: string | null): StoredGuildSettings { return this.getGuildSettings(guildId); } @@ -208,17 +208,17 @@ class UserGuildSettings { return result as Record; } - private updateMutedChannelsCache(guildId: string, settings: GuildSettings): void { + private updateMutedChannelsCache(guildId: string, settings: StoredGuildSettings): void { const set = new Set(); for (const [channelId, override] of Object.entries(settings.channel_overrides)) { - if (isMuted(override)) { + if (isMuteActive(override)) { set.add(channelId as ChannelId); } } if (set.size > 0) { - this.mutedChannels.set(guildId as GuildId, set); + this.mutedChannelsByGuild.set(guildId as GuildId, set); } else { - this.mutedChannels.delete(guildId as GuildId); + this.mutedChannelsByGuild.delete(guildId as GuildId); } } @@ -251,7 +251,7 @@ class UserGuildSettings { } } - private setupMuteTimers(guildId: string, settings: GuildSettings): void { + private setupMuteTimers(guildId: string, settings: StoredGuildSettings): void { this.clearGuildMuteTimer(guildId); this.clearGuildChannelTimers(guildId); if (settings.muted && settings.mute_config?.end_time) { @@ -287,7 +287,7 @@ class UserGuildSettings { } } - private sanitizeGuildMute(settings: GuildSettings): GuildSettings { + private sanitizeGuildMute(settings: StoredGuildSettings): StoredGuildSettings { if (!settings.muted) return settings; const endMs = parseEndTime(settings.mute_config?.end_time); if (endMs != null && endMs <= Date.now()) { @@ -296,7 +296,7 @@ class UserGuildSettings { return settings; } - private sanitizeChannelMutes(settings: GuildSettings): GuildSettings { + private sanitizeChannelMutes(settings: StoredGuildSettings): StoredGuildSettings { let changed = false; const overrides: Record = {...settings.channel_overrides}; for (const [channelId, override] of Object.entries(overrides)) { @@ -314,7 +314,7 @@ class UserGuildSettings { const key = this.storageKeyFor(guildId); const existing = this.ensureGuildSettings(key); const overridesInput = 'channel_overrides' in updates ? updates.channel_overrides : existing.channel_overrides; - const merged: GuildSettings = { + const merged: StoredGuildSettings = { ...existing, ...updates, guild_id: key, @@ -333,7 +333,7 @@ class UserGuildSettings { const settings = this.ensureGuildSettings(key); const existing = settings.channel_overrides[channelId as ChannelId] ?? DEFAULT_CHANNEL_OVERRIDE(channelId); const updated = {...existing, ...updates, channel_id: channelId}; - const next: GuildSettings = { + const next: StoredGuildSettings = { ...settings, channel_overrides: {...settings.channel_overrides, [channelId]: updated} as Record, }; @@ -353,7 +353,10 @@ class UserGuildSettings { const existing = nextOverrides[channelId] ?? DEFAULT_CHANNEL_OVERRIDE(channelId); nextOverrides[channelId] = {...existing, ...updates, channel_id: channelId}; } - const next: GuildSettings = {...settings, channel_overrides: nextOverrides as Record}; + const next: StoredGuildSettings = { + ...settings, + channel_overrides: nextOverrides as Record, + }; const sanitized = this.sanitizeChannelMutes(this.sanitizeGuildMute(next)); this.guildSettings.set(key as GuildId, sanitized); this.updateMutedChannelsCache(key, sanitized); @@ -362,24 +365,24 @@ class UserGuildSettings { this.notifyChange(); } - isSuppressEveryoneEnabled(guildId: string | null): boolean { + isEveryoneMentionSuppressed(guildId: string | null): boolean { return guildId != null && this.getGuildSettings(guildId).suppress_everyone; } - isSuppressRolesEnabled(guildId: string | null): boolean { + isRoleMentionSuppressed(guildId: string | null): boolean { return guildId != null && this.getGuildSettings(guildId).suppress_roles; } - isMuteScheduledEventsEnabled(guildId: string | null): boolean { + areScheduledEventsMuted(guildId: string | null): boolean { return guildId != null && this.getGuildSettings(guildId).mute_scheduled_events; } - isMobilePushEnabled(guildId: string | null): boolean { + isMobilePushAllowed(guildId: string | null): boolean { return guildId == null || this.getGuildSettings(guildId).mobile_push; } - isMuted(guildId: string | null): boolean { - return isMuted(this.getGuildSettings(guildId)); + isWholeGuildMuted(guildId: string | null): boolean { + return isMuteActive(this.getGuildSettings(guildId)); } getGuildMessageNotifications(guildId: string | null): number { @@ -401,7 +404,7 @@ class UserGuildSettings { return settings.message_notifications; } - getGuildIds(): Array { + getKnownGuildIds(): Array { return Array.from(this.guildSettings.keys()); } @@ -409,7 +412,7 @@ class UserGuildSettings { return this.getGuildSettings(guildId).message_notifications; } - getChannelOverrides(guildId: string | null): Record { + getAllChannelOverrides(guildId: string | null): Record { return this.getGuildSettings(guildId).channel_overrides; } @@ -417,50 +420,59 @@ class UserGuildSettings { return this.getGuildSettings(guildId).channel_overrides[channelId as ChannelId]; } - getChannelMessageNotifications(guildId: string | null, channelId: string): number { + getChannelNotificationLevel(guildId: string | null, channelId: string): number { return ( this.getGuildSettings(guildId).channel_overrides[channelId as ChannelId]?.message_notifications ?? MessageNotifications.NULL ); } - isChannelMuted(guildId: string | null, channelId: string): boolean { + isChannelDirectlyMuted(guildId: string | null, channelId: string): boolean { const override = this.getGuildSettings(guildId).channel_overrides[channelId as ChannelId]; - return override != null && isMuted(override); + return override != null && isMuteActive(override); } - isCategoryMuted(guildId: string | null, channelId: string): boolean { + isParentCategoryMuted(guildId: string | null, channelId: string): boolean { if (guildId == null) return false; const channel = Channels.getChannel(channelId); - return channel?.parentId != null && this.isChannelMuted(guildId, channel.parentId); + return channel?.parentId != null && this.isChannelDirectlyMuted(guildId, channel.parentId); } - isGuildOrCategoryOrChannelMuted(guildId: string | null, channelId: string): boolean { - return this.isMuted(guildId) || this.isCategoryMuted(guildId, channelId) || this.isChannelMuted(guildId, channelId); + isMutedAtAnyLevel(guildId: string | null, channelId: string): boolean { + return ( + this.isWholeGuildMuted(guildId) || + this.isParentCategoryMuted(guildId, channelId) || + this.isChannelDirectlyMuted(guildId, channelId) + ); } isGuildOrChannelMuted(guildId: string | null, channelId: string): boolean { - return this.isMuted(guildId) || this.isChannelMuted(guildId, channelId); + return this.isWholeGuildMuted(guildId) || this.isChannelDirectlyMuted(guildId, channelId); } isCategoryOrChannelMuted(guildId: string | null, channelId: string): boolean { - return this.isCategoryMuted(guildId, channelId) || this.isChannelMuted(guildId, channelId); + return this.isParentCategoryMuted(guildId, channelId) || this.isChannelDirectlyMuted(guildId, channelId); } - getMutedChannels(guildId: string): Set { - return new Set(this.mutedChannels.get(guildId as GuildId) ?? []); + getMutedChannelIds(guildId: string): Set { + return new Set(this.mutedChannelsByGuild.get(guildId as GuildId) ?? []); } - isChannelCollapsed(guildId: string | null, channelId: string): boolean { + isChannelSectionCollapsed(guildId: string | null, channelId: string): boolean { return this.getChannelOverride(guildId, channelId)?.collapsed ?? false; } - resolvedMessageNotifications(channel: {id: string; guildId?: string; parentId?: string; type: number}): number { + resolveEffectiveMessageNotifications(channel: { + id: string; + guildId?: string; + parentId?: string; + type: number; + }): number { const guildId = channel.guildId ?? null; - const direct = this.getChannelMessageNotifications(guildId, channel.id); + const direct = this.getChannelNotificationLevel(guildId, channel.id); if (direct !== MessageNotifications.NULL && direct !== MessageNotifications.INHERIT) return direct; if (channel.parentId != null && guildId != null) { - const parent = this.getChannelMessageNotifications(guildId, channel.parentId); + const parent = this.getChannelNotificationLevel(guildId, channel.parentId); if (parent !== MessageNotifications.NULL && parent !== MessageNotifications.INHERIT) return parent; } return this.getGuildMessageNotifications(guildId); @@ -480,7 +492,7 @@ class UserGuildSettings { private getLockedCommunityUnreadBadgesLevel(guildId: string | null): number | null { if (guildId == null) return null; - if (this.isMuted(guildId)) return MessageNotifications.NO_MESSAGES; + if (this.isWholeGuildMuted(guildId)) return MessageNotifications.NO_MESSAGES; const notificationLevel = this.getGuildMessageNotifications(guildId); if (notificationLevel === MessageNotifications.ALL_MESSAGES) return MessageNotifications.ALL_MESSAGES; if (notificationLevel === MessageNotifications.NO_MESSAGES) return MessageNotifications.NO_MESSAGES; @@ -527,35 +539,35 @@ class UserGuildSettings { const guildId = channel.guildId ?? null; return resolveUnreadInboxVisibility({ unreadBadgesLevel: this.resolvedUnreadBadgesLevel(channel), - isMuted: this.isGuildOrCategoryOrChannelMuted(guildId, channel.id), + isMuted: this.isMutedAtAnyLevel(guildId, channel.id), hasUnread: readState.hasUnread, hasMentions: readState.hasMentions, }); } - resolveUnreadSetting(channel: {id: string; guildId?: string; parentId?: string; type: number}): string { - const level = this.resolvedMessageNotifications(channel); + getUnreadSettingKey(channel: {id: string; guildId?: string; parentId?: string; type: number}): string { + const level = this.resolveEffectiveMessageNotifications(channel); return level === MessageNotifications.ALL_MESSAGES ? 'all_messages' : 'only_mentions'; } - allowNoMessages(channel: {id: string; guildId?: string; parentId?: string; type: number}): boolean { + resolvesToNoMessages(channel: {id: string; guildId?: string; parentId?: string; type: number}): boolean { return ( this.isGuildOrChannelMuted(channel.guildId ?? null, channel.id) || - this.resolvedMessageNotifications(channel) === MessageNotifications.NO_MESSAGES + this.resolveEffectiveMessageNotifications(channel) === MessageNotifications.NO_MESSAGES ); } - allowAllMessages(channel: {id: string; guildId?: string; parentId?: string; type: number}): boolean { + resolvesToAllMessages(channel: {id: string; guildId?: string; parentId?: string; type: number}): boolean { return ( !this.isGuildOrChannelMuted(channel.guildId ?? null, channel.id) && - this.resolvedMessageNotifications(channel) === MessageNotifications.ALL_MESSAGES + this.resolveEffectiveMessageNotifications(channel) === MessageNotifications.ALL_MESSAGES ); } handleConnectionOpen(userGuildSettings: Array): void { this.clearAllTimers(); this.guildSettings.clear(); - this.mutedChannels.clear(); + this.mutedChannelsByGuild.clear(); for (const settings of userGuildSettings) { this.updateGuildSettings(settings.guild_id, settings); } diff --git a/fluxer_app/src/features/voice/utils/DeepFilterNoiseProcessor.test.ts b/fluxer_app/src/features/voice/utils/DeepFilterNoiseProcessor.test.ts deleted file mode 100644 index f6c3a35dc..000000000 --- a/fluxer_app/src/features/voice/utils/DeepFilterNoiseProcessor.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; - -vi.mock('@app/features/app/state/RuntimeConfig', () => ({ - default: {staticCdnEndpoint: 'https://cdn.test'}, -})); - -vi.mock('@app/features/voice/state/VoiceSettings', () => ({ - default: { - getDeepFilterNoiseSuppression: () => true, - getDeepFilterNoiseSuppressionLevel: () => 80, - }, -})); - -const deepFilterHarness = vi.hoisted(() => ({ - instances: [] as Array<{ - audioContext: AudioContext | null; - processedTrack: MediaStreamTrack | null; - sampleRateAtInit: number | null; - }>, -})); - -vi.mock('deepfilternet3-noise-filter', () => ({ - DeepFilterNoiseFilterProcessor: class { - audioContext: AudioContext | null = null; - processedTrack: MediaStreamTrack | null = null; - sampleRateAtInit: number | null = null; - - constructor() { - deepFilterHarness.instances.push(this); - } - - async init(): Promise { - this.sampleRateAtInit = this.audioContext?.sampleRate ?? null; - this.processedTrack = {} as MediaStreamTrack; - } - - async destroy(): Promise {} - }, -})); - -const MODEL_SAMPLE_RATE = 48000; - -interface ContextHarness { - requestedRates: Array; - closedRates: Array; -} - -function installAudioGraph(config: {hardwareSampleRate: number; canBridgeSampleRates: boolean}): ContextHarness { - const harness: ContextHarness = {requestedRates: [], closedRates: []}; - - class FakeAudioContext { - state: AudioContextState = 'running'; - sampleRate: number; - - constructor(options?: AudioContextOptions) { - harness.requestedRates.push(options?.sampleRate); - this.sampleRate = options?.sampleRate ?? config.hardwareSampleRate; - } - - createMediaStreamSource(): {disconnect: () => void} { - if (!config.canBridgeSampleRates && this.sampleRate !== config.hardwareSampleRate) { - throw new DOMException( - 'Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported.', - 'NotSupportedError', - ); - } - return {disconnect: (): void => undefined}; - } - - async close(): Promise { - harness.closedRates.push(this.sampleRate); - this.state = 'closed'; - } - } - - vi.stubGlobal('window', {AudioContext: FakeAudioContext}); - vi.stubGlobal('MediaStream', class {}); - return harness; -} - -async function loadModule() { - vi.resetModules(); - return await import('@app/features/voice/utils/DeepFilterNoiseProcessor'); -} - -function createSourceContext(sampleRate: number): AudioContext { - return {sampleRate} as unknown as AudioContext; -} - -const feedTrack = {} as MediaStreamTrack; - -describe('resolveDeepFilterAudioContext', () => { - afterEach(() => { - vi.unstubAllGlobals(); - }); - - it('runs the worklet at the model rate when the capture graph already runs there', async () => { - const harness = installAudioGraph({hardwareSampleRate: MODEL_SAMPLE_RATE, canBridgeSampleRates: false}); - const {resolveDeepFilterAudioContext} = await loadModule(); - - const context = resolveDeepFilterAudioContext(createSourceContext(MODEL_SAMPLE_RATE), feedTrack); - - expect(context?.sampleRate).toBe(MODEL_SAMPLE_RATE); - expect(harness.requestedRates).toEqual([MODEL_SAMPLE_RATE]); - expect(harness.closedRates).toEqual([]); - }); - - it('keeps the model rate on browsers that resample across graphs', async () => { - const harness = installAudioGraph({hardwareSampleRate: 44100, canBridgeSampleRates: true}); - const {resolveDeepFilterAudioContext} = await loadModule(); - - const context = resolveDeepFilterAudioContext(createSourceContext(44100), feedTrack); - - expect(context?.sampleRate).toBe(MODEL_SAMPLE_RATE); - expect(harness.requestedRates).toEqual([MODEL_SAMPLE_RATE]); - expect(harness.closedRates).toEqual([]); - }); - - it('falls back to the capture rate when the browser refuses to bridge sample rates', async () => { - const harness = installAudioGraph({hardwareSampleRate: 44100, canBridgeSampleRates: false}); - const {resolveDeepFilterAudioContext} = await loadModule(); - - const context = resolveDeepFilterAudioContext(createSourceContext(44100), feedTrack); - - expect(context?.sampleRate).toBe(44100); - expect(harness.requestedRates).toEqual([MODEL_SAMPLE_RATE, 44100]); - expect(harness.closedRates).toEqual([MODEL_SAMPLE_RATE]); - }); - - it('falls back to the capture rate for a bluetooth headset running well below the model rate', async () => { - installAudioGraph({hardwareSampleRate: 16000, canBridgeSampleRates: false}); - const {resolveDeepFilterAudioContext} = await loadModule(); - - const context = resolveDeepFilterAudioContext(createSourceContext(16000), feedTrack); - - expect(context?.sampleRate).toBe(16000); - }); -}); - -function createCaptureContext(sampleRate: number): AudioContext { - const track = {stop: (): void => undefined} as unknown as MediaStreamTrack; - const node = { - connect: (next: unknown): unknown => next, - disconnect: (): void => undefined, - stream: {getAudioTracks: (): Array => [track]}, - type: '', - frequency: {value: 0}, - Q: {value: 0}, - threshold: {value: 0}, - knee: {value: 0}, - ratio: {value: 0}, - attack: {value: 0}, - release: {value: 0}, - }; - return { - sampleRate, - createMediaStreamDestination: () => node, - createMediaStreamSource: () => node, - createBiquadFilter: () => node, - createDynamicsCompressor: () => node, - } as unknown as AudioContext; -} - -describe('buildDeepFilterAudioChain', () => { - beforeEach(() => { - deepFilterHarness.instances.length = 0; - }); - - afterEach(() => { - vi.unstubAllGlobals(); - }); - - it('gives the processor its audio context before the processor builds its own graph', async () => { - installAudioGraph({hardwareSampleRate: MODEL_SAMPLE_RATE, canBridgeSampleRates: false}); - const {buildDeepFilterAudioChain} = await loadModule(); - - await buildDeepFilterAudioChain({audioContext: createCaptureContext(MODEL_SAMPLE_RATE)}); - - expect(deepFilterHarness.instances[0]?.sampleRateAtInit).toBe(MODEL_SAMPLE_RATE); - }); - - it('gives the processor a capture-rate context when the browser refuses to bridge sample rates', async () => { - installAudioGraph({hardwareSampleRate: 44100, canBridgeSampleRates: false}); - const {buildDeepFilterAudioChain} = await loadModule(); - - await buildDeepFilterAudioChain({audioContext: createCaptureContext(44100)}); - - expect(deepFilterHarness.instances[0]?.sampleRateAtInit).toBe(44100); - }); -}); diff --git a/fluxer_app/src/features/voice/utils/InAppMediaSoundCapture.test.ts b/fluxer_app/src/features/voice/utils/InAppMediaSoundCapture.test.ts deleted file mode 100644 index c649e8a82..000000000 --- a/fluxer_app/src/features/voice/utils/InAppMediaSoundCapture.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import {beforeEach, describe, expect, it, vi} from 'vitest'; - -const master = {connect: vi.fn(), disconnect: vi.fn()}; -const createdGains: Array<{connect: ReturnType; disconnect: ReturnType}> = []; -let captureActive = true; - -vi.mock('@app/features/notification/utils/SoundUtils', () => ({ - getSoundCaptureAudioContext: () => ({ - createMediaElementSource: vi.fn(() => ({connect: vi.fn(), disconnect: vi.fn()})), - createGain: vi.fn(() => { - const gain = {connect: vi.fn(), disconnect: vi.fn(), gain: {value: 1}}; - createdGains.push(gain); - return gain; - }), - }), - getSoundCaptureMasterGainNode: () => master, - isSoundCaptureActive: () => captureActive, - onSoundCaptureActivated: () => () => undefined, -})); - -const {routeMediaElementForSoundCapture} = await import('@app/features/voice/utils/InAppMediaSoundCapture'); - -function createElement(): HTMLMediaElement { - const listeners = new Map void>(); - return { - volume: 1, - muted: false, - addEventListener: (type: string, listener: () => void) => listeners.set(type, listener), - removeEventListener: (type: string) => listeners.delete(type), - listenerCount: () => listeners.size, - } as unknown as HTMLMediaElement & {listenerCount: () => number}; -} - -describe('in-app media sound capture', () => { - beforeEach(() => { - createdGains.length = 0; - captureActive = true; - }); - - it('disconnects the element from the master graph when the caller disposes', () => { - const element = createElement(); - const dispose = routeMediaElementForSoundCapture(element); - const gain = createdGains.at(-1); - expect(gain?.connect).toHaveBeenCalledWith(master); - dispose(); - expect(gain?.disconnect).toHaveBeenCalled(); - }); - - it('removes the volume listener when the caller disposes', () => { - const element = createElement() as HTMLMediaElement & {listenerCount: () => number}; - const dispose = routeMediaElementForSoundCapture(element); - expect(element.listenerCount()).toBe(1); - dispose(); - expect(element.listenerCount()).toBe(0); - }); - - it('reuses the existing source node when the same element is routed again', () => { - const element = createElement(); - routeMediaElementForSoundCapture(element)(); - const gainsAfterFirst = createdGains.length; - const dispose = routeMediaElementForSoundCapture(element); - expect(createdGains.length).toBe(gainsAfterFirst); - expect(createdGains.at(-1)?.connect).toHaveBeenCalledTimes(2); - dispose(); - }); -}); diff --git a/fluxer_app/src/features/voice/utils/VoiceProcessingProfile.test.ts b/fluxer_app/src/features/voice/utils/VoiceProcessingProfile.test.ts deleted file mode 100644 index 7d40035b9..000000000 --- a/fluxer_app/src/features/voice/utils/VoiceProcessingProfile.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { - FOCUSED_VOICE_DEEP_FILTER_NOISE_REDUCTION_LEVEL, - resolveVoiceProcessing, - type VoiceProcessingSettingsLike, -} from '@app/features/voice/utils/VoiceProcessingProfile'; -import {describe, expect, it, vi} from 'vitest'; - -vi.mock('@app/features/voice/engine/VoiceDevicePermissionState', () => ({ - default: {getState: () => ({inputDevices: []})}, -})); - -vi.mock('@app/features/voice/utils/VoiceDeviceManager', () => ({ - resolveEffectiveDeviceId: () => null, -})); - -function createSettings(overrides: Partial = {}): VoiceProcessingSettingsLike { - return { - voiceProcessingMode: 'voice', - echoCancellation: true, - noiseSuppression: true, - autoGainControl: true, - deepFilterNoiseSuppression: true, - deepFilterNoiseSuppressionLevel: 80, - ...overrides, - }; -} - -describe('resolveVoiceProcessing', () => { - it('keeps automatic gain control on for focused voice at factory defaults', () => { - const resolved = resolveVoiceProcessing(createSettings()); - expect(resolved.mode).toBe('voice'); - expect(resolved.autoGainControl).toBe(true); - expect(resolved.deepFilter).toBe(true); - expect(resolved.deepFilterNoiseReductionLevel).toBe(FOCUSED_VOICE_DEEP_FILTER_NOISE_REDUCTION_LEVEL); - }); - - it('honours an explicit opt out of automatic gain control in focused voice', () => { - const resolved = resolveVoiceProcessing(createSettings({autoGainControl: false})); - expect(resolved.autoGainControl).toBe(false); - }); - - it('keeps automatic gain control on in custom mode while deep filter is enabled', () => { - const resolved = resolveVoiceProcessing( - createSettings({voiceProcessingMode: 'custom', deepFilterNoiseSuppression: true}), - ); - expect(resolved.autoGainControl).toBe(true); - expect(resolved.deepFilter).toBe(true); - }); - - it('still suppresses browser noise suppression when deep filter is enabled in custom mode', () => { - const resolved = resolveVoiceProcessing( - createSettings({voiceProcessingMode: 'custom', noiseSuppression: true, deepFilterNoiseSuppression: true}), - ); - expect(resolved.browserNoiseSuppression).toBe(false); - }); - - it('leaves studio mode as the untouched bypass', () => { - const resolved = resolveVoiceProcessing(createSettings({voiceProcessingMode: 'studio'})); - expect(resolved.autoGainControl).toBe(false); - expect(resolved.echoCancellation).toBe(false); - expect(resolved.browserNoiseSuppression).toBe(false); - expect(resolved.deepFilter).toBe(false); - expect(resolved.contentHint).toBe('music'); - }); -}); diff --git a/fluxer_app/src/features/voice/utils/VoiceTtsUtils.tsx b/fluxer_app/src/features/voice/utils/VoiceTtsUtils.tsx index d408ba7bb..41bda5fb5 100644 --- a/fluxer_app/src/features/voice/utils/VoiceTtsUtils.tsx +++ b/fluxer_app/src/features/voice/utils/VoiceTtsUtils.tsx @@ -419,7 +419,7 @@ function handleIncomingTtsMessage(message: Message): void { } if ( UserGuildSettings.isGuildOrChannelMuted(channel.guildId ?? null, channel.id) || - UserGuildSettings.isCategoryMuted(channel.guildId ?? null, channel.id) + UserGuildSettings.isParentCategoryMuted(channel.guildId ?? null, channel.id) ) { return; } diff --git a/fluxer_app/src/features/window/commands/WindowCommands.ts b/fluxer_app/src/features/window/commands/WindowCommands.ts index db4fa8574..2be341551 100644 --- a/fluxer_app/src/features/window/commands/WindowCommands.ts +++ b/fluxer_app/src/features/window/commands/WindowCommands.ts @@ -6,7 +6,7 @@ import Notification from '@app/features/ui/state/Notification'; import Window from '@app/features/window/state/Window'; function applyFocusSideEffects(focused: boolean): void { - GuildReadState.handleWindowFocus(); + GuildReadState.handleWindowFocused(); Notification.handleWindowFocus({focused}); if (focused) { Idle.recordActivity();