mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-05 06:50:28 +03:00
Use a common CollapsibleCard element for collapsible settings cards
This commit is contained in:
parent
af9460ef8b
commit
d42bcc6e3d
7 changed files with 322 additions and 390 deletions
|
|
@ -195,8 +195,8 @@ function AccountDataEdit({
|
||||||
type AccountDataViewProps = {
|
type AccountDataViewProps = {
|
||||||
type: string;
|
type: string;
|
||||||
defaultContent: string;
|
defaultContent: string;
|
||||||
onEdit: () => void;
|
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
|
onEdit?: () => void;
|
||||||
submitDelete?: AccountDataDeleteCallback;
|
submitDelete?: AccountDataDeleteCallback;
|
||||||
};
|
};
|
||||||
function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDelete }: AccountDataViewProps) {
|
function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDelete }: AccountDataViewProps) {
|
||||||
|
|
@ -231,9 +231,11 @@ function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDel
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
{onEdit && (
|
||||||
<Button variant="Secondary" size="400" radii="300" onClick={onEdit}>
|
<Button variant="Secondary" size="400" radii="300" onClick={onEdit}>
|
||||||
<Text size="B400">Edit</Text>
|
<Text size="B400">Edit</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
{submitDelete && (
|
{submitDelete && (
|
||||||
<Button
|
<Button
|
||||||
variant="Critical"
|
variant="Critical"
|
||||||
|
|
@ -269,7 +271,7 @@ function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDel
|
||||||
export type AccountDataEditorProps = {
|
export type AccountDataEditorProps = {
|
||||||
type?: string;
|
type?: string;
|
||||||
content?: unknown;
|
content?: unknown;
|
||||||
submitChange: AccountDataSubmitCallback;
|
submitChange?: AccountDataSubmitCallback;
|
||||||
submitDelete?: AccountDataDeleteCallback;
|
submitDelete?: AccountDataDeleteCallback;
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
@ -328,7 +330,7 @@ export function AccountDataEditor({
|
||||||
</Box>
|
</Box>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
{edit ? (
|
{(edit && submitChange) ? (
|
||||||
<AccountDataEdit
|
<AccountDataEdit
|
||||||
type={data.type}
|
type={data.type}
|
||||||
defaultContent={contentJSONStr}
|
defaultContent={contentJSONStr}
|
||||||
|
|
@ -340,8 +342,8 @@ export function AccountDataEditor({
|
||||||
<AccountDataView
|
<AccountDataView
|
||||||
type={data.type}
|
type={data.type}
|
||||||
defaultContent={contentJSONStr}
|
defaultContent={contentJSONStr}
|
||||||
onEdit={() => setEdit(true)}
|
|
||||||
requestClose={requestClose}
|
requestClose={requestClose}
|
||||||
|
onEdit={submitChange ? () => setEdit(true) : undefined}
|
||||||
submitDelete={submitDelete}
|
submitDelete={submitDelete}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
54
src/app/components/CollapsibleCard.tsx
Normal file
54
src/app/components/CollapsibleCard.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { Button, Icon, Icons, Text } from 'folds';
|
||||||
|
import { SequenceCard } from './sequence-card';
|
||||||
|
import { SequenceCardStyle } from '../features/settings/styles.css';
|
||||||
|
import { SettingTile } from './setting-tile';
|
||||||
|
|
||||||
|
type CollapsibleCardProps = {
|
||||||
|
expand: boolean;
|
||||||
|
setExpand: (expand: boolean) => void;
|
||||||
|
title?: ReactNode;
|
||||||
|
description?: ReactNode;
|
||||||
|
before?: ReactNode;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CollapsibleCard({
|
||||||
|
expand,
|
||||||
|
setExpand,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
before,
|
||||||
|
children,
|
||||||
|
}: CollapsibleCardProps) {
|
||||||
|
return (
|
||||||
|
<SequenceCard
|
||||||
|
className={SequenceCardStyle}
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
direction="Column"
|
||||||
|
gap="400"
|
||||||
|
>
|
||||||
|
<SettingTile
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
before={before}
|
||||||
|
after={
|
||||||
|
<Button
|
||||||
|
onClick={() => setExpand(!expand)}
|
||||||
|
variant="Secondary"
|
||||||
|
fill="Soft"
|
||||||
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
outlined
|
||||||
|
before={
|
||||||
|
<Icon src={expand ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text size="B300">{expand ? 'Collapse' : 'Expand'}</Text>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{expand && children}
|
||||||
|
</SequenceCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@ import {
|
||||||
AccountDataSubmitCallback,
|
AccountDataSubmitCallback,
|
||||||
} from '../../../components/AccountDataEditor';
|
} from '../../../components/AccountDataEditor';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
|
import { CollapsibleCard } from '../../../components/CollapsibleCard';
|
||||||
|
|
||||||
type DeveloperToolsProps = {
|
type DeveloperToolsProps = {
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
|
|
@ -175,36 +176,12 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SequenceCard>
|
</SequenceCard>
|
||||||
<SequenceCard
|
<CollapsibleCard
|
||||||
className={SequenceCardStyle}
|
expand={expandState}
|
||||||
variant="SurfaceVariant"
|
setExpand={setExpandState}
|
||||||
direction="Column"
|
|
||||||
gap="400"
|
|
||||||
>
|
|
||||||
<SettingTile
|
|
||||||
title="Room State"
|
title="Room State"
|
||||||
description="State events of the room."
|
description="State events of the room."
|
||||||
after={
|
|
||||||
<Button
|
|
||||||
onClick={() => setExpandState(!expandState)}
|
|
||||||
variant="Secondary"
|
|
||||||
fill="Soft"
|
|
||||||
size="300"
|
|
||||||
radii="300"
|
|
||||||
outlined
|
|
||||||
before={
|
|
||||||
<Icon
|
|
||||||
src={expandState ? Icons.ChevronTop : Icons.ChevronBottom}
|
|
||||||
size="100"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Text size="B300">{expandState ? 'Collapse' : 'Expand'}</Text>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{expandState && (
|
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Box justifyContent="SpaceBetween">
|
<Box justifyContent="SpaceBetween">
|
||||||
<Text size="L400">Events</Text>
|
<Text size="L400">Events</Text>
|
||||||
|
|
@ -310,38 +287,13 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
||||||
})}
|
})}
|
||||||
</CutoutCard>
|
</CutoutCard>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</CollapsibleCard>
|
||||||
</SequenceCard>
|
<CollapsibleCard
|
||||||
<SequenceCard
|
expand={expandAccountData}
|
||||||
className={SequenceCardStyle}
|
setExpand={setExpandAccountData}
|
||||||
variant="SurfaceVariant"
|
|
||||||
direction="Column"
|
|
||||||
gap="400"
|
|
||||||
>
|
|
||||||
<SettingTile
|
|
||||||
title="Account Data"
|
title="Account Data"
|
||||||
description="Private personalization data stored within room."
|
description="Private personalization data stored within room"
|
||||||
after={
|
|
||||||
<Button
|
|
||||||
onClick={() => setExpandAccountData(!expandAccountData)}
|
|
||||||
variant="Secondary"
|
|
||||||
fill="Soft"
|
|
||||||
size="300"
|
|
||||||
radii="300"
|
|
||||||
outlined
|
|
||||||
before={
|
|
||||||
<Icon
|
|
||||||
src={expandAccountData ? Icons.ChevronTop : Icons.ChevronBottom}
|
|
||||||
size="100"
|
|
||||||
filled
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Text size="B300">{expandAccountData ? 'Collapse' : 'Expand'}</Text>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{expandAccountData && (
|
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Box justifyContent="SpaceBetween">
|
<Box justifyContent="SpaceBetween">
|
||||||
<Text size="L400">Events</Text>
|
<Text size="L400">Events</Text>
|
||||||
|
|
@ -383,8 +335,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
||||||
))}
|
))}
|
||||||
</CutoutCard>
|
</CutoutCard>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</CollapsibleCard>
|
||||||
</SequenceCard>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import {
|
||||||
Chip,
|
Chip,
|
||||||
color,
|
color,
|
||||||
config,
|
config,
|
||||||
Icon,
|
|
||||||
Icons,
|
|
||||||
Input,
|
Input,
|
||||||
Spinner,
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -33,6 +31,7 @@ import { useAlive } from '../../../hooks/useAlive';
|
||||||
import { StateEvent } from '../../../../types/matrix/room';
|
import { StateEvent } from '../../../../types/matrix/room';
|
||||||
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
|
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
|
||||||
import { getMxIdServer } from '../../../utils/matrix';
|
import { getMxIdServer } from '../../../utils/matrix';
|
||||||
|
import { CollapsibleCard } from '../../../components/CollapsibleCard';
|
||||||
|
|
||||||
type RoomPublishedAddressesProps = {
|
type RoomPublishedAddressesProps = {
|
||||||
permissions: RoomPermissionsAPI;
|
permissions: RoomPermissionsAPI;
|
||||||
|
|
@ -373,35 +372,12 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
|
||||||
const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(room.roomId);
|
const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(room.roomId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SequenceCard
|
<CollapsibleCard
|
||||||
className={SequenceCardStyle}
|
expand={expand}
|
||||||
variant="SurfaceVariant"
|
setExpand={setExpand}
|
||||||
direction="Column"
|
|
||||||
gap="400"
|
|
||||||
>
|
|
||||||
<SettingTile
|
|
||||||
title="Local Addresses"
|
title="Local Addresses"
|
||||||
description="Set local address so users can join through your homeserver."
|
description="Set local address so users can join through your homeserver."
|
||||||
after={
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setExpand(!expand)}
|
|
||||||
size="300"
|
|
||||||
variant="Secondary"
|
|
||||||
fill="Soft"
|
|
||||||
outlined
|
|
||||||
radii="300"
|
|
||||||
before={
|
|
||||||
<Icon size="100" src={expand ? Icons.ChevronTop : Icons.ChevronBottom} filled />
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Text as="span" size="B300" truncate>
|
|
||||||
{expand ? 'Collapse' : 'Expand'}
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{expand && (
|
|
||||||
<CutoutCard variant="Surface" style={{ padding: config.space.S300 }}>
|
<CutoutCard variant="Surface" style={{ padding: config.space.S300 }}>
|
||||||
{localAliasesState.status === AsyncStatus.Loading && (
|
{localAliasesState.status === AsyncStatus.Loading && (
|
||||||
<Box gap="100">
|
<Box gap="100">
|
||||||
|
|
@ -429,8 +405,7 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</CutoutCard>
|
</CutoutCard>
|
||||||
)}
|
|
||||||
{expand && <LocalAddressInput addLocalAlias={addLocalAlias} />}
|
{expand && <LocalAddressInput addLocalAlias={addLocalAlias} />}
|
||||||
</SequenceCard>
|
</CollapsibleCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Text, Icon, Icons, Button, MenuItem } from 'folds';
|
import { Box, Text, Icon, Icons, MenuItem } from 'folds';
|
||||||
import { SequenceCard } from '../../../components/sequence-card';
|
|
||||||
import { SequenceCardStyle } from '../styles.css';
|
|
||||||
import { SettingTile } from '../../../components/setting-tile';
|
|
||||||
import { CutoutCard } from '../../../components/cutout-card';
|
import { CutoutCard } from '../../../components/cutout-card';
|
||||||
|
|
||||||
type AccountDataListProps = {
|
type AccountDataListProps = {
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
expand: boolean;
|
|
||||||
setExpand: (expand: boolean) => void;
|
|
||||||
types: string[];
|
types: string[];
|
||||||
onSelect: (type: string | null) => void;
|
onSelect: (type: string | null) => void;
|
||||||
};
|
};
|
||||||
export function AccountDataList({ types, onSelect, expand, setExpand, title, description }: AccountDataListProps) {
|
export function AccountDataList({
|
||||||
|
types,
|
||||||
|
onSelect,
|
||||||
|
}: AccountDataListProps) {
|
||||||
return (
|
return (
|
||||||
<SequenceCard
|
|
||||||
className={SequenceCardStyle}
|
|
||||||
variant="SurfaceVariant"
|
|
||||||
direction="Column"
|
|
||||||
gap="400"
|
|
||||||
>
|
|
||||||
<SettingTile
|
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
after={
|
|
||||||
<Button
|
|
||||||
onClick={() => setExpand(!expand)}
|
|
||||||
variant="Secondary"
|
|
||||||
fill="Soft"
|
|
||||||
size="300"
|
|
||||||
radii="300"
|
|
||||||
outlined
|
|
||||||
before={
|
|
||||||
<Icon src={expand ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text size="B300">{expand ? 'Collapse' : 'Expand'}</Text>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{expand && (
|
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Box justifyContent="SpaceBetween">
|
<Box justifyContent="SpaceBetween">
|
||||||
<Text size="L400">Fields</Text>
|
<Text size="L400">Fields</Text>
|
||||||
|
|
@ -80,7 +50,5 @@ export function AccountDataList({ types, onSelect, expand, setExpand, title, des
|
||||||
))}
|
))}
|
||||||
</CutoutCard>
|
</CutoutCard>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
</SequenceCard>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { copyToClipboard } from '../../../utils/dom';
|
||||||
import { AccountDataList } from './AccountDataList';
|
import { AccountDataList } from './AccountDataList';
|
||||||
import { useExtendedProfile } from '../../../hooks/useExtendedProfile';
|
import { useExtendedProfile } from '../../../hooks/useExtendedProfile';
|
||||||
import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback';
|
import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback';
|
||||||
|
import { CollapsibleCard } from '../../../components/CollapsibleCard';
|
||||||
|
|
||||||
type DeveloperToolsPage =
|
type DeveloperToolsPage =
|
||||||
| { name: 'index' }
|
| { name: 'index' }
|
||||||
|
|
@ -165,23 +166,29 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
||||||
{developerTools && (
|
{developerTools && (
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Text size="L400">Account Data</Text>
|
<Text size="L400">Account Data</Text>
|
||||||
<AccountDataList
|
<CollapsibleCard
|
||||||
title="Account"
|
|
||||||
description="Private data stored in your account."
|
|
||||||
expand={globalExpand}
|
expand={globalExpand}
|
||||||
setExpand={setGlobalExpand}
|
setExpand={setGlobalExpand}
|
||||||
|
title="Account"
|
||||||
|
description="Private data stored in your account."
|
||||||
|
>
|
||||||
|
<AccountDataList
|
||||||
types={accountDataTypes}
|
types={accountDataTypes}
|
||||||
onSelect={(type) => setPage({ name: 'account-data', type })}
|
onSelect={(type) => setPage({ name: 'account-data', type })}
|
||||||
/>
|
/>
|
||||||
|
</CollapsibleCard>
|
||||||
{extendedProfile && (
|
{extendedProfile && (
|
||||||
<AccountDataList
|
<CollapsibleCard
|
||||||
title="Profile"
|
|
||||||
description="Public data attached to your Matrix profile."
|
|
||||||
expand={profileExpand}
|
expand={profileExpand}
|
||||||
setExpand={setProfileExpand}
|
setExpand={setProfileExpand}
|
||||||
|
title="Profile"
|
||||||
|
description="Public data attached to your Matrix profile."
|
||||||
|
>
|
||||||
|
<AccountDataList
|
||||||
types={Object.keys(extendedProfile)}
|
types={Object.keys(extendedProfile)}
|
||||||
onSelect={(type) => setPage({ name: 'profile-field', type })}
|
onSelect={(type) => setPage({ name: 'profile-field', type })}
|
||||||
/>
|
/>
|
||||||
|
</CollapsibleCard>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { decryptMegolmKeyFile, encryptMegolmKeyFile } from '../../../../util/cryptE2ERoomKeys';
|
import { decryptMegolmKeyFile, encryptMegolmKeyFile } from '../../../../util/cryptE2ERoomKeys';
|
||||||
import { useAlive } from '../../../hooks/useAlive';
|
import { useAlive } from '../../../hooks/useAlive';
|
||||||
import { useFilePicker } from '../../../hooks/useFilePicker';
|
import { useFilePicker } from '../../../hooks/useFilePicker';
|
||||||
|
import { CollapsibleCard } from '../../../components/CollapsibleCard';
|
||||||
|
|
||||||
function ExportKeys() {
|
function ExportKeys() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
|
@ -121,37 +122,18 @@ function ExportKeys() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExportKeysTile() {
|
function ExportKeysCard() {
|
||||||
const [expand, setExpand] = useState(false);
|
const [expand, setExpand] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<CollapsibleCard
|
||||||
<SettingTile
|
expand={expand}
|
||||||
|
setExpand={setExpand}
|
||||||
title="Export Messages Data"
|
title="Export Messages Data"
|
||||||
description="Save password protected copy of encryption data on your device to decrypt messages later."
|
description="Save password protected copy of encryption data on your device to decrypt messages later."
|
||||||
after={
|
|
||||||
<Box>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setExpand(!expand)}
|
|
||||||
size="300"
|
|
||||||
variant="Secondary"
|
|
||||||
fill="Soft"
|
|
||||||
outlined
|
|
||||||
radii="300"
|
|
||||||
before={
|
|
||||||
<Icon size="100" src={expand ? Icons.ChevronTop : Icons.ChevronBottom} filled />
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Text as="span" size="B300" truncate>
|
<ExportKeys />
|
||||||
{expand ? 'Collapse' : 'Expand'}
|
</CollapsibleCard>
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{expand && <ExportKeys />}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,14 +286,7 @@ export function LocalBackup() {
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Text size="L400">Local Backup</Text>
|
<Text size="L400">Local Backup</Text>
|
||||||
<SequenceCard
|
<ExportKeysCard />
|
||||||
className={SequenceCardStyle}
|
|
||||||
variant="SurfaceVariant"
|
|
||||||
direction="Column"
|
|
||||||
gap="400"
|
|
||||||
>
|
|
||||||
<ExportKeysTile />
|
|
||||||
</SequenceCard>
|
|
||||||
<SequenceCard
|
<SequenceCard
|
||||||
className={SequenceCardStyle}
|
className={SequenceCardStyle}
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue