diff --git a/src/__tests__/lib/exampleData.js b/src/__tests__/lib/exampleData.js index 50adce83c02..726f20bcbd3 100644 --- a/src/__tests__/lib/exampleData.js +++ b/src/__tests__/lib/exampleData.js @@ -164,11 +164,14 @@ export const zulipVersion = new ZulipVersion('2.1.0-234-g7c3acf4'); export const zulipFeatureLevel = 1; +export const realmIcon = new URL('/icon.png', realm); + export const makeAccount = ( args: {| user?: User, email?: string, realm?: URL, + realmIcon?: URL, apiKey?: string, zulipFeatureLevel?: number | null, zulipVersion?: ZulipVersion | null, @@ -179,6 +182,7 @@ export const makeAccount = ( user = makeUser({ name: randString() }), email = user.email, realm: realmInner = realm, + realmIcon: realmIconInner = realmIcon, apiKey = randString() + randString(), zulipFeatureLevel: zulipFeatureLevelInner = zulipFeatureLevel, zulipVersion: zulipVersionInner = zulipVersion, @@ -187,6 +191,7 @@ export const makeAccount = ( return deepFreeze({ realm: realmInner, email, + realmIcon: realmIconInner, apiKey, zulipFeatureLevel: zulipFeatureLevelInner, zulipVersion: zulipVersionInner, @@ -506,7 +511,7 @@ export const reduxState = (extra?: $Rest): GlobalState => * See `baseReduxState` for a minimal version of the state. */ export const plusReduxState: GlobalState = reduxState({ - accounts: [{ ...selfAuth, ackedPushToken: null, zulipVersion, zulipFeatureLevel }], + accounts: [{ ...selfAuth, realmIcon, ackedPushToken: null, zulipVersion, zulipFeatureLevel }], realm: { ...baseReduxState.realm, user_id: selfUser.user_id, email: selfUser.email }, // TODO add crossRealmBot users: [selfUser, otherUser, thirdUser], @@ -546,6 +551,7 @@ export const action = deepFreeze({ type: LOGIN_SUCCESS, realm: selfAccount.realm, email: selfAccount.email, + realmIcon: selfAccount.realmIcon, apiKey: selfAccount.apiKey, }, realm_init: { diff --git a/src/account/AccountItem.js b/src/account/AccountItem.js index bdcabb58d66..114670fb527 100644 --- a/src/account/AccountItem.js +++ b/src/account/AccountItem.js @@ -1,6 +1,6 @@ /* @flow strict-local */ import React from 'react'; -import { View } from 'react-native'; +import { View, Image } from 'react-native'; import { BRAND_COLOR, createStyleSheet } from '../styles'; import { RawLabel, Touchable, Label } from '../common'; @@ -31,13 +31,19 @@ const styles = createStyleSheet({ }, icon: { padding: 12, - margin: 12, + margin: 10, }, signedOutText: { fontStyle: 'italic', color: 'gray', marginVertical: 2, }, + realmIcon: { + height: 52, + width: 52, + borderRadius: 4, + marginLeft: 10, + }, }); type Props = $ReadOnly<{| @@ -48,7 +54,7 @@ type Props = $ReadOnly<{| |}>; export default function AccountItem(props: Props) { - const { email, realm, isLoggedIn } = props.account; + const { email, realm, isLoggedIn, realmIcon } = props.account; const showDoneIcon = props.index === 0 && isLoggedIn; const backgroundItemColor = isLoggedIn ? 'hsla(177, 70%, 47%, 0.1)' : 'hsla(0,0%,50%,0.1)'; @@ -63,6 +69,11 @@ export default function AccountItem(props: Props) { { backgroundColor: backgroundItemColor }, ]} > + { const newApiKey = eg.randString(); const newEmail = 'newaccount@example.com'; const newRealm = new URL('https://new.realm.org'); - + const newRealmIcon = new URL('/icon.png', newRealm); const action = deepFreeze({ type: LOGIN_SUCCESS, apiKey: newApiKey, email: newEmail, realm: newRealm, + realmIcon: newRealmIcon, }); const expectedState = [ @@ -100,6 +101,7 @@ describe('accountsReducer', () => { realm: newRealm, email: newEmail, apiKey: newApiKey, + realmIcon: newRealmIcon, zulipVersion: null, zulipFeatureLevel: null, }), @@ -120,6 +122,7 @@ describe('accountsReducer', () => { apiKey: newApiKey, realm: account2.realm, email: account2.email, + realmIcon: account2.realmIcon, }); const expectedState = [{ ...account2, apiKey: newApiKey, ackedPushToken: null }, account1]; diff --git a/src/account/accountActions.js b/src/account/accountActions.js index 3e23a17aa77..97c5d4cd729 100644 --- a/src/account/accountActions.js +++ b/src/account/accountActions.js @@ -19,19 +19,20 @@ export const removeAccount = (index: number): Action => ({ index, }); -const loginSuccessPlain = (realm: URL, email: string, apiKey: string): Action => ({ +const loginSuccessPlain = (realm: URL, email: string, realmIcon: URL, apiKey: string): Action => ({ type: LOGIN_SUCCESS, realm, email, apiKey, + realmIcon, }); -export const loginSuccess = (realm: URL, email: string, apiKey: string) => ( +export const loginSuccess = (realm: URL, email: string, realmIcon: URL, apiKey: string) => ( dispatch: Dispatch, getState: GetState, ) => { NavigationService.dispatch(resetToMainTabs()); - dispatch(loginSuccessPlain(realm, email, apiKey)); + dispatch(loginSuccessPlain(realm, email, realmIcon, apiKey)); }; const logoutPlain = (): Action => ({ diff --git a/src/account/accountsReducer.js b/src/account/accountsReducer.js index 96f69a236ac..7432e917a4d 100644 --- a/src/account/accountsReducer.js +++ b/src/account/accountsReducer.js @@ -39,11 +39,19 @@ const findAccount = (state: AccountsState, identity: Identity): number => { }; const loginSuccess = (state, action) => { - const { realm, email, apiKey } = action; + const { realm, email, apiKey, realmIcon } = action; const accountIndex = findAccount(state, { realm, email }); if (accountIndex === -1) { return [ - { realm, email, apiKey, ackedPushToken: null, zulipVersion: null, zulipFeatureLevel: null }, + { + realm, + email, + apiKey, + realmIcon, + ackedPushToken: null, + zulipVersion: null, + zulipFeatureLevel: null, + }, ...state, ]; } diff --git a/src/account/accountsSelectors.js b/src/account/accountsSelectors.js index 09e8d79460e..3827ed9ee62 100644 --- a/src/account/accountsSelectors.js +++ b/src/account/accountsSelectors.js @@ -6,7 +6,7 @@ import { identityOfAccount, keyOfIdentity, identityOfAuth, authOfAccount } from import { ZulipVersion } from '../utils/zulipVersion'; /** See `getAccountStatuses`. */ -export type AccountStatus = {| ...Identity, isLoggedIn: boolean |}; +export type AccountStatus = {| ...Identity, realmIcon: URL, isLoggedIn: boolean |}; /** * The list of known accounts, with a boolean for logged-in vs. not. @@ -17,7 +17,12 @@ export type AccountStatus = {| ...Identity, isLoggedIn: boolean |}; export const getAccountStatuses: Selector<$ReadOnlyArray> = createSelector( getAccounts, accounts => - accounts.map(({ realm, email, apiKey }) => ({ realm, email, isLoggedIn: apiKey !== '' })), + accounts.map(({ realm, email, realmIcon, apiKey }) => ({ + realm, + email, + realmIcon, + isLoggedIn: apiKey !== '', + })), ); /** The list of known accounts, reduced to `Identity`. */ diff --git a/src/actionTypes.js b/src/actionTypes.js index 753c81b3e17..7b2fc4a62fc 100644 --- a/src/actionTypes.js +++ b/src/actionTypes.js @@ -140,6 +140,7 @@ type LoginSuccessAction = {| realm: URL, email: string, apiKey: string, + realmIcon: URL, |}; type LogoutAction = {| diff --git a/src/nav/navActions.js b/src/nav/navActions.js index 27b8bd784f9..502c188a23f 100644 --- a/src/nav/navActions.js +++ b/src/nav/navActions.js @@ -40,15 +40,20 @@ export const navigateToAuth = ( serverSettings: ApiResponseServerSettings, ): GenericNavigationAction => StackActions.push('auth', { serverSettings }); -export const navigateToDevAuth = (args: {| realm: URL |}): GenericNavigationAction => - StackActions.push('dev-auth', { realm: args.realm }); +export const navigateToDevAuth = (args: {| + realm: URL, + realmIcon: URL, +|}): GenericNavigationAction => + StackActions.push('dev-auth', { realm: args.realm, realmIcon: args.realmIcon }); export const navigateToPasswordAuth = (args: {| realm: URL, + realmIcon: URL, requireEmailFormat: boolean, |}): GenericNavigationAction => StackActions.push('password-auth', { realm: args.realm, + realmIcon: args.realmIcon, requireEmailFormat: args.requireEmailFormat, }); diff --git a/src/start/AuthScreen.js b/src/start/AuthScreen.js index fc95c9aac59..b58c51a776f 100644 --- a/src/start/AuthScreen.js +++ b/src/start/AuthScreen.js @@ -227,15 +227,19 @@ class AuthScreen extends PureComponent { endWebAuth = (event: LinkingEvent) => { webAuth.closeBrowser(); - const { dispatch, realm } = this.props; + const { dispatch, realm, route } = this.props; + const { realm_icon, realm_uri } = route.params.serverSettings; const auth = webAuth.authFromCallbackUrl(event.url, otp, realm); if (auth) { - dispatch(loginSuccess(auth.realm, auth.email, auth.apiKey)); + dispatch(loginSuccess(auth.realm, auth.email, new URL(realm_icon, realm_uri), auth.apiKey)); } }; handleDevAuth = () => { - NavigationService.dispatch(navigateToDevAuth({ realm: this.props.realm })); + const { realm_icon, realm_uri } = this.props.route.params.serverSettings; + NavigationService.dispatch( + navigateToDevAuth({ realm: this.props.realm, realmIcon: new URL(realm_icon, realm_uri) }), + ); }; handlePassword = () => { @@ -244,6 +248,7 @@ class AuthScreen extends PureComponent { NavigationService.dispatch( navigateToPasswordAuth({ realm, + realmIcon: new URL(serverSettings.realm_icon, serverSettings.realm_uri), requireEmailFormat: serverSettings.require_email_format_usernames, }), ); diff --git a/src/start/DevAuthScreen.js b/src/start/DevAuthScreen.js index e718c54b3de..9f0fa96db1d 100644 --- a/src/start/DevAuthScreen.js +++ b/src/start/DevAuthScreen.js @@ -29,7 +29,7 @@ const componentStyles = createStyleSheet({ type Props = $ReadOnly<{| navigation: AppNavigationProp<'dev-auth'>, - route: RouteProp<'dev-auth', {| realm: URL |}>, + route: RouteProp<'dev-auth', {| realm: URL, realmIcon: URL |}>, dispatch: Dispatch, |}>; @@ -70,13 +70,13 @@ class DevAuthScreen extends PureComponent { }; tryDevLogin = async (email: string) => { - const realm = this.props.route.params.realm; + const { realm, realmIcon } = this.props.route.params; this.setState({ progress: true, error: undefined }); try { const { api_key } = await api.devFetchApiKey({ realm, apiKey: '', email }, email); - this.props.dispatch(loginSuccess(realm, email, api_key)); + this.props.dispatch(loginSuccess(realm, email, realmIcon, api_key)); this.setState({ progress: false }); } catch (err) { this.setState({ progress: false, error: err.data && err.data.msg }); diff --git a/src/start/PasswordAuthScreen.js b/src/start/PasswordAuthScreen.js index ecd4d2f2b2c..d56bd71df6e 100644 --- a/src/start/PasswordAuthScreen.js +++ b/src/start/PasswordAuthScreen.js @@ -28,7 +28,7 @@ const styles = createStyleSheet({ type Props = $ReadOnly<{| navigation: AppNavigationProp<'password-auth'>, - route: RouteProp<'password-auth', {| realm: URL, requireEmailFormat: boolean |}>, + route: RouteProp<'password-auth', {| realm: URL, realmIcon: URL, requireEmailFormat: boolean |}>, dispatch: Dispatch, |}>; @@ -50,7 +50,7 @@ class PasswordAuthScreen extends PureComponent { tryPasswordLogin = async () => { const { dispatch, route } = this.props; - const { requireEmailFormat, realm } = route.params; + const { requireEmailFormat, realm, realmIcon } = route.params; const { email, password } = this.state; this.setState({ progress: true, error: undefined }); @@ -58,7 +58,7 @@ class PasswordAuthScreen extends PureComponent { try { const fetchedKey = await api.fetchApiKey({ realm, apiKey: '', email }, email, password); this.setState({ progress: false }); - dispatch(loginSuccess(realm, fetchedKey.email, fetchedKey.api_key)); + dispatch(loginSuccess(realm, fetchedKey.email, realmIcon, fetchedKey.api_key)); } catch (err) { this.setState({ progress: false, diff --git a/src/types.js b/src/types.js index 86f2898d766..3c84cabd125 100644 --- a/src/types.js +++ b/src/types.js @@ -52,6 +52,8 @@ export type InputSelection = {| export type Account = {| ...Auth, + realmIcon: URL, + /** * The version of the Zulip server. *