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: add expiration to mobile send flow #5336

Merged
merged 3 commits into from Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/mobile/components/ExpirationTimePicker.svelte
@@ -0,0 +1,33 @@
<script lang="typescript">
import { formatDate, localize } from '@core/i18n'
import { Icon, Text } from 'shared/components'

export let value: Date = undefined
export let disabled: boolean = false
export let onClick: () => unknown = () => {}
</script>

<button
class="flex items-center justify-center {disabled ? 'cursor-default' : 'cursor-pointer'}"
{disabled}
on:click={onClick}
>
<div class="flex flex-row hover:text-blue-600">
<Text
highlighted={!disabled}
color="gray-600"
darkColor="gray-500"
classes={disabled ? '' : 'hover:text-blue-600'}
>
{value
? formatDate(value, {
dateStyle: 'long',
timeStyle: 'medium',
})
: localize('general.none')}
</Text>
{#if !disabled}
<Icon icon="chevron-down" width="10" classes="text-blue-500 ml-1" />
{/if}
</div>
</button>
1 change: 1 addition & 0 deletions packages/mobile/components/index.js
Expand Up @@ -5,6 +5,7 @@ export { default as AsyncActivityTileFooter } from './AsyncActivityTileFooter.sv
export { default as ColorPicker } from './ColorPicker.svelte'
export { default as Confirmation } from './Confirmation.svelte'
export { default as Drawer } from './Drawer.svelte'
export { default as ExpirationTimePicker } from './ExpirationTimePicker.svelte'
export { default as OnboardingLayout } from './OnboardingLayout.svelte'
export { default as QRScanner } from './QRScanner.svelte'
export { default as RecoveryPhrase } from './RecoveryPhrase.svelte'
Expand Down
Expand Up @@ -3,4 +3,5 @@ import { IRouterEvent } from '@core/router'
export interface ISendRouterEvent extends IRouterEvent {
needsUnlock?: boolean
addReference?: boolean
addExpiration?: boolean
}
5 changes: 4 additions & 1 deletion packages/mobile/lib/routers/routers/dashboard/send-router.ts
Expand Up @@ -17,7 +17,7 @@ export class SendRouter extends Subrouter<SendRoute> {
}

next(event: ISendRouterEvent = {}): void {
const { needsUnlock, addReference } = event
const { needsUnlock, addReference, addExpiration } = event

let nextRoute: SendRoute
const currentRoute = get(this.routeStore)
Expand All @@ -38,6 +38,8 @@ export class SendRouter extends Subrouter<SendRoute> {
case SendRoute.Review: {
if (addReference) {
nextRoute = SendRoute.Reference
} else if (addExpiration) {
nextRoute = SendRoute.Expiration
} else {
if (needsUnlock) {
nextRoute = SendRoute.Password
Expand All @@ -48,6 +50,7 @@ export class SendRouter extends Subrouter<SendRoute> {
break
}
case SendRoute.Reference:
case SendRoute.Expiration:
case SendRoute.Password: {
super.previous()
break
Expand Down
Expand Up @@ -18,6 +18,9 @@
case SendRoute.Reference:
title = localize('actions.addReference')
break
case SendRoute.Expiration:
title = localize('general.expirationTime')
break
default:
title = localize('popups.sendForm.title')
break
Expand Down
14 changes: 9 additions & 5 deletions packages/mobile/views/dashboard/drawers/send/SendRouter.svelte
Expand Up @@ -18,29 +18,27 @@
} from '@core/wallet'
import { getStorageDepositFromOutput } from '@core/wallet/utils/generateActivity/helper'
import type { OutputOptions } from '@iota/wallet'
import { ExpirationTimePicker } from 'shared/components'
import { get } from 'svelte/store'
import { StrongholdUnlock } from '../../../../components'
import { sendRoute, SendRoute, sendRouter } from '../../../../lib/routers'
import { AmountView, RecipientView, ReferenceView, ReviewView, TokenView } from './views'
import { AmountView, Expiration, RecipientView, ReferenceView, ReviewView, TokenView } from './views'

$: ({ recipient, expirationDate, giftStorageDeposit, surplus } = $newTransactionDetails)

let storageDeposit = 0
let preparedOutput: Output
let outputOptions: OutputOptions
let expirationTimePicker: ExpirationTimePicker
let initialExpirationDate: ExpirationTime = getInitialExpirationDate()

let triggerSendOnMount: boolean = false

$: transactionDetails = get(newTransactionDetails)
$: recipientAddress = recipient?.type === 'account' ? recipient?.account?.depositAddress : recipient?.address
$: expirationTimePicker?.setNull(giftStorageDeposit)
$: asset =
transactionDetails.type === NewTransactionType.TokenTransfer
? getAssetById(transactionDetails.assetId)
: undefined
$: expirationDate, giftStorageDeposit, refreshSendConfirmationState()

async function sendTransaction(): Promise<void> {
triggerSendOnMount = false
Expand Down Expand Up @@ -112,6 +110,10 @@
}
}

function refreshSendConfirmationState(): void {
void prepareTransactionOutput()
}

function getInitialExpirationDate(): ExpirationTime {
if (expirationDate) {
return ExpirationTime.Custom
Expand All @@ -137,7 +139,9 @@
{:else if $sendRoute === SendRoute.Reference}
<ReferenceView />
{:else if $sendRoute === SendRoute.Review}
<ReviewView {sendTransaction} {triggerSendOnMount} {storageDeposit} />
<ReviewView {sendTransaction} {triggerSendOnMount} {storageDeposit} {initialExpirationDate} bind:expirationDate />
{:else if $sendRoute === SendRoute.Expiration}
<Expiration onCancel={() => $sendRouter.previous()} />
{:else if $sendRoute === SendRoute.Password}
<StrongholdUnlock onSuccess={onUnlockSuccess} onCancel={() => $sendRouter.previous()} />
{/if}
@@ -0,0 +1,62 @@
<script lang="typescript">
import { localize } from '@core/i18n'
import { ExpirationTime } from '@core/utils'
import { newTransactionDetails, updateNewTransactionDetails } from '@core/wallet'
import { Button, HR } from 'shared/components'
import { sendRouter } from '../../../../../lib/routers'

export let onCancel: () => unknown = () => {}

const DATE_NOW = Date.now()

const dateIn1Hour = new Date(DATE_NOW)
dateIn1Hour.setTime(dateIn1Hour.getTime() + 1 * 60 * 60 * 1000)

const dateIn1Day = new Date(DATE_NOW)
dateIn1Day.setDate(dateIn1Day.getDate() + 1)

const dateIn1Week = new Date(DATE_NOW)
dateIn1Week.setDate(dateIn1Week.getDate() + 7)

function handleChooseExpirationTimeClick(_expiration: ExpirationTime): void {
let expirationDate: Date
switch (_expiration) {
case ExpirationTime.OneHour:
expirationDate = dateIn1Hour
break
case ExpirationTime.OneDay:
expirationDate = dateIn1Day
break
case ExpirationTime.OneWeek:
expirationDate = dateIn1Week
break
case ExpirationTime.None:
expirationDate = null
break
case ExpirationTime.Custom:
default:
break
}
updateNewTransactionDetails({ type: $newTransactionDetails.type, expirationDate })
$sendRouter.next()
}
</script>

<div class="w-full flex flex-col space-y-4">
<Button outline onClick={() => handleChooseExpirationTimeClick(ExpirationTime.None)} classes="w-full">
{localize('menus.expirationTimePicker.none')}
</Button>
<Button outline onClick={() => handleChooseExpirationTimeClick(ExpirationTime.OneHour)} classes="w-full">
{localize('menus.expirationTimePicker.1hour')}
</Button>
<Button outline onClick={() => handleChooseExpirationTimeClick(ExpirationTime.OneDay)} classes="w-full">
{localize('menus.expirationTimePicker.1day')}
</Button>
<Button outline onClick={() => handleChooseExpirationTimeClick(ExpirationTime.OneWeek)} classes="w-full">
{localize('menus.expirationTimePicker.1week')}
</Button>
<HR />
<Button outline onClick={onCancel} classes="w-full">
{localize('actions.cancel')}
</Button>
</div>
@@ -1,6 +1,7 @@
<script lang="typescript">
import { selectedAccount } from '@core/account'
import { localize } from '@core/i18n'
import { ExpirationTime } from '@core/utils'
import {
ActivityDirection,
ActivityType,
Expand All @@ -12,13 +13,16 @@
import { ActivityInformation, BasicActivityDetails, Button, KeyValueBox, TextHint, Toggle } from 'shared/components'
import { onMount } from 'svelte'
import { get } from 'svelte/store'
import { ExpirationTimePicker } from '../../../../../components'
import { sendRouter } from '../../../../../lib/routers'

export let sendTransaction: () => Promise<void>
export let triggerSendOnMount: boolean = false
export let storageDeposit: number
export let initialExpirationDate: ExpirationTime
export let expirationDate: Date

const { recipient, surplus, layer2Parameters } = get(newTransactionDetails)
const { recipient, surplus, layer2Parameters, disableChangeExpiration } = get(newTransactionDetails)

let loading: boolean = false

Expand Down Expand Up @@ -85,6 +89,18 @@
/>
</KeyValueBox>
{/if}
{#if initialExpirationDate !== undefined}
<KeyValueBox keyText={localize('general.expirationTime')}>
<ExpirationTimePicker
slot="value"
bind:value={expirationDate}
disabled={disableChangeExpiration}
onClick={() => {
$sendRouter.next({ addExpiration: true })
}}
/>
</KeyValueBox>
{/if}
{#if surplus}
<TextHint warning text={localize('popups.transaction.surplusIncluded')} />
{/if}
Expand Down
@@ -1,4 +1,5 @@
export { default as AmountView } from './AmountView.svelte'
export { default as Expiration } from './Expiration.svelte'
export { default as RecipientView } from './RecipientView.svelte'
export { default as ReferenceView } from './ReferenceView.svelte'
export { default as ReviewView } from './ReviewView.svelte'
Expand Down