diff --git a/backend/canisters/community/api/can.did b/backend/canisters/community/api/can.did index c24551abda..a95d1d97aa 100644 --- a/backend/canisters/community/api/can.did +++ b/backend/canisters/community/api/can.did @@ -299,6 +299,22 @@ type AcceptP2PSwapResponse = variant { InternalError : text; }; +type CancelP2PSwapArgs = record { + channel_id : ChannelId; + thread_root_message_index : opt MessageIndex; + message_id : MessageId; +}; + +type CancelP2PSwapResponse = variant { + Success; + StatusError : SwapStatusError; + SwapNotFound; + ChannelNotFound; + UserNotInCommunity; + UserNotInChannel; + ChatFrozen; +}; + type AddMembersToChannelArgs = record { channel_id : ChannelId; user_ids : vec UserId; @@ -992,6 +1008,7 @@ service : { add_members_to_channel : (AddMembersToChannelArgs) -> (AddMembersToChannelResponse); add_reaction : (AddReactionArgs) -> (AddReactionResponse); block_user : (BlockUserArgs) -> (BlockUserResponse); + cancel_p2p_swap : (CancelP2PSwapArgs) -> (CancelP2PSwapResponse); change_channel_role : (ChangeChannelRoleArgs) -> (ChangeChannelRoleResponse); change_role : (ChangeRoleArgs) -> (ChangeRoleResponse); claim_prize : (ClaimPrizeArgs) -> (ClaimPrizeResponse); diff --git a/backend/canisters/community/api/src/main.rs b/backend/canisters/community/api/src/main.rs index 83d0216541..494707fbb3 100644 --- a/backend/canisters/community/api/src/main.rs +++ b/backend/canisters/community/api/src/main.rs @@ -27,6 +27,7 @@ fn main() { generate_candid_method!(community, add_members_to_channel, update); generate_candid_method!(community, add_reaction, update); generate_candid_method!(community, block_user, update); + generate_candid_method!(community, cancel_p2p_swap, update); generate_candid_method!(community, change_channel_role, update); generate_candid_method!(community, change_role, update); generate_candid_method!(community, claim_prize, update); diff --git a/backend/canisters/group/api/can.did b/backend/canisters/group/api/can.did index aed33e75e6..b1c4dc85f9 100644 --- a/backend/canisters/group/api/can.did +++ b/backend/canisters/group/api/can.did @@ -116,6 +116,19 @@ type AcceptP2PSwapResponse = variant { InternalError : text; }; +type CancelP2PSwapArgs = record { + thread_root_message_index : opt MessageIndex; + message_id : MessageId; +}; + +type CancelP2PSwapResponse = variant { + Success; + StatusError : SwapStatusError; + SwapNotFound; + UserNotInGroup; + ChatFrozen; +}; + type AddReactionArgs = record { thread_root_message_index : opt MessageIndex; message_id : MessageId; @@ -645,6 +658,7 @@ service : { undelete_messages : (UndeleteMessagesArgs) -> (UndeleteMessagesResponse); register_poll_vote : (RegisterPollVoteArgs) -> (RegisterPollVoteResponse); accept_p2p_swap : (AcceptP2PSwapArgs) -> (AcceptP2PSwapResponse); + cancel_p2p_swap : (CancelP2PSwapArgs) -> (CancelP2PSwapResponse); add_reaction : (AddReactionArgs) -> (AddReactionResponse); remove_reaction : (RemoveReactionArgs) -> (RemoveReactionResponse); report_message : (ReportMessageArgs) -> (ReportMessageResponse); diff --git a/backend/canisters/group/api/src/main.rs b/backend/canisters/group/api/src/main.rs index 78329f9621..ebd82fffd3 100644 --- a/backend/canisters/group/api/src/main.rs +++ b/backend/canisters/group/api/src/main.rs @@ -21,6 +21,7 @@ fn main() { generate_candid_method!(group, accept_p2p_swap, update); generate_candid_method!(group, add_reaction, update); generate_candid_method!(group, block_user, update); + generate_candid_method!(group, cancel_p2p_swap, update); generate_candid_method!(group, change_role, update); generate_candid_method!(group, claim_prize, update); generate_candid_method!(group, convert_into_community, update); diff --git a/frontend/app/src/components/home/AcceptP2PSwapModal.svelte b/frontend/app/src/components/home/AcceptP2PSwapModal.svelte new file mode 100644 index 0000000000..dffb4747f9 --- /dev/null +++ b/frontend/app/src/components/home/AcceptP2PSwapModal.svelte @@ -0,0 +1,140 @@ + + + + + +
+ +
+ +
+
+
+ {#if insufficient} +

+ +

+ +

+ + + + {:else} + + {/if} +
+
+ + + + {#if insufficient} + + {:else} + + {/if} + + +
+
+ + diff --git a/frontend/app/src/components/home/ChatMessage.svelte b/frontend/app/src/components/home/ChatMessage.svelte index 692f7a3982..57a0f40aa6 100644 --- a/frontend/app/src/components/home/ChatMessage.svelte +++ b/frontend/app/src/components/home/ChatMessage.svelte @@ -102,7 +102,10 @@ let multiUserChat = chatType === "group_chat" || chatType === "channel"; let showEmojiPicker = false; let debug = false; - let crypto = msg.content.kind === "crypto_content" || msg.content.kind === "prize_content"; + let crypto = + msg.content.kind === "crypto_content" || + msg.content.kind === "prize_content" || + msg.content.kind === "p2p_swap_content"; let poll = msg.content.kind === "poll_content"; let canRevealDeleted = false; let showRemindMe = false; @@ -202,7 +205,7 @@ messageIndex: msg.messageIndex, edited: msg.edited, isThreadRoot: msg.thread !== undefined, - sourceContext: { chatId, threadRootMessageIndex: threadRootMessage?.messageIndex }, + sourceContext: messageContext, }; } @@ -528,10 +531,11 @@ {readonly} {fill} {me} - {chatId} + {messageContext} {collapsed} {undeleting} {intersecting} + {failed} messageIndex={msg.messageIndex} messageId={msg.messageId} myUserId={user.userId} diff --git a/frontend/app/src/components/home/ChatMessageContent.svelte b/frontend/app/src/components/home/ChatMessageContent.svelte index 29842712ee..fe3864a320 100644 --- a/frontend/app/src/components/home/ChatMessageContent.svelte +++ b/frontend/app/src/components/home/ChatMessageContent.svelte @@ -19,8 +19,11 @@ import MessageReminderContent from "./MessageReminderContent.svelte"; import MessageReminderCreatedContent from "./MessageReminderCreatedContent.svelte"; import ProposalContent from "./proposals/ProposalContent.svelte"; - import type { ChatIdentifier, MessageContent } from "openchat-client"; - import PrizeContentInitial from "./PrizeContentInitial.svelte"; + import type { MessageContent, MessageContext } from "openchat-client"; + import { _ } from "svelte-i18n"; + import MessageContentInitial from "./MessageContentInitial.svelte"; + import P2PSwapContent from "./P2PSwapContent.svelte"; + import { i18nKey } from "../../i18n/i18n"; export let content: MessageContent; export let me: boolean = false; @@ -34,11 +37,12 @@ export let myUserId: string | undefined; export let messageId: bigint; export let edited: boolean; - export let chatId: ChatIdentifier; + export let messageContext: MessageContext; export let messageIndex: number; export let collapsed = false; export let undeleting: boolean = false; export let intersecting: boolean; + export let failed: boolean; {#if content.kind === "text_content"} @@ -60,9 +64,15 @@ {:else if content.kind === "placeholder_content"} {:else if content.kind === "prize_content_initial"} - + +{:else if content.kind === "p2p_swap_content_initial"} + {:else if content.kind === "prize_content"} - + +{:else if content.kind === "p2p_swap_content"} + {:else if content.kind === "prize_winner_content"} {:else if content.kind === "poll_content"} @@ -72,7 +82,7 @@ {:else if content.kind === "proposal_content"} ) { draftMessagesStore.setAttachment({ chatId: chat.id }, ev.detail); } @@ -298,6 +304,13 @@ on:close={() => (creatingPrizeMessage = false)} /> {/if} +{#if creatingP2PSwapMessage} + (creatingP2PSwapMessage = false)} /> +{/if} + @@ -380,6 +393,7 @@ on:makeMeme={makeMeme} on:tokenTransfer={tokenTransfer} on:createPrizeMessage={createPrizeMessage} + on:createP2PSwapMessage={createP2PSwapMessage} on:searchChat={searchChat} on:createPoll={createPoll} /> {/if} diff --git a/frontend/app/src/components/home/DisappearingMessages.svelte b/frontend/app/src/components/home/DisappearingMessages.svelte deleted file mode 100644 index 78a1769d39..0000000000 --- a/frontend/app/src/components/home/DisappearingMessages.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - -
-
- -
- -
- -
-
- - diff --git a/frontend/app/src/components/home/DurationPicker.svelte b/frontend/app/src/components/home/DurationPicker.svelte new file mode 100644 index 0000000000..21ca6063ca --- /dev/null +++ b/frontend/app/src/components/home/DurationPicker.svelte @@ -0,0 +1,89 @@ + + +
+
+ +
+ +
+ +
+
+ + diff --git a/frontend/app/src/components/home/Footer.svelte b/frontend/app/src/components/home/Footer.svelte index 88a5d72104..a108a4d31f 100644 --- a/frontend/app/src/components/home/Footer.svelte +++ b/frontend/app/src/components/home/Footer.svelte @@ -123,7 +123,7 @@ {#if editingEvent === undefined && (replyingTo || attachment !== undefined)}
{#if replyingTo} - + {/if} {#if attachment !== undefined} @@ -154,6 +154,7 @@ on:searchChat on:tokenTransfer on:createPrizeMessage + on:createP2PSwapMessage on:attachGif on:makeMeme on:clearAttachment diff --git a/frontend/app/src/components/home/GroupPermissionsViewer.svelte b/frontend/app/src/components/home/GroupPermissionsViewer.svelte index 1466a407fb..8f7a77e05f 100644 --- a/frontend/app/src/components/home/GroupPermissionsViewer.svelte +++ b/frontend/app/src/components/home/GroupPermissionsViewer.svelte @@ -32,13 +32,10 @@ }, "", ); - messagePartition = partitionMessagePermissions( - permissions.messagePermissions, - "messagePermissions.", - ); + messagePartition = partitionMessagePermissions(permissions.messagePermissions, false); threadPartition = partitionMessagePermissions( permissions.threadPermissions ?? permissions.messagePermissions, - "threadPermissions.", + true, ); } @@ -46,22 +43,28 @@ function partitionMessagePermissions( mps: MessagePermissions, - translationExt: string, + thread: boolean, ): PermissionsByRole { + let permissions: Record = { + text: mps.text ?? mps.default, + image: mps.image ?? mps.default, + video: mps.video ?? mps.default, + audio: mps.audio ?? mps.default, + file: mps.file ?? mps.default, + poll: mps.poll ?? mps.default, + crypto: mps.crypto ?? mps.default, + giphy: mps.giphy ?? mps.default, + memeFighter: mps.memeFighter ?? mps.default, + p2pSwap: mps.p2pSwap ?? mps.default, + }; + + if (!thread) { + permissions = { ...permissions, prize: mps.prize ?? mps.default }; + } + return partitionPermissions( - { - text: mps.text ?? mps.default, - image: mps.image ?? mps.default, - video: mps.video ?? mps.default, - audio: mps.audio ?? mps.default, - file: mps.file ?? mps.default, - poll: mps.poll ?? mps.default, - crypto: mps.crypto ?? mps.default, - giphy: mps.giphy ?? mps.default, - prize: mps.prize ?? mps.default, - memeFighter: mps.memeFighter ?? mps.default, - }, - translationExt, + permissions, + thread ? "threadPermissions." : "messagePermissions.", ); } diff --git a/frontend/app/src/components/home/MessageActions.svelte b/frontend/app/src/components/home/MessageActions.svelte index 185d754542..43573fb497 100644 --- a/frontend/app/src/components/home/MessageActions.svelte +++ b/frontend/app/src/components/home/MessageActions.svelte @@ -5,6 +5,7 @@ import { _ } from "svelte-i18n"; import Smiley from "./Smiley.svelte"; import Gift from "svelte-material-icons/GiftOutline.svelte"; + import SwapIcon from "svelte-material-icons/SwapHorizontal.svelte"; import Bitcoin from "../icons/Bitcoin.svelte"; import MemeFighter from "../icons/MemeFighter.svelte"; import StickerEmoji from "svelte-material-icons/StickerEmoji.svelte"; @@ -51,6 +52,11 @@ drawOpen = false; } + function createP2PSwapMessage() { + dispatch("createP2PSwapMessage"); + drawOpen = false; + } + function openEmojiPicker() { messageAction = "emoji"; } @@ -116,6 +122,9 @@ if (permissions.get("prize")) { actions.set("prize", cssValues(++index)); } + if (permissions.get("p2pSwap")) { + actions.set("swap", cssValues(++index)); + } return actions; } @@ -236,6 +245,16 @@
{/if} + {#if supportedActions.has("swap")} +
+ + + +
+ {/if} {/if} @@ -264,6 +283,7 @@ .meme, .poll, .prize, + .swap, .send-icp { flex: 0 0 15px; } @@ -290,6 +310,7 @@ .meme, .send-icp, .prize, + .swap, .poll { top: -18px; left: toRem(-44); @@ -308,6 +329,7 @@ .meme, .send-icp, .prize, + .swap, .poll { left: unset; right: toRem(-44); @@ -324,6 +346,7 @@ .gif, .meme, .poll, + .swap, .prize { top: var(--top); transition-delay: var(--transition-delay); diff --git a/frontend/app/src/components/home/PrizeContentInitial.svelte b/frontend/app/src/components/home/MessageContentInitial.svelte similarity index 65% rename from frontend/app/src/components/home/PrizeContentInitial.svelte rename to frontend/app/src/components/home/MessageContentInitial.svelte index defc98d24b..afbd730119 100644 --- a/frontend/app/src/components/home/PrizeContentInitial.svelte +++ b/frontend/app/src/components/home/MessageContentInitial.svelte @@ -1,19 +1,20 @@ -{#if me} -
+
+ {#if !failed}
-

-
-{/if} + {/if} +

+
diff --git a/frontend/app/src/components/home/P2PSwapContentBuilder.svelte b/frontend/app/src/components/home/P2PSwapContentBuilder.svelte new file mode 100644 index 0000000000..39d9a6edff --- /dev/null +++ b/frontend/app/src/components/home/P2PSwapContentBuilder.svelte @@ -0,0 +1,270 @@ + + +{#if confirming} + +{/if} + + + + +
+ +
+ +
+
+
+
+ +
+ t.balance > 0} + bind:ledger={fromLedger} + on:select={onSelectFromToken} /> +
+
+
+ +
+
+
+
+ +
+ t.ledger !== fromLedger} + bind:ledger={toLedger} /> +
+
+
+ +
+
+
+ + +
+
+ +