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

redux: Various small prep changes for multi-org support #5016

Merged
merged 13 commits into from
Sep 21, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/account-info/ProfileScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import AccountDetails from './AccountDetails';
import AwayStatusSwitch from './AwayStatusSwitch';
import { getOwnUser } from '../users/userSelectors';
import { getActiveAccount } from '../account/accountsSelectors';
import { getIdentity } from '../account/accountsSelectors';

const styles = createStyleSheet({
buttonRow: {
Expand Down Expand Up @@ -61,7 +61,7 @@ function SwitchAccountButton(props: {||}) {
function LogoutButton(props: {||}) {
const dispatch = useDispatch();
const _ = useContext(TranslationContext);
const activeAccount = useSelector(getActiveAccount);
const identity = useSelector(getIdentity);
return (
<ZulipButton
style={styles.button}
Expand All @@ -71,8 +71,8 @@ function LogoutButton(props: {||}) {
Alert.alert(
_('Log out?'),
_('This will log out {email} on {realmUrl} from the mobile app on this device.', {
email: activeAccount.email,
realmUrl: activeAccount.realm.toString(),
email: identity.email,
realmUrl: identity.realm.toString(),
}),
[
{ text: _('Cancel'), style: 'cancel' },
Expand Down
10 changes: 0 additions & 10 deletions src/account/accountsSelectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,6 @@ export const getActiveAccount = (state: GlobalState): Account => {
/** The realm of the active account; throws if none. */
export const getCurrentRealm = (state: GlobalState): URL => getActiveAccount(state).realm;

/**
* The realm currently foregrounded in the UI; undefined if none.
*
* This is for use in early startup, onboarding, account-switch,
* authentication flows, or other times where there may be no active account
* or it may not be logged in.
*/
export const tryGetCurrentRealm = (state: GlobalState): URL | void =>
tryGetActiveAccount(state)?.realm;

/**
* The auth object for the active, logged-in account, or undefined if none.
*
Expand Down
4 changes: 2 additions & 2 deletions src/boot/AppEventHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class AppEventHandlersInner extends PureComponent<Props> {
};

notificationListener = new NotificationListener(this.props.dispatch);
shareListener = new ShareReceivedListener(this.props.dispatch);
shareListener = new ShareReceivedListener();

handleMemoryWarning = () => {
// Release memory here
Expand All @@ -129,7 +129,7 @@ class AppEventHandlersInner extends PureComponent<Props> {
componentDidMount() {
const { dispatch } = this.props;
handleInitialNotification(dispatch);
handleInitialShare(dispatch);
handleInitialShare();

NetInfo.configure({
// This is the default, as of 6.0.0, but `OfflineNotice` depends on this
Expand Down
6 changes: 3 additions & 3 deletions src/common/ServerCompatBanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { createStyleSheet, HALF_COLOR } from '../styles';
import { useSelector, useDispatch } from '../react-redux';
import Label from './Label';
import { getActiveAccount } from '../account/accountsSelectors';
import { getIdentity, getServerVersion } from '../account/accountsSelectors';
import { getIsAdmin, getSession, getSettings } from '../directSelectors';
import { dismissCompatNotice } from '../session/sessionActions';
import ZulipTextButton from './ZulipTextButton';
Expand Down Expand Up @@ -53,8 +53,8 @@ export default function ServerCompatBanner(props: Props): Node {
const hasDismissedServerCompatNotice = useSelector(
state => getSession(state).hasDismissedServerCompatNotice,
);
const zulipVersion = useSelector(state => getActiveAccount(state).zulipVersion);
const realm = useSelector(state => getActiveAccount(state).realm);
const zulipVersion = useSelector(getServerVersion);
const realm = useSelector(state => getIdentity(state).realm);
const isAdmin = useSelector(getIsAdmin);
const settings = useSelector(getSettings);

Expand Down
4 changes: 2 additions & 2 deletions src/events/eventActions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* @flow strict-local */
import type { Dispatch, GeneralEvent, GetState, ThunkAction } from '../types';
import type { GeneralEvent, ThunkAction } from '../types';
import * as api from '../api';
import { logout } from '../account/accountActions';
import { deadQueue } from '../session/sessionActions';
Expand All @@ -16,7 +16,7 @@ import * as logging from '../utils/logging';
* This is part of our use of the Zulip events system; see `doInitialFetch`
* for discussion.
*/
const handleEvent = (event: GeneralEvent, dispatch: Dispatch, getState: GetState) => {
const handleEvent = (event: GeneralEvent, dispatch, getState) => {
try {
const action = eventToAction(getState(), event);
if (!action) {
Expand Down
17 changes: 4 additions & 13 deletions src/message/fetchActions.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
/* @flow strict-local */
import * as logging from '../utils/logging';
import * as NavigationService from '../nav/NavigationService';
import type {
Narrow,
Dispatch,
GetState,
GlobalState,
Message,
Action,
ThunkAction,
UserId,
} from '../types';
import type { Narrow, GlobalState, Message, Action, ThunkAction, UserId } from '../types';
import { ensureUnreachable } from '../types';
import type { InitialFetchAbortReason } from '../actionTypes';
import type { ApiResponseServerSettings } from '../api/settings/getServerSettings';
Expand All @@ -26,7 +17,7 @@ import {
getCaughtUpForNarrow,
getFetchingForNarrow,
getIsAdmin,
getActiveAccount,
getIdentity,
} from '../selectors';
import config from '../config';
import {
Expand Down Expand Up @@ -248,7 +239,7 @@ export const initialFetchAbort = (
// `TranslationProvider`.
'Connection failed',
(() => {
const realmStr = getActiveAccount(getState()).realm.toString();
const realmStr = getIdentity(getState()).realm.toString();
switch (reason) {
case 'server':
return getIsAdmin(getState())
Expand Down Expand Up @@ -331,7 +322,7 @@ export const fetchMessagesInNarrow = (
* See `fetchMessagesInNarrow` for further background.
*/
// TODO(server-2.1): Delete this.
const fetchPrivateMessages = () => async (dispatch: Dispatch, getState: GetState) => {
const fetchPrivateMessages = () => async (dispatch, getState) => {
const auth = getAuth(getState());
const { messages, found_newest, found_oldest } = await api.getMessages(auth, {
narrow: apiNarrowOfNarrow(ALL_PRIVATE_NARROW, getAllUsersById(getState())),
Expand Down
4 changes: 1 addition & 3 deletions src/outbox/outboxActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import invariant from 'invariant';

import * as logging from '../utils/logging';
import type {
Dispatch,
GetState,
GlobalState,
Narrow,
Outbox,
Expand Down Expand Up @@ -53,7 +51,7 @@ export const messageSendComplete = (localMessageId: number): Action => ({
local_message_id: localMessageId,
});

export const trySendMessages = (dispatch: Dispatch, getState: GetState): boolean => {
const trySendMessages = (dispatch, getState): boolean => {
const state = getState();
const auth = getAuth(state);
const outboxToSend = state.outbox.filter(outbox => !outbox.isSent);
Expand Down
15 changes: 9 additions & 6 deletions src/presence/PresenceHeartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
import { PureComponent } from 'react';
import type { ComponentType } from 'react';
import { AppState } from 'react-native';
import type { Auth, Dispatch } from '../types';
import type { Dispatch } from '../types';

import { connect } from '../react-redux';
import { tryGetAuth } from '../account/accountsSelectors';
import { getHasAuth } from '../account/accountsSelectors';
import { reportPresence } from '../actions';
import Heartbeat from './heartbeat';

type OuterProps = $ReadOnly<{||}>;

type SelectorProps = $ReadOnly<{|
auth: Auth | void,
hasAuth: boolean,
|}>;

type Props = $ReadOnly<{|
Expand All @@ -36,7 +36,8 @@ type Props = $ReadOnly<{|
class PresenceHeartbeatInner extends PureComponent<Props> {
/** Callback for Heartbeat object. */
onHeartbeat = () => {
if (this.props.auth) {
if (this.props.hasAuth) {
// TODO(#5005): should ensure this gets the intended account
this.props.dispatch(reportPresence(true));
}
};
Expand All @@ -56,7 +57,7 @@ class PresenceHeartbeatInner extends PureComponent<Props> {
// React to any state change.
updateHeartbeatState = () => {
// heartbeat.toState is idempotent
this.heartbeat.toState(AppState.currentState === 'active' && !!this.props.auth);
this.heartbeat.toState(AppState.currentState === 'active' && this.props.hasAuth);
};

// React to props changes.
Expand All @@ -72,8 +73,10 @@ class PresenceHeartbeatInner extends PureComponent<Props> {
}
}

/** (NB this is a per-account component.) */
// TODO(#5005): either make one of these per account, or make it act on all accounts
const PresenceHeartbeat: ComponentType<OuterProps> = connect(state => ({
auth: tryGetAuth(state),
hasAuth: getHasAuth(state),
}))(PresenceHeartbeatInner);

export default PresenceHeartbeat;
5 changes: 2 additions & 3 deletions src/react-redux.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import type { BoundedDiff } from './generics';

/* eslint-disable flowtype/generic-spacing */

export type OwnProps<-C, -SP> = $Diff<
BoundedDiff<$Exact<ElementConfig<C>>, SP>,
{| dispatch: Dispatch |},
export type OwnProps<-C, -SP> = $ReadOnly<
$Diff<$ReadOnly<BoundedDiff<$Exact<ElementConfig<C>>, SP>>, {| dispatch: Dispatch |}>,
>;

/**
Expand Down
6 changes: 1 addition & 5 deletions src/reduxTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,9 @@ type NonMaybeGlobalState = NonMaybeProperties<GlobalState>;
// the corresponding parameter when used in `createSelector`, and this does.
export type Selector<TResult, TParam = void> = InputSelector<GlobalState, TParam, TResult>;

export type GetState = () => GlobalState;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, really? Nice!


export type PlainDispatch = <A: Action>(action: A) => A;

export interface Dispatch {
<A: Action>(action: A): A;
<T>(ThunkAction<T>): T; // eslint-disable-line no-use-before-define
}

export type ThunkAction<T> = (Dispatch, GetState) => T;
export type ThunkAction<T> = (Dispatch, () => GlobalState) => T;
14 changes: 4 additions & 10 deletions src/sharing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { NativeModules, DeviceEventEmitter, Platform } from 'react-native';

import * as NavigationService from '../nav/NavigationService';
import type { Dispatch, GetState } from '../types';
import type { SharedData } from './types';
import { navigateToSharing } from '../actions';

Expand All @@ -12,25 +11,20 @@ const Sharing = NativeModules.Sharing ?? {
null,
};

const goToSharing = (data: SharedData) => (dispatch: Dispatch, getState: GetState) => {
const goToSharing = (data: SharedData) => {
NavigationService.dispatch(navigateToSharing(data));
};

export const handleInitialShare = async (dispatch: Dispatch) => {
export const handleInitialShare = async () => {
const initialSharedData: SharedData | null = await Sharing.readInitialSharedContent();
if (initialSharedData !== null) {
dispatch(goToSharing(initialSharedData));
goToSharing(initialSharedData);
}
};

export class ShareReceivedListener {
dispatch: Dispatch;
unsubs: Array<() => void> = [];

constructor(dispatch: Dispatch) {
this.dispatch = dispatch;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sharing [nfc]: Stop taking dispatch, no longer used here

This module hasn't needed a Redux `dispatch` since 543c07c, when
we stopped using Redux for dispatching navigation actions.  So,
simplify by not taking one in the first place.

This is great!!

}

/** Private. */
listen(name: string, handler: (...empty) => void | Promise<void>) {
if (Platform.OS === 'android') {
Expand All @@ -47,7 +41,7 @@ export class ShareReceivedListener {
}

handleShareReceived: SharedData => void = data => {
this.dispatch(goToSharing(data));
goToSharing(data);
};

/** Start listening. Don't call twice without intervening `stop`. */
Expand Down
3 changes: 1 addition & 2 deletions src/typing/__tests__/typingSelectors-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* @flow strict-local */

import type { GlobalState } from '../../types';
import { getCurrentTypingUsers } from '../typingSelectors';
import { HOME_NARROW, pm1to1NarrowFromUser, pmNarrowFromUsersUnsafe } from '../../utils/narrow';
import { NULL_ARRAY } from '../../nullObjects';
Expand All @@ -9,7 +8,7 @@ import { pmTypingKeyFromPmKeyIds } from '../../utils/recipient';

describe('getCurrentTypingUsers', () => {
test('return NULL_ARRAY when current narrow is not private or group', () => {
const state: GlobalState = eg.reduxState({});
const state = eg.reduxState({});

const typingUsers = getCurrentTypingUsers(state, HOME_NARROW);

Expand Down
4 changes: 2 additions & 2 deletions src/typing/typingActions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* @flow strict-local */
import type { Action, ThunkAction, Dispatch, GetState } from '../types';
import type { Action, ThunkAction } from '../types';

import { sleep } from '../utils/async';
import { getTyping } from '../directSelectors';
Expand All @@ -9,7 +9,7 @@ export const clearTyping = (outdatedNotifications: string[]): Action => ({
outdatedNotifications,
});

const typingStatusExpiryLoop = () => async (dispatch: Dispatch, getState: GetState) => {
const typingStatusExpiryLoop = () => async (dispatch, getState) => {
// loop to auto dismiss typing notifications after typingNotificationTimeout
// eslint-disable-next-line no-constant-condition
while (true) {
Expand Down
16 changes: 7 additions & 9 deletions src/users/usersActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@ import * as typing_status from '@zulip/shared/js/typing_status';
import type { Auth, GlobalState, Narrow, UserId, ThunkAction } from '../types';
import * as api from '../api';
import { PRESENCE_RESPONSE } from '../actionConstants';
import { getAuth, tryGetAuth, getServerVersion } from '../selectors';
import { getAuth, getServerVersion } from '../selectors';
import { isPmNarrow, userIdsOfPmNarrow } from '../utils/narrow';
import { getUserForId } from './userSelectors';
import { ZulipVersion } from '../utils/zulipVersion';

export const reportPresence = (
isActive: boolean = true,
newUserInput: boolean = false,
): ThunkAction<Promise<void>> => async (dispatch, getState) => {
const auth = tryGetAuth(getState());
if (!auth) {
return; // not logged in
}
export const reportPresence = (isActive: boolean): ThunkAction<Promise<void>> => async (
dispatch,
getState,
) => {
const newUserInput = false; // TODO Why this value? Maybe it's the right one... but why?

const auth = getAuth(getState());
const response = await api.reportPresence(auth, isActive, newUserInput);
dispatch({
type: PRESENCE_RESPONSE,
Expand Down
4 changes: 2 additions & 2 deletions src/webview/MessageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ export type BackgroundData = $ReadOnly<{|
twentyFourHourTime: boolean,
|}>;

type OuterProps = {|
type OuterProps = $ReadOnly<{|
narrow: Narrow,
messages: $ReadOnlyArray<Message | Outbox>,
initialScrollMessageId: number | null,
showMessagePlaceholders: boolean,
startEditMessage: (editMessage: EditMessage) => void,
|};
|}>;

type SelectorProps = {|
// Data independent of the particular narrow or messages we're displaying.
Expand Down