-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1945 from oasisprotocol/ml/app-update-wall
Add Android update screen
- Loading branch information
Showing
19 changed files
with
376 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add Android update screen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
16 changes: 16 additions & 0 deletions
16
src/app/components/Ionic/components/IonicNativePlatformProvider/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { FC, PropsWithChildren } from 'react' | ||
import { Capacitor } from '@capacitor/core' | ||
import { IonicContextProvider } from '../../providers/IonicProvider' | ||
import { UpdateGate } from '../UpdateGate' | ||
|
||
export const IonicNativePlatformProvider: FC<PropsWithChildren> = ({ children }) => { | ||
if (Capacitor.isNativePlatform()) { | ||
return ( | ||
<IonicContextProvider> | ||
<UpdateGate>{children}</UpdateGate> | ||
</IonicContextProvider> | ||
) | ||
} | ||
|
||
return children | ||
} |
176 changes: 176 additions & 0 deletions
176
src/app/components/Ionic/components/UpdateGate/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import React, { FC, PropsWithChildren, useContext } from 'react' | ||
import { IonicContext, UpdateAvailability } from '../../providers/IonicContext' | ||
import { Box } from 'grommet/es6/components/Box' | ||
import { Button } from 'grommet/es6/components/Button' | ||
import { navigateToAppStore } from '../../utils/capacitor-app-update' | ||
import { Paragraph } from 'grommet/es6/components/Paragraph' | ||
import walletWhiteLogotype from '../../../../../../public/Rose Wallet White.svg' | ||
import { Text } from 'grommet/es6/components/Text' | ||
import { ShareRounded } from 'grommet-icons/es6/icons/ShareRounded' | ||
import { Refresh } from 'grommet-icons/es6/icons/Refresh' | ||
import styled, { keyframes } from 'styled-components' | ||
import { normalizeColor } from 'grommet/es6/utils' | ||
import { MuiWalletIcon } from '../../../../../styles/theme/icons/mui-icons/MuiWalletIcon' | ||
import { Spinner } from 'grommet/es6/components/Spinner' | ||
import { ResponsiveContext } from 'grommet/es6/contexts/ResponsiveContext' | ||
import { useTranslation } from 'react-i18next' | ||
import { TFunction } from 'i18next' | ||
|
||
const SpinKeyFrames = keyframes` | ||
0% { | ||
transform: rotate(0deg) | ||
} | ||
100% { | ||
transform: rotate(359deg) | ||
} | ||
` | ||
|
||
// TODO: Merge with Spinner.icon when grommet dependency is updated | ||
const RefreshSpin = styled(Refresh)` | ||
transform: rotate(0deg); | ||
animation: ${SpinKeyFrames} 1s 0s infinite linear; | ||
` | ||
|
||
const CTAButton = styled(Button)` | ||
background-color: ${({ theme }) => normalizeColor('brand-light-blue', theme)}; | ||
border-width: 0; | ||
border-radius: 8px; | ||
` | ||
|
||
const getUpdateStatusMap: (t: TFunction) => { | ||
[key in UpdateAvailability]?: { title: string; desc: string } | ||
} = t => ({ | ||
[UpdateAvailability.UPDATE_AVAILABLE]: { | ||
title: t('mobileUpdate.updateAvailableTitle', 'Update pending...'), | ||
desc: t( | ||
'mobileUpdate.updateAvailableDescription', | ||
'A new update is available for your ROSE Wallet. We recommend updating to the latest version for bug fixes, enhanced security and new features.', | ||
), | ||
}, | ||
[UpdateAvailability.UPDATE_IN_PROGRESS]: { | ||
title: t('mobileUpdate.updateInProgressTitle', 'Update in progress...'), | ||
desc: t( | ||
'mobileUpdate.updateInProgressDescription', | ||
'Your ROSE Wallet is currently undergoing an update. Please check back at later time. Alternatively, you may choose to retry by clicking on the "{{retryButtonLabel}}" button.', | ||
{ retryButtonLabel: t('mobileUpdate.retry', 'Retry') }, | ||
), | ||
}, | ||
[UpdateAvailability.UNKNOWN]: { | ||
title: t('mobileUpdate.unknownTitle', 'Unknown error'), | ||
desc: t( | ||
'mobileUpdate.unknownOrErrorDescription', | ||
'Apologies for the inconvenience, an unexpected error has transpired. Please verify your internet connection and retry by clicking on the "{{retryButtonLabel}}" button.', | ||
{ retryButtonLabel: t('mobileUpdate.retry', 'Retry') }, | ||
), | ||
}, | ||
[UpdateAvailability.ERROR]: { | ||
title: t('mobileUpdate.errorTitle', 'Unexpected error'), | ||
desc: t( | ||
'mobileUpdate.unknownOrErrorDescription', | ||
'Apologies for the inconvenience, an unexpected error has transpired. Please verify your internet connection and retry by clicking on the "{{retryButtonLabel}}" button.', | ||
{ retryButtonLabel: t('mobileUpdate.retry', 'Retry') }, | ||
), | ||
}, | ||
}) | ||
|
||
export const UpdateGate: FC<PropsWithChildren> = ({ children }) => { | ||
const { t } = useTranslation() | ||
const isMobile = useContext(ResponsiveContext) === 'small' | ||
const { | ||
state: { updateAvailability }, | ||
checkForUpdateAvailability, | ||
skipUpdate, | ||
} = useContext(IonicContext) | ||
|
||
if (updateAvailability === UpdateAvailability.UPDATE_NOT_AVAILABLE) return children | ||
|
||
const handleNavigateToAppStore = () => { | ||
navigateToAppStore() | ||
} | ||
|
||
const updateStatusMap = getUpdateStatusMap(t) | ||
|
||
return ( | ||
<Box direction="column" background="brand-blue" fill pad="large" style={{ minHeight: '100dvh' }}> | ||
<Box alignSelf={isMobile ? 'start' : 'center'}> | ||
<img alt="ROSE Wallet" src={walletWhiteLogotype} style={{ height: '35px' }} /> | ||
</Box> | ||
{[UpdateAvailability.NOT_INITIALIZED, UpdateAvailability.LOADING].includes(updateAvailability) && ( | ||
<Box align="center" justify="center" flex="grow"> | ||
<Spinner /> | ||
</Box> | ||
)} | ||
{[ | ||
UpdateAvailability.UPDATE_AVAILABLE, | ||
UpdateAvailability.UPDATE_IN_PROGRESS, | ||
UpdateAvailability.UNKNOWN, | ||
UpdateAvailability.ERROR, | ||
].includes(updateAvailability) && ( | ||
<Box flex="grow"> | ||
<Box align="center" justify="center" flex="grow"> | ||
<Box margin={{ bottom: '70px', top: 'none' }} align="center"> | ||
<RefreshSpin color="white" size="44" /> | ||
<MuiWalletIcon color="white" size="84px" /> | ||
</Box> | ||
<Paragraph | ||
size="medium" | ||
color="brand-light-blue" | ||
alignSelf={isMobile ? 'start' : 'center'} | ||
textAlign={isMobile ? 'start' : 'center'} | ||
margin={{ bottom: 'small', top: 'none' }} | ||
> | ||
<Text weight="bolder">{updateStatusMap[updateAvailability]?.title}</Text> | ||
</Paragraph> | ||
<Paragraph | ||
size="small" | ||
color="brand-light-blue" | ||
alignSelf={isMobile ? 'start' : 'center'} | ||
textAlign={isMobile ? 'start' : 'center'} | ||
margin="none" | ||
> | ||
{updateStatusMap[updateAvailability]?.desc} | ||
</Paragraph> | ||
</Box> | ||
<Box align="center" justify="end" flex="shrink"> | ||
{updateAvailability === UpdateAvailability.UPDATE_AVAILABLE && ( | ||
<CTAButton | ||
type="button" | ||
onClick={handleNavigateToAppStore} | ||
margin="medium" | ||
pad={{ vertical: 'small', horizontal: 'large' }} | ||
label={ | ||
<Text color="brand-blue" weight="bolder" size="medium"> | ||
{t('mobileUpdate.updateNow', 'Update now')} | ||
</Text> | ||
} | ||
icon={<ShareRounded color="brand-blue" size="18px" />} | ||
reverse | ||
/> | ||
)} | ||
{updateAvailability !== UpdateAvailability.UPDATE_AVAILABLE && ( | ||
<CTAButton | ||
type="button" | ||
onClick={checkForUpdateAvailability} | ||
margin="medium" | ||
pad={{ vertical: 'small', horizontal: 'large' }} | ||
label={ | ||
<Text color="brand-blue" weight="bolder" size="medium"> | ||
{t('mobileUpdate.retry', 'Retry')} | ||
</Text> | ||
} | ||
/> | ||
)} | ||
{[UpdateAvailability.UNKNOWN, UpdateAvailability.ERROR].includes(updateAvailability) && ( | ||
<Button type="button" onClick={skipUpdate}> | ||
<Text color="white" weight="bolder" size="small"> | ||
{t('mobileUpdate.later', 'Later')} | ||
</Text> | ||
</Button> | ||
)} | ||
</Box> | ||
</Box> | ||
)} | ||
</Box> | ||
) | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { Dispatch, SetStateAction, useEffect } from 'react' | ||
import { IonicProviderState, UpdateAvailability } from '../providers/IonicContext' | ||
import { updateAvailable } from '../utils/capacitor-app-update' | ||
|
||
export const useIonicRequiresUpdate = ( | ||
state: IonicProviderState, | ||
setState: Dispatch<SetStateAction<IonicProviderState>>, | ||
) => { | ||
const checkForUpdateAvailability = async () => { | ||
if (state.updateAvailability === UpdateAvailability.LOADING) { | ||
return | ||
} | ||
|
||
setState(prevState => ({ ...prevState, updateAvailability: UpdateAvailability.LOADING })) | ||
|
||
try { | ||
const updateAvailability = await updateAvailable() | ||
|
||
setState(prevState => ({ ...prevState, updateAvailability })) | ||
} catch (error) { | ||
setState(prevState => ({ | ||
...prevState, | ||
updateAvailability: UpdateAvailability.ERROR, | ||
error: error as Error, | ||
})) | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
checkForUpdateAvailability() | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []) | ||
|
||
const skipUpdate = () => { | ||
setState(prevState => ({ ...prevState, updateAvailability: UpdateAvailability.UPDATE_NOT_AVAILABLE })) | ||
} | ||
|
||
return { checkForUpdateAvailability, skipUpdate } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { createContext } from 'react' | ||
|
||
export enum UpdateAvailability { | ||
NOT_INITIALIZED, | ||
LOADING, | ||
UPDATE_AVAILABLE, | ||
UPDATE_NOT_AVAILABLE, | ||
UPDATE_IN_PROGRESS, | ||
ERROR, | ||
UNKNOWN, | ||
} | ||
|
||
export interface IonicProviderState { | ||
updateAvailability: UpdateAvailability | ||
error: Error | null | ||
} | ||
|
||
export interface IonicProviderContext { | ||
readonly state: IonicProviderState | ||
checkForUpdateAvailability: () => void | ||
skipUpdate: () => void | ||
} | ||
|
||
export const IonicContext = createContext<IonicProviderContext>({} as IonicProviderContext) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { FC, PropsWithChildren, useState } from 'react' | ||
import { useIonicBackButtonListener } from '../hooks/useIonicBackButtonListener' | ||
import { useIonicAppStateChangeListener } from '../hooks/useIonicAppStateChangeListener' | ||
import { IonicContext, IonicProviderContext, IonicProviderState, UpdateAvailability } from './IonicContext' | ||
import { useIonicRequiresUpdate } from '../hooks/useIonicRequiresUpdate' | ||
|
||
const ionicProviderInitialState: IonicProviderState = { | ||
updateAvailability: UpdateAvailability.NOT_INITIALIZED, | ||
error: null, | ||
} | ||
|
||
export const IonicContextProvider: FC<PropsWithChildren> = ({ children }) => { | ||
const [state, setState] = useState<IonicProviderState>({ ...ionicProviderInitialState }) | ||
|
||
const { checkForUpdateAvailability, skipUpdate } = useIonicRequiresUpdate(state, setState) | ||
useIonicAppStateChangeListener() | ||
useIonicBackButtonListener() | ||
|
||
const providerState: IonicProviderContext = { | ||
state, | ||
checkForUpdateAvailability, | ||
skipUpdate, | ||
} | ||
|
||
return <IonicContext.Provider value={providerState}>{children}</IonicContext.Provider> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { | ||
AppUpdate, | ||
AppUpdateAvailability as IonicAppUpdateAvailability, | ||
} from '@capawesome/capacitor-app-update' | ||
import { Capacitor } from '@capacitor/core' | ||
import { UpdateAvailability } from '../providers/IonicContext' | ||
|
||
// TODO: Skip on local builds | ||
export const updateAvailable = async (): Promise<UpdateAvailability> => { | ||
const result = await AppUpdate.getAppUpdateInfo() | ||
const { updateAvailability, currentVersionCode, availableVersionCode } = result | ||
|
||
switch (updateAvailability) { | ||
case IonicAppUpdateAvailability.UPDATE_IN_PROGRESS: | ||
return UpdateAvailability.UPDATE_IN_PROGRESS | ||
case IonicAppUpdateAvailability.UPDATE_NOT_AVAILABLE: | ||
return UpdateAvailability.UPDATE_NOT_AVAILABLE | ||
// Returns UNKNOWN when unable to determine with mobile app store if update is available or not | ||
case IonicAppUpdateAvailability.UNKNOWN: | ||
return UpdateAvailability.UNKNOWN | ||
} | ||
|
||
// Example of version code -> "1", "2", ... | ||
if ( | ||
Capacitor.getPlatform() === 'android' && | ||
parseInt(availableVersionCode ?? `${Number.MAX_SAFE_INTEGER}`, 10) > | ||
parseInt(currentVersionCode ?? '0', 10) | ||
) { | ||
return UpdateAvailability.UPDATE_AVAILABLE | ||
} | ||
|
||
// TODO: Add for iOS | ||
// Compare semVer between currentVersionName and availableVersionName | ||
throw new Error('Unknown Capacitor platform!') | ||
} | ||
|
||
export const navigateToAppStore = async () => { | ||
await AppUpdate.openAppStore() | ||
} |
Oops, something went wrong.