Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: snaps dynamic UI #12429

Draft
wants to merge 47 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1a12630
added content from the original POC branch
Daniel-Cross Nov 22, 2024
280b4a7
working branch
Daniel-Cross Nov 26, 2024
fc9b773
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Nov 26, 2024
24f219e
fixed import issue
Daniel-Cross Nov 27, 2024
6c7f28d
add SnapInterfaceController to types
owencraston Nov 28, 2024
02f2054
add requestUserApproval snap method
owencraston Nov 28, 2024
f86e326
render snaps dialog
owencraston Nov 28, 2024
1426dc6
disable old state check
owencraston Nov 28, 2024
4bf801e
working dialogs except for custom
Daniel-Cross Dec 4, 2024
db88bb6
added updateInterface
Daniel-Cross Dec 5, 2024
2786acd
input on custom dialog now working
Daniel-Cross Dec 10, 2024
8d20924
removed some unused imports and comsole logs
Daniel-Cross Dec 13, 2024
f0d31c4
added missing components
Daniel-Cross Dec 13, 2024
351cebf
removed debounce
Daniel-Cross Dec 13, 2024
04876a5
resolved conflicts
Daniel-Cross Dec 16, 2024
34bed71
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Dec 16, 2024
d8aea79
changed import location
Daniel-Cross Dec 17, 2024
2f34a7d
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Dec 17, 2024
0b14396
updated snapshots
Daniel-Cross Dec 17, 2024
c141fb5
fix engine lint errors
owencraston Dec 18, 2024
93a3d17
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Dec 18, 2024
7bc4a32
fix engine tests
owencraston Dec 18, 2024
a3d5413
add @nobles/hashes
owencraston Dec 18, 2024
b180225
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Dec 18, 2024
683f4cd
updated snapshots
Daniel-Cross Dec 18, 2024
956aac3
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Dec 19, 2024
c5664c1
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Dec 19, 2024
8666385
updated based on feedback and synced files with the extension
Daniel-Cross Dec 21, 2024
4fecdd1
resolved conflicts
Daniel-Cross Dec 21, 2024
41923b1
added input props
Daniel-Cross Dec 25, 2024
26476fb
made changes to types for input
Daniel-Cross Dec 28, 2024
dcd89af
addressed footer type issue
Daniel-Cross Dec 29, 2024
29ea7f6
fixed some liter issues
Daniel-Cross Dec 31, 2024
ef4832b
fixed colours
Daniel-Cross Dec 31, 2024
5da4e2e
fixed colour import
Daniel-Cross Dec 31, 2024
e8bd935
fixed global element declaration issue
Daniel-Cross Dec 31, 2024
c8f3a18
removed the need to use flex on mobile
Daniel-Cross Dec 31, 2024
d7d00ad
changed enum related values in footer
Daniel-Cross Dec 31, 2024
d8e191c
fixed linting issues from enums
Daniel-Cross Dec 31, 2024
5f42682
removed enum usage
Daniel-Cross Jan 1, 2025
125e2da
removed unused import
Daniel-Cross Jan 1, 2025
53a16d9
added a package for handling html entities
Daniel-Cross Jan 7, 2025
cde7cee
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Jan 7, 2025
59f2c7d
yarn deduplicate
Daniel-Cross Jan 7, 2025
4a1d0fb
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Jan 7, 2025
79d81bf
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Jan 8, 2025
7d03e1b
Merge branch 'main' into epic/snaps-ui
Daniel-Cross Jan 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const ButtonPrimary = ({
onPressOut,
isDanger = false,
label,
startIconName,
endIconName,
...props
}: ButtonPrimaryProps) => {
const [pressed, setPressed] = useState(false);
Expand Down Expand Up @@ -60,9 +62,9 @@ const ButtonPrimary = ({
label
);

const renderLoading = () => (
<ActivityIndicator size="small" color={DEFAULT_BUTTONPRIMARY_LABEL_COLOR} />
);
const renderLoading = () => (
<ActivityIndicator size="small" color={DEFAULT_BUTTONPRIMARY_LABEL_COLOR} />
);

return (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,6 @@ exports[`ButtonPrimary render matches latest snapshot 1`] = `
}
}
>
<SvgMock
color="#ffffff"
height={16}
name="Add"
style={
{
"height": 16,
"marginRight": 8,
"width": 16,
}
}
width={16}
/>
<Text
accessibilityRole="text"
style={
Expand All @@ -50,18 +37,5 @@ exports[`ButtonPrimary render matches latest snapshot 1`] = `
>
Sample label
</Text>
<SvgMock
color="#ffffff"
height={16}
name="AddSquare"
style={
{
"height": 16,
"marginLeft": 8,
"width": 16,
}
}
width={16}
/>
</TouchableOpacity>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { StyleSheet } from 'react-native';
import { Theme } from '../../../../util/theme/models';
import Device from '../../../../util/device';

/**
*
* @param params Style sheet params.
* @param params.theme App theme from ThemeContext.
* @param params.vars Inputs that the style sheet depends on.
* @returns StyleSheet object.
*/
const styleSheet = (params: { theme: Theme }) => {
const { theme } = params;
const { colors } = theme;
return StyleSheet.create({
root: {
backgroundColor: colors.background.default,
paddingTop: 24,
paddingLeft: 16,
paddingRight: 16,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
minHeight: 200,
paddingBottom: Device.isIphoneX() ? 20 : 0,
},
actionContainer: {
flex: 0,
paddingVertical: 16,
justifyContent: 'center',
},
});
};

export default styleSheet;
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps)
import React, { useState } from 'react';
import { useStyles } from '../../../hooks/useStyles';
import { strings } from '../../../../../locales/i18n';
import stylesheet from './SnapDialogApproval.styles';
import useApprovalRequest from '../../../Views/confirmations/hooks/useApprovalRequest';
import { View } from 'react-native-animatable';
import ApprovalModal from '../../ApprovalModal';
import BottomSheetFooter, {
ButtonsAlignment,
} from '../../../../component-library/components/BottomSheets/BottomSheetFooter';
import {
ButtonVariants,
ButtonSize,
} from '../../../../component-library/components/Buttons/Button';
import Engine from '../../../../core/Engine';
import { SnapUIRenderer } from '../SnapUIRenderer/SnapUIRenderer';
import { SnapId } from '@metamask/snaps-sdk';
import { IconName } from '../../../../component-library/components/Icons/Icon';

enum SnapDialogTypes {
ALERT = 'snap_dialog:alert',
CONFIRM = 'snap_dialog:confirmation',
PROMPT = 'snap_dialog:prompt',
CUSTOM = 'snap_dialog',
}

enum TemplateConfirmation {
Ok = 'template_confirmation.ok',
CANCEL = 'template_confirmation.cancel',
}

const SnapDialogApproval = () => {
const [isLoading, setIsLoading] = useState(false);
const { approvalRequest } = useApprovalRequest();
const { styles } = useStyles(stylesheet, {});

const onCancel = async () => {
if (!approvalRequest) return;

await Engine.acceptPendingApproval(approvalRequest.id, null as any);
};

const onConfirmInput = async () => {
setIsLoading(true);
if (!approvalRequest) return;

const inputState =
await Engine.context.SnapInterfaceController.getInterface(
approvalRequest?.origin as SnapId,
approvalRequest.requestData.id,
);
await Engine.acceptPendingApproval(
approvalRequest.id,
inputState.state['custom-input'] as any,
);
setIsLoading(false);
};

const onConfirm = async () => {
setIsLoading(true);
if (!approvalRequest) return;

await Engine.acceptPendingApproval(approvalRequest.id, true as any);

setIsLoading(false);
};

const onReject = async () => {
if (!approvalRequest) return;

await Engine.acceptPendingApproval(approvalRequest.id, false as any);
};

if (
approvalRequest?.type !== SnapDialogTypes.ALERT &&
approvalRequest?.type !== SnapDialogTypes.CONFIRM &&
approvalRequest?.type !== SnapDialogTypes.PROMPT &&
approvalRequest?.type !== SnapDialogTypes.CUSTOM
)
return null;

const getDialogButtons = (type: SnapDialogTypes | undefined) => {
switch (type) {
case SnapDialogTypes.ALERT:
return [
{
variant: ButtonVariants.Primary,
label: strings(TemplateConfirmation.Ok),
size: ButtonSize.Lg,
onPress: onCancel,
},
];

case SnapDialogTypes.CONFIRM:
case SnapDialogTypes.PROMPT:
return [
{
variant: ButtonVariants.Secondary,
label: strings(TemplateConfirmation.CANCEL),
size: ButtonSize.Lg,
onPress: onReject,
},
{
variant: ButtonVariants.Primary,
label: strings(TemplateConfirmation.Ok),
size: ButtonSize.Lg,
onPress: onConfirm,
},
];
case SnapDialogTypes.CUSTOM:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom dialogs can define their own buttons. We will need to support that instead of hardcoding their text and the logic to confirm/cancel

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a Type for the expected data on these custom buttons? I took a look at the extension code but it's much different to how we implement on Mobile so it's hard to make sense of what data should be expected for the custom buttons. Also, it doesn't seem to have any custom data coming from the snaps when I search for the button content.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return [
{
variant: ButtonVariants.Secondary,
label: strings(TemplateConfirmation.CANCEL),
size: ButtonSize.Lg,
onPress: onCancel,
startIconName: IconName.Close,
},
{
variant: ButtonVariants.Primary,
label: strings(TemplateConfirmation.Ok),
size: ButtonSize.Lg,
onPress: onConfirmInput,
endIconName: IconName.Check,
},
];

default:
return [];
}
};

const buttons = getDialogButtons(approvalRequest?.type);
const snapId = approvalRequest?.origin;
const interfaceId = approvalRequest?.requestData?.id;

return (
<ApprovalModal
isVisible={
approvalRequest?.type === SnapDialogTypes.ALERT ||
approvalRequest?.type === SnapDialogTypes.CONFIRM ||
approvalRequest?.type === SnapDialogTypes.PROMPT ||
approvalRequest?.type === SnapDialogTypes.CUSTOM
}
onCancel={onCancel}
>
<View style={styles.root}>
<SnapUIRenderer
snapId={snapId}
interfaceId={interfaceId}
isLoading={isLoading}
/>
<View style={styles.actionContainer}>
<BottomSheetFooter
buttonsAlignment={ButtonsAlignment.Horizontal}
buttonPropsArray={buttons}
/>
</View>
</View>
</ApprovalModal>
);
};

export default SnapDialogApproval;
///: END:ONLY_INCLUDE_IF
3 changes: 3 additions & 0 deletions app/components/Approvals/Snaps/SnapDialogApproval/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps)
export { default } from './SnapDialogApproval';

Check failure on line 2 in app/components/Approvals/Snaps/SnapDialogApproval/index.ts

View workflow job for this annotation

GitHub Actions / scripts (lint:tsc)

Cannot find module './SnapDialogApproval' or its corresponding type declarations.
///: END:ONLY_INCLUDE_IF
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ImageSourcePropType } from 'react-native';

export interface SnapUICardProps {
image?: ImageSourcePropType;
title?: string;
description?: string;
value?: string;
extra?: string;
}
89 changes: 89 additions & 0 deletions app/components/Approvals/Snaps/SnapUICard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
import React from 'react';
import { Image } from 'react-native';
// External dependencies.
import {
AlignItems,
Display,
FlexDirection,
JustifyContent,
TextAlign,
} from '../SnapUIRenderer/utils';
import Text, {
TextColor,
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import { SnapUICardProps } from './SnapUICard.props';
import styles, { Box } from '../../../UI/Box';

export const SnapUICard: React.FC<SnapUICardProps> = ({
image,
title,
description,
value,
extra,
}) => (
<Box
testID="snaps-ui-card"
display={Display.Flex}
justifyContent={JustifyContent.spaceBetween}
alignItems={AlignItems.center}
>
<Box display={Display.Flex} gap={4} alignItems={AlignItems.center}>
{image && (
<Image
width={32}
height={32}
source={image}
style={styles.overflowHidden}
/>
)}
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
style={styles.overflowHidden}
>
<Text
variant={TextVariant.BodyMDMedium}
numberOfLines={1}
ellipsizeMode="tail"
>
{title}
</Text>
{description && (
<Text
color={TextColor.Alternative}
numberOfLines={1}
ellipsizeMode="tail"
>
{description}
</Text>
)}
</Box>
</Box>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
textAlign={TextAlign.right}
style={styles.overflowHidden}
>
<Text
variant={TextVariant.BodyMDMedium}
numberOfLines={1}
ellipsizeMode="tail"
>
{value}
</Text>
{extra && (
<Text
color={TextColor.Alternative}
numberOfLines={1}
ellipsizeMode="tail"
>
{extra}
</Text>
)}
</Box>
</Box>
);
///: END:ONLY_INCLUDE_IF
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type SetCurrentInputFocus = (name: string | null) => void;
Loading
Loading