feat(app): toggle for sequential msg file send order (#1245)

Co-authored-by: Hampus <[email protected]>
This commit is contained in:
MizarcandHampus authored and GitHub committed 2026-07-02 03:35:03 +02:00
1 parent 33d05f9763
commit de56e3fd81
7 files changed
+150 -3

No files matched your search

@@ -601,6 +601,7 @@ export interface AccessibilitySettings {
stayInteractiveWhenUnfocused: boolean; stayInteractiveWhenUnfocused: boolean;
firstClickPassThroughWhenUnfocused: boolean; firstClickPassThroughWhenUnfocused: boolean;
scrollToBottomOnMessageSend: boolean; scrollToBottomOnMessageSend: boolean;
sequentialFileSend: boolean;
showNeko: boolean; showNeko: boolean;
keepNekoStill: boolean; keepNekoStill: boolean;
showVideoSeekPreviewThumbnails: boolean; showVideoSeekPreviewThumbnails: boolean;
@@ -718,6 +719,7 @@ class Accessibility {
stayInteractiveWhenUnfocused = false; stayInteractiveWhenUnfocused = false;
firstClickPassThroughWhenUnfocused = false; firstClickPassThroughWhenUnfocused = false;
scrollToBottomOnMessageSend = true; scrollToBottomOnMessageSend = true;
sequentialFileSend = false;
showNeko = false; showNeko = false;
keepNekoStill = false; keepNekoStill = false;
showVideoSeekPreviewThumbnails = false; showVideoSeekPreviewThumbnails = false;
@@ -828,6 +830,7 @@ class Accessibility {
'stayInteractiveWhenUnfocused', 'stayInteractiveWhenUnfocused',
'firstClickPassThroughWhenUnfocused', 'firstClickPassThroughWhenUnfocused',
'scrollToBottomOnMessageSend', 'scrollToBottomOnMessageSend',
'sequentialFileSend',
], ],
toMessage: (s) => ({ toMessage: (s) => ({
saturationFactor: s.saturationFactor, saturationFactor: s.saturationFactor,
@@ -888,6 +891,7 @@ class Accessibility {
stayInteractiveWhenUnfocused: s.stayInteractiveWhenUnfocused, stayInteractiveWhenUnfocused: s.stayInteractiveWhenUnfocused,
firstClickPassThroughWhenUnfocused: s.firstClickPassThroughWhenUnfocused, firstClickPassThroughWhenUnfocused: s.firstClickPassThroughWhenUnfocused,
scrollToBottomOnMessageSend: s.scrollToBottomOnMessageSend, scrollToBottomOnMessageSend: s.scrollToBottomOnMessageSend,
sequentialFileSend: s.sequentialFileSend,
}), }),
applyMessage: (s, m) => { applyMessage: (s, m) => {
if (m.saturationFactor !== undefined) s.saturationFactor = m.saturationFactor; if (m.saturationFactor !== undefined) s.saturationFactor = m.saturationFactor;
@@ -978,6 +982,7 @@ class Accessibility {
if (m.firstClickPassThroughWhenUnfocused !== undefined) if (m.firstClickPassThroughWhenUnfocused !== undefined)
s.firstClickPassThroughWhenUnfocused = m.firstClickPassThroughWhenUnfocused; s.firstClickPassThroughWhenUnfocused = m.firstClickPassThroughWhenUnfocused;
if (m.scrollToBottomOnMessageSend !== undefined) s.scrollToBottomOnMessageSend = m.scrollToBottomOnMessageSend; if (m.scrollToBottomOnMessageSend !== undefined) s.scrollToBottomOnMessageSend = m.scrollToBottomOnMessageSend;
if (m.sequentialFileSend !== undefined) s.sequentialFileSend = m.sequentialFileSend;
}, },
}); });
await this.applyStoredZoom(); await this.applyStoredZoom();
@@ -1314,6 +1319,8 @@ class Accessibility {
this.firstClickPassThroughWhenUnfocused = validated.firstClickPassThroughWhenUnfocused; this.firstClickPassThroughWhenUnfocused = validated.firstClickPassThroughWhenUnfocused;
if (validated.scrollToBottomOnMessageSend !== undefined) if (validated.scrollToBottomOnMessageSend !== undefined)
this.scrollToBottomOnMessageSend = validated.scrollToBottomOnMessageSend; this.scrollToBottomOnMessageSend = validated.scrollToBottomOnMessageSend;
if (validated.sequentialFileSend !== undefined)
this.sequentialFileSend = validated.sequentialFileSend;
if (validated.showNeko !== undefined && validated.showNeko !== this.showNeko) { if (validated.showNeko !== undefined && validated.showNeko !== this.showNeko) {
this.showNeko = validated.showNeko; this.showNeko = validated.showNeko;
persistLocalShowNeko(validated.showNeko); persistLocalShowNeko(validated.showNeko);
@@ -1414,6 +1421,7 @@ class Accessibility {
firstClickPassThroughWhenUnfocused: firstClickPassThroughWhenUnfocused:
data.firstClickPassThroughWhenUnfocused ?? this.firstClickPassThroughWhenUnfocused, data.firstClickPassThroughWhenUnfocused ?? this.firstClickPassThroughWhenUnfocused,
scrollToBottomOnMessageSend: data.scrollToBottomOnMessageSend ?? this.scrollToBottomOnMessageSend, scrollToBottomOnMessageSend: data.scrollToBottomOnMessageSend ?? this.scrollToBottomOnMessageSend,
sequentialFileSend: data.sequentialFileSend ?? this.sequentialFileSend,
showNeko: data.showNeko ?? this.showNeko, showNeko: data.showNeko ?? this.showNeko,
keepNekoStill: data.keepNekoStill ?? this.keepNekoStill, keepNekoStill: data.keepNekoStill ?? this.keepNekoStill,
showVideoSeekPreviewThumbnails: data.showVideoSeekPreviewThumbnails ?? this.showVideoSeekPreviewThumbnails, showVideoSeekPreviewThumbnails: data.showVideoSeekPreviewThumbnails ?? this.showVideoSeekPreviewThumbnails,
@@ -44,6 +44,7 @@ import {Logger} from '@app/features/platform/utils/AppLogger';
import {ComponentDispatch} from '@app/features/platform/utils/ComponentBus'; import {ComponentDispatch} from '@app/features/platform/utils/ComponentBus';
import {failureCode} from '@app/features/platform/utils/ResponseInspection'; import {failureCode} from '@app/features/platform/utils/ResponseInspection';
import * as ReadStateCommands from '@app/features/read_state/commands/ReadStateCommands'; import * as ReadStateCommands from '@app/features/read_state/commands/ReadStateCommands';
import type {RestResponse} from '@app/features/platform/types/TransportTypes';
import ReadStates from '@app/features/read_state/state/ReadStates'; import ReadStates from '@app/features/read_state/state/ReadStates';
import * as SlowmodeCommands from '@app/features/slowmode/commands/SlowmodeCommands'; import * as SlowmodeCommands from '@app/features/slowmode/commands/SlowmodeCommands';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands'; import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
@@ -426,6 +427,61 @@ export async function fetchMessages(
return promise; return promise;
} }
interface SequentialSendEntry {
task: () => Promise<RestResponse<WireMessage> | undefined>;
resolve: (value: RestResponse<WireMessage> | undefined) => void;
}
interface ChannelSendOrderState {
nextOrder: number;
nextExpected: number;
pending: Map<number, SequentialSendEntry>;
processing: boolean;
channelId: string;
}
const channelSendOrders = new Map<string, ChannelSendOrderState>();
function getOrCreateChannelState(channelId: string): ChannelSendOrderState {
let state = channelSendOrders.get(channelId);
if (!state) {
state = {nextOrder: 0, nextExpected: 0, pending: new Map(), processing: false, channelId};
channelSendOrders.set(channelId, state);
}
return state;
}
function orderedSendImmediately(
channelId: string,
order: number,
task: () => Promise<RestResponse<WireMessage> | undefined>,
): Promise<RestResponse<WireMessage> | undefined> {
return new Promise<RestResponse<WireMessage> | undefined>((resolve) => {
const state = getOrCreateChannelState(channelId);
state.pending.set(order, {task, resolve});
void processSequentialQueue(state);
});
}
async function processSequentialQueue(state: ChannelSendOrderState): Promise<void> {
if (state.processing) return;
state.processing = true;
try {
while (state.pending.has(state.nextExpected)) {
const entry = state.pending.get(state.nextExpected)!;
state.pending.delete(state.nextExpected);
state.nextExpected++;
const result = await entry.task();
entry.resolve(result);
}
} finally {
state.processing = false;
if (state.pending.size === 0 && state.nextExpected === state.nextOrder) {
channelSendOrders.delete(state.channelId);
}
}
}
async function prepareSendAttachments( async function prepareSendAttachments(
channelId: string, channelId: string,
params: SendMessageParams, params: SendMessageParams,
@@ -445,13 +501,24 @@ async function prepareSendAttachments(
return {attachments: prepared.attachments, files: prepared.files}; return {attachments: prepared.attachments, files: prepared.files};
} }
function nextChannelOrder(channelId: string): number {
return getOrCreateChannelState(channelId).nextOrder++;
}
export async function send(channelId: string, params: SendMessageParams): Promise<WireMessage | null> { export async function send(channelId: string, params: SendMessageParams): Promise<WireMessage | null> {
if (!MessageQueue.consumeLocalSendReservation(channelId, params.nonce)) { if (!MessageQueue.consumeLocalSendReservation(channelId, params.nonce)) {
MessageQueue.rejectLocalRateLimitedSend(channelId, params.nonce, params.hasAttachments); MessageQueue.rejectLocalRateLimitedSend(channelId, params.nonce, params.hasAttachments);
return null; return null;
} }
const sendOrder =
Accessibility.sequentialFileSend && params.hasAttachments ? nextChannelOrder(channelId) : -1;
const prepared = await prepareSendAttachments(channelId, params); const prepared = await prepareSendAttachments(channelId, params);
if (!prepared) return null; if (!prepared) {
if (Accessibility.sequentialFileSend) {
orderedSendImmediately(channelId, sendOrder, () => Promise.resolve(undefined));
}
return null;
}
const payload = { const payload = {
type: 'send' as const, type: 'send' as const,
channelId, channelId,
@@ -469,7 +536,9 @@ export async function send(channelId: string, params: SendMessageParams): Promis
}; };
if (params.hasAttachments) { if (params.hasAttachments) {
logger.debug(`Sending attachment message immediately for channel ${channelId}`); logger.debug(`Sending attachment message immediately for channel ${channelId}`);
const result = await MessageQueue.sendImmediately(payload); const result = Accessibility.sequentialFileSend
? await orderedSendImmediately(channelId, sendOrder, () => MessageQueue.sendImmediately(payload))
: await MessageQueue.sendImmediately(payload);
if (result?.body) { if (result?.body) {
logger.debug(`Attachment message sent successfully in channel ${channelId}`); logger.debug(`Attachment message sent successfully in channel ${channelId}`);
Messages.handleIncomingMessage({channelId, message: result.body}); Messages.handleIncomingMessage({channelId, message: result.body});
@@ -26,6 +26,7 @@ import {
SaveGifFavoritesControl, SaveGifFavoritesControl,
ScrollToBottomOnSendControl, ScrollToBottomOnSendControl,
SearchEnginesControl, SearchEnginesControl,
SequentialFileSendControl,
SkipMarkAllAsReadControl, SkipMarkAllAsReadControl,
StripTrackingControl, StripTrackingControl,
TranslatorsControl, TranslatorsControl,
@@ -76,6 +77,7 @@ export const DIRECT_CONTROL_ITEM_IDS = new Set([
'chat-settings-input-buttons', 'chat-settings-input-buttons',
'chat-settings-convert-emoticons', 'chat-settings-convert-emoticons',
'chat-settings-preupload-attachments', 'chat-settings-preupload-attachments',
'chat-settings-sequential-file-send',
'chat-settings-scroll-to-bottom-on-send', 'chat-settings-scroll-to-bottom-on-send',
'chat-settings-skip-mark-all-as-read-confirmation', 'chat-settings-skip-mark-all-as-read-confirmation',
'chat-settings-hide-muted-channels', 'chat-settings-hide-muted-channels',
@@ -115,6 +117,7 @@ export const COMPACT_SWITCH_CONTROL_ITEM_IDS = new Set([
'chat-settings-trust-domains', 'chat-settings-trust-domains',
'chat-settings-convert-emoticons', 'chat-settings-convert-emoticons',
'chat-settings-preupload-attachments', 'chat-settings-preupload-attachments',
'chat-settings-sequential-file-send',
'chat-settings-scroll-to-bottom-on-send', 'chat-settings-scroll-to-bottom-on-send',
'chat-settings-skip-mark-all-as-read-confirmation', 'chat-settings-skip-mark-all-as-read-confirmation',
'chat-settings-hide-muted-channels', 'chat-settings-hide-muted-channels',
@@ -240,6 +243,10 @@ export const AdvancedSettingControl = observer(({item}: {item: SearchableSetting
return ( return (
<PreuploadMessageAttachmentsControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.preupload-message-attachments-control" /> <PreuploadMessageAttachmentsControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.preupload-message-attachments-control" />
); );
case 'chat-settings-sequential-file-send':
return (
<SequentialFileSendControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.sequential-file-send-control" />
);
case 'chat-settings-scroll-to-bottom-on-send': case 'chat-settings-scroll-to-bottom-on-send':
return ( return (
<ScrollToBottomOnSendControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.scroll-to-bottom-on-send-control" /> <ScrollToBottomOnSendControl data-flx="user.advanced-setting-direct-controls.advanced-setting-control.scroll-to-bottom-on-send-control" />
@@ -109,6 +109,10 @@ const UPLOAD_ATTACHMENTS_BEFORE_SENDING_DESCRIPTOR = msg({
message: 'Upload attachments before sending', message: 'Upload attachments before sending',
comment: 'Short label for an advanced message input privacy preference.', comment: 'Short label for an advanced message input privacy preference.',
}); });
const SEND_FILES_IN_SEQUENTIAL_ORDER_DESCRIPTOR = msg({
message: 'Send file messages in order',
comment: 'Short label for an advanced message input preference.',
});
const SHOW_STICKERS_BUTTON_DESCRIPTOR = msg({ const SHOW_STICKERS_BUTTON_DESCRIPTOR = msg({
message: 'Show stickers button', message: 'Show stickers button',
comment: 'Short label for an advanced message input preference.', comment: 'Short label for an advanced message input preference.',
@@ -579,6 +583,19 @@ export const PreuploadMessageAttachmentsControl = observer(() => {
); );
}); });
export const SequentialFileSendControl = observer(() => {
const {i18n} = useLingui();
return (
<Switch
ariaLabel={i18n._(SEND_FILES_IN_SEQUENTIAL_ORDER_DESCRIPTOR)}
value={Accessibility.sequentialFileSend}
onChange={(value) => AccessibilityCommands.update({sequentialFileSend: value})}
compact
data-flx="user.advanced-settings-tab.switch.sequential-file-send"
/>
);
});
export const ScrollToBottomOnSendControl = observer(() => { export const ScrollToBottomOnSendControl = observer(() => {
const {i18n} = useLingui(); const {i18n} = useLingui();
return ( return (
@@ -124,6 +124,30 @@ const FILE_UPLOAD_DESCRIPTOR = msg({
message: 'File upload', message: 'File upload',
comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.', comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.',
}); });
const SEQUENTIAL_FILE_SEND_DESCRIPTOR = msg({
message: 'Send file messages in order',
comment: 'Settings search entry label. Names the settings search entry in the settings UI.',
});
const SEQUENTIAL_SEND_DESCRIPTOR = msg({
message: 'Sequential send',
comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.',
});
const FILE_ORDERING_DESCRIPTOR = msg({
message: 'File ordering',
comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.',
});
const ORDERED_UPLOAD_DESCRIPTOR = msg({
message: 'Ordered upload',
comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.',
});
const SERIAL_SEND_DESCRIPTOR = msg({
message: 'Serial send',
comment: 'Settings search synonym. Used to match this term when the user types it in the settings search bar.',
});
const ENSURES_FILES_ARE_SENT_IN_THE_ORDER_THEY_WERE_ADDED_DESCRIPTOR = msg({
message: 'Ensures file messages appear in the order you sent them',
comment: 'Settings search entry description. One-line summary of what the settings search entry controls.',
});
const START_UPLOADING_ATTACHMENTS_WHEN_THEY_ARE_ADDED_DESCRIPTOR = msg({ const START_UPLOADING_ATTACHMENTS_WHEN_THEY_ARE_ADDED_DESCRIPTOR = msg({
message: 'Start uploading attachments as soon as they are added to the message input', message: 'Start uploading attachments as soon as they are added to the message input',
comment: 'Settings search entry description. One-line summary of what the settings search entry controls.', comment: 'Settings search entry description. One-line summary of what the settings search entry controls.',
@@ -711,4 +735,20 @@ export const chatSettingsIndex: Array<SearchableSettingDescriptor> = [
audience: 'advanced', audience: 'advanced',
tags: ['chat'], tags: ['chat'],
}, },
{
id: 'chat-settings-sequential-file-send',
tabType: 'advanced_settings',
sectionId: 'chat',
label: SEQUENTIAL_FILE_SEND_DESCRIPTOR,
keywords: [
SEQUENTIAL_SEND_DESCRIPTOR,
FILE_ORDERING_DESCRIPTOR,
ORDERED_UPLOAD_DESCRIPTOR,
SERIAL_SEND_DESCRIPTOR,
FILE_UPLOAD_DESCRIPTOR,
],
description: ENSURES_FILES_ARE_SENT_IN_THE_ORDER_THEY_WERE_ADDED_DESCRIPTOR,
audience: 'advanced',
tags: ['chat'],
},
]; ];
@@ -76,6 +76,7 @@ message AccessibilitySettings {
optional double compact_message_group_spacing = 60; optional double compact_message_group_spacing = 60;
optional bool scroll_to_bottom_on_message_send = 61; optional bool scroll_to_bottom_on_message_send = 61;
optional bool dim_strikethrough_text = 62; optional bool dim_strikethrough_text = 62;
optional bool sequential_file_send = 63;
} }
message AccessibilityOverrides { message AccessibilityOverrides {
@@ -6,7 +6,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file fluxer/user/preferences/v1/accessibility.proto. * Describes the file fluxer/user/preferences/v1/accessibility.proto.
*/ */
export const file_fluxer_user_preferences_v1_accessibility: GenFile = /*@__PURE__*/ export const file_fluxer_user_preferences_v1_accessibility: GenFile = /*@__PURE__*/
fileDesc("Ci5mbHV4ZXIvdXNlci9wcmVmZXJlbmNlcy92MS9hY2Nlc3NpYmlsaXR5LnByb3RvEhpmbHV4ZXIudXNlci5wcmVmZXJlbmNlcy52MSLjIQoVQWNjZXNzaWJpbGl0eVNldHRpbmdzEh4KEXNhdHVyYXRpb25fZmFjdG9yGAEgASgBSACIAQESHgoWYWx3YXlzX3VuZGVybGluZV9saW5rcxgCIAEoCBIiChVlbmFibGVfdGV4dF9zZWxlY3Rpb24YAyABKAhIAYgBARIlChhzaG93X21lc3NhZ2Vfc2VuZF9idXR0b24YBCABKAhIAogBARIlChhzaG93X3RleHRhcmVhX2ZvY3VzX3JpbmcYBSABKAhIA4gBARIbChNoaWRlX2tleWJvYXJkX2hpbnRzGAYgASgIEicKGmVzY2FwZV9leGl0c19rZXlib2FyZF9tb2RlGAcgASgISASIAQESLAofc3luY19yZWR1Y2VkX21vdGlvbl93aXRoX3N5c3RlbRgIIAEoCEgFiAEBEiQKF3JlZHVjZWRfbW90aW9uX292ZXJyaWRlGAkgASgISAaIAQESIgoVbWVzc2FnZV9ncm91cF9zcGFjaW5nGAogASgBSAeIAQESGwoObWVzc2FnZV9ndXR0ZXIYCyABKAFICIgBARIWCglmb250X3NpemUYDCABKAFICYgBARIuCiFzaG93X3VzZXJfYXZhdGFyc19pbl9jb21wYWN0X21vZGUYDSABKAhICogBARIrCiNtb2JpbGVfc3RpY2tlcl9hbmltYXRpb25fb3ZlcnJpZGRlbhgOIAEoCBImCh5tb2JpbGVfZ2lmX2F1dG9wbGF5X292ZXJyaWRkZW4YDyABKAgSJwofbW9iaWxlX2FuaW1hdGVfZW1vamlfb3ZlcnJpZGRlbhgQIAEoCBIrCh5tb2JpbGVfc3RpY2tlcl9hbmltYXRpb25fdmFsdWUYESABKAVIC4gBARImChltb2JpbGVfZ2lmX2F1dG9wbGF5X3ZhbHVlGBIgASgISAyIAQESJwoabW9iaWxlX2FuaW1hdGVfZW1vamlfdmFsdWUYEyABKAhIDYgBARIcChRhdXRvX3NlbmRfa2xpcHlfZ2lmcxgUIAEoCBIcCg9zaG93X2dpZl9idXR0b24YFSABKAhIDogBARIeChFzaG93X21lbWVzX2J1dHRvbhgWIAEoCEgPiAEBEiEKFHNob3dfc3RpY2tlcnNfYnV0dG9uGBcgASgISBCIAQESHgoRc2hvd19lbW9qaV9idXR0b24YGCABKAhIEYgBARInChpzaG93X21lZGlhX2Zhdm9yaXRlX2J1dHRvbhgZIAEoCEgSiAEBEicKGnNob3dfbWVkaWFfZG93bmxvYWRfYnV0dG9uGBogASgISBOIAQESJQoYc2hvd19tZWRpYV9kZWxldGVfYnV0dG9uGBsgASgISBSIAQESKAobc2hvd19zdXBwcmVzc19lbWJlZHNfYnV0dG9uGBwgASgISBWIAQESHwoSc2hvd19naWZfaW5kaWNhdG9yGB0gASgISBaIAQESLQogc2hvd19hdHRhY2htZW50X2V4cGlyeV9pbmRpY2F0b3IYHiABKAhIF4gBARIvCiJ1c2VfYnJvd3Nlcl9sb2NhbGVfZm9yX3RpbWVfZm9ybWF0GB8gASgISBiIAQESXQodY2hhbm5lbF90eXBpbmdfaW5kaWNhdG9yX21vZGUYICABKA4yNi5mbHV4ZXIudXNlci5wcmVmZXJlbmNlcy52MS5DaGFubmVsVHlwaW5nSW5kaWNhdG9yTW9kZRIzCiZzaG93X3NlbGVjdGVkX2NoYW5uZWxfdHlwaW5nX2luZGljYXRvchghIAEoCEgZiAEBEiQKF3Nob3dfbWVzc2FnZV9hY3Rpb25fYmFyGCIgASgISBqIAQESNAonc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfcXVpY2tfcmVhY3Rpb25zGCMgASgISBuIAQESMQokc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfc2hpZnRfZXhwYW5kGCQgASgISByIAQESNQooc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfb25seV9tb3JlX2J1dHRvbhglIAEoCEgdiAEBEjAKI3Nob3dfZGVmYXVsdF9lbW9qaXNfaW5fYXV0b2NvbXBsZXRlGCYgASgISB6IAQESLwoic2hvd19jdXN0b21fZW1vamlzX2luX2F1dG9jb21wbGV0ZRgnIAEoCEgfiAEBEioKHXNob3dfc3RpY2tlcnNfaW5fYXV0b2NvbXBsZXRlGCggASgISCCIAQESJwoac2hvd19tZW1lc19pbl9hdXRvY29tcGxldGUYKSABKAhIIYgBARJXCh9hdHRhY2htZW50X21lZGlhX2RpbWVuc2lvbl9zaXplGCogASgOMi4uZmx1eGVyLnVzZXIucHJlZmVyZW5jZXMudjEuTWVkaWFEaW1lbnNpb25TaXplElIKGmVtYmVkX21lZGlhX2RpbWVuc2lvbl9zaXplGCsgASgOMi4uZmx1eGVyLnVzZXIucHJlZmVyZW5jZXMudjEuTWVkaWFEaW1lbnNpb25TaXplEjUKKHZvaWNlX2NoYW5uZWxfam9pbl9yZXF1aXJlc19kb3VibGVfY2xpY2sYLCABKAhIIogBARIdChBjdXN0b21fdGhlbWVfY3NzGC0gASgJSCOIAQESGwoOc2hvd19mYXZvcml0ZXMYLiABKAhIJIgBARIXCgp6b29tX2xldmVsGC8gASgBSCWIAQESUQoXZG1fbWVzc2FnZV9wcmV2aWV3X21vZGUYMCABKA4yMC5mbHV4ZXIudXNlci5wcmVmZXJlbmNlcy52MS5EbU1lc3NhZ2VQcmV2aWV3TW9kZRIfChJlbmFibGVfdHRzX2NvbW1hbmQYMSABKAhIJogBARIVCgh0dHNfcmF0ZRgyIAEoAUgniAEBEjAKI3Nob3dfZmFkZWRfdW5yZWFkX29uX211dGVkX2NoYW5uZWxzGDMgASgISCiIAQESKAobc2hvd19jb250ZXh0X21lbnVfc2hvcnRjdXRzGDQgASgISCmIAQESKgodY29uZmlybV9iZWZvcmVfc3RhcnRpbmdfY2FsbHMYNSABKAhIKogBARJEChBoZHJfZGlzcGxheV9tb2RlGDYgASgOMiouZmx1eGVyLnVzZXIucHJlZmVyZW5jZXMudjEuSGRyRGlzcGxheU1vZGUSIAoTcHJlc2VydmVfZWRpdF9kcmFmdBg3IAEoCEgriAEBEiwKH3N0YXlfaW50ZXJhY3RpdmVfd2hlbl91bmZvY3VzZWQYOCABKAhILIgBARIyCiVjb25maXJtX2JlZm9yZV9qb2luaW5nX3ZvaWNlX2NoYW5uZWxzGDkgASgISC2IAQESMAojc2NyZWVuX3JlYWRlcl9hbm5vdW5jZV9uZXdfbWVzc2FnZXMYOiABKAhILogBARI0CidmaXJzdF9jbGlja19wYXNzX3Rocm91Z2hfd2hlbl91bmZvY3VzZWQYOyABKAhIL4gBARIqCh1jb21wYWN0X21lc3NhZ2VfZ3JvdXBfc3BhY2luZxg8IAEoAUgwiAEBEi0KIHNjcm9sbF90b19ib3R0b21fb25fbWVzc2FnZV9zZW5kGD0gASgISDGIAQESIwoWZGltX3N0cmlrZXRocm91Z2hfdGV4dBg+IAEoCEgyiAEBQhQKEl9zYXR1cmF0aW9uX2ZhY3RvckIYChZfZW5hYmxlX3RleHRfc2VsZWN0aW9uQhsKGV9zaG93X21lc3NhZ2Vfc2VuZF9idXR0b25CGwoZX3Nob3dfdGV4dGFyZWFfZm9jdXNfcmluZ0IdChtfZXNjYXBlX2V4aXRzX2tleWJvYXJkX21vZGVCIgogX3N5bmNfcmVkdWNlZF9tb3Rpb25fd2l0aF9zeXN0ZW1CGgoYX3JlZHVjZWRfbW90aW9uX292ZXJyaWRlQhgKFl9tZXNzYWdlX2dyb3VwX3NwYWNpbmdCEQoPX21lc3NhZ2VfZ3V0dGVyQgwKCl9mb250X3NpemVCJAoiX3Nob3dfdXNlcl9hdmF0YXJzX2luX2NvbXBhY3RfbW9kZUIhCh9fbW9iaWxlX3N0aWNrZXJfYW5pbWF0aW9uX3ZhbHVlQhwKGl9tb2JpbGVfZ2lmX2F1dG9wbGF5X3ZhbHVlQh0KG19tb2JpbGVfYW5pbWF0ZV9lbW9qaV92YWx1ZUISChBfc2hvd19naWZfYnV0dG9uQhQKEl9zaG93X21lbWVzX2J1dHRvbkIXChVfc2hvd19zdGlja2Vyc19idXR0b25CFAoSX3Nob3dfZW1vamlfYnV0dG9uQh0KG19zaG93X21lZGlhX2Zhdm9yaXRlX2J1dHRvbkIdChtfc2hvd19tZWRpYV9kb3dubG9hZF9idXR0b25CGwoZX3Nob3dfbWVkaWFfZGVsZXRlX2J1dHRvbkIeChxfc2hvd19zdXBwcmVzc19lbWJlZHNfYnV0dG9uQhUKE19zaG93X2dpZl9pbmRpY2F0b3JCIwohX3Nob3dfYXR0YWNobWVudF9leHBpcnlfaW5kaWNhdG9yQiUKI191c2VfYnJvd3Nlcl9sb2NhbGVfZm9yX3RpbWVfZm9ybWF0QikKJ19zaG93X3NlbGVjdGVkX2NoYW5uZWxfdHlwaW5nX2luZGljYXRvckIaChhfc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJCKgooX3Nob3dfbWVzc2FnZV9hY3Rpb25fYmFyX3F1aWNrX3JlYWN0aW9uc0InCiVfc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfc2hpZnRfZXhwYW5kQisKKV9zaG93X21lc3NhZ2VfYWN0aW9uX2Jhcl9vbmx5X21vcmVfYnV0dG9uQiYKJF9zaG9Line truncated fileDesc("Ci5mbHV4ZXIvdXNlci9wcmVmZXJlbmNlcy92MS9hY2Nlc3NpYmlsaXR5LnByb3RvEhpmbHV4ZXIudXNlci5wcmVmZXJlbmNlcy52MSKfIgoVQWNjZXNzaWJpbGl0eVNldHRpbmdzEh4KEXNhdHVyYXRpb25fZmFjdG9yGAEgASgBSACIAQESHgoWYWx3YXlzX3VuZGVybGluZV9saW5rcxgCIAEoCBIiChVlbmFibGVfdGV4dF9zZWxlY3Rpb24YAyABKAhIAYgBARIlChhzaG93X21lc3NhZ2Vfc2VuZF9idXR0b24YBCABKAhIAogBARIlChhzaG93X3RleHRhcmVhX2ZvY3VzX3JpbmcYBSABKAhIA4gBARIbChNoaWRlX2tleWJvYXJkX2hpbnRzGAYgASgIEicKGmVzY2FwZV9leGl0c19rZXlib2FyZF9tb2RlGAcgASgISASIAQESLAofc3luY19yZWR1Y2VkX21vdGlvbl93aXRoX3N5c3RlbRgIIAEoCEgFiAEBEiQKF3JlZHVjZWRfbW90aW9uX292ZXJyaWRlGAkgASgISAaIAQESIgoVbWVzc2FnZV9ncm91cF9zcGFjaW5nGAogASgBSAeIAQESGwoObWVzc2FnZV9ndXR0ZXIYCyABKAFICIgBARIWCglmb250X3NpemUYDCABKAFICYgBARIuCiFzaG93X3VzZXJfYXZhdGFyc19pbl9jb21wYWN0X21vZGUYDSABKAhICogBARIrCiNtb2JpbGVfc3RpY2tlcl9hbmltYXRpb25fb3ZlcnJpZGRlbhgOIAEoCBImCh5tb2JpbGVfZ2lmX2F1dG9wbGF5X292ZXJyaWRkZW4YDyABKAgSJwofbW9iaWxlX2FuaW1hdGVfZW1vamlfb3ZlcnJpZGRlbhgQIAEoCBIrCh5tb2JpbGVfc3RpY2tlcl9hbmltYXRpb25fdmFsdWUYESABKAVIC4gBARImChltb2JpbGVfZ2lmX2F1dG9wbGF5X3ZhbHVlGBIgASgISAyIAQESJwoabW9iaWxlX2FuaW1hdGVfZW1vamlfdmFsdWUYEyABKAhIDYgBARIcChRhdXRvX3NlbmRfa2xpcHlfZ2lmcxgUIAEoCBIcCg9zaG93X2dpZl9idXR0b24YFSABKAhIDogBARIeChFzaG93X21lbWVzX2J1dHRvbhgWIAEoCEgPiAEBEiEKFHNob3dfc3RpY2tlcnNfYnV0dG9uGBcgASgISBCIAQESHgoRc2hvd19lbW9qaV9idXR0b24YGCABKAhIEYgBARInChpzaG93X21lZGlhX2Zhdm9yaXRlX2J1dHRvbhgZIAEoCEgSiAEBEicKGnNob3dfbWVkaWFfZG93bmxvYWRfYnV0dG9uGBogASgISBOIAQESJQoYc2hvd19tZWRpYV9kZWxldGVfYnV0dG9uGBsgASgISBSIAQESKAobc2hvd19zdXBwcmVzc19lbWJlZHNfYnV0dG9uGBwgASgISBWIAQESHwoSc2hvd19naWZfaW5kaWNhdG9yGB0gASgISBaIAQESLQogc2hvd19hdHRhY2htZW50X2V4cGlyeV9pbmRpY2F0b3IYHiABKAhIF4gBARIvCiJ1c2VfYnJvd3Nlcl9sb2NhbGVfZm9yX3RpbWVfZm9ybWF0GB8gASgISBiIAQESXQodY2hhbm5lbF90eXBpbmdfaW5kaWNhdG9yX21vZGUYICABKA4yNi5mbHV4ZXIudXNlci5wcmVmZXJlbmNlcy52MS5DaGFubmVsVHlwaW5nSW5kaWNhdG9yTW9kZRIzCiZzaG93X3NlbGVjdGVkX2NoYW5uZWxfdHlwaW5nX2luZGljYXRvchghIAEoCEgZiAEBEiQKF3Nob3dfbWVzc2FnZV9hY3Rpb25fYmFyGCIgASgISBqIAQESNAonc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfcXVpY2tfcmVhY3Rpb25zGCMgASgISBuIAQESMQokc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfc2hpZnRfZXhwYW5kGCQgASgISByIAQESNQooc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfb25seV9tb3JlX2J1dHRvbhglIAEoCEgdiAEBEjAKI3Nob3dfZGVmYXVsdF9lbW9qaXNfaW5fYXV0b2NvbXBsZXRlGCYgASgISB6IAQESLwoic2hvd19jdXN0b21fZW1vamlzX2luX2F1dG9jb21wbGV0ZRgnIAEoCEgfiAEBEioKHXNob3dfc3RpY2tlcnNfaW5fYXV0b2NvbXBsZXRlGCggASgISCCIAQESJwoac2hvd19tZW1lc19pbl9hdXRvY29tcGxldGUYKSABKAhIIYgBARJXCh9hdHRhY2htZW50X21lZGlhX2RpbWVuc2lvbl9zaXplGCogASgOMi4uZmx1eGVyLnVzZXIucHJlZmVyZW5jZXMudjEuTWVkaWFEaW1lbnNpb25TaXplElIKGmVtYmVkX21lZGlhX2RpbWVuc2lvbl9zaXplGCsgASgOMi4uZmx1eGVyLnVzZXIucHJlZmVyZW5jZXMudjEuTWVkaWFEaW1lbnNpb25TaXplEjUKKHZvaWNlX2NoYW5uZWxfam9pbl9yZXF1aXJlc19kb3VibGVfY2xpY2sYLCABKAhIIogBARIdChBjdXN0b21fdGhlbWVfY3NzGC0gASgJSCOIAQESGwoOc2hvd19mYXZvcml0ZXMYLiABKAhIJIgBARIXCgp6b29tX2xldmVsGC8gASgBSCWIAQESUQoXZG1fbWVzc2FnZV9wcmV2aWV3X21vZGUYMCABKA4yMC5mbHV4ZXIudXNlci5wcmVmZXJlbmNlcy52MS5EbU1lc3NhZ2VQcmV2aWV3TW9kZRIfChJlbmFibGVfdHRzX2NvbW1hbmQYMSABKAhIJogBARIVCgh0dHNfcmF0ZRgyIAEoAUgniAEBEjAKI3Nob3dfZmFkZWRfdW5yZWFkX29uX211dGVkX2NoYW5uZWxzGDMgASgISCiIAQESKAobc2hvd19jb250ZXh0X21lbnVfc2hvcnRjdXRzGDQgASgISCmIAQESKgodY29uZmlybV9iZWZvcmVfc3RhcnRpbmdfY2FsbHMYNSABKAhIKogBARJEChBoZHJfZGlzcGxheV9tb2RlGDYgASgOMiouZmx1eGVyLnVzZXIucHJlZmVyZW5jZXMudjEuSGRyRGlzcGxheU1vZGUSIAoTcHJlc2VydmVfZWRpdF9kcmFmdBg3IAEoCEgriAEBEiwKH3N0YXlfaW50ZXJhY3RpdmVfd2hlbl91bmZvY3VzZWQYOCABKAhILIgBARIyCiVjb25maXJtX2JlZm9yZV9qb2luaW5nX3ZvaWNlX2NoYW5uZWxzGDkgASgISC2IAQESMAojc2NyZWVuX3JlYWRlcl9hbm5vdW5jZV9uZXdfbWVzc2FnZXMYOiABKAhILogBARI0CidmaXJzdF9jbGlja19wYXNzX3Rocm91Z2hfd2hlbl91bmZvY3VzZWQYOyABKAhIL4gBARIqCh1jb21wYWN0X21lc3NhZ2VfZ3JvdXBfc3BhY2luZxg8IAEoAUgwiAEBEi0KIHNjcm9sbF90b19ib3R0b21fb25fbWVzc2FnZV9zZW5kGD0gASgISDGIAQESIwoWZGltX3N0cmlrZXRocm91Z2hfdGV4dBg+IAEoCEgyiAEBEiEKFHNlcXVlbnRpYWxfZmlsZV9zZW5kGD8gASgISDOIAQFCFAoSX3NhdHVyYXRpb25fZmFjdG9yQhgKFl9lbmFibGVfdGV4dF9zZWxlY3Rpb25CGwoZX3Nob3dfbWVzc2FnZV9zZW5kX2J1dHRvbkIbChlfc2hvd190ZXh0YXJlYV9mb2N1c19yaW5nQh0KG19lc2NhcGVfZXhpdHNfa2V5Ym9hcmRfbW9kZUIiCiBfc3luY19yZWR1Y2VkX21vdGlvbl93aXRoX3N5c3RlbUIaChhfcmVkdWNlZF9tb3Rpb25fb3ZlcnJpZGVCGAoWX21lc3NhZ2VfZ3JvdXBfc3BhY2luZ0IRCg9fbWVzc2FnZV9ndXR0ZXJCDAoKX2ZvbnRfc2l6ZUIkCiJfc2hvd191c2VyX2F2YXRhcnNfaW5fY29tcGFjdF9tb2RlQiEKH19tb2JpbGVfc3RpY2tlcl9hbmltYXRpb25fdmFsdWVCHAoaX21vYmlsZV9naWZfYXV0b3BsYXlfdmFsdWVCHQobX21vYmlsZV9hbmltYXRlX2Vtb2ppX3ZhbHVlQhIKEF9zaG93X2dpZl9idXR0b25CFAoSX3Nob3dfbWVtZXNfYnV0dG9uQhcKFV9zaG93X3N0aWNrZXJzX2J1dHRvbkIUChJfc2hvd19lbW9qaV9idXR0b25CHQobX3Nob3dfbWVkaWFfZmF2b3JpdGVfYnV0dG9uQh0KG19zaG93X21lZGlhX2Rvd25sb2FkX2J1dHRvbkIbChlfc2hvd19tZWRpYV9kZWxldGVfYnV0dG9uQh4KHF9zaG93X3N1cHByZXNzX2VtYmVkc19idXR0b25CFQoTX3Nob3dfZ2lmX2luZGljYXRvckIjCiFfc2hvd19hdHRhY2htZW50X2V4cGlyeV9pbmRpY2F0b3JCJQojX3VzZV9icm93c2VyX2xvY2FsZV9mb3JfdGltZV9mb3JtYXRCKQonX3Nob3dfc2VsZWN0ZWRfY2hhbm5lbF90eXBpbmdfaW5kaWNhdG9yQhoKGF9zaG93X21lc3NhZ2VfYWN0aW9uX2JhckIqCihfc2hvd19tZXNzYWdlX2FjdGlvbl9iYXJfcXVpY2tfcmVhY3Rpb25zQicKJV9zaG93X21lc3NhZ2VfYWN0aW9uX2Jhcl9zaGlmdF9leHBhbmRCKwopX3Nob3dfbWVzc2FnZV9Line truncated
/** /**
* @generated from message fluxer.user.preferences.v1.AccessibilitySettings * @generated from message fluxer.user.preferences.v1.AccessibilitySettings
@@ -321,6 +321,11 @@ export type AccessibilitySettings = Message<"fluxer.user.preferences.v1.Accessib
* @generated from field: optional bool dim_strikethrough_text = 62; * @generated from field: optional bool dim_strikethrough_text = 62;
*/ */
dimStrikethroughText?: boolean | undefined; dimStrikethroughText?: boolean | undefined;
/**
* @generated from field: optional bool sequential_file_send = 63;
*/
sequentialFileSend?: boolean | undefined;
}; };
/** /**