mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-07 03:10:12 +00:00
* MM-70516 - Add ability to edit channel attribute values from Channel Info * Add API methods for patching property fields and setting channel attribute requirements * Update channel attribute clearing behavior for required and non-required fields * MM-70564 - use channel attributes on channel creation * Fix undefined propertyValues and ternary formatting in channel attributes * Fix spelling of "colour" to "color" in attribute chip tests and related comments * Add visibility checks for channel attribute labels in tests * Fix data-integrity, permission, and accessibility issues in channel attributes: scoped pruning, atomic writes, race serialization, required clear/stale-option guards * Fix handling of null values in apiGetChannelAttributeValue and update test assertions for channel attributes * Improve channel attribute editor sizing for long labels, keep Save visible, and prevent keyboard overlap on Android * Address CodeRabbit feedback for nullable values, bottom-sheet state, async validation, and reliable E2E cleanup * lock-safe field fetch, create-time gate recheck, scoped value pruning, and tappable header chips * Address CodeRabbit feedback: drop queued attribute saves on channel switch and use page object selectors in E2E * Respect the ChannelAttributesRequired flag on channel creation and enable it for channel attribute E2E * Skip required channel attribute E2E tests and stop enabling ChannelAttributesRequired on the shared server until they move to SITE_3 * Fix clearing channel attributes on iOS by sending an empty value instead of null, and stop E2E suites from turning off the ChannelAttributes flag mid-run * Refactor getEmptyPropertyValue to accept string type for fieldType parameter * E2E: give channel attribute test fields a suite-unique suffix Pool shards may only create optional property fields named <name>_<6 hex>; fixed names were blocked by the pool guard. * E2E: restore channel attribute cleanup checks; name network error codes Brings back the PR's afterEach/afterAll error handling that the main merge replaced, and makes a no-response error show its codes (an AggregateError has an empty message). * E2E: run channel attribute creation and editing specs on server B Their 11 required-attribute tests could not run on the shared servers. Both specs now run on the mutation server with ChannelAttributesRequired on for their run, and the tests are re-enabled. setChannelAttributesRequired moves to the shared helper. * Check header chips instead of their wrapper and use toExist for the create screen in channel attribute E2E --------- Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
105 lines
4.9 KiB
TypeScript
105 lines
4.9 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {BUTTON_HEIGHT} from '@screens/bottom_sheet/button';
|
|
import {TITLE_HEIGHT, TITLE_LINE_HEIGHT, TITLE_SEPARATOR_MARGIN, TITLE_SEPARATOR_MARGIN_TABLET} from '@screens/bottom_sheet/content';
|
|
import {bottomSheetSnapPoint} from '@utils/helpers';
|
|
|
|
import type {ResolvedChannelAttribute} from '@utils/channel_attributes';
|
|
|
|
export const OPTION_ROW_HEIGHT = 48;
|
|
export const TEXT_INPUT_MARGIN_BOTTOM = 12;
|
|
|
|
// FloatingTextInput's own rendered box while focused: DEFAULT_INPUT_HEIGHT (48)
|
|
// plus its focused 2px border on both edges. The field autoFocuses on open, so
|
|
// this is the height it actually renders at, not its unfocused 1px-border size.
|
|
const TEXT_INPUT_HEIGHT = 52;
|
|
|
|
const TITLE_SEPARATOR_LINE_HEIGHT = 1;
|
|
|
|
// A fixed cushion under Save, on top of BottomSheetButton's own safe-area
|
|
// padding: Android reports that inset as 0 while a keyboard is up (real or,
|
|
// with a hardware/passthrough keyboard, merely believed to be), which the text
|
|
// editor's autofocused input triggers on open and the option editors never do.
|
|
export const SAVE_BUTTON_BOTTOM_PADDING = 12;
|
|
|
|
// Past this many rows the sheet opens at a fraction of the window rather than
|
|
// growing to fit every row, and the rest is reached by scrolling.
|
|
export const SHEET_MAX_ROWS = 5;
|
|
|
|
// The sheet never opens taller than this fraction of the window, matching the
|
|
// '80%' ceiling every other sheet in the app already opens at.
|
|
const MAX_SHEET_HEIGHT_RATIO = 0.8;
|
|
|
|
type FieldType = ResolvedChannelAttribute['field']['type'];
|
|
|
|
/**
|
|
* Snap points for the Set {attribute} sheet, ordered bottom to top as Gorhom
|
|
* requires.
|
|
*
|
|
* The numeric point is a starting estimate, not a guarantee: a long option name
|
|
* or a wrapped title can still make the real content taller than this predicts.
|
|
* That is fine because the sheet's own content — title included — is rendered
|
|
* inside a scroll area, and Save is laid out in its own row after that area
|
|
* rather than inside it: an underestimate here costs a scroll, never a clipped
|
|
* Save button. The estimate only exists so the sheet opens at roughly the right
|
|
* height instead of always starting at the tallest point allowed.
|
|
*
|
|
* totalRows is the full, unclamped row count so the caller does not have to
|
|
* duplicate the SHEET_MAX_ROWS comparison to know whether a taller snap point
|
|
* is worth offering.
|
|
*/
|
|
export function getChannelAttributeEditorSnapPoints({
|
|
totalRows,
|
|
fieldType,
|
|
bottomInset,
|
|
isTablet,
|
|
windowHeight,
|
|
}: {
|
|
totalRows: number;
|
|
fieldType: FieldType;
|
|
bottomInset: number;
|
|
isTablet: boolean;
|
|
windowHeight: number;
|
|
}): number[] {
|
|
const hasSaveButton = fieldType === 'text' || fieldType === 'multiselect';
|
|
const separatorMargin = isTablet ? TITLE_SEPARATOR_MARGIN_TABLET : TITLE_SEPARATOR_MARGIN;
|
|
const rowsHeight = fieldType === 'text' ? TEXT_INPUT_HEIGHT + TEXT_INPUT_MARGIN_BOTTOM : bottomSheetSnapPoint(Math.min(totalRows, SHEET_MAX_ROWS), OPTION_ROW_HEIGHT);
|
|
|
|
const estimatedHeight = rowsHeight +
|
|
TITLE_HEIGHT + TITLE_LINE_HEIGHT + separatorMargin + TITLE_SEPARATOR_LINE_HEIGHT +
|
|
|
|
// BUTTON_HEIGHT does not include the button's own bottom safe-area padding
|
|
// (see screens/bottom_sheet/button.tsx), so that inset is added here too.
|
|
(hasSaveButton ? BUTTON_HEIGHT + bottomInset + SAVE_BUTTON_BOTTOM_PADDING : 0);
|
|
|
|
const maxHeight = windowHeight * MAX_SHEET_HEIGHT_RATIO;
|
|
|
|
// GenericBottomSheetRoute adds this same margin (the safe-area bottom inset)
|
|
// to index 1 (and only index 1) after this function returns, to give every
|
|
// sheet a bottom cushion. A third point too close to the second would end up
|
|
// smaller than the second once that addition lands, leaving Gorhom an
|
|
// unordered array — so a third point is only offered when it clears the
|
|
// second by more than that.
|
|
const routeMargin = bottomInset;
|
|
|
|
// Capped at maxHeight - routeMargin, not maxHeight itself: the route adds
|
|
// routeMargin to this point after this function returns, so capping at
|
|
// maxHeight here would let the effective, on-screen height exceed the 80%
|
|
// ceiling by up to that margin.
|
|
const collapsedHeight = Math.min(estimatedHeight, maxHeight - routeMargin);
|
|
|
|
// A taller point is only worth offering when there are more rows than the
|
|
// collapsed estimate already accounts for. A text field never has more than
|
|
// one row and never needs one for keyboard purposes either: the caller
|
|
// opens it with keyboardBehavior="interactive", which grows the sheet by
|
|
// exactly the keyboard's height instead of jumping to a configured snap
|
|
// point, so there is nothing here for a keyboard to need room to extend to.
|
|
const rowsOverflow = fieldType !== 'text' && totalRows > SHEET_MAX_ROWS;
|
|
if (!rowsOverflow || collapsedHeight + routeMargin >= maxHeight) {
|
|
return [1, collapsedHeight];
|
|
}
|
|
|
|
return [1, collapsedHeight, maxHeight];
|
|
}
|