-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
base: main
Are you sure you want to change the base?
feat: snaps dynamic UI #12429
Changes from 25 commits
1a12630
280b4a7
fc9b773
24f219e
6c7f28d
02f2054
f86e326
1426dc6
4bf801e
db88bb6
2786acd
8d20924
f0d31c4
351cebf
04876a5
34bed71
d8aea79
2f34a7d
0b14396
c141fb5
93a3d17
7bc4a32
a3d5413
b180225
683f4cd
956aac3
c5664c1
8666385
4fecdd1
41923b1
26476fb
dcd89af
29ea7f6
ef4832b
5da4e2e
e8bd935
c8f3a18
d7d00ad
d8e191c
5f42682
125e2da
53a16d9
cde7cee
59f2c7d
4a1d0fb
79d81bf
7d03e1b
f9665d3
82bbfd5
4c43ab0
ece756d
02e9381
d4bb190
8946c40
9313660
b4cee38
cb608e7
ea17669
e7b545e
e11a162
d5691e6
fb7769a
e02d763
1f7d313
2830669
e548a60
96e2b62
478cf3a
1b7d9a7
2302c37
2ea5db0
dbce24c
2097ad7
09d3ea4
590a82a
c522d51
a574fbd
ede58f0
9b3be41
f78b392
fc0fb8a
2ff8865
a79a624
221785e
3fe8fd9
67834d7
1b55d6c
9e9ea6c
8537e3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,19 +22,6 @@ exports[`ButtonPrimary render matches latest snapshot 1`] = ` | |
} | ||
} | ||
> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
<SvgMock | ||
color="#ffffff" | ||
height={16} | ||
name="Add" | ||
style={ | ||
{ | ||
"height": 16, | ||
"marginRight": 8, | ||
"width": 16, | ||
} | ||
} | ||
width={16} | ||
/> | ||
<Text | ||
accessibilityRole="text" | ||
style={ | ||
|
@@ -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', | ||
} | ||
Comment on lines
+20
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The buttons should be extracted from On the extension we have some fallback buttons if the Snap doesn't define some, the logic is somewhat complicated, but can be found here: https://github.com/MetaMask/metamask-extension/blob/main/ui/components/app/snaps/snap-ui-renderer/components/footer.ts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has also not been addressed yet, unresolving. |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps) | ||
export { default } from './SnapDialogApproval'; | ||
///: 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; | ||
} |
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 | ||
Daniel-Cross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @brianacnguyen pointed out, what is the goal of this change?