Skip to content

Commit

Permalink
Noe/transactional frames mobile (#1100)
Browse files Browse the repository at this point in the history
* Reorg evm utils

* Revoke other installations outside of onboarding (Coinbase Wallet only for now)

* WIP: wallet drawer

* Use privy signer for revoke

* Ability to select different wallets for external wallet setup

* Rebase on main, fix import

* Rebase on main

* useXmtpSigner hook

* comment

* autoconnect even if kill the app

* Keep link between app & wallet as long as possible

* Fix dark mode & use translate

* Updated revocation strings

Removes references to the unfamiliar XMTP/MLS term "installation".

* First working flow of tx frames

* WIP: tx frame preview

* Tx frame preview & trigger

* Fix import

* UI for transactions

* Disable switch chain step for now

* Only coinbase for now

* Fix typing

* Increment version numbers

* Fix chain switching

* [create-pull-request] automated change (#1016)

Co-authored-by: nmalzieu <[email protected]>

* [create-pull-request] automated change (#1017)

Co-authored-by: nmalzieu <[email protected]>

* Display error

* feat: Group Sync Notifications (#950)

Added Handling for Group Sync Notifications on Android & iOS

Co-authored-by: Alex Risch <[email protected]>

* chore: Add Min system version for macos warning (#994)

Added LSMinimumSystemVersion to 12.0 from app store connect emails

* feat: Show Profile info in join requests (#999)

Added React Query Profiles query
Added batshit for batching queries
Added helper hooks

* Make tx error work

* [create-pull-request] automated change (#1019)

Co-authored-by: nmalzieu <[email protected]>

* fix: Android Push Notificaition parsing (#1018)

Safely parse notification

* chore: Increment versions

* feat: Android Variants (#984)

* feat: Android Variants

Added Android Variants
Moved folder structures
Removed Android build scripts used to update new variant info
Aligned eas.json profiles
Added new manifest and strings for Android resource merger

* missed file commit

* [create-pull-request] automated change (#1024)

Co-authored-by: alexrisch <[email protected]>

* feat: more design system stuff (#1006)

* wip

* more color fix

* fix hstack file name

* fix hstack file name

* fix hstack file name

* fix types + fix button + add more theme stuff

* fix button

* more fixes

* fix snapshots

* wip

* bottom sheet wip

* fix button component and refactor bottom sheet into multiple files

* more bottom sheet fixes

* clean up

* fix

* Simulation endpoint

* remove bottom sheet example stuff

* Move TransactionPreview

* Using design-system

* cleanup

* Split transactionPreview in multiple components

* More UI for tx simulation

* feat: Android Variants (#984)

* feat: Android Variants

Added Android Variants
Moved folder structures
Removed Android build scripts used to update new variant info
Aligned eas.json profiles
Added new manifest and strings for Android resource merger

* missed file commit

* [create-pull-request] automated change (#1024)

Co-authored-by: alexrisch <[email protected]>

* feat: more design system stuff (#1006)

* wip

* more color fix

* fix hstack file name

* fix hstack file name

* fix hstack file name

* fix types + fix button + add more theme stuff

* fix button

* more fixes

* fix snapshots

* wip

* bottom sheet wip

* fix button component and refactor bottom sheet into multiple files

* more bottom sheet fixes

* clean up

* fix

* remove bottom sheet example stuff

* Rolled-up reactions (#1037)

* Always show reactions outside of the message bubble

No matter which content type it is

* useMemo on useStyles

* Revert "useMemo on useStyles"

This reverts commit 93657c4.

* Use app theme and start implementing values in styles

* WIP Implement new styling and colors to reaction bubbles

* Update reactors container outer margin

* Remove avatars in reaction bubbles; apply new design system for styling; use alias to import the theme

* Add `borderWidth` to theme

* Use border radius and border width from theme

* Implement rolled up reactions

* Move const

* Show top 3 reactions

* Put comment back in

* Change border color to match the background for user's own reactions

* Change chat background to theme `colors.background.surface`

* Set new background to `surface` also in App, Chat, and Input

* Remove the export default, put the memo inline with the component

* More design system implementation

- Replace `<View>` with `HStack` and `VStack` components
- Use the `Text` from the design-system not from `react-native`

* Upgrade Thirdweb, make sure to pass a max amount of chains to walletconnect

* Fix typing

* feat: TextField design system (#1074)

* add TextField and fix IconButton

* delete old button

* feat: Pressable Group Updates

Added handling when pressing a display name in the group updated messages

* Add Tests

* Correct styles on pressables

* Update pressable style

* update to design system

Fixed tsconfig
Added util to create text styles
Updated Chat Group Updated message to match design system
Added ParsedText component

* fix tests

* recs for cleaner (#1012)

* Update to follow design system

* Remove unused component

* Update pods

* Placeholder image

* Color update & cleanup

* feat: Android Variants (#984)

* feat: Android Variants

Added Android Variants
Moved folder structures
Removed Android build scripts used to update new variant info
Aligned eas.json profiles
Added new manifest and strings for Android resource merger

* missed file commit

* [create-pull-request] automated change (#1024)

Co-authored-by: alexrisch <[email protected]>

* feat: more design system stuff (#1006)

* wip

* more color fix

* fix hstack file name

* fix hstack file name

* fix hstack file name

* fix types + fix button + add more theme stuff

* fix button

* more fixes

* fix snapshots

* wip

* bottom sheet wip

* fix button component and refactor bottom sheet into multiple files

* more bottom sheet fixes

* clean up

* fix

* remove bottom sheet example stuff

* Rolled-up reactions (#1037)

* Always show reactions outside of the message bubble

No matter which content type it is

* useMemo on useStyles

* Revert "useMemo on useStyles"

This reverts commit 93657c4.

* Use app theme and start implementing values in styles

* WIP Implement new styling and colors to reaction bubbles

* Update reactors container outer margin

* Remove avatars in reaction bubbles; apply new design system for styling; use alias to import the theme

* Add `borderWidth` to theme

* Use border radius and border width from theme

* Implement rolled up reactions

* Move const

* Show top 3 reactions

* Put comment back in

* Change border color to match the background for user's own reactions

* Change chat background to theme `colors.background.surface`

* Set new background to `surface` also in App, Chat, and Input

* Remove the export default, put the memo inline with the component

* More design system implementation

- Replace `<View>` with `HStack` and `VStack` components
- Use the `Text` from the design-system not from `react-native`

* feat: TextField design system (#1074)

* add TextField and fix IconButton

* delete old button

* feat: Pressable Group Updates

Added handling when pressing a display name in the group updated messages

* Add Tests

* Correct styles on pressables

* Update pressable style

* update to design system

Fixed tsconfig
Added util to create text styles
Updated Chat Group Updated message to match design system
Added ParsedText component

* fix tests

* recs for cleaner (#1012)

* Update to follow design system

* fix icon button styling (#1076)

* More UI & full flow

* fix: Xmtp Engine Rerenders, Race Conditions, Crashes  (#1036)

* fix: Xmtp Engine Rerenders, Race Conditions, Crashes

Refactored Xmtp Engine to be mostly outside of React Context
Adds subscriptions
Moves app state into folder and adds new app state util

* Moved cron to class component

* fix: EAS Build Fixes (#1099)

* remove testflight action

* fix eas

* oops

* Set to remote

* add platform checks

* fixes

* Expo is great

---------

Co-authored-by: Thierry <[email protected]>

* Only show changes that concern me

* feat: Android Variants (#984)

* feat: Android Variants

Added Android Variants
Moved folder structures
Removed Android build scripts used to update new variant info
Aligned eas.json profiles
Added new manifest and strings for Android resource merger

* missed file commit

* [create-pull-request] automated change (#1024)

Co-authored-by: alexrisch <[email protected]>

* feat: more design system stuff (#1006)

* wip

* more color fix

* fix hstack file name

* fix hstack file name

* fix hstack file name

* fix types + fix button + add more theme stuff

* fix button

* more fixes

* fix snapshots

* wip

* bottom sheet wip

* fix button component and refactor bottom sheet into multiple files

* more bottom sheet fixes

* clean up

* fix

* remove bottom sheet example stuff

* Rolled-up reactions (#1037)

* Always show reactions outside of the message bubble

No matter which content type it is

* useMemo on useStyles

* Revert "useMemo on useStyles"

This reverts commit 93657c4.

* Use app theme and start implementing values in styles

* WIP Implement new styling and colors to reaction bubbles

* Update reactors container outer margin

* Remove avatars in reaction bubbles; apply new design system for styling; use alias to import the theme

* Add `borderWidth` to theme

* Use border radius and border width from theme

* Implement rolled up reactions

* Move const

* Show top 3 reactions

* Put comment back in

* Change border color to match the background for user's own reactions

* Change chat background to theme `colors.background.surface`

* Set new background to `surface` also in App, Chat, and Input

* Remove the export default, put the memo inline with the component

* More design system implementation

- Replace `<View>` with `HStack` and `VStack` components
- Use the `Text` from the design-system not from `react-native`

* feat: TextField design system (#1074)

* add TextField and fix IconButton

* delete old button

* feat: Pressable Group Updates

Added handling when pressing a display name in the group updated messages

* Add Tests

* Correct styles on pressables

* Update pressable style

* update to design system

Fixed tsconfig
Added util to create text styles
Updated Chat Group Updated message to match design system
Added ParsedText component

* fix tests

* recs for cleaner (#1012)

* Update to follow design system

* fix icon button styling (#1076)

* fix: Xmtp Engine Rerenders, Race Conditions, Crashes  (#1036)

* fix: Xmtp Engine Rerenders, Race Conditions, Crashes

Refactored Xmtp Engine to be mostly outside of React Context
Adds subscriptions
Moves app state into folder and adds new app state util

* Moved cron to class component

* fix: EAS Build Fixes (#1099)

* remove testflight action

* fix eas

* oops

* Set to remote

* add platform checks

* fixes

* Expo is great

---------

Co-authored-by: Thierry <[email protected]>

* Feature: Navigation Refactor (#1025)

navigation refactor

* feat: remove web stuff (#1102)

remove web stuff

* rebase 2.0.8 (#1105)

* fix: Sync Account on Add (#1104)

* fix: Sync Account on Add

Added subscription for accounts store

* Safety

* fix: Run Android profile (#1107)

* [create-pull-request] automated change (#1106)

Co-authored-by: alexrisch <[email protected]>

* fix open conversation

* [create-pull-request] automated change (#1110)

Co-authored-by: thierryskoda <[email protected]>

* [create-pull-request] automated change (#1115)

Co-authored-by: alexrisch <[email protected]>

* Remove duplicated component

* Add missing tx preview component

* Fix snapshot

* Fix tests

* Comments from PR + fix tx frame success

* move default chain to its own file

* move default chain to its own file

---------

Co-authored-by: Saul Carlin <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nmalzieu <[email protected]>
Co-authored-by: Alex Risch <[email protected]>
Co-authored-by: Alex Risch <[email protected]>
Co-authored-by: alexrisch <[email protected]>
Co-authored-by: Thierry Skoda <[email protected]>
Co-authored-by: Thierry <[email protected]>
Co-authored-by: Louis Rouffineau <[email protected]>
Co-authored-by: thierryskoda <[email protected]>
  • Loading branch information
11 people authored Nov 1, 2024
1 parent 33ea20d commit 85d7db7
Show file tree
Hide file tree
Showing 43 changed files with 3,109 additions and 3,147 deletions.
4 changes: 4 additions & 0 deletions assets/transaction-close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/transaction-next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/transaction-send.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/transaction-to.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 30 additions & 24 deletions components/Chat/Frame/FramePreview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logger from "@utils/logger";
import { FrameActionInputs } from "@xmtp/frames-client";
import { ethers } from "ethers";
import { FrameActionInputs, FramePostPayload } from "@xmtp/frames-client";
import { Image } from "expo-image";
import * as Linking from "expo-linking";
import { useCallback, useEffect, useRef, useState } from "react";
Expand All @@ -13,7 +12,6 @@ import config from "../../../config";
import { useCurrentAccount } from "../../../data/store/accountsStore";
import { cacheForMedia, fetchAndCacheMedia } from "../../../utils/cache/cache";
import { useConversationContext } from "../../../utils/conversation";
import { useExternalSigner } from "../../../utils/evm/external";
import {
FrameButtonType,
FrameToDisplay,
Expand All @@ -22,7 +20,7 @@ import {
getFrameButtons,
getFrameImage,
getFramesClient,
handleTxAction,
useHandleTxAction,
validateFrame,
} from "../../../utils/frames";
import { MessageToDisplay } from "../Message/Message";
Expand Down Expand Up @@ -58,7 +56,7 @@ export default function FramePreview({
const messageId = useRef(message.id);
const fetchingInitialForMessageId = useRef(undefined as undefined | string);

const { getExternalSigner } = useExternalSigner();
const { handleTxAction } = useHandleTxAction();

// Components are recycled, let's fix when stuff changes
if (message.id !== messageId.current) {
Expand Down Expand Up @@ -182,27 +180,35 @@ export default function FramePreview({
button.action === "tx" ||
!button.action
) {
const payload = await framesClient.signFrameAction(actionInput);

let payload: FramePostPayload | undefined = undefined;
if (button.action === "tx") {
if (!config.enableTransactionFrames) {
alert("Transaction frames are not supported yet.");
throw new Error("Transaction frames not supported yet");
const { buttonPostUrl, transactionReceipt, fromAddress } =
await handleTxAction({
frame,
button,
actionInput,
framesClient,
});
if (
!transactionReceipt ||
transactionReceipt.status === "reverted" ||
!transactionReceipt?.transactionHash
) {
// error, let's fail
setPostingActionForButton(undefined);
return;
}
// For tx, we get the tx data from target, then trigger it, then do a POST action
const externalSigner = await getExternalSigner();
if (!externalSigner || !externalSigner.provider)
throw new Error("Could not get an external signer");

const { buttonPostUrl, txHash } = await handleTxAction(
frame,
button,
payload,
externalSigner.provider as ethers.providers.Web3Provider
);

payload.untrustedData.transactionId = txHash;
// The payload includes address used for the tx and
// the transaction hash to display transaction success frames
payload = await framesClient.signFrameAction({
...actionInput,
address: fromAddress,
transactionId: transactionReceipt.transactionHash,
});
actionPostUrl = buttonPostUrl;
} else {
// Regular payload for post action
payload = await framesClient.signFrameAction(actionInput);
}

const frameResponse = await framesProxy.post(actionPostUrl, payload);
Expand Down Expand Up @@ -275,7 +281,7 @@ export default function FramePreview({
conversation,
frame,
frameTextInputValue,
getExternalSigner,
handleTxAction,
initialFrame.url,
message.topic,
setFrameTextInputFocused,
Expand Down
40 changes: 40 additions & 0 deletions components/CurrentAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { currentAccount, useProfilesStore } from "@data/store/accountsStore";
import { HStack } from "@design-system/HStack";
import { Text } from "@design-system/Text";
import { AvatarSizes } from "@styles/sizes";
import { spacing } from "@theme/spacing";
import {
getPreferredAvatar,
getPreferredName,
getProfile,
} from "@utils/profile";
import { ViewStyle, StyleSheet } from "react-native";

import Avatar from "./Avatar";

type ICurrentAccountProps = {
style?: ViewStyle;
};

const styles = StyleSheet.create({
container: { alignItems: "center" },
avatar: { marginRight: spacing.xs },
});

export function CurrentAccount(props: ICurrentAccountProps) {
const account = currentAccount();
const profiles = useProfilesStore((state) => state.profiles);
const socials = getProfile(account, profiles)?.socials;
const name = getPreferredName(socials, account);
return (
<HStack style={[styles.container, props.style]}>
<Avatar
uri={getPreferredAvatar(socials)}
size={AvatarSizes.profileSettings}
name={name}
style={styles.avatar}
/>
<Text>{name}</Text>
</HStack>
);
}
15 changes: 9 additions & 6 deletions components/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AnimatedScrollView } from "@design-system/ScrollView";
import { backgroundColor } from "@styles/colors";
import { ReanimatedView } from "@utils/animations";
import { useKeyboardAnimation } from "@utils/animations/keyboardAnimation";
Expand Down Expand Up @@ -42,6 +43,7 @@ export interface DrawerProps {
children: React.ReactNode;
onClose?: () => void;
style?: ViewStyle;
showHandle?: boolean;
}

export const DrawerContext = React.createContext<{
Expand All @@ -59,7 +61,7 @@ export interface DrawerRef {
}

export const Drawer = forwardRef<DrawerRef, DrawerProps>(function Drawer(
{ children, visible, onClose, style },
{ children, visible, onClose, style, showHandle },
ref
) {
const styles = useStyles();
Expand Down Expand Up @@ -154,7 +156,7 @@ export const Drawer = forwardRef<DrawerRef, DrawerProps>(function Drawer(
const { height: keyboardHeight } = useKeyboardAnimation();
const { bottom } = useSafeAreaInsets();

const animtedStyle = useAnimatedStyle(() => ({
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translation.value }],
paddingBottom: Platform.OS === "ios" ? keyboardHeight.value + bottom : 0,
}));
Expand All @@ -172,13 +174,14 @@ export const Drawer = forwardRef<DrawerRef, DrawerProps>(function Drawer(
<ReanimatedView style={[styles.backdrop, backgroundStyle]} />
</TouchableWithoutFeedback>
<GestureDetector gesture={composed}>
<ReanimatedView
style={[styles.trayContainer, animtedStyle, style]}
<AnimatedScrollView
style={[styles.trayContainer, animatedStyle, style]}
layout={LinearTransition.springify()}
alwaysBounceVertical={false}
>
<View style={styles.handle} />
{showHandle && <View style={styles.handle} />}
{children}
</ReanimatedView>
</AnimatedScrollView>
</GestureDetector>
</GestureHandlerRootView>
</Portal>
Expand Down
9 changes: 4 additions & 5 deletions components/ExternalWalletPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { waitUntilAppActive } from "@utils/appState/waitUntilAppActive";
import { converseEventEmitter } from "@utils/events";
import { thirdwebClient } from "@utils/thirdweb";
import { Image } from "expo-image";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import {
ScrollView,
StyleSheet,
Expand All @@ -21,9 +21,9 @@ import {
} from "react-native";
import { Account, Wallet, createWallet } from "thirdweb/wallets";

import config from "../config";
import Button from "./Button/Button";
import { Drawer, DrawerRef } from "./Drawer";
import { Drawer } from "./Drawer";
import config from "../config";
import {
InstalledWallet,
useInstalledWallets,
Expand All @@ -33,7 +33,6 @@ import Picto from "./Picto/Picto";
export default function ExternalWalletPicker() {
const styles = useStyles();
const colorScheme = useColorScheme();
const drawerRef = useRef<DrawerRef>(null);
const [visible, setVisible] = useState(false);
const [title, setTitle] = useState<string | undefined>(undefined);
const [subtitle, setSubtitle] = useState<string | undefined>(undefined);
Expand Down Expand Up @@ -126,8 +125,8 @@ export default function ExternalWalletPicker() {
<Drawer
visible={visible}
onClose={closeMenu}
ref={drawerRef}
style={styles.drawer}
showHandle
>
<ScrollView style={styles.wallets} alwaysBounceVertical={false}>
{title && <Text style={styles.title}>{title}</Text>}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
// TODO: move out of ConnectViaWallet
import * as Linking from "expo-linking";
import { useCallback, useEffect, useState } from "react";
import { Chain } from "thirdweb";
import {
arbitrum,
avalanche,
base,
blast,
ethereum,
optimism,
polygon,
zora,
} from "thirdweb/chains";
import { WalletId } from "thirdweb/wallets";

import { useAppStateHandlers } from "../../../hooks/useAppStateHandlers";
import { isEthOS } from "../../../utils/ethos";

Expand Down Expand Up @@ -69,6 +81,18 @@ const SUPPORTED_WALLETS = [
"https://explorer-api.walletconnect.com/v3/logo/sm/7a33d7f1-3d12-4b5c-f3ee-5cd83cb1b500?projectId=2f05ae7f1116030fde2d36508f472bfb",
customScheme: "rainbow://",
thirdwebId: "me.rainbow",
// Rainbow Mobile does not support tesnets (even Sepolia)
// https://rainbow.me/en/support/app/testnets
supportedChains: [
ethereum,
base,
optimism,
polygon,
arbitrum,
avalanche,
blast,
zora,
],
},
{
name: "MetaMask",
Expand Down Expand Up @@ -125,7 +149,7 @@ const SUPPORTED_WALLETS = [
// customScheme: "oneinch://",
// universalLink: "https://wallet.1inch.io",
// },
] as const;
];

type ISupportedWalletName =
| (typeof SUPPORTED_WALLETS)[number]["name"]
Expand All @@ -139,6 +163,7 @@ export type InstalledWallet = {
walletConnectId?: string;
platforms?: string[];
thirdwebId?: WalletId;
supportedChains?: Chain[];
};

let hasCheckedInstalled = false;
Expand All @@ -161,7 +186,11 @@ export const getInstalledWallets = async (
});
}

wallets.push(...SUPPORTED_WALLETS.filter((w, i) => checkInstalled[i]));
wallets.push(
...(SUPPORTED_WALLETS as InstalledWallet[]).filter(
(w, i) => checkInstalled[i]
)
);
installedWallets = wallets;
hasCheckedInstalled = true;
return installedWallets;
Expand Down
72 changes: 72 additions & 0 deletions components/TransactionPreview/TransactionActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { InstalledWallet } from "@components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets";
import { Text } from "@design-system/Text";
import { TouchableOpacity } from "@design-system/TouchableOpacity";
import { VStack } from "@design-system/VStack";
import { translate } from "@i18n";
import { spacing } from "@theme/spacing";

import { TransactionToTrigger } from "./TransactionPreview";
import TransactionNext from "../../assets/transaction-next.svg";
import { CHAIN_BY_ID } from "@utils/evm/wallets";

type ITransactionActionsProps = {
shouldSwitchChain?: boolean;
showTriggerButton?: boolean;
transactionToPreview: TransactionToTrigger | undefined;
switchChain: (chainId: number) => Promise<void>;
trigger: () => Promise<void>;
walletApp: InstalledWallet | undefined;
};

export const TransactionActions = ({
shouldSwitchChain,
showTriggerButton,
transactionToPreview,
switchChain,
trigger,
walletApp,
}: ITransactionActionsProps) => (
<>
{shouldSwitchChain && (
<VStack style={{ alignItems: "center" }}>
<TouchableOpacity
onPress={
transactionToPreview?.chainId
? () => switchChain(transactionToPreview.chainId)
: undefined
}
>
<TransactionNext />
</TouchableOpacity>
<Text
preset="smaller"
color="secondary"
style={{ marginTop: spacing.xs }}
>
{translate("transaction_switch_chain", {
chainName: transactionToPreview?.chainId
? CHAIN_BY_ID[transactionToPreview.chainId].name
: undefined,
wallet: walletApp?.name,
})}
</Text>
</VStack>
)}
{showTriggerButton && (
<VStack style={{ alignItems: "center" }}>
<TouchableOpacity onPress={trigger}>
<TransactionNext />
</TouchableOpacity>
<Text
preset="smaller"
color="secondary"
style={{ marginTop: spacing.xs }}
>
{translate("transaction_pending", {
wallet: walletApp?.name,
})}
</Text>
</VStack>
)}
</>
);
Loading

0 comments on commit 85d7db7

Please sign in to comment.