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-mobile: filter assets #5750

Merged
merged 7 commits into from Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
26 changes: 20 additions & 6 deletions packages/mobile/components/AssetList.svelte
@@ -1,36 +1,50 @@
<script lang="ts">
import { localize } from '@core/i18n'
import { IAccountAssets, IAsset } from '@core/wallet'
import { assetFilter } from '@core/wallet/stores'
import { isVisibleAsset } from '@core/wallet/utils/isVisibleAsset'
import VirtualList from '@sveltejs/svelte-virtual-list'
import { AssetTile, Text } from 'shared/components'
import { Filter } from '../components'

export let assets: IAccountAssets
export let onAssetTileClick: (asset: IAsset) => unknown = () => {}

let assetList: IAsset[]
$: assets, (assetList = getAssetList())
$: $assetFilter, assets, (assetList = getFilteredAssetList()), scrollToTop()
$: isEmptyBecauseOfFilter = (assets.baseCoin || assets.nativeTokens?.length > 0) && assetList.length === 0

function getAssetList(): IAsset[] {
function scrollToTop(): void {
const listElement = document.querySelector('.asset-list')?.querySelector('svelte-virtual-list-viewport')
if (listElement) {
listElement.scroll(0, 0)
}
}

function getFilteredAssetList(): IAsset[] {
const list = []

if (assets?.baseCoin) {
list.push(assets.baseCoin)
}
list.push(...assets?.nativeTokens)
return list
return list.filter((_nativeToken) => isVisibleAsset(_nativeToken))
}
</script>

{#if assets}
<div class="asset-list h-full flex flex-auto flex-col flex-grow flex-shrink-0">
<asset-list-container class="asset-list h-full flex flex-auto flex-col flex-grow flex-shrink-0">
<asset-list-header class="sticky pb-4">
<Filter filterStoreValue={$assetFilter} filterType="asset" />
</asset-list-header>
{#if assetList.length > 0}
<VirtualList items={assetList} let:item>
<AssetTile classes="mb-2" onClick={() => onAssetTileClick(item)} asset={item} />
</VirtualList>
{:else}
<div class="h-full flex flex-col items-center justify-center text-center">
<Text secondary>{localize('general.noAssets')}</Text>
<Text secondary>{localize(`general.${isEmptyBecauseOfFilter ? 'noFilteredAsset' : 'noAssets'}`)}</Text>
</div>
{/if}
</div>
</asset-list-container>
{/if}
30 changes: 30 additions & 0 deletions packages/mobile/components/filters/Filter.svelte
@@ -0,0 +1,30 @@
<script lang="ts">
import { TogglableButton } from 'shared/components'
import { DashboardRoute, dashboardRoute, filterRouter, FilterType } from '../../lib/routers'
import type { Filter } from '@core/utils/types'
import { selectedFilter } from '../../lib/contexts/dashboard'
import { activeProfileId } from '@core/profile'

export let filterStoreValue: Filter
export let filterType: FilterType

function onClick(): void {
const filter = structuredClone(filterStoreValue)
$filterRouter.next({ filter, filterType })
}

$: activeFilterCount = Object.keys(filterStoreValue).filter((f) => filterStoreValue[f].active).length
$: isFilterActive = $dashboardRoute === DashboardRoute.Filter && $selectedFilter !== null
$: $activeProfileId, $filterRouter?.clearFilterStores()
</script>

<div class="h-6 relative">
<TogglableButton icon="filter" bind:active={isFilterActive} {onClick} />
{#if activeFilterCount}
<filter-badge
class="inline-flex items-center justify-center h-3 w-3 -ml-2 -mt-0.5 absolute rounded-full bg-blue-500 text-white text-8"
>
{activeFilterCount}
</filter-badge>
{/if}
</div>
60 changes: 60 additions & 0 deletions packages/mobile/components/filters/FilterItem.svelte
@@ -0,0 +1,60 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import { FontWeight, Text, Toggle } from '@ui'
import { localize } from '@core/i18n'
import {
DateFilterItem,
NumberFilterItem,
SelectionFilterItem,
AssetFilterItem,
OrderFilterItem,
} from 'shared/components/filters/items'
import { FilterUnit } from '@core/utils/interfaces/filter'

export let filterUnit: FilterUnit
</script>

<filter-item class="block px-5 -mx-5 border-t border-solid border-gray-200 dark:border-gray-800">
<filter-item-toggle class="block py-4 flex flex-row justify-between">
jeeanribeiro marked this conversation as resolved.
Show resolved Hide resolved
<Text fontWeight={FontWeight.medium} fontSize="15"
>{localize(filterUnit.labelKey ?? filterUnit.localeKey + '.label')}</Text
>
<Toggle
onClick={() => (filterUnit.active = !filterUnit.active)}
bind:active={filterUnit.active}
color="green"
/>
</filter-item-toggle>
{#if filterUnit.active}
<filter-item-type
class="block expanded bg-gray-50 px-4 py-3 -mx-5 dark:bg-transparent border-t border-solid border-gray-200 dark:border-gray-800"
transition:slide
>
{#if filterUnit.type === 'number'}
<NumberFilterItem bind:filterUnit />
{:else if filterUnit.type === 'date'}
<DateFilterItem bind:filterUnit />
{:else if filterUnit.type === 'selection'}
<SelectionFilterItem bind:filterUnit />
{:else if filterUnit.type === 'order'}
<OrderFilterItem bind:filterUnit />
{:else if filterUnit.type === 'asset'}
<AssetFilterItem bind:filterUnit />
{/if}
</filter-item-type>
{/if}
</filter-item>

<style lang="scss">
filter-item:last-of-type {
@apply border-b;
@apply border-solid;
@apply border-gray-200;
@apply dark:border-gray-800;
jeeanribeiro marked this conversation as resolved.
Show resolved Hide resolved
}

filter-item-type :global(p) {
@apply text-14;
@apply font-500;
}
</style>
2 changes: 2 additions & 0 deletions packages/mobile/components/filters/index.js
@@ -0,0 +1,2 @@
export { default as Filter } from './Filter.svelte'
export { default as FilterItem } from './FilterItem.svelte'
1 change: 1 addition & 0 deletions packages/mobile/components/index.js
Expand Up @@ -20,6 +20,7 @@ export { default as TokenWithMax } from './TokenWithMax.svelte'
export { default as TopBar } from './TopBar.svelte'

export * from './buttons'
export * from './filters'
export * from './inputs'
export * from './labels'
export * from './molecules'
2 changes: 1 addition & 1 deletion packages/mobile/lib/contexts/dashboard/constants/index.ts
@@ -1,3 +1,3 @@
export * from './dashboard-tab-component.constant'
export * from './initial-active-dashboard-tab.constant'
export * from './dashboard-tab-component.constant'
export * from './drawer-component.constants'
@@ -0,0 +1,4 @@
export enum FilterAction {
Apply = 'apply',
Clear = 'clear',
}
1 change: 1 addition & 0 deletions packages/mobile/lib/contexts/dashboard/enums/index.ts
Expand Up @@ -2,3 +2,4 @@ export * from './account-action.enum'
export * from './activity-action.enum'
export * from './dashboard-tab.enum'
export * from './token-action.enum'
export * from './filter-action.enum'
1 change: 1 addition & 0 deletions packages/mobile/lib/contexts/dashboard/stores/index.ts
@@ -1,3 +1,4 @@
export * from './active-dashboard-tab.store'
export * from './selected-activity.store'
export * from './selected-asset.store'
export * from './selected-filter.store'
@@ -0,0 +1,4 @@
import { writable } from 'svelte/store'
import type { Filter } from '@core/utils/types'

export const selectedFilter = writable<Filter>(null)
3 changes: 3 additions & 0 deletions packages/mobile/lib/routers/actions/initialiseRouters.ts
Expand Up @@ -39,6 +39,8 @@ import {
StrongholdSetupRouter,
TokenRouter,
tokenRouter,
FilterRouter,
filterRouter,
} from '../routers'

export function initialiseRouters(): void {
Expand Down Expand Up @@ -94,6 +96,7 @@ function initialiseDashboardSubrouters(): void {
activityRouter.set(new ActivityRouter())
sendRouter.set(new SendRouter())
tokenRouter.set(new TokenRouter())
filterRouter.set(new FilterRouter())
profileRouter.set(new ProfileRouter())
settingsRouter.set(new SettingsRouter())
}
Expand Down
2 changes: 2 additions & 0 deletions packages/mobile/lib/routers/actions/resetRouters.ts
Expand Up @@ -21,6 +21,7 @@ import {
storageProtectionSetupRouter,
strongholdSetupRouter,
tokenRouter,
filterRouter,
} from '../routers'

export function resetRouters(): void {
Expand Down Expand Up @@ -58,6 +59,7 @@ function resetDashboardSubrouters(): void {
get(activityRouter).reset()
get(sendRouter).reset()
get(tokenRouter).reset()
get(filterRouter).reset()
get(profileRouter).reset()
get(settingsRouter).reset()
}
Expand Down
1 change: 1 addition & 0 deletions packages/mobile/lib/routers/enums/dashboard-route.enum.ts
Expand Up @@ -2,6 +2,7 @@ export enum DashboardRoute {
AccountSwitcher = 'accountSwitcher',
Activity = 'activity',
AccountActions = 'accountActions',
Filter = 'filter',
Token = 'token',
Init = 'init',
Profile = 'profile',
Expand Down
@@ -0,0 +1,3 @@
export enum FilterRoute {
Filter = 'filter',
}
1 change: 1 addition & 0 deletions packages/mobile/lib/routers/enums/dashboard/index.ts
@@ -1,6 +1,7 @@
export * from './account-actions-route.enum'
export * from './account-switcher-route.enum'
export * from './activity-route.enum'
export * from './filter-route.enum'
export * from './profile-route.enum'
export * from './send-route.enum'
export * from './token-route.enum'
@@ -0,0 +1,10 @@
import { IRouterEvent } from '@core/router'
import type { Filter } from '@core/utils/types'
import { FilterType } from '..'
import { FilterAction } from '../../contexts/dashboard/enums'

export interface IFilterRouterEvent extends IRouterEvent {
filter?: Filter
filterType?: FilterType
action?: FilterAction
}
1 change: 1 addition & 0 deletions packages/mobile/lib/routers/interfaces/index.ts
Expand Up @@ -5,3 +5,4 @@ export * from './profile-router-event.interface'
export * from './send-router-event.interface'
export * from './settings-router-event.interface'
export * from './token-router-event.interface'
export * from './filter-router-event.interface'
107 changes: 107 additions & 0 deletions packages/mobile/lib/routers/routers/dashboard/filter-router.ts
@@ -0,0 +1,107 @@
import { get, writable, Writable } from 'svelte/store'

import { Subrouter } from '@core/router'
import { deepCopy } from '@core/utils'

import { dashboardRouter } from '../'

import { FilterRoute } from '../../enums'
import { IFilterRouterEvent } from '../../interfaces'
import { resetRouterWithDrawerDelay } from '../../utils'
import { selectedFilter } from '../../../contexts/dashboard/stores'
import { FilterAction } from '../../../contexts/dashboard/enums'
import { Filter } from '@core/utils/types'

import { activityFilter, assetFilter } from '@core/wallet/stores'
import { proposalFilter } from '@contexts/governance/stores'
import { DEFAULT_ACTIVITY_FILTER, DEFAULT_ASSET_FILTER } from '@core/wallet/constants'
import { DEFAULT_PROPOSAL_FILTER } from '@contexts/governance/constants'

export const filterRoute = writable<FilterRoute>(null)
export const filterRouter = writable<FilterRouter>(null)

export enum FilterType {
Activity = 'activity',
Asset = 'asset',
Proposal = 'proposal',
}

export class FilterRouter extends Subrouter<FilterRoute> {
constructor() {
super(FilterRoute.Filter, filterRoute, get(dashboardRouter))
}

private filterType: FilterType

public next(event: IFilterRouterEvent = {}): void {
const { action, filter, filterType } = event

if (get(filterRoute) === FilterRoute.Filter) {
if (!action && filter && filterType) {
this.filterType = filterType
selectedFilter.set(filter)
}

if (action && filter) {
this.handleFilterAction(action, filter)
}
}
}

public closeDrawer(): void {
selectedFilter.set(null)
get(dashboardRouter).previous()
resetRouterWithDrawerDelay(get(filterRouter))
}

private handleFilterAction(action: FilterAction, filter: Filter): void {
if (!filter) {
return
}

switch (action) {
case FilterAction.Apply:
this.closeDrawer()
this.applyFilter(filter)
return
case FilterAction.Clear:
this.clearStoreByFilterType()
return
}
}

private applyFilter(filter: Filter): void {
this.getFilterStore().set(deepCopy(filter))
}

private clearStoreByFilterType(filterType: FilterType = this.filterType): void {
switch (filterType) {
case FilterType.Activity:
activityFilter.set(DEFAULT_ACTIVITY_FILTER)
break
case FilterType.Asset:
assetFilter.set(DEFAULT_ASSET_FILTER)
break
case FilterType.Proposal:
proposalFilter.set(DEFAULT_PROPOSAL_FILTER)
break
}
}

public getFilterStore(filterType: FilterType = this.filterType): Writable<Filter> {
switch (filterType) {
case FilterType.Activity:
return activityFilter
case FilterType.Asset:
return assetFilter
case FilterType.Proposal:
return proposalFilter
default:
return writable(null)
}
}

public clearFilterStores(): void {
Object.values(FilterType).forEach((filterType) => this.clearStoreByFilterType(filterType))
}
}
1 change: 1 addition & 0 deletions packages/mobile/lib/routers/routers/dashboard/index.ts
Expand Up @@ -4,3 +4,4 @@ export * from './activity-router'
export * from './profile-router'
export * from './send-router'
export * from './token-router'
export * from './filter-router'
Expand Up @@ -19,6 +19,7 @@ import {
storageProtectionSetupRouter,
strongholdSetupRouter,
tokenRouter,
filterRouter,
} from '../routers'

export function getSubroutersForAppContext(context: AppContext): IRouter[] {
Expand All @@ -30,6 +31,7 @@ export function getSubroutersForAppContext(context: AppContext): IRouter[] {
get(sendRouter),
get(activityRouter),
get(tokenRouter),
get(filterRouter),
get(profileRouter),
get(settingsRouter),
get(networkConfigurationSettingsRouter),
Expand Down