Skip to content

Commit

Permalink
feat(native-app): add app widgets to home screen (#15902)
Browse files Browse the repository at this point in the history
* feat: first version of vehicle module for home screen

* feat: add base for air discount module on home screen

* feat: first version of license widget

* feat: fix routing to vehicles and air discount

* feat: allow override styling for walletItems

* fix: correct size for all modules based on screensize

* feat: first version of home-options

* fix: better coordination for sizes of licenses

* feat: UI for home options screen ready

* feat: add empty states for home screen widgets

* feat: add loading states for cards

* chore: remove unused illustration

* feat: temp adding another illustration for card until better solution

* fix: finalize empty states for home screen widgets

* feat: show mileage vehicles first in vehicles module

* feat: disable pressable heading if no data, move inbox query into widget

* chore: rename singular to plural to match applications name

* feat: add minHeight for vehicle cards in vehicle module

* fix: add seconds to appLock label in settings

* feat: move all data fetching and validation to home screen

* fix: should be plural for licenses and vehicles everywhere

* feat: skip fetching data on home screen if widgets are disabled

* fix: remove duplicate translations

* fix: update feature flags to be plural in all places

* fix: don't set initializedWidget until queries have loaded

* fix: inbox check

* fix: spelling of initialised

* feat: resetting homescreen preferences on logout

* fix: final touches

* fix: rename preferences flags to be shorter

* fix: import preferences separately

* fix: renaming children to items

* feat: remove reset call even later

* fix: see all in widgets should be variant heading5

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
thoreyjona and kodiakhq[bot] authored Sep 9, 2024
1 parent bab1072 commit d1fbbda
Show file tree
Hide file tree
Showing 53 changed files with 1,298 additions and 247 deletions.
Binary file added apps/native/app/src/assets/icons/options.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 apps/native/app/src/assets/icons/[email protected]
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 apps/native/app/src/assets/icons/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/native/app/src/assets/illustrations/le-retirement-s3.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 modified apps/native/app/src/assets/illustrations/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/native/app/src/assets/illustrations/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed apps/native/app/src/assets/illustrations/moving.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 13 additions & 1 deletion apps/native/app/src/messages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const en: TranslatedMessages = {
'settings.security.appLockTimeoutLabel': 'App lock timeout',
'settings.security.appLockTimeoutDescription':
'Time until app lock will appear',
'settings.security.appLockTimeoutSeconds': 'sec.',
'settings.about.groupTitle': 'About',
'settings.about.versionLabel': 'Version',
'settings.about.logoutLabel': 'Logout',
Expand Down Expand Up @@ -168,7 +169,6 @@ export const en: TranslatedMessages = {
'home.screenTitle': 'Overview',
'home.applicationsStatus': 'Applications',
'home.allApplications': 'Digital applications',
'home.inbox': 'Latest in inbox',
'home.welcomeText': 'Hi',
'home.goodDay': 'Good day,',
'home.onboardingModule.card1':
Expand All @@ -184,6 +184,18 @@ export const en: TranslatedMessages = {
'home.vehicleModule.button': 'My vehicles',
'button.seeAll': 'See all',

// home options
'homeOptions.screenTitle': 'Home screen',
'homeOptions.heading.title': 'Configure home screen',
'homeOptions.heading.subtitle':
'Here you can configure what is displayed on the home screen.',
'homeOptions.graphic': 'Display graphic',
'homeOptions.inbox': 'Latest in inbox',
'homeOptions.licenses': 'Licenses',
'homeOptions.applications': 'Applications',
'homeOptions.vehicles': 'Vehicles',
'homeOptions.airDiscount': 'Air discount scheme',

// inbox
'inbox.screenTitle': 'Inbox',
'inbox.bottomTabText': 'Inbox',
Expand Down
14 changes: 13 additions & 1 deletion apps/native/app/src/messages/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const is = {
'settings.security.appLockTimeoutLabel': 'Biðtími skjálæsingar',
'settings.security.appLockTimeoutDescription':
'Tíminn þar til skjálæsing fer í gang',
'settings.security.appLockTimeoutSeconds': 'sek.',
'settings.about.groupTitle': 'Um appið',
'settings.about.versionLabel': 'Útgáfa',
'settings.about.logoutLabel': 'Útskrá',
Expand Down Expand Up @@ -169,7 +170,6 @@ export const is = {
'home.applicationsStatus': 'Staða umsókna',
'home.allApplications': 'Stafrænar umsóknir',
'home.welcomeText': 'Hæ',
'home.inbox': 'Nýjast í pósthólfinu',
'home.goodDay': 'Góðan dag,',
'home.onboardingModule.card1':
'Nú sérð þú upplýsingar um ökutæki, fasteignir og fjölskyldu þína í appinu til viðbótar við skjöl og skírteini.',
Expand All @@ -184,6 +184,18 @@ export const is = {
'home.vehicleModule.button': 'Mín ökutæki',
'button.seeAll': 'Sjá allt',

// home options
'homeOptions.screenTitle': 'Heimaskjár',
'homeOptions.heading.title': 'Stilla heimaskjá',
'homeOptions.heading.subtitle':
'Hér er hægt að stilla hvað birtist á heimaskjá.',
'homeOptions.graphic': 'Birta myndskreytingu',
'homeOptions.inbox': 'Nýjast í pósthólfinu',
'homeOptions.licenses': 'Skírteini',
'homeOptions.applications': 'Umsóknir',
'homeOptions.vehicles': 'Ökutæki',
'homeOptions.airDiscount': 'Loftbrú',

// inbox
'inbox.screenTitle': 'Pósthólf',
'inbox.bottomTabText': 'Pósthólf',
Expand Down
6 changes: 1 addition & 5 deletions apps/native/app/src/screens/applications/applications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,7 @@ export const ApplicationsScreen: NavigationFunctionComponent = ({
ListHeaderComponent={
<View style={{ flex: 1 }}>
<ApplicationsModule
applications={
(applicationsRes.data?.applicationApplications ??
[]) as Application[]
}
loading={applicationsRes.loading}
{...applicationsRes}
componentId={componentId}
hideAction={true}
hideSeeAllButton={true}
Expand Down
167 changes: 167 additions & 0 deletions apps/native/app/src/screens/home/air-discount-module.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {
Typography,
Heading,
ChevronRight,
ViewPager,
EmptyCard,
GeneralCardSkeleton,
} from '@ui'

import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { Image, SafeAreaView, TouchableOpacity } from 'react-native'
import styled, { useTheme } from 'styled-components/native'
import { ApolloError } from '@apollo/client'

import illustrationSrc from '../../assets/illustrations/le-jobs-s2.png'
import { navigateTo } from '../../lib/deep-linking'
import {
GetAirDiscountQuery,
useGetAirDiscountQuery,
} from '../../graphql/types/schema'
import { AirDiscountCard } from '@ui/lib/card/air-discount-card'
import { screenWidth } from '../../utils/dimensions'

const Host = styled.View`
margin-bottom: ${({ theme }) => theme.spacing[2]}px;
`

interface AirDiscountModuleProps {
data: GetAirDiscountQuery | undefined
loading: boolean
error?: ApolloError | undefined
}

const validateAirDiscountInitialData = ({
data,
loading,
}: {
data: GetAirDiscountQuery | undefined
loading: boolean
}) => {
if (loading) {
return true
}

const noRights =
data?.airDiscountSchemeDiscounts?.filter(
(item) => item.user.fund?.credit === 0 && item.user.fund.used === 0,
).length === data?.airDiscountSchemeDiscounts?.length

// Only show widget initially if the user has air discount rights
if (!noRights) {
return true
}

return false
}

const AirDiscountModule = React.memo(
({ data, loading, error }: AirDiscountModuleProps) => {
const theme = useTheme()
const intl = useIntl()

if (error && !data) {
return null
}

const noRights =
data?.airDiscountSchemeDiscounts?.filter(
(item) => item.user.fund?.credit === 0 && item.user.fund.used === 0,
).length === data?.airDiscountSchemeDiscounts?.length

const discounts = data?.airDiscountSchemeDiscounts?.filter(
({ user }) => !(user.fund?.used === 0 && user.fund.credit === 0),
)

const count = discounts?.length ?? 0

const items = discounts?.slice(0, 3).map(({ discountCode, user }) => (
<AirDiscountCard
key={`loftbru-item-${discountCode}`}
name={user.name}
code={discountCode}
credit={user.fund?.credit}
text={intl.formatMessage(
{ id: 'airDiscount.remainingFares' },
{
remaining: user.fund?.credit,
total: user.fund?.total,
},
)}
style={
count > 1
? {
width: screenWidth - theme.spacing[2] * 4,
marginLeft: theme.spacing[2],
}
: {
width: '100%',
}
}
/>
))

return (
<SafeAreaView
style={{
marginHorizontal: theme.spacing[2],
marginBottom: theme.spacing[2],
}}
>
<Host>
<TouchableOpacity
disabled={count === 0}
onPress={() => navigateTo(`/air-discount`)}
>
<Heading
button={
count === 0 ? null : (
<TouchableOpacity
onPress={() => navigateTo('/air-discount')}
style={{
flexDirection: 'row',
alignItems: 'center',
}}
>
<Typography variant="heading5" color={theme.color.blue400}>
<FormattedMessage id="button.seeAll" />
</Typography>
<ChevronRight />
</TouchableOpacity>
)
}
>
<FormattedMessage id="homeOptions.airDiscount" />
</Heading>
</TouchableOpacity>
{loading && !data ? (
<GeneralCardSkeleton height={146} />
) : (
<>
{noRights && (
<EmptyCard
text={intl.formatMessage({
id: 'airDiscount.emptyListDescription',
})}
image={
<Image source={illustrationSrc} resizeMode="contain" />
}
link={null}
/>
)}
{count === 1 && items}
{count >= 2 && <ViewPager>{items}</ViewPager>}
</>
)}
</Host>
</SafeAreaView>
)
},
)

export {
AirDiscountModule,
useGetAirDiscountQuery,
validateAirDiscountInitialData,
}
64 changes: 51 additions & 13 deletions apps/native/app/src/screens/home/applications-module.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,65 @@ import {
import React from 'react'
import { useIntl } from 'react-intl'
import { Image, SafeAreaView, TouchableOpacity } from 'react-native'
import { useTheme } from 'styled-components'
import { ApolloError } from '@apollo/client'

import leJobss3 from '../../assets/illustrations/le-jobs-s3.png'
import { Application } from '../../graphql/types/schema'
import {
ListApplicationsQuery,
useListApplicationsQuery,
} from '../../graphql/types/schema'
import { navigateTo } from '../../lib/deep-linking'
import { useBrowser } from '../../lib/use-browser'
import { getApplicationUrl } from '../../utils/applications-utils'
import { useTheme } from 'styled-components'
import { screenWidth } from '../../utils/dimensions'

interface ApplicationsModuleProps {
applications: Application[]
data: ListApplicationsQuery | undefined
loading: boolean
error?: ApolloError | undefined
componentId: string
hideAction?: boolean
hideSeeAllButton?: boolean
}

export const ApplicationsModule = React.memo(
const validateApplicationsInitialData = ({
data,
loading,
}: {
data: ListApplicationsQuery | undefined
loading: boolean
}) => {
if (loading) {
return true
}
// Only show widget initially if there are applications
if (data?.applicationApplications?.length !== 0) {
return true
}
return false
}

const ApplicationsModule = React.memo(
({
applications,
data,
loading,
error,
componentId,
hideAction,
hideSeeAllButton = false,
}: ApplicationsModuleProps) => {
const intl = useIntl()
const theme = useTheme()
const applications = data?.applicationApplications ?? []
const count = applications.length
const { openBrowser } = useBrowser()

const children = applications.slice(0, 5).map((application) => (
if (error && !data) {
return null
}

const items = applications.slice(0, 3).map((application) => (
<StatusCard
key={application.id}
title={application.name ?? ''}
Expand All @@ -68,7 +98,7 @@ export const ApplicationsModule = React.memo(
style={
count > 1
? {
width: 283,
width: screenWidth - theme.spacing[2] * 4,
marginLeft: 16,
}
: {}
Expand All @@ -80,10 +110,12 @@ export const ApplicationsModule = React.memo(
<SafeAreaView
style={{
marginHorizontal: theme.spacing[2],
marginBottom: theme.spacing[2],
}}
>
<TouchableOpacity onPress={() => navigateTo(`/applications`)}>
<TouchableOpacity
disabled={count === 0}
onPress={() => navigateTo(`/applications`)}
>
<Heading
button={
count === 0 || hideSeeAllButton ? null : (
Expand All @@ -94,7 +126,7 @@ export const ApplicationsModule = React.memo(
alignItems: 'center',
}}
>
<Typography weight="400" color={blue400}>
<Typography variant="heading5" color={blue400}>
{intl.formatMessage({ id: 'button.seeAll' })}
</Typography>
<ChevronRight />
Expand All @@ -105,7 +137,7 @@ export const ApplicationsModule = React.memo(
{intl.formatMessage({ id: 'home.applicationsStatus' })}
</Heading>
</TouchableOpacity>
{loading ? (
{loading && !data ? (
<StatusCardSkeleton />
) : (
<>
Expand Down Expand Up @@ -136,11 +168,17 @@ export const ApplicationsModule = React.memo(
}
/>
)}
{count === 1 && children.slice(0, 1)}
{count >= 2 && <ViewPager>{children}</ViewPager>}
{count === 1 && items}
{count >= 2 && <ViewPager>{items}</ViewPager>}
</>
)}
</SafeAreaView>
)
},
)

export {
ApplicationsModule,
useListApplicationsQuery,
validateApplicationsInitialData,
}
Loading

0 comments on commit d1fbbda

Please sign in to comment.